前言
在网络编程中,有时需要通过域名或者主机名来获取IP地址。
以为通常使用gethostbyname() API。
但是今天碰到了一个BUG,使我觉得应该在有这种需求的时候,使用getaddrinfo()而不是gethostbyname()。
BUG描述
我在ubuntu虚拟机中,通过gethostbyname(),传入参数为主机名,想要获取主机的IP地址。
gethostbyname()返回的struct hostent指针不为NULL,但是只要访问struct hostent指针中的任何成员,都会段错误。
对于这个段错误我没有找到原因。
如果gethostbyname()无法获取主机名对应的地址信息,为什么不返回NULL,现在返回了指针却不能访问,
如果在真实项目中,岂不是GG。
之前我遇到过虚拟机ping不通主机的情况,把主机的防火墙关掉就行了。
这次即使关了主机的防火墙,还是一直段错误。
如果参数是百度的域名,就可以正常访问返回的指针。
这里,我很不明白的点,不在与无法获取主机地址,而在于为什么gethostbyname()返回的非空指针会段错误。
对策
将gethostbyname()改为使用getaddrinfo(),再传入主机名,会返回一个错误:
此处,我认为,API返回一个错误,没有问题,说明我哪里的配置有问题。
但是,它没有段错误,这让程序更可靠了。
基于这个原因,要使用getaddrinfo()获取地址。
代码
为了便于阅读,我将一个函数中的代码,拆分成几段。
这里我本想用gethostname()获取自己的hostname,然后再根据hostname获取自己的IP地址的
char buff[MIN_BUFFSISE] = {0x00}; sockaddr_in local_addr, addr; socklen_t len = sizeof(sockaddr_in); // local_addr.sin_addr.s_addr = htonl(INADDR_ANY); char local_host_name[256] = { 0x00 }; if(gethostname(local_host_name, 256)) { perror("gethostname"); return -1; } std::cout << "gethostname:" << local_host_name << std::endl;
使用getaddrinfo()通过主机名获取主机IP,报错,没有段错误
代码参考:getaddrinfo函数实现域名解析
int rv; struct addrinfo hints; struct addrinfo *ai_list = NULL, *tmp_ptr = NULL; struct sockaddr_in *sin; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; // rv = getaddrinfo(local_host_name, NULL, &hints, &ai_list); rv = getaddrinfo("LAPTOP-6UC841M5", NULL, &hints, &ai_list); //这里我尝试用主机名获取主机地址,失败了,并报错,程序没有崩溃 // rv = getaddrinfo("192", NULL, &hints, &ai_list); if (rv != 0) { std::cout << "error:" << gai_strerror(rv) << std::endl; return -1; } sin = (struct sockaddr_in *)ai_list->ai_addr; std::cout << inet_ntoa(sin->sin_addr) << std::endl; tmp_ptr = ai_list; while (tmp_ptr->ai_next != NULL) { tmp_ptr = tmp_ptr->ai_next; sin = (struct sockaddr_in *)tmp_ptr->ai_addr; std::cout << inet_ntoa(sin->sin_addr) << std::endl; }
使用gethostbyname()同过主机名获取主机IP,没有报错,但是段错误
#if 0 //使用gethostbyname() // struct hostent *phost = gethostbyname(local_host_name); struct hostent *phost = gethostbyname("LAPTOP-6UC841M5"); // struct hostent *phost = gethostbyname("www.baidu.com"); std::cout << "1:" << std::endl; strerror(h_errno); if(phost == NULL) //这里是可以顺利同过的,指针非空 { strerror(h_errno); } std::cout << "2:" << std::endl; //从这之后,任何对指针成员的访问,都会段错误 if(phost->h_name == NULL) std::cout << "phost->h_name is NULL " << std::endl; std::cout << phost->h_length << std::endl; std::cout << "3:" << std::endl; int i = 0; for (i = 0;; i++) { std::cout << inet_ntoa(*(in_addr *)phost->h_addr_list[i]) << std::endl; if (phost->h_addr_list[i] + phost->h_length >= phost->h_name) break; } // while (phost->h_addr_list[i] != 0) // { // addr.sin_addr.s_addr = *(uint32_t *)phost->h_addr_list[i++]; // std::cout << "gethostbyname:" << inet_ntoa(addr.sin_addr) << std::endl; // } #endif getchar();
更多应该使用getaddrinfo()来代替gethostbyname()的原因
- gethostbyname 函数只能返回 name 参数的 IPv4 地址。而getaddrinfo 函数可以返回IPv4和IPv6地址。
- gethostbyname 函数已经被废弃,应该有限使用getaddrinfo函数。
The gethostbyname function has been deprecated by the introduction of the getaddrinfo function. - 多线程
在多线程下面,gethostbyname会一个更严重的问题,就是如果有一个线程的gethostbyname发生阻塞,其它线程都会在gethostbyname处发生阻塞。
getaddrinfo在linux等平台是线程安全的(某些平台可能不是)。
其余参考
getaddrinfo()函数使用详解以及注意事项