Php 如何将IPv6从二进制转换为MySQL中的存储
我试图以一种高效的方式在MySQL 5.0中存储IPv6地址。我已经阅读了与此相关的其他问题。这个问题的作者最终选择了两个BIGINT字段。我的搜索还发现了另一种常用的机制:使用十进制(39,0)存储IPv6地址。关于这一点,我有两个问题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)
下面是我现在用来将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的超出范围的值
- 不是一种非常有效的存储机制
- 可能导致混淆哪些数学运算符或函数起作用,哪些不起作用
- 最有效的精确表示格式
- 使用基本索引(精确和范围)
- 使用8位倍数前缀的前缀索引
- 仅存储有效的IPv6值(尽管不保证有效寻址)
- 在更高版本中,MySQL的函数支持这种格式与IPv6表示(但不是4in6)之间的转换
- 不适合展示
- 对用于数字的运算符或函数不友好
- 人类可读(如果你可以称之为IPv6)
- 支持基本索引(精确和范围)
- 支持4位的倍数的前缀索引
- 直接兼容IPv6。不需要转换
- 不适用于任何数学函数或运算符
- 最低效的存储
- 可以允许无效的表示
- 如果需要不区分大小写之类的内容,则会变得复杂
- IPv6还有其他显示格式,尽管使用这些格式会导致更复杂的问题,例如同一地址可能有两个表示形式,或者丢失范围查找。甚至可能最终不得不将其设置为45字节长或使用varchar/varbinary
- 这种差异可以支持保留最初收到的地址。这可能很少是你想要的,但当你失去了很多