如何在C#中将IPv4地址转换为整数?

118
我正在寻找一个函数,可以将标准IPv4地址转换为整数。如果函数能够实现反向转换,则可获得额外积分。解决方案应使用C#编写。

18
警告:接受的答案所产生的整数将是“错误”的,因为IP地址是按网络字节序(大端序)排列的,而大多数系统中的int是小端序。因此,在转换之前必须颠倒字节顺序。请参见我的答案以获取正确的转换方式。即使对于IPv4,一个int也无法容纳大于127.255.255.255的地址,例如广播地址,因此请使用uint - Saeb Amini
@SaebAmini我的答案使用NetworkToHostOrder来反转字节。且int可以包含大于127.255.255.255的地址;它们以负数形式存储。当将类型转换为int时,Address属性中大于2^31的数字,即long类型,会变成负数;将其转换为int是为了调用NetworkToHostOrder,并且不会截断数字。 - Barry Kelly
22个回答

155

32位无符号整数是IPv4地址。同时,虽然已被弃用,但IPAddress.Address属性是一个Int64,返回IPv4地址的无符号32位值(关键是,它以网络字节顺序提供,所以您需要交换它)。

例如,我的本地google.com在64.233.187.99。这相当于:

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

的确,http://1089059683/按预期工作(至少在Windows上测试过,使用IE、Firefox和Chrome;但在iPhone上无法使用)。

以下是用于显示转换的测试程序,包括网络/主机字节交换:

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));
    }
}

2
在Ubuntu Linux 10.04上的Opera 11中确认:它将int转换回熟悉的w.x.y.z形式,而且它有效。 - Piskvor left the building
8
自 .Net 4.0 起,IPAddress.Address 已过时。 - Erik Philips
这是一个架构决策。它有它的优点和缺点,而评论并不是讨论那个特定问题的最佳位置。 - Erik Philips
2
你可以使用BitConverter.ToUInt32(IPAddress.Parse(value).GetAddressBytes().Reverse().ToArray()来修复IPAddress.Address已过时的问题。 - Mhmd
IPv4的8个字节必须使用ulong - Mason.Chase
显示剩余7条评论

57

以下是一对将IPv4地址转换为正确整数以及将整数转换回IPv4地址的方法:

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();
}

示例

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

说明

IP地址采用网络字节序(大端序)表示,而在Windows中int使用的是小端序,因此在小端序系统上进行转换前必须先反转字节顺序以得到正确的值。

同时,即使是对于IPv4地址,int也无法容纳大于127.255.255.255的地址(例如广播地址(255.255.255.255)),因此请使用uint


在Windows上使用.NET似乎并没有实际影响-不确定Mono。请参见https://dotnetfiddle.net/cBIOj2 - Jesse
2
@Jesse,对于你输入的1.1.1.1来说,这并没有什么区别,因为它的字节数组是回文的。试试非回文的IP地址,比如127.0.0.1192.168.1.1 - Saeb Amini
我看到了问题。重复的数字,如1.1.1.12.2.2.2123.123.123.123总是产生相同的结果。为了记录,请查看更新后的fiddle链接:https://dotnetfiddle.net/aR6fhc - Jesse
想要注意的是,我不得不使用 System.Net.IPAddress 才能使其正常工作。效果很好! - Shrout1
1
@Jesse 这不仅适用于重复数字,而且适用于所有回文 IP地址,例如2.1.1.2 - Saeb Amini

43

@Barry Kelly和@Andrew Hare,实际上我认为乘法并不是最清晰的方式来做到这一点(虽然正确)。

一个Int32的“格式化”IP地址可以被视为以下结构

[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

如果要将 IP 地址 64.233.187.99 转换为十六进制,可以这样做:

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

你可以使用加号将它们相加,也可以将它们二进制或在一起。结果为0x40E9BB63,即1089059683。(个人认为,在十六进制下查看字节更容易一些)

因此,你可以编写这个函数如下:

int ipToInt(int first, int second, 
    int third, int fourth)
{
    return (first << 24) | (second << 16) | (third << 8) | (fourth);
}

1
我相信IP地址被规定为这样是为了特别允许这种行为... 在大多数微处理器上,位移比乘加更有效率。 - Ape-inago
1
@Ape-inago 顺便说一下,常数次幂的乘法通常会被优化为位移操作。 - Barry Kelly
你可以使用 LayoutKind.ExplicitFieldOffset 来反转字节存储的顺序,而不是使用位移。当然,这仅适用于小端架构。示例在github上 - RubberDuck
2
必须指出,int 是有符号的,因此如果您将 192 向左移动 24 位,您将得到负整数,因此此代码对于高位字节的第一位为高位的情况是错误的。 - Mateusz

12

可以尝试这些:

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();
}

Reverse() 返回 void,因此您无法在其上调用 ToArray()(对于未来的读者)。相反,将一个值分配给反转的字节,然后就可以调用 ToArray() - Erik Philips
1
Reverse()IEnumerable 的扩展方法。上述代码完全没问题。 - Ant
4
这种方法会给某些IP地址(例如140.117.0.0)产生负数。 - lightxx
我不介意负数,但对于诸如fe80::202:b3ff:fe1e:8329的IPv6地址,它无法正常工作(无法完全还原)。 - ebhh2001

9

在处理非常大的IP地址时,我遇到了一些问题,使用上述解决方案无法正常工作。 结果是byte[0]*16777216会溢出并成为负数int值。 对我有用的解决方法是进行简单的类型转换操作。

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;
}

在我看来,这似乎是最好的解决方案,但我会更改一行代码,我会使用byte[] bytes=ip.MapToIPv4().GetAddressBytes(); - Walter Verhoeven

8

由于没有人发布使用 BitConverter 并实际检查字节序的代码,因此在这里提供:

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

并返回:

byte[] ip = BitConverter.GetBytes(num);
if (BitConverter.IsLittleEndian) {
  Array.Reverse(ip);
}
string address = String.Join(".", ip.Select(n => n.ToString()));

2
你需要使用一个uint,但这是这里最正确的答案。 - RubberDuck
2
@RubberDuck:只有当你想要将32位数据作为无符号数使用时,才需要使用uint。而int可以容纳相同的信息。如果你想将其存储在数据库中,那么int更适合,但如果你想以无符号形式存储它,则需要使用bigint - Guffa
1
在我看来,uint是更好的表示方式。有符号整数需要一个位用于符号,因此您会失去范围顶部的地址。是的,它可以保存相同的数据,但输出将作为负数,如果您在地址栏中键入它,则不是有效的IP。 - RubberDuck
@RubberDuck:以 uint 的形式,你也不能在地址栏中输入它,你必须先将其转换为文本形式才能这样做。仅仅因为使用最简单的将 int 转换为文本形式并不会产生可用的 IP 地址,并不是不使用它的好理由。 - Guffa
@RubberDuck:那不是一个 uint,那是一个 uint 的文本表示。 - Guffa

4

我将以上几个答案整合成了一个扩展方法,处理机器的字节序(即大小端)并处理映射到IPv6的IPv4地址。

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");
    }
}

单元测试:


[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);
    }
}

4
Davy Landman的函数的反向
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;
}

你能详细解释一下吗? - Developerium

3

我的问题被关闭了,我不知道为什么。这里接受的答案与我需要的不同。

这给出了正确的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;
    }
}

只要你能够双向转换,我认为输出数字是否正确并不重要,只要保持一致即可。 - GateKiller
取决于您要使用数字的用途。您不能使用其他转换来执行>=和<=查询以查找IP。 - Coolcoder

3

将UInt32转换为正确的小端格式后,这里有两个简单的转换函数:

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();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接