【问题标题】:How to convert IPv6 from binary for storage in MySQL如何将 IPv6 从二进制转换为 MySQL 中的存储
【发布时间】:2010-11-10 08:38:20
【问题描述】:

我正在尝试以一种有效的方式将 IPv6 地址存储在 MySQL 5.0 中。我已经阅读了与此相关的其他问题,such as this one。该问题的作者最终选择了两个 BIGINT 字段。我的搜索还发现了另一种常用的机制:使用 DECIMAL(39,0) 来存储 IPv6 地址。我对此有两个问题。

  1. 与 2*BIGINT 等其他方法相比,使用 DECIMAL(39,0) 有哪些优点和缺点?
  2. 如何(在 PHP 中)从 inet_pton() 返回的二进制格式转换为 MySQL 可用的十进制字符串格式,以及如何转换回来以便可以使用 inet_ntop() 进行漂亮的打印?

【问题讨论】:

  • 使用 VARCHAR 字段有什么问题?
  • 一个简单的IP范围匹配。

标签: php mysql ipv6


【解决方案1】:

我们改为使用VARBINARY(16) 列并使用inet_pton()inet_ntop() 进行转换:

https://github.com/skion/mysql-udf-ipv6

这些函数可以加载到正在运行的 MySQL 服务器中,并会在 SQL 中为您提供 INET6_NTOPINET6_PTON,就像 IPv4 中熟悉的 INET_NTOAINET_ATON 函数一样。

编辑:现在 MySQL 中有兼容的函数,只有 different names。仅当您使用的是 5.6 之前的 MySQL 并且正在寻找方便的未来升级路径时才使用上述方法。

【讨论】:

  • 只是想指出,上述模块已与 vanilla MySQL v5.6.3 中的新 IPv6 函数兼容:INET6_ATON() 和 INET6_NTOA()。因此,您可以将上述模块用于 5.6 之前的安装,并在 5.6 之后卸载一次;无需更改您的查询。
  • @PieterEnnes Ty,在等待 MariaDB 团队将其实施到他们的下一个版本中时,刚刚使用了您的 UDF。 mariadb.atlassian.net/browse/MDEV-4051 (:
【解决方案2】:

以下是我现在用来将 IP 地址从 DECIMAL(39,0) 格式转换为 DECIMAL(39,0) 格式的函数。它们被命名为 inet_ptod 和 inet_dtop 用于“表示到十进制”和“十进制到表示”。它需要 PHP 中的 IPv6 和 bcmath 支持。

/**
 * Convert an IP address from presentation to decimal(39,0) format suitable for storage in MySQL
 *
 * @param string $ip_address An IP address in IPv4, IPv6 or decimal notation
 * @return string The IP address in decimal notation
 */
function inet_ptod($ip_address)
{
    // IPv4 address
    if (strpos($ip_address, ':') === false && strpos($ip_address, '.') !== false) {
        $ip_address = '::' . $ip_address;
    }

    // IPv6 address
    if (strpos($ip_address, ':') !== false) {
        $network = inet_pton($ip_address);
        $parts = unpack('N*', $network);

        foreach ($parts as &$part) {
            if ($part < 0) {
                $part = bcadd((string) $part, '4294967296');
            }

            if (!is_string($part)) {
                $part = (string) $part;
            }
        }

        $decimal = $parts[4];
        $decimal = bcadd($decimal, bcmul($parts[3], '4294967296'));
        $decimal = bcadd($decimal, bcmul($parts[2], '18446744073709551616'));
        $decimal = bcadd($decimal, bcmul($parts[1], '79228162514264337593543950336'));

        return $decimal;
    }

    // Decimal address
    return $ip_address;
}

/**
 * Convert an IP address from decimal format to presentation format
 *
 * @param string $decimal An IP address in IPv4, IPv6 or decimal notation
 * @return string The IP address in presentation format
 */
function inet_dtop($decimal)
{
    // IPv4 or IPv6 format
    if (strpos($decimal, ':') !== false || strpos($decimal, '.') !== false) {
        return $decimal;
    }

    // Decimal format
    $parts = array();
    $parts[1] = bcdiv($decimal, '79228162514264337593543950336', 0);
    $decimal = bcsub($decimal, bcmul($parts[1], '79228162514264337593543950336'));
    $parts[2] = bcdiv($decimal, '18446744073709551616', 0);
    $decimal = bcsub($decimal, bcmul($parts[2], '18446744073709551616'));
    $parts[3] = bcdiv($decimal, '4294967296', 0);
    $decimal = bcsub($decimal, bcmul($parts[3], '4294967296'));
    $parts[4] = $decimal;

    foreach ($parts as &$part) {
        if (bccomp($part, '2147483647') == 1) {
            $part = bcsub($part, '4294967296');
        }

        $part = (int) $part;
    }

    $network = pack('N4', $parts[1], $parts[2], $parts[3], $parts[4]);
    $ip_address = inet_ntop($network);

    // Turn IPv6 to IPv4 if it's IPv4
    if (preg_match('/^::\d+.\d+.\d+.\d+$/', $ip_address)) {
        return substr($ip_address, 2);
    }

    return $ip_address;
}

【讨论】:

  • 男孩。好在该代码中没有任意的幻数。
  • 如果你知道你的 2 的力量,它们就不是武断或魔法 ;-)
  • 79228162514264337593543950336 = 2^96 --- 18446744073709551616 = 2^64 --- 4294967296 = 2^32 --- 请注意,您不能按原样使用这种速记符号,因为它们'对 PHP 来说太大了。
  • @alexanderpas 我认为它们对于大多数语言(2^32 除外)都很大,而不仅仅是 PHP(因为 int 大小主要取决于系统)。并且扩展的数字格式可能是有利的,因为随着时间的推移,这些函数往往会被频繁使用,节省机器每次计算一些 2 的幂可能会减少全局周期数。
【解决方案3】:

十进制(39)

优点:

  • 适用于基本算术运算符(例如 + 和 -)。
  • 适用于基本索引(精确或范围)。
  • 格式便于显示。

缺点:

  • 可以接受超出范围的 IPv6 值。
  • 不是一种非常有效的存储机制。
  • 可能会导致混淆哪些数学运算符或函数有效,哪些无效。

BINARY(16)...

优点:

  • 精确表示的最有效格式。
  • 适用于基本索引(精确和范围)。
  • 适用于 8 位倍数前缀的前缀索引。
  • 仅存储有效的 IPv6 值(尽管不保证有效寻址)。
  • 更高版本的 MySQL 具有支持这种格式与 IPv6 表示(但不支持 4in6)之间的转换的函数。

缺点:

  • 不利于展示。
  • 对用于数字的运算符或函数不友好。

BINARY(39)...

这适用于完整地址(即使是 4in6 也使用 hexdec)。也可以是 ascii 而不是二进制。

优点:

  • 人类可读(如果你可以调用 IPv6 的话)。
  • 支持基本索引(精确和范围)。
  • 支持 4 位的倍数的前缀索引。
  • 直接兼容 IPv6。无需转换。

缺点:

  • 不适用于任何数学函数或运算符。
  • 存储效率最低。
  • 可以允许无效的表示。

奇事:

  • 如果您想要不区分大小写等内容,则会变得复杂。
  • IPv6 有其他显示格式,尽管使用这些格式会导致更复杂的情况,例如同一地址可以有两种表示形式,或者丢失范围查找。甚至可能最终不得不使其长度为 45 个字节或使用 varchar/varbinary。
  • 这方面的差异可以支持保留最初收到的地址。这可能很少需要,但当您失去很多好处时。
  • 删除完整格式的分隔符,并将其存储为十六进制字符串,以减少麻烦并提高效率。如果前缀索引很重要 (BINARY(128)),您可以长期使用。

BIGINT 无符号 * 2

优点:

  • 与数学运算符和函数一起使用,但需要注意的是必须围绕它做额外的事情,因为它是两列。
  • 高效,但需要注意的是,它是两列会增加一些开销。
  • 适用于基本索引(精确、范围)。
  • 前缀为 64 位时使用前缀索引。
  • 显示友好的格式。

缺点:

  • 两列使其成为非原子的,这意味着对它的大量操作加倍。

奇事:

  • 许多现代语言和系统提供 64 位整数,但不是无符号数。签名是有问题的。负数呈现为低于正数,但它们的位序列实际上更高。因此,通常使用 4 * INT UNSIGNED。
  • 同样,人们可能会将其分解为前缀索引,您至少可以达到 8 位 (TINYINT UNSIGNED)。有些人可能还会使用 BIT(1) 类型进行完整前缀索引,假设 MySQL 正确地在位类型上共同放置索引。
  • 同样,对于四列,由于计算期间的位松弛(计算中的中间值仍然可以是 64 位),一些需要诸如从另一列进行的操作具有讽刺意味的是更容易。

总结

人们会出于不同的原因使用不同的格式。向后兼容性可能是原因之一,这取决于为 IPv4 所做的工作。其他人则取决于地址的使用方式和围绕它的优化。您可能会看到使用了不止一种方法。

B16 是一种很好的默认方法,因为它是最高效且最轻松的。

对于 PHP 中的转换,如果您研究过,您可以手动进行:

  • gmp 或 bcmath
  • PHP 的数字处理和位运算符,请特别注意对 int 或 float 的限制以及依赖它们的函数,否则这些函数可能看起来很有用
  • IPv6 格式
  • 打包/解包,bin2hex/hex2bin。

不过,我建议使用通用库来处理 IPv6 的各种显示格式。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-25
    • 2011-03-27
    • 2015-09-22
    • 2013-11-16
    • 2018-05-01
    • 1970-01-01
    • 2013-03-10
    相关资源
    最近更新 更多