gethostbyname和getaddrinfo都是做地址解析的,但是二者表现却有所差异,一定程度上来讲,getaddrinfo是意欲取代gethostbyname的,似乎这些和IPv6没有太大关系,但是gethostbyname中没有关于IPv6的逻辑,而getaddrinfo中是有关于IPv6的逻辑的;在php中实现php_network_getaddresses 时,判断了是否支持 getaddrinfo,如果支持则使用getaddrinfo,否则使用gethostbyname。
如果使用了getaddrinfo,当一个域名可以解析出过个ip地址时,则会根据rfc3484的说明来返回一个地址。而且如果使用strace跟踪的话,会发现多个ip地址都会使用数据包的socket类型connect一下(见西面说明)。
遇到的问题是:
1. getaddrinfo的实现可能会出现死锁的情况。
2. 如果一个域名可以返回多个IP,在php中,使用gethostbyname则根据dns返回的顺序,返回第一个ip(gethostbynamel()函数会返回所有的ip),参看php_gethostbyname(…)函数; 而在file_get_contents、fsockopen等函数中的域名解析则可能(编译时会自动检查是否支持,参看configure.in,搜索getaddrinfo,相关预定义常量HAVE_GETADDRINFO)会总使用同一个ip,其ip选择策略参看 rfc3484. 具体的算法实现参考glibc/sysdeps/posix/getaddrinfo.c 中的getaddrinfo()函数和rfc3484_sort(…)函数;这个就给负载均衡带来了麻烦
3. 对于域名返回多个IP地址时,使用getaddrinfo()函数会出现多次的数据包类型的connect()操作,如下:
socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr(“172.16.139.229”)}, 16) = 0
getsockname(3, {sa_family=AF_INET, sin_port=htons(54610), sin_addr=inet_addr(“10.49.4.65”)}, [16]) = 0
socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr(“10.49.4.245”)}, 16) = 0
getsockname(3, {sa_family=AF_INET, sin_port=htons(54610), sin_addr=inet_addr(“10.49.4.65”)}, [16]) = 0
socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr(“10.77.7.251”)}, 16) = 0
getsockname(3, {sa_family=AF_INET, sin_port=htons(54610), sin_addr=inet_addr(“10.49.4.65”)}, [16]) = 0
为什么要connect呢?
这个也是因为域名返回了多个IP地址,为了决定返回哪一个IP地址,要根据rfc3484的算法来返回;而rfc3484算法是在计算源ip和目标ip之间的距离的,对于多接口的主机来讲,目的ip不同则源ip也可能不同(上面例子中的源ip是相同的),所以就借用了connect来获取一下如果要去往某个目的ip,使用的源ip会是什么,这里使用了数据包类型的socket做连接,这样不会产生一次真正的连接,也就是说,connect是为getsockname做准备的。
4. 不知道PHP在什么时候已经默认支持IPv6了(至少php5.3.3已经这样了),当然必须支持getaddrinfo才能启用IPv6的一些逻辑,如果编译时支持了IPv6,但是运行环境中没有启用IPv6,会在访问http资源的时候,使用strace会发现如下一次系统调用:
socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP) = -1 EAFNOSUPPORT (Address family not supported by protocol)
这个是在检查是否支持IPv6.
相关代码参看:main/network.c中的函数 php_network_getaddresses(…)
5. 关于gethostbyname的glibc实现参看: glibc/resolv/gethnamaddr.c
或许你无法通过ctag的方式跳转到 gethostbyname定义的地方,是因为该函数定义的前面有一个奇怪的宏: libresolv_hidden_proto (gethostbyname2) , 因为后面没有分号,所以, gethostbyname就被隐藏了:

6. 关于使用getaddrinfo不能做到dns轮询的解决办法
使用gethostbyname来解析出ip,如:
1 2 3 4 5 6 7 8 9 10 11 |
<?php function http($url) { $curl = curl_init(); $arr = parse_url($url); $host = $arr["host"]; $ip = gethostbyname($host); $url = preg_replace("#^http([s]?)://$host#", "http\$1://$ip", $url); curl_setopt($curl, CURLOPT_HTTPHEADER, "Host: $host"); ... } |
(gdb) bt
#0 0x00000031a58d4590 in getsockname () from /lib64/libc.so.6
#1 0x00000031a58f4eb1 in __check_pf () from /lib64/libc.so.6 // __check_pf 是check protol family的意思
#2 0x00000031a58bd744 in getaddrinfo () from /lib64/libc.so.6
#3 0x00000031a642eb70 in Curl_getaddrinfo () from /usr/lib64/libcurl.so.3
#4 0x00000031a640be2d in Curl_resolv () from /usr/lib64/libcurl.so.3
#5 0x00000031a641a7ce in Curl_connect () from /usr/lib64/libcurl.so.3
#6 0x00000031a6425fd1 in ?? () from /usr/lib64/libcurl.so.3
#7 0x00000031a642814a in Curl_perform () from /usr/lib64/libcurl.so.3
#8 0x0000000000406adc in ?? ()
#9 0x00000031a581d994 in __libc_start_main () from /lib64/libc.so.6
#10 0x0000000000401de9 in ?? ()
#11 0x00007fffea5fec38 in ?? ()
#12 0x0000000000000000 in ?? ()