Php 如何将IPv6从二进制转换为MySQL中的存储

Php 如何将IPv6从二进制转换为MySQL中的存储,php,mysql,ipv6,Php,Mysql,Ipv6,我试图以一种高效的方式在MySQL 5.0中存储IPv6地址。我已经阅读了与此相关的其他问题。这个问题的作者最终选择了两个BIGINT字段。我的搜索还发现了另一种常用的机制:使用十进制(39,0)存储IPv6地址。关于这一点,我有两个问题 与其他方法(如2*BIGINT)相比,使用十进制(39,0)有哪些优点和缺点 如何将(在PHP中)从返回的二进制格式转换为MySQL可用的十进制字符串格式,以及如何转换回使用inet_ntop()进行打印 下面是我现在用来将IP地址从和转换为十进制(39,0)

我试图以一种高效的方式在MySQL 5.0中存储IPv6地址。我已经阅读了与此相关的其他问题。这个问题的作者最终选择了两个BIGINT字段。我的搜索还发现了另一种常用的机制:使用十进制(39,0)存储IPv6地址。关于这一点,我有两个问题

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

  • 下面是我现在用来将IP地址从和转换为十进制(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;
    }
    
    /**
    *将IP地址从表示转换为适合存储在MySQL中的十进制(39,0)格式
    *
    *@param string$ip_address IPv4、IPv6或十进制表示法中的ip地址
    *@return string以十进制表示的IP地址
    */
    函数inet\u ptod($ip\u地址)
    {
    //IPv4地址
    if(strpos($ip地址,,:')==false&&strpos($ip地址,,)!==false){
    $ip地址='::'。$ip地址;
    }
    //IPv6地址
    if(strpos($ip_地址,,:')!==false){
    $network=inet\u pton($ip\u地址);
    $parts=unpack('N*',$network);
    foreach($parts as&$part){
    如果($part<0){
    $part=bcadd((字符串)$part,'4294967296');
    }
    如果(!是字符串($part)){
    $part=(字符串)$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'));
    返回$decimal;
    }
    //十进制地址
    返回$ip_地址;
    }
    /**
    *将IP地址从十进制格式转换为表示格式
    *
    *@param string$decimal IPv4、IPv6或十进制表示法中的IP地址
    *@return string表示格式的IP地址
    */
    函数inet\u dtop($decimal)
    {
    //IPv4或IPv6格式
    if(strpos($decimal,,:')!==false | | strpos($decimal,,)!==false){
    返回$decimal;
    }
    //十进制格式
    $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){
    如果(bccomp($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);
    //如果是IPv4,则将IPv6转换为IPv4
    if(preg_match('/^::\d+。\d+。\d+。\d+。$d+,$ip地址)){
    返回substr($ip_地址,2);
    }
    返回$ip_地址;
    }
    
    我们选择了一个
    VARBINARY(16)
    列,使用and进行转换:

    这些函数可以加载到正在运行的MySQL服务器中,并在SQL中为您提供
    INET6\u NTOP
    INET6\u PTON
    ,就像熟悉的IPv4的
    INET\u NTOA
    INET\u ATON
    函数一样

    编辑:MySQL中现在有兼容的函数,只需使用。如果您使用的是5.6版本之前的MySQL,并且正在寻找方便的未来升级路径,请仅使用上述选项。

    DECIMAL(39)

    优点:

    • 使用基本算术运算符(如+和-)
    • 使用基本索引(精确或范围)
    • 格式是显示友好
    缺点:

    • 可以接受IPv6的超出范围的值
    • 不是一种非常有效的存储机制
    • 可能导致混淆哪些数学运算符或函数起作用,哪些不起作用
    二进制(16).

    优点:

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

    • 不适合展示
    • 对用于数字的运算符或函数不友好
    二进制(39).

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

    优点:

    • 人类可读(如果你可以称之为IPv6)
    • 支持基本索引(精确和范围)
    • 支持4位的倍数的前缀索引
    • 直接兼容IPv6。不需要转换
    缺点:

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

    • 如果需要不区分大小写之类的内容,则会变得复杂
    • IPv6还有其他显示格式,尽管使用这些格式会导致更复杂的问题,例如同一地址可能有两个表示形式,或者丢失范围查找。甚至可能最终不得不将其设置为45字节长或使用varchar/varbinary
    • 这种差异可以支持保留最初收到的地址。这可能很少是你想要的,但当你失去了很多