Jak przekonwertować adres IPv4 na liczbę całkowitą w C #?

99

Szukam funkcji, która zamieni standardowy adres IPv4 na liczbę całkowitą. Dodatkowe punkty dostępne za funkcję, która zrobi odwrotnie.

Rozwiązanie powinno być w C #.

GateKiller
źródło
15
UWAGA : liczby całkowite wynikające z zaakceptowanej odpowiedzi będą błędne , ponieważ adresy IP są w kolejności sieciowej (big-endian), podczas gdy ints to little-endian w większości systemów. Musisz więc odwrócić bajty przed konwersją. Zobacz moją odpowiedź dotyczącą prawidłowych konwersji. Ponadto, nawet dla IPv4, intmoże nie posiadać adresy większe niż 127.255.255.255, na przykład adres broadcast, więc użyć uint.
Saeb Amini

Odpowiedzi:

137

32-bitowe liczby całkowite bez znaku to adresy IPv4. Tymczasem IPAddress.Addresswłaściwość, mimo że jest przestarzała, jest wartością Int64, która zwraca 32-bitową wartość adresu IPv4 bez znaku (haczyk jest w kolejności bajtów sieci, więc musisz ją zamienić).

Na przykład moja lokalna witryna google.com to 64.233.187.99. To jest równoważne z:

64*2^24 + 233*2^16 + 187*2^8 + 99
= 1089059683

I rzeczywiście, http: // 1089059683 / działa zgodnie z oczekiwaniami (przynajmniej w systemie Windows, testowany z IE, Firefox i Chrome; nie działa jednak na iPhonie).

Oto program testowy pokazujący obie konwersje, w tym zamianę bajtów sieci / hosta:

using System;
using System.Net;

class App
{
    static long ToInt(string addr)
    {
        // careful of sign extension: convert to uint first;
        // unsigned NetworkToHostOrder ought to be provided.
        return (long) (uint) IPAddress.NetworkToHostOrder(
             (int) IPAddress.Parse(addr).Address);
    }

    static string ToAddr(long address)
    {
        return IPAddress.Parse(address.ToString()).ToString();
        // This also works:
        // return new IPAddress((uint) IPAddress.HostToNetworkOrder(
        //    (int) address)).ToString();
    }

    static void Main()
    {
        Console.WriteLine(ToInt("64.233.187.99"));
        Console.WriteLine(ToAddr(1089059683));
    }
}
Barry Kelly
źródło
2
Potwierdzone w Operze 11 na Ubuntu Linux 10.04: konwertuje int z powrotem do znanego formularza wxyz i działa.
Piskvor opuścił budynek
6
IPAddress.Address jest przestarzały od .Net 4.0.
Erik Philips,
@ErikPhilips Czy to ma znaczenie, jeśli używasz tylko protokołu IPv4?
Ray
To decyzja architektoniczna. Ma swoje wady i zalety, a komentarze nie są najlepszym miejscem do dyskusji na ten temat.
Erik Philips,
1
możesz użyć BitConverter.ToUInt32 (IPAddress.Parse (wartość) .GetAddressBytes (). Reverse (). ToArray (), aby naprawić
adres IP.Address
43

Oto kilka metod konwersji z protokołu IPv4 na poprawną liczbę całkowitą iz powrotem:

public static uint ConvertFromIpAddressToInteger(string ipAddress)
{
    var address = IPAddress.Parse(ipAddress);
    byte[] bytes = address.GetAddressBytes();

    // flip big-endian(network order) to little-endian
    if (BitConverter.IsLittleEndian)
    {
        Array.Reverse(bytes);
    }

    return BitConverter.ToUInt32(bytes, 0);
}

public static string ConvertFromIntegerToIpAddress(uint ipAddress)
{
    byte[] bytes = BitConverter.GetBytes(ipAddress);

    // flip little-endian to big-endian(network order)
    if (BitConverter.IsLittleEndian)
    {
        Array.Reverse(bytes);
    }

    return new IPAddress(bytes).ToString();
}

Przykład

ConvertFromIpAddressToInteger("255.255.255.254"); // 4294967294
ConvertFromIntegerToIpAddress(4294967294); // 255.255.255.254

Wyjaśnienie

Adresy IP są w kolejności sieciowej (big-endian), podczas gdy ints to little-endian w systemie Windows, więc aby uzyskać poprawną wartość, należy odwrócić bajty przed konwersją w systemie little-endian.

Ponadto, nawet dla IPv4, a intnie może przechowywać adresów większych niż 127.255.255.255np. Adres rozgłoszeniowy (255.255.255.255), więc użyj uint.

Saeb Amini
źródło
Wydaje się, że nie robi to różnicy w systemie Windows używającym .NET - nie mam pewności co do Mono. Zobacz dotnetfiddle.net/cBIOj2
Jesse,
2
@Jesse zdarza się, że nie robi to różnicy dla twojego wejścia, 1.1.1.1ponieważ jego tablica bajtów jest palindromiczna. Spróbuj z nie-palindromicznymi, takimi jak 127.0.0.1lub 192.168.1.1.
Saeb Amini
Widzę problem. Powtarzające liczby takie jak 1.1.1.1, 2.2.2.2, 123.123.123.123zawsze osiąga się ten sam wynik. Dla potomnych zobacz zaktualizowane skrzypce: dotnetfiddle.net/aR6fhc
Jesse
Chciałbym zauważyć, że musiałem użyć, System.Net.IPAddressaby to zadziałało. Działa świetnie!
Shrout 1
1
@Jesse to nie tylko dla powtarzających się liczb, ale dla wszystkich palindromicznych adresów IP. Więc 2.1.1.2byłoby to samo.
Saeb Amini
40

@Barry Kelly i @Andrew Hare, tak naprawdę, nie sądzę, aby mnożenie było najbardziej przejrzystym sposobem na zrobienie tego (wszystko w porządku).

„Sformatowany” adres IP Int32 można zobaczyć jako następującą strukturę

[StructLayout(LayoutKind.Sequential, Pack = 1)] 
struct IPv4Address
{
   public Byte A;
   public Byte B;
   public Byte C;
   public Byte D;
} 
// to actually cast it from or to an int32 I think you 
// need to reverse the fields due to little endian

Aby przekonwertować adres IP 64.233.187.99, możesz zrobić:

(64  = 0x40) << 24 == 0x40000000
(233 = 0xE9) << 16 == 0x00E90000
(187 = 0xBB) << 8  == 0x0000BB00
(99  = 0x63)       == 0x00000063
                      ---------- =|
                      0x40E9BB63

więc możesz je dodać za pomocą + lub możesz je połączyć lub obustronnie. Wynik 0x40E9BB63, czyli 1089059683. (Moim zdaniem patrząc w szesnastce dużo łatwiej jest zobaczyć bajty)

Możesz więc zapisać funkcję jako:

int ipToInt(int first, int second, 
    int third, int fourth)
{
    return (first << 24) | (second << 16) | (third << 8) | (fourth);
}
Davy Landman
źródło
1
Uważam, że adresy IP zostały określone w ten sposób, aby umożliwić takie zachowanie ... przesunięcia bitowe są znacznie bardziej wydajne na większości mikroprocesorów niż muls i dodaje.
Ape-inago
1
@ Mnożenie Ape-inago przez stałe potęgi dwójki jest zwykle optymalizowane do przesunięć bitowych, fwiw.
Barry Kelly
Zamiast przesuwania bitów możesz użyć LayoutKind.Expliciti FieldOffsetdo odwrócenia kolejności, w jakiej przechowywane są bajty. Oczywiście działa to tylko w przypadku architektury little endian. Przykład na github .
RubberDuck
2
Należy zwrócić uwagę, że intjest on podpisany, więc jeśli przesuniesz 192 na 24 bity, otrzymasz ujemną liczbę całkowitą, więc ten kod jest uszkodzony dla wysokiego oktetu, który ma wysoki bit na pierwszym miejscu.
Mateusz
12

Wypróbuj te:

private int IpToInt32(string ipAddress)
{
   return BitConverter.ToInt32(IPAddress.Parse(ipAddress).GetAddressBytes().Reverse().ToArray(), 0);
}

private string Int32ToIp(int ipAddress)
{
   return new IPAddress(BitConverter.GetBytes(ipAddress).Reverse().ToArray()).ToString();
}
Rubens Farias
źródło
Reverse()zwraca void, więc nie możesz go wywołać ToArray()(dla przyszłych czytelników). Zamiast tego przypisz wartość do odwróconych bajtów, a następnie możesz wywołać ToArray ().
Erik Philips,
1
Reverse () jest metodą rozszerzającą IEnumerable. Powyższy kod jest w porządku.
Ant,
3
Ta metoda daje liczby ujemne dla niektórych adresów IP (np. 140.117.0.0)
lightxx
7

Ponieważ nikt nie opublikował kodu, który używa BitConverteri faktycznie sprawdza endianness, oto:

byte[] ip = address.Split('.').Select(s => Byte.Parse(s)).ToArray();
if (BitConverter.IsLittleEndian) {
  Array.Reverse(ip);
}
int num = BitConverter.ToInt32(ip, 0);

i z powrotem:

byte[] ip = BitConverter.GetBytes(num);
if (BitConverter.IsLittleEndian) {
  Array.Reverse(ip);
}
string address = String.Join(".", ip.Select(n => n.ToString()));
Guffa
źródło
2
Musisz użyć uint, ale to jest tutaj najbardziej poprawna odpowiedź.
RubberDuck
1
@RubberDuck: Musisz użyć tylko uintjeśli chcesz, aby 32 bity danych były liczbą bez znaku, intjest w stanie przechowywać te same informacje. Jeśli chcesz przechowywać go w bazie danych, a intjest lepiej dopasowany, musisz bigintmieć możliwość przechowywania go w postaci bez znaku.
Guffa
1
Uint jest lepszą reprezentacją IMO. Podpisany int wymaga trochę na znak, więc tracisz adresy na samej górze zakresu. Tak, może przechowywać te same dane, ale zostaną wyprowadzone jako liczba ujemna, co nie jest prawidłowym adresem IP, jeśli wpiszesz go w pasku adresu.
RubberDuck,
@RubberDuck: W postaci uint, której nie możesz również wpisać w pasku adresu, musisz najpierw przekonwertować go na formę tekstową, aby to zrobić. Tylko dlatego, że użycie najprostszej formy zamiany intna tekst nie daje działającego adresu IP, nie jest dobrym argumentem za jego nieużywaniem.
Guffa
@RubberDuck: To nie jest uint, to tekstowa reprezentacja pliku uint.
Guffa
7

Napotkałem pewne problemy z opisanymi rozwiązaniami, mając adresy IP o bardzo dużej wartości. W rezultacie bajt [0] * 16777216 thingy przepełniłby się i stałby się ujemną wartością int. to, co mnie naprawiło, to prosta operacja odlewania.

public static long ConvertIPToLong(string ipAddress)
{
    System.Net.IPAddress ip;

    if (System.Net.IPAddress.TryParse(ipAddress, out ip))
    {
        byte[] bytes = ip.GetAddressBytes();

        return
            16777216L * bytes[0] +
            65536 * bytes[1] +
            256 * bytes[2] +
            bytes[3]
            ;
    }
    else
        return 0;
}
Qiong Wu
źródło
wydaje mi się najlepszym rozwiązaniem, ale zmieniłbym 1 linię, zrobiłbym byte [] bytes = ip.MapToIPv4 (). GetAddressBytes ();
Walter Vehoeven
4

Odwrotność funkcji Davy'ego Landmana

string IntToIp(int d)
{
  int v1 = d & 0xff;
  int v2 = (d >> 8) & 0xff;
  int v3 = (d >> 16) & 0xff;
  int v4 = (d >> 24);
  return v4 + "." + v3 + "." + v2 + "." + v1;
}
Aris Schizas
źródło
czy mógłbyś wyjaśnić więcej na ten temat?
Developerium,
3

Moje pytanie zostało zamknięte, nie mam pojęcia dlaczego. Przyjęta tutaj odpowiedź nie jest tym samym, czego potrzebuję.

To daje mi poprawną wartość całkowitą dla adresu IP.

public double IPAddressToNumber(string IPaddress)
{
    int i;
    string [] arrDec;
    double num = 0;
    if (IPaddress == "")
    {
        return 0;
    }
    else
    {
        arrDec = IPaddress.Split('.');
        for(i = arrDec.Length - 1; i >= 0 ; i = i -1)
            {
                num += ((int.Parse(arrDec[i])%256) * Math.Pow(256 ,(3 - i )));
            }
        return num;
    }
}
Coolcoder
źródło
Dopóki możesz wykonać konwersję w obie strony, nie rozumiem, dlaczego numer wyjściowy ma być poprawny, o ile jest spójny.
GateKiller
Zależy od tego, do czego chcesz używać numeru. Nie możesz użyć drugiej konwersji do wykonania zapytania> = i <= w celu znalezienia adresu IP.
Coolcoder
2

Mając UInt32 w odpowiednim formacie little-endian, oto dwie proste funkcje konwersji:

public uint GetIpAsUInt32(string ipString)
{
    IPAddress address = IPAddress.Parse(ipString);

    byte[] ipBytes = address.GetAddressBytes();

    Array.Reverse(ipBytes);

    return BitConverter.ToUInt32(ipBytes, 0);
}

public string GetIpAsString(uint ipVal)
{
    byte[] ipBytes = BitConverter.GetBytes(ipVal);

    Array.Reverse(ipBytes);

    return new IPAddress(ipBytes).ToString();
}
DanM7
źródło
2

Połączono kilka z powyższych odpowiedzi w metodę rozszerzenia, która obsługuje Endianness maszyny i obsługuje adresy IPv4, które zostały zmapowane na IPv6.

public static class IPAddressExtensions
{
    /// <summary>
    /// Converts IPv4 and IPv4 mapped to IPv6 addresses to an unsigned integer.
    /// </summary>
    /// <param name="address">The address to conver</param>
    /// <returns>An unsigned integer that represents an IPv4 address.</returns>
    public static uint ToUint(this IPAddress address)
    {
        if (address.AddressFamily == AddressFamily.InterNetwork || address.IsIPv4MappedToIPv6)
        {
            var bytes = address.GetAddressBytes();
            if (BitConverter.IsLittleEndian)
                Array.Reverse(bytes);

            return BitConverter.ToUInt32(bytes, 0);
        }
        throw new ArgumentOutOfRangeException("address", "Address must be IPv4 or IPv4 mapped to IPv6");
    }
}

Testy jednostkowe:

[TestClass]
public class IPAddressExtensionsTests
{
    [TestMethod]
    public void SimpleIp1()
    {
        var ip = IPAddress.Parse("0.0.0.15");
        uint expected = GetExpected(0, 0, 0, 15);
        Assert.AreEqual(expected, ip.ToUint());
    }
    [TestMethod]
    public void SimpleIp2()
    {
        var ip = IPAddress.Parse("0.0.1.15");
        uint expected = GetExpected(0, 0, 1, 15);
        Assert.AreEqual(expected, ip.ToUint());
    }
    [TestMethod]
    public void SimpleIpSix1()
    {
        var ip = IPAddress.Parse("0.0.0.15").MapToIPv6();
        uint expected = GetExpected(0, 0, 0, 15);
        Assert.AreEqual(expected, ip.ToUint());
    }
    [TestMethod]
    public void SimpleIpSix2()
    {
        var ip = IPAddress.Parse("0.0.1.15").MapToIPv6();
        uint expected = GetExpected(0, 0, 1, 15);
        Assert.AreEqual(expected, ip.ToUint());
    }
    [TestMethod]
    public void HighBits()
    {
        var ip = IPAddress.Parse("200.12.1.15").MapToIPv6();
        uint expected = GetExpected(200, 12, 1, 15);
        Assert.AreEqual(expected, ip.ToUint());
    }
    uint GetExpected(uint a, uint b, uint c, uint d)
    {
        return
            (a * 256u * 256u * 256u) +
            (b * 256u * 256u) +
            (c * 256u) +
            (d);
    }
}
Doug Clutter
źródło
1

Jeśli interesowała Cię funkcja, a nie tylko odpowiedź, oto jak to się robi:

int ipToInt(int first, int second, 
    int third, int fourth)
{
    return Convert.ToInt32((first * Math.Pow(256, 3))
        + (second * Math.Pow(256, 2)) + (third * 256) + fourth);
}

ze firstprzez fourthbycie segmenty adresu IPv4.

Andrew Hare
źródło
1
Myślę, że byłoby bardziej jasne, gdybyś użył shift zamiast Math.Pow.
Mehrdad Afshari
Spowoduje to zgłoszenie wyjątku przepełnienia, jeśli najpierw jest> 127. Odpowiedź Davy'ego Landmana jest najlepszym sposobem na zrobienie tego.
mhenry1384
int32 jest podpisany, więc zwróci wartość ujemną> 127 dla pierwszego oktetu
Mateusz
1
public bool TryParseIPv4Address(string value, out uint result)
{
    IPAddress ipAddress;

    if (!IPAddress.TryParse(value, out ipAddress) ||
        (ipAddress.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork))
    {
        result = 0;
        return false;
    }

    result = BitConverter.ToUInt32(ipAddress.GetAddressBytes().Reverse().ToArray(), 0);
    return true;
}
xadd
źródło
1
    public static Int32 getLongIPAddress(string ipAddress)
    {
        return IPAddress.NetworkToHostOrder(BitConverter.ToInt32(IPAddress.Parse(ipAddress).GetAddressBytes(), 0));
    }

Powyższy przykład byłby właściwą drogą. Jedyne, co możesz zrobić, to przekonwertować na UInt32 do celów wyświetlania lub do celów łańcuchowych, w tym użyć go jako długiego adresu w postaci ciągu.

Co jest potrzebne przy korzystaniu z funkcji IPAddress.Parse (String). Westchnienie.

Gerard ONeill
źródło
0

oto rozwiązanie, które wypracowałem dzisiaj (powinienem był najpierw googlować!):

    private static string IpToDecimal2(string ipAddress)
    {
        // need a shift counter
        int shift = 3;

        // loop through the octets and compute the decimal version
        var octets = ipAddress.Split('.').Select(p => long.Parse(p));
        return octets.Aggregate(0L, (total, octet) => (total + (octet << (shift-- * 8)))).ToString();
    }

Używam LINQ, lambda i niektórych rozszerzeń w generycznych, więc chociaż daje ten sam wynik, używa niektórych nowych funkcji języka i możesz to zrobić w trzech wierszach kodu.

Mam wyjaśnienie na moim blogu, jeśli jesteś zainteresowany.

okrzyki, -jc


źródło
0

Myślę, że to źle: "65536" ==> 0.0.255.255 "Powinno być:" 65535 "==> 0.0.255.255" lub "65536" ==> 0.1.0.0 "


źródło
0

@Davy Ladman Twoje rozwiązanie z przesunięciem jest aktualne, ale tylko dla IP zaczynającego się od numeru mniejszego lub równego 99, w rzeczywistości pierwszy oktekt musi być rzucony zbyt długo.

W każdym razie konwersja z powrotem z typem długim jest dość trudna, ponieważ przechowuj 64 bity (nie 32 dla Ip) i wypełnij 4 bajty zerami

static uint ToInt(string addr)
{
   return BitConverter.ToUInt32(IPAddress.Parse(addr).GetAddressBytes(), 0);
}

static string ToAddr(uint address)
{
    return new IPAddress(address).ToString();
}

Cieszyć się!

Massimo

Massimo
źródło
0

Zakładając, że masz adres IP w postaci ciągu (np. 254.254.254.254)

string[] vals = inVal.Split('.');
uint output = 0;
for (byte i = 0; i < vals.Length; i++) output += (uint)(byte.Parse(vals[i]) << 8 * (vals.GetUpperBound(0) - i));
Andrzej
źródło
0
var ipAddress = "10.101.5.56";

var longAddress = long.Parse(string.Join("", ipAddress.Split('.').Select(x => x.PadLeft(3, '0'))));

Console.WriteLine(longAddress);

Wyjście: 10101005056

xagyg
źródło
0
var address = IPAddress.Parse("10.0.11.174").GetAddressBytes();
long m_Address = ((address[3] << 24 | address[2] << 16 | address[1] << 8 | address[0]) & 0x0FFFFFFFF);
shzy2012
źródło
metoda GetAddressBytes może zwracać bajty w odwrotnej kolejności, w zależności od tego, czy maszyna jest endian, czy endian, więc poprawna instrukcja może być dla niektórych maszyn: long m_Address = (address [0] << 24 | address [1] << 16 | adres [2] << 8 | adres [3]) & 0x0FFFFFFFF
MiguelSlv
0

Zauważyłem, że System.Net.IPAddress ma właściwość Address (System.Int64) i konstruktor, które również akceptują typ danych Int64. Możesz więc użyć tego do konwersji adresu IP na / z formatu liczbowego (chociaż nie Int32, ale Int64).

Ondřej
źródło
-1

Spójrz na niektóre zwariowane przykłady analizowania w .Net's IPAddress.Parse: ( MSDN )

"65536" ==> 0.0.255.255
"20.2" ==> 20.0.0.2
"20.65535" ==> 20.0.255.255
"128.1.2" ==> 128.1.0.2

abelenky
źródło
To naprawdę nie jest odpowiedź. Dlaczego nie zamieścić tego jako komentarza do jednej z najpopularniejszych odpowiedzi?
DanM7