在C#中将IPv6格式化为整数并存储在SQL Server中

21

IPv4 下,我一直将 IP 地址的字符串表示解析为 Int32 并将其作为 SQL Server 中的 INT 存储。

现在,对于 IPv6,我想知道是否有标准或被接受的方法来使用 C# 将字符串表示的 IPv6 解析为两个 Int64

此外,人们如何将这些值存储在 SQL Server 中 - 是作为两个 BIGINT 字段吗?

5个回答

21

IPv6地址实际上是一个128位的数字,就像IPv4地址实际上是一个32位的数字一样。有不同的地址字符串表示方式,但实际的地址是数字,而不是字符串。

因此,您不需要将IP地址转换为数字,而是需要将地址的字符串表示解析成实际的地址。

甚至连十进制都无法容纳128位的数字,因此只剩下三种明显的选择:

  • 将数值分为两个bigint字段进行存储
  • varchar字段中存储地址的字符串表示
  • 在16字节的binary字段中存储数值

这些存储方式都没有像在int中存储IPv4地址那样方便,因此您必须考虑它们的限制以及您需要对地址执行的操作。


2
@Bill:您理解有误。没有规定必须将IPv4地址的字节存储在无符号值中。Int32和UInt32都拥有32位,因此完全适合存储四个字节。 - Guffa
好在@RBarryYoung没有发现这个帖子;-) https://dev59.com/vHM_5IYBdhLWcg3waieJ#1385701 IPv4是4字节二进制,不是“真正的32位数字”。四个三位数的八位组只是一种方便的表示方法。 - EBarr
@EBarr:不,IPv4中的IP号码确实是32位数字,而不是4字节数字。查看IP数据包的描述,您会发现地址是32位单元,而不是分为四个8位值。 - Guffa
4
抱歉,如果我的幽默不清楚的话,我是在咯咯笑那两篇文章之间的矛盾。对我来说,4字节或32位似乎是同一个硬币的两面。关于int或二进制的歧义在其他地方也存在:RFC 719第12页指出,“源地址:32位”,在此问题上没有更多的说明。《核心互联网协议》第408页将其描述为“实际上是一个32位的二进制数”,维基百科说“32位(四字节)地址”。《IP地址基础知识》第22页说:“IP地址定义在一个32位长度的数学空间内。”四字节或32位?请! - EBarr
2
@EBarr 虽然 IP 地址是一个 32 位的数字(或 4 个字节),但它并不总是一个 int 类型(由于机器的字节序问题),因此建议将其存储为一个字节数组。例如,如果你要在 x86/x64 机器上将 IP 192.168.1.1 转换为 int 类型,则其值为 0xC0A80101。如果你将该整数写入文件(或将其推送到网络上),则其位数顺序为 0x0101A8C0。 - Trisped

13

最简单的方法是让框架为您执行此操作。使用IPAddress.Parse解析地址,然后使用IPAddress.GetAddressBytes以byte[]形式获取“数字”。

最后,将数组分成前8个字节和后8个字节进行转换为两个Int64,例如通过创建一个MemoryStream在字节数组上并通过BinaryReader进行读取。

这样就避免了需要理解所有可用的IPv6地址快捷表示法。


1
为什么使用两个int64而不是二进制(128)字段? - Henry
1
@Henry:这就是Q所指定的。 - Richard

4
如果您使用的是SQL Server 2005,您可以使用uniqueidentifier类型。该类型可存储16个字节,非常适合IPv6 IP地址。您可以通过使用构造函数和ToByteArrayIPAddressGuid之间进行转换。

6
“uniqueidentifier”存在的问题是无法进行范围搜索。也就是说,您不能搜索包含特定IP地址的IP范围。我们尝试使用一个拥有300万行数据的表进行此操作。但结果不是3行,而是大约73,000行。“ - NoLifeKing

3

我使用以下方法将IP地址转换为两个UInt64(C# 3.0)。

/// <summary>
/// Converts an IP address to its UInt64[2] equivalent.
/// For an IPv4 address, the first element will be 0,
/// and the second will be a UInt32 representation of the four bytes.
/// For an IPv6 address, the first element will be a UInt64
/// representation of the first eight bytes, and the second will be the
/// last eight bytes.
/// </summary>
/// <param name="ipAddress">The IP address to convert.</param>
/// <returns></returns>
private static ulong[] ConvertIPAddressToUInt64Array(string ipAddress)
{
    byte[] addrBytes = System.Net.IPAddress.Parse(ipAddress).GetAddressBytes();
    if (System.BitConverter.IsLittleEndian)
    {
        //little-endian machines store multi-byte integers with the
        //least significant byte first. this is a problem, as integer
        //values are sent over the network in big-endian mode. reversing
        //the order of the bytes is a quick way to get the BitConverter
        //methods to convert the byte arrays in big-endian mode.
        System.Collections.Generic.List<byte> byteList = new System.Collections.Generic.List<byte>(addrBytes);
        byteList.Reverse();
        addrBytes = byteList.ToArray();
    }
    ulong[] addrWords = new ulong[2];
    if (addrBytes.Length > 8)
    {
        addrWords[0] = System.BitConverter.ToUInt64(addrBytes, 8);
        addrWords[1] = System.BitConverter.ToUInt64(addrBytes, 0);
    }
    else
    {
        addrWords[0] = 0;
        addrWords[1] = System.BitConverter.ToUInt32(addrBytes, 0);
    }
    return addrWords;
}

请确保在将 UInt64 放入数据库之前将其转换为 Int64,否则会出现 ArgumentException。当您取回值时,可以将它们转换回 UInt64 以获取无符号值。
我没有需要反向执行的需求(即将 UInt64[2] 转换为 IP 字符串),因此我从未构建过该方法。

很少有情况需要存储255.255.255.255或FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF。 - Gavin
1
-1:使用Int32来存储IPv4地址完全没有问题。你盲目地盯着整数的最大值,但这是无关紧要的。32位就是32位,它们显然足以表示IPv4地址的四个字节。 - Guffa
我的错。我以为CLR在尝试将UInt32.MaxValue转换为Int32时会抛出溢出错误,而不是“绕过拐角”。我已经编辑了我的答案以反映这一点。 - Billy Jo
@Guffa:是的,但IPv6地址有128位... System.Net.IPAddress必须同时处理两者。 - Stefan Steiger

0
function encode_ip ($ip)
{
    return bin2hex(inet_pton($ip));
}

function decode_ip($ip)
{
    function hex2bin($temp) {
       $data="";
       for ($i=0; $i < strlen($temp); $i+=2) $data.=chr(hexdec(substr($temp,$i,2)));
       return $data;
    }
    return inet_ntop(hex2bin($ip));
}

-- max len row db
echo strlen(inet_pton('2001:db8:85a3::8a2e:370:7334'));

-- db row info
ip varchar(16)

-- sql binary save and read
save base
$bin_ip='0x'.bin2hex(inet_pton($data['ip_address']));

-- db read
select ip_address from users;

-- encode binary from db
echo inet_ntop($row['ip_address']);

5
这是PHP,OP表示是C#。 - nokturnal

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