winsock2扩展的getaddrinfo()函数提供了一种与协议无关的地址获取和表示方法,地址结构中的内容都以网络字节的顺序表示。getaddrinfo()用结构体addrinfo来描述地址信息,下面看addrinfo的结构
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;
先不解释每个结构体成员所表示的含义。看getaddrinfo()函数的声明
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 );
以上两个声明均是从MSDN上扣下来的。先看getaddrinfo()函数的参数,参数前的宏已经指明了前3个参数是传入参数,最后一个是存放输出结果的参数。
第1个参数pNodeName表示主机名(或节点名)或一串数字地址字符串,简而言之可以是本地计算机全名如“lenovo-PC”或者百度的域名“www.baidu.com”或用点分十进制表示的IPV4地址或用十六进制表示的IPV6地址,下面以某度为例演示:
第2个参数pServiceName存放服务名或者端口号,服务与端口号一一对应,服务是针对应用层而言,与下面的传输层、网络层、链路层无关,常用的服务包括http、ftp、telnet、domain等,对应关系可以在%WINDIR%\system32\drivers\etc\services这个文件中看到
第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表示都不接受。第三人任何协议都不接受。第三个参数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上都有,这里就不贴了。