[网络编程] 应该使用getaddrinfo()来代替gethostbyname()

前言

在网络编程中,有时需要通过域名或者主机名来获取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()的原因

  1. gethostbyname 函数只能返回 name 参数的 IPv4 地址。而getaddrinfo 函数可以返回IPv4和IPv6地址。
  2. gethostbyname 函数已经被废弃,应该有限使用getaddrinfo函数。
    The gethostbyname function has been deprecated by the introduction of the getaddrinfo function.
  3. 多线程
    在多线程下面,gethostbyname会一个更严重的问题,就是如果有一个线程的gethostbyname发生阻塞,其它线程都会在gethostbyname处发生阻塞。
    getaddrinfo在linux等平台是线程安全的(某些平台可能不是)。

其余参考

getaddrinfo()函数使用详解以及注意事项