ip2long+php耗性能,PHP: 详解ip2long和long2ip

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

右移

逻辑右移

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

算术右移

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

注意事项

对于C来说,只提供了>>右移运算符,究竟是逻辑右移还是算术右移这取决于编译器的行为,因此一般只提倡对无符号数进行位操作。

IPv4地址是如何表示的

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

IPv4地址转换成无符号整型

6d7e1f9afa62a0c8d3fa3bf1e464bd8d.png

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

#include

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

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方法,因此很容易解决出现负数的问题:

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

$ php -f test.php

2635544666

JavaScript是如何做的

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

console.log(((157 << 24) | (23 << 16) | (56 << 8) | 90) >>> 0);

2635544666

PHP和JavaScript的long2ip的实现

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

PHP的long2ip的实现

$ip_long = 2635544666;

echo long2ip($ip_long) . "\n";

php -f test.php

157.23.56.90

以上代码是由PHP的内置函数long2ip来实现的。但是对于想通过移位来自己实现的童鞋来说,可能没有那么简单。因为PHP的>>运算符是算术右移运算符,所以如果最高位是1的话,右移的结果是在高位补1,这跟结果不符。但是我们可以用另一种思路去解决:保存最高位(符号位),然后将最高位置0,之后再将高8位的最高位置1(这取决于之前保存的符号位)。代码实现如下:

$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位,这样做的好处是兼容性好。代码如下:

$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的实现

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);

157.23.56.90

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`IPAddress` 是 ESP32 Arduino 中用于表示 IP 地址的类。它提供了一些接口函数来处理 IP 地址。以下是一些常用的接口函数: 1. `IPAddress()`:默认构造函数,创建一个空的 IP 地址对象。 2. `IPAddress(byte, byte, byte, byte)`:创建一个新的 IP 地址对象,使用四个字节的值来表示 IP 地址。 3. `IPAddress(unsigned long)`:创建一个新的 IP 地址对象,使用一个 32 位无符号整数来表示 IP 地址。 4. `IPAddress(const uint8_t*)`:创建一个新的 IP 地址对象,使用一个指向 4 个字节的数组的指针来表示 IP 地址。 5. `String toString() const`:将 IP 地址转换为字符串形式并返回。 6. `operator[]`:用于访问 IP 地址中的字节。 7. `operator uint32_t()`:将 IP 地址转换为 32 位无符号整数并返回。 例如,以下代码演示了如何使用 `IPAddress` 类来创建和处理 IP 地址: ```c++ #include <WiFi.h> IPAddress ip(192, 168, 0, 1); Serial.println(ip.toString()); byte ipArray[] = {192, 168, 1, 1}; IPAddress ip1(ipArray); Serial.println(ip1[0]); Serial.println(ip1.operator uint32_t()); ``` 在这个例子中,我们首先创建了一个 `IPAddress` 对象 `ip`,它表示 IP 地址 192.168.0.1。然后,我们使用 `toString()` 函数将其转换为字符串形式并打印出来。接下来,我们创建了一个字节数组 `ipArray`,它包含 IP 地址 192.168.1.1 的四个字节。我们使用这个数组来创建了另一个 `IPAddress` 对象 `ip1`,然后使用 `operator[]` 函数访问该对象中的字节并打印出来。最后,我们使用 `operator uint32_t()` 函数将 `ip1` 转换为 32 位无符号整数并打印出来。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值