winsock2扩展的getaddrinfo()函数提供了一种与协议无关的地址获取和表示方法,地址结构中的内容都以网络字节的顺序表示。getaddrinfo()用结构体addrinfo来描述地址信息,下面看addrinfo的结构

Addressables 加载Group_IPV4

Addressables 加载Group_#define_02

1 typedef struct addrinfo {
 2   int             ai_flags;
 3   int             ai_family;
 4   int             ai_socktype;
 5   int             ai_protocol;
 6   size_t          ai_addrlen;
 7   char            *ai_canonname;
 8   struct sockaddr  *ai_addr;
 9   struct addrinfo  *ai_next;
10 } ADDRINFOA, *PADDRINFOA;

addrinfo结构体

  先不解释每个结构体成员所表示的含义。看getaddrinfo()函数的声明

Addressables 加载Group_IPV4

Addressables 加载Group_#define_02

1 int WSAAPI getaddrinfo(
2   _In_opt_  PCSTR pNodeName,
3   _In_opt_  PCSTR pServiceName,
4   _In_opt_  const ADDRINFOA *pHints,
5   _Out_     PADDRINFOA *ppResult
6 );

getaddrinfo()函数声明

  以上两个声明均是从MSDN上扣下来的。先看getaddrinfo()函数的参数,参数前的宏已经指明了前3个参数是传入参数,最后一个是存放输出结果的参数。

第1个参数pNodeName表示主机名(或节点名)或一串数字地址字符串,简而言之可以是本地计算机全名如“lenovo-PC”或者百度的域名“www.baidu.com”或用点分十进制表示的IPV4地址或用十六进制表示的IPV6地址,下面以某度为例演示:

Addressables 加载Group_MSDN_05

Addressables 加载Group_MSDN_06

第2个参数pServiceName存放服务名或者端口号,服务与端口号一一对应,服务是针对应用层而言,与下面的传输层、网络层、链路层无关,常用的服务包括http、ftp、telnet、domain等,对应关系可以在%WINDIR%\system32\drivers\etc\services这个文件中看到

Addressables 加载Group_MSDN_07

第3个参数pHints是ADDRINFOA类型的指针,ADDRINFOA是结构体addrinfo的别名。关于这个参数MSDN上说了一大堆巴拉巴拉的,简单来说,这个参数类似一个过滤器,通过给结构体成员赋值,过滤器会筛选出我们需要的数据。我们来看addrinfo结构体,关于这个结构体MSDN给出的说明又是巴拉巴拉一大堆,我只捡我理解的说,对其他有兴趣的可以去http://msdn.microsoft.com/en-us/library/windows/desktop/ms738520(v=vs.85).aspx看看。第一个参数ai_flags我不多说,MSDN关于它的说明超多,自己看。第二个参数ai_family表示协议族,AF_INET表示IPV4;AF_INET6表示IPV6;AF_NETBIOS我也不知道具体是啥,反正有这么一种,稍后再看;AF_UNSPEC和PF_UNSPEC表示都IPV4和IPV6都可以返回。第三人任何协议都不接受。第三个参数ai_socktype表示接受的数据类型,0表示任何类型都接受。在WinSock2.h中可以看到它的宏定义

1 #define SOCK_STREAM     1               /* stream socket */
2 #define SOCK_DGRAM      2               /* datagram socket */
3 #define SOCK_RAW        3               /* raw-protocol interface */
4 #define SOCK_RDM        4               /* reliably-delivered message */
5 #define SOCK_SEQPACKET  5               /* sequenced packet stream */

第四个参数表示支持的传输层协议类型,IPPROTO_TCP接受TCP,IPPROTO_UDP接受UDP,0表示都接受。后四个参数用来存放返回的地址信息。

第4个参数ppResult是PADDRINFOA类型的指针,即addrinfo类型的二重指针。用来存放返回结果。返回的地址常常是一个链表,如上面实验中获得了某度的两个IP地址115.239.211.110和115.239.210.27。

准备知识普及完了,下面来看具体如何利用getaddrinfo()函数获得本地主机或远端域名的IP。

首先初始化ws2_32.dll

1  iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
2     if (iResult != 0) {
3         printf("WSAStartup failed: %d\n", iResult);
4         return 1;
5     }

填写过滤信息到hints中

1     ZeroMemory( &hints, sizeof(hints) );
2     hints.ai_family = AF_UNSPEC;
3     hints.ai_socktype = SOCK_STREAM;
4     hints.ai_protocol = IPPROTO_TCP;

调用getaddrinfo()函数获得地址信息

1 dwRetval = getaddrinfo(argv[1], argv[2], &hints, &result);
2     if ( dwRetval != 0 ) {
3         printf("getaddrinfo failed with error: %d\n", dwRetval);
4         WSACleanup();
5         return 1;
6     }

然后是有意思的部分,对返回信息进行解读。

如果是IPV4,返回的IP地址存放在addrinfo结构体的成员变量ai_addr中,首先做一个判断,如果是IPV4,由于struct sockaddr和struct sockaddr_in的大小均是14字节,用强制类型转换将struct sockaddr *ai_addr转换成IPV4的地址结构struct sockaddr_in *ai_addr。然后调用inet_ntoa()函数将结果转换为点分十进制表示的IP地址形式

1 case AF_INET:
2                 printf("AF_INET (IPv4)\n");
3                 sockaddr_ipv4 = (struct sockaddr_in *) ptr->ai_addr;
4                 printf("\tIPv4 address %s\n",
5                     inet_ntoa(sockaddr_ipv4->sin_addr) );
6                 break;

如果是IPV6,在这里不详细说了,以后再讨论,直接上代码

1 sockaddr_ip = (LPSOCKADDR) ptr->ai_addr;
 2                 // The buffer length is changed by each call to WSAAddresstoString
 3                 // So we need to set it for each iteration through the loop for safety
 4                 ipbufferlength = 46;
 5                 iRetval = WSAAddressToString(sockaddr_ip, (DWORD) ptr->ai_addrlen, NULL, 
 6                     ipstringbuffer, &ipbufferlength );
 7                 if (iRetval)
 8                     printf("WSAAddressToString failed with %u\n", WSAGetLastError() );
 9                 else    
10                     printf("\tIPv6 address %s\n", ipstringbuffer);

完整代码在MSDN上都有,这里就不贴了。