PHP: 详解ip2long和long2ip

在开发中,常常须要将IP地址转成整型进行保存,这样不只有利于作索引,而且本来须要15个字节的存储空间,转换后只需4个字节就能存储了。可是不少人对于ip2long的结果有时候是负数并不理解,本文将详细解释这一点。由于ip2long只支持IPv4,因此本文也是基于IPv4来描述和编码的。javascript

右移

逻辑右移

右移多少位,则在高位补多少位0。
php

算术右移

对无符号数作算术右移和逻辑右移的结果是相同的。可是对一个有符号数作算术右移,则右移多少位,即在高位补多少位1。java

注意事项

对于C来讲,只提供了>>右移运算符,到底是逻辑右移仍是算术右移这取决于编译器的行为,所以通常只提倡对无符号数进行位操做。
shell

IPv4地址是如何表示的

IPv4使用无符号32位地址,所以最多有2的32次方减1(4294967295)个地址。通常的书写法为用4个小数点分开的十进制数,记为:A.B.C.D,好比:157.23.56.90。函数

IPv4地址转换成无符号整型

IPv4地址的每个十进制数都为无符号的字节,所以范围在0~255,将IPv4地址转成无符号整型其实就是将每一个十进制数放在对应的8位上组成一个4字节的无符号整型。依上图表示:157在高8位,90在低8位,23和56在中间对应的8位上。来看一个C实现的例子:ui

#include <stdio.h>

int main(int argc, char** argv)
{
	unsigned int ip_long = (157 << 24) | (23 << 16) | (56 << 8) | 90;
	printf("%u\n", ip_long);
	printf("%d\n", ip_long);

	return 0;
}

$ gcc -o ip2long main.c
$ ./ip2long
2635544666
-1659422630

能够看到,即便ip_long声明为无符号整型,在输出时也须要指明%u来格式化输出为无符号整型。这是由于157大于127(二进制为01111111),也就是说若是157(8位)用二进制来表示,最高位必然是1。当将157放在一个4字节整型的高8位时,致使这个4字节整型的最高位为1。虽然ip_long定义为无符号整型,但printf函数并不知道,所以须要指明无符号格式化字符。若是最高位为0,则使用%d就能够了,来看另外一个例子:编码

#include <stdio.h>

int main(int argc, char** argv)
{
	unsigned int ip_long = (120 << 24) | (23 << 16) | (56 << 8) | 90;
	printf("%u\n", ip_long);
	printf("%d\n", ip_long);

	return 0;
}

$ gcc -o ip2long main.c
$ ./ip2long
2014787674
2014787674

PHP是如何作的

如今已经知道了为何会出现负数。对于动态类型语言来讲,数据类型通常是有符号的,因此须要咱们本身转成无符号整型。PHP有内置函数ip2long来将IPv4地址转换成整型,也提供了类C的sprintf方法,所以很容易解决出现负数的问题:spa

<?php
echo sprintf("%u\n", ip2long("157.23.56.90"));

$ php -f test.php
2635544666

JavaScript是如何作的

JavaScript既没有提供ip2long方法,也没有提供类C的格式化函数。但JavaScript却同时提供了逻辑右移(>>>)和算术右移(>>)运算符,因此解决的方法也很简单,对结果再跟0作逻辑右移便可:code

<script type="text/javascript">
console.log(((157 << 24) | (23 << 16) | (56 << 8) | 90) >>> 0);
</script>

2635544666

PHP和JavaScript的long2ip的实现

有了前面的知识,long2ip的实现就很简单了。只须从ip2long的结果中取出每8位造成的十进制数,再用点(.)链接就能够了。以前的例子都是用IP(157.23.56.90)来举例的,它的ip2long的结果是:2635544666。索引

PHP的long2ip的实现

<?php
$ip_long = 2635544666;
echo long2ip($ip_long) . "\n";

php -f test.php
157.23.56.90

以上代码是由PHP的内置函数long2ip来实现的。可是对于想经过移位来本身实现的童鞋来讲,可能没有那么简单。由于PHP的>>运算符是算术右移运算符,因此若是最高位是1的话,右移的结果是在高位补1,这跟结果不符。可是咱们能够用另外一种思路去解决:保存最高位(符号位),而后将最高位置0,以后再将高8位的最高位置1(这取决于以前保存的符号位)。代码实现以下:

<?php
$ip_long = 2635544666;
// 保存最高位(符号位)
$msb = 0;
if ($ip_long & 0x80000000)
{
	$msb = 1;
}

// 将最高位(符号位)置0变成无符号数
$uip_long = $ip_long & 0x7fffffff;
$ip1 = $uip_long >> 24;
if ($msb == 1)
{
	$ip1 |= 0x80;
}

$ip2 = ($uip_long >> 16) & 0xff; // 跟0xff作与运算的目的是取低8位
$ip3 = ($uip_long >> 8) & 0xff;
$ip4 = $uip_long & 0xff;
echo $ip1 . '.' . $ip2 . '.' . $ip3 . '.' . $ip4 . "\n";

$ php -f test.php
157.23.56.90

虽然以上代码能获得正确的结果,可是并不推荐这样作。由于以上代码是假设PHP中的数据类型是32位的。这样将会有移植性问题。咱们能够跟0xff作与运算来取得低8位,这样作的好处是兼容性好。代码以下:

<?php
$ip_long = 2635544666;
$ip1 = ($ip_long >> 24) & 0xff; // 跟0xff作与运算的目的是取低8位
$ip2 = ($ip_long >> 16) & 0xff;
$ip3 = ($ip_long >> 8) & 0xff;
$ip4 = $ip_long & 0xff;
echo $ip1 . '.' . $ip2 . '.' . $ip3 . '.' . $ip4 . "\n";

$ php -f test.php
157.23.56.90

另外还能够经过pack和unpack方法来实现,但要注意的是IPv4应使用大端序。

JavaScript的long2ip的实现

<script type="text/javascript">
var ip_long = 2635544666;
var ip1 = (ip_long >> 24) & 0xff;
var ip2 = (ip_long >> 16) & 0xff;
var ip3 = (ip_long >> 8) & 0xff;
var ip4 = ip_long & 0xff;
console.log(ip1 + "." + ip2 + "." + ip3 + "." + ip4);
</script>

157.23.56.90

知其然,而知其因此然。这样之后就不会为ip2long的结果是负数而感到惊讶了。

相关文章
相关标签/搜索