IP
入门。
很多语言都内置了很多功能用来处理 IP,比如 Java 中就有InetAddress
。
jshell> java.net.InetAddress.getByName("192.168.0.1")
$1 ==> /192.168.0.1
注意如果数字前有占位的0
,这个 IP 也是合法的:
jshell> java.net.InetAddress.getByName("18.032.4.011")
$2 ==> /18.32.4.11
放弃?
用 C/C++试试?毕竟很多低层的东西是用 C 写的。inet_aton
可以把一个struct in_addr
转换成 dots-and-numbers 格式,而inet_ntoa
则正好相反,所以连用这两个转换应该得到原来的 IP:
#include <iostream>
#include <arpa/inet.h>
int main() {
struct in_addr a;
inet_aton("18.032.4.011", &a);
std::cout << inet_ntoa(a) << std::endl;
return 0;
}
打印的结果竟然是18.26.4.9
,而不是18.32.4.11
!
进阶!
这里的关键问题在于,C 看到数字前面的0
的时候,会把这个数当成八进制 (octal number),所以八进制的032
就是十进制的26
,八进制的011
就是十进制的9
.
在 Go 当中也有相应的函数net.ParseIP()
。
package main
import (
"fmt"
"net"
)
func main() {
fmt.Println(net.ParseIP("18.032.4.011"))
}
有趣的是,即使都是 Go1(理论上应该兼容),1.16 之前和 1.17 之后的结果却是不同的。假设你已经有了一个更现代版本的 go
,你可以再下载一个 1.16 版:
$ go install golang.org/dl/go1.16@latest
$ go1.16 download
这时你运行同样的上面的代码:
go1.16 run ip.go
18.32.4.11
$ go run ip.go
<nil>
1.16 之前是可以解析的,但和 Java 结果一致,和 C 结果不同。而 1.17 之后则缴械投降返回了 nil
。而这个改变正式因为 Go 团队发现了 Go 的结果与 C 的结果不一致,与其导致一些隐秘的 bug,干脆不允许带 0 了。(具体可参考后附的 GopherCon 2022 的视频。)
另一个躺平的语言是 Python,如果带 0
,直接返回ValueError: '18.032.4.011' does not appear to be an IPv4 or IPv6 address
:
>>> import ipaddress
>>> ipaddress.ip_address('192.168.0.1')
IPv4Address('192.168.0.1')
>>> ipaddress.ip_address('18.032.4.011')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/ipaddress.py", line 53, in ip_address
raise ValueError('%r does not appear to be an IPv4 or IPv6 address' %
ValueError: '18.032.4.011' does not appear to be an IPv4 or IPv6 address
参考
GopherCon 2022 https://www.youtube.com/watch?v=v24wrd3RwGo