1. 问题现象
在连接上网络的情况下,使用 linux 命令 nslookup 查看域名,可以正常解析到对应的IP地址,但在代码中调用 gethostbyname 函数返回失败。
2. 问题原因
2.1 打印错误信息
当 gethostbyname 发生错误时,它不设置 errno 变量,而是将全局变量 h_errno 设置为<netdb.h>中定义的常值之一。
h_errno 是在 C 语言中用于存储 DNS 查询错误代码的全局变量。当使用 gethostbyname 或 gethostbyaddr 等函数进行域名解析时,如果发生错误,h_errno 会被设置为一个错误代码,这个代码可以用来确定错误的类型,以下是一些常见的 h_errno 错误代码及其含义:
- (1)HOST_NOT_FOUND:没有找到主机(域名无法解析)。
- (2)TRY_AGAIN:再次尝试(通常是临时性错误,如网络问题)。
- (3)NO_RECOVERY:不可恢复的错误(如格式错误)。
- (4)NO_DATA(等同于NO_ADDRESS):没有数据记录(域名解析没有返回任何数据)。
注:前面的编号表示错误码号。
h_errno需要使用 herror 打印,可通过 hstrerror 查看错误码对应的详细信息:
herror("gethostbyname failed");
printf("gethostbyname failed, id is %d, %s.\n", h_errno, hstrerror(h_errno));
博主这里调用 h_errno 打印的错误信息为:Host name lookup failure。
报该错误的原因可能是代码中有其它地方更新了DNS解析缓存,导致应用程序的网络不通。
注:可通过仅重启该应用程序来确定是否为该原因;如果应用程序重启后 gethostbyname 解析成功了,即可定位为该问题。
2.2 根因分析
gethostbyname 这个DNS解析器系统调用,其查询的信息来自于系统DNS解析缓存( 库函数只读一次存储dns的文件,放到缓存里);当之前存在过错误的解析或者说解析错误时,系统DNS解析缓存不会被更新,因此之后再调用就会一直失败。
因此当DNS SERVER信息更新后,gethostbyname就会解析不出地址了。
3. 解决措施
更新解析缓存。当出现 gethostbyname 调用失败时,使用 res_init() 函数来更新DNS缓存,然后再调用 gethostbyname。
注: res_init 函数用来初始化 DNS 解析库,成功返回0,失败返回-1。
4. 例程
#include <stdio.h>
#include <netdb.h>
#include <resolv.h>
int test(const char* hostname)
{
struct hostent* host = gethostbyname(hostname);
if (NULL == host) {
herror("gethostbyname failed");
//printf("gethostbyname failed, id is %d, %s.\n", h_errno, hstrerror(h_errno));
if (TRY_AGAIN == h_errno) {
if (res_init() != 0) {
printf("res_init error\n");
return -2;
} else {
printf("Init DNS configuration again\n");
return 1;
}
} else {
return -1;
}
}
return 0;
}
void main(void)
{
int ret;
const char* hostName = "www.baidu.com";
ret = test(hostName);
if (ret > 0) {
ret = test(hostName); //重读一次.
if (0 == ret) {
printf("gethostbyname Pass after one retry.\n");
} else {
printf("gethostbyname still fail after one retry.\n");
}
} else if (0 == ret) {
printf("gethostbyname One-time pass.\n");
} else {
printf("gethostbyname unknow error\n");
}
return;
}