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的結果是負數而感到驚訝了。

相關文章
相關標籤/搜索