设置 DNS 解析超时和重试

网络编程中经常使用gethostbynamegetaddrinfo来实现域名解析,这两个C函数并未提供超时参数。实际上可以修改/etc/resolv.conf来设置超时和重试逻辑。

可参考man resolv.conf文档

多个 NameServer

nameserver 192.168.1.3
nameserver 192.168.1.5
option rotate

可配置多个nameserver,底层会自动轮询,在第一个nameserver查询失败时会自动切换为第二个nameserver进行重试。

option rotate配置的作用是,进行nameserver负载均衡,使用轮询模式。

超时控制

option timeout:1 attempts:2
  • timeout:控制UDP接收的超时时间,单位为秒,默认为5
  • attempts:控制尝试的次数,配置为2时表示,最多尝试2次,默认为5

假设有2nameserverattempts2,超时为1,那么如果所有DNS服务器无响应的情况下,最长等待时间为4秒(2x2x1)。

调用跟踪

可使用strace跟踪确认。

nameserver设置为两个不存在的IPPHP代码使用var_dump(gethostbyname('www.baidu.com'));解析域名。

socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.20.128.16")}, 16) = 0
poll([{fd=3, events=POLLOUT}], 1, 0)    = 1 ([{fd=3, revents=POLLOUT}])
sendto(3, "\346\5\1\0\0\1\0\0\0\0\0\0\3www\5baidu\3com\0\0\1\0\1", 31, MSG_NOSIGNAL, NULL, 0) = 31
poll([{fd=3, events=POLLIN}], 1, 1000

)  = 0 (Timeout)
socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 4
connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.20.128.18")}, 16) = 0
poll([{fd=4, events=POLLOUT}], 1, 0)    = 1 ([{fd=4, revents=POLLOUT}])
sendto(4, "\346\5\1\0\0\1\0\0\0\0\0\0\3www\5baidu\3com\0\0\1\0\1", 31, MSG_NOSIGNAL, NULL, 0) = 31
poll([{fd=4, events=POLLIN}], 1, 1000


)  = 0 (Timeout)
poll([{fd=3, events=POLLOUT}], 1, 0)    = 1 ([{fd=3, revents=POLLOUT}])
sendto(3, "\346\5\1\0\0\1\0\0\0\0\0\0\3www\5baidu\3com\0\0\1\0\1", 31, MSG_NOSIGNAL, NULL, 0) = 31
poll([{fd=3, events=POLLIN}], 1, 1000


)  = 0 (Timeout)
poll([{fd=4, events=POLLOUT}], 1, 0)    = 1 ([{fd=4, revents=POLLOUT}])
sendto(4, "\346\5\1\0\0\1\0\0\0\0\0\0\3www\5baidu\3com\0\0\1\0\1", 31, MSG_NOSIGNAL, NULL, 0) = 31
poll([{fd=4, events=POLLIN}], 1, 1000



)  = 0 (Timeout)
close(3)                                = 0
close(4)                                = 0

可以看到这里一共重试了4次,poll调用超时设置为1000ms1秒)。