在开发中,常常须要将IP地址转成整型进行保存,这样不只有利于作索引,而且本来须要15个字节的存储空间,转换后只需4个字节就能存储了。可是不少人对于ip2long的结果有时候是负数并不理解,本文将详细解释这一点。由于ip2long只支持IPv4,因此本文也是基于IPv4来描述和编码的。javascript
右移多少位,则在高位补多少位0。
php
对无符号数作算术右移和逻辑右移的结果是相同的。可是对一个有符号数作算术右移,则右移多少位,即在高位补多少位1。java
对于C来讲,只提供了>>右移运算符,到底是逻辑右移仍是算术右移这取决于编译器的行为,所以通常只提倡对无符号数进行位操做。
shell
IPv4使用无符号32位地址,所以最多有2的32次方减1(4294967295)个地址。通常的书写法为用4个小数点分开的十进制数,记为:A.B.C.D,好比:157.23.56.90。函数
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有内置函数ip2long来将IPv4地址转换成整型,也提供了类C的sprintf方法,所以很容易解决出现负数的问题:spa
<?php echo sprintf("%u\n", ip2long("157.23.56.90"));
$ php -f test.php 2635544666
JavaScript既没有提供ip2long方法,也没有提供类C的格式化函数。但JavaScript却同时提供了逻辑右移(>>>)和算术右移(>>)运算符,因此解决的方法也很简单,对结果再跟0作逻辑右移便可:code
<script type="text/javascript"> console.log(((157 << 24) | (23 << 16) | (56 << 8) | 90) >>> 0); </script>
2635544666
有了前面的知识,long2ip的实现就很简单了。只须从ip2long的结果中取出每8位造成的十进制数,再用点(.)链接就能够了。以前的例子都是用IP(157.23.56.90)来举例的,它的ip2long的结果是:2635544666。索引
<?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应使用大端序。
<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的结果是负数而感到惊讶了。