addrinfo结构体的定义如下:
- struct addrinfo {
- int ai_flags; /* customize behavior */
- int ai_family; /* address family */
- int ai_socktype; /* socket type */
- int ai_protocol; /* protocol */
- socklen_t ai_addrlen; /* length in bytes of address */
- struct sockaddr *ai_addr; /* address */
- char *ai_canonname; /* canonical name of host */
- struct addrinfo *ai_next; /* next in list */
- .
- .
- .
- };
ai_family指定了地址族,可取值如下:
AF_INET 2 IPv4
AF_INET6 23 IPv6
AF_UNSPEC 0 协议无关
ai_socktype指定我套接字的类型
SOCK_STREAM 1 流
SOCK_DGRAM 2 数据报
在AF_INET通信域中套接字类型SOCK_STREAM的默认协议是TCP(传输控制协议)
在AF_INET通信域中套接字类型SOCK_DGRAM的默认协议是UDP(用户数据报协议)
ai_protocol指定协议类型。可取的值取决于ai_address和ai_socktype的值
ai_flags指定了如何来处理地址和名字,可取值如下:
getaddrinfo函数 定义及需要的头文件如下:
- #include <sys/socket.h>
- #include <netdb.h>
- int getaddrinfo(const char *restrict host,
- const char *restrict service,
- const struct addrinfo *restrict hint,
- struct addrinfo **restrict res);
- Returns: 0 if OK, nonzero error code on error
- void freeaddrinfo(struct addrinfo *ai);
getaddrinfo函数允许将一个主机名字和服务名字映射到一个地址。
使用示例如下:
- #include<stdio.h>
- #include<stdlib.h>
- #include <netdb.h>
- #include <arpa/inet.h>
- void
- print_family(struct addrinfo *aip)
- {
- printf(" family ");
- switch (aip->ai_family) {
- case AF_INET:
- printf("inet");
- break;
- case AF_INET6:
- printf("inet6");
- break;
- case AF_UNIX:
- printf("unix");
- break;
- case AF_UNSPEC:
- printf("unspecified");
- break;
- default:
- printf("unknown");
- }
- }
- void
- print_type(struct addrinfo *aip)
- {
- printf(" type ");
- switch (aip->ai_socktype) {
- case SOCK_STREAM:
- printf("stream");
- break;
- case SOCK_DGRAM:
- printf("datagram");
- break;
- case SOCK_SEQPACKET:
- printf("seqpacket");
- break;
- case SOCK_RAW:
- printf("raw");
- break;
- default:
- printf("unknown (%d)", aip->ai_socktype);
- }
- }
- void
- print_protocol(struct addrinfo *aip)
- {
- printf(" protocol ");
- switch (aip->ai_protocol) {
- case 0:
- printf("default");
- break;
- case IPPROTO_TCP:
- printf("TCP");
- break;
- case IPPROTO_UDP:
- printf("UDP");
- break;
- case IPPROTO_RAW:
- printf("raw");
- break;
- default:
- printf("unknown (%d)", aip->ai_protocol);
- }
- }
- void
- print_flags(struct addrinfo *aip)
- {
- printf("flags");
- if (aip->ai_flags == 0) {
- printf(" 0");
- } else {
- if (aip->ai_flags & AI_PASSIVE)
- printf(" passive");
- if (aip->ai_flags & AI_CANONNAME)
- printf(" canon");
- if (aip->ai_flags & AI_NUMERICHOST)
- printf(" numhost");
- #if defined(AI_NUMERICSERV)
- if (aip->ai_flags & AI_NUMERICSERV)
- printf(" numserv");
- #endif
- #if defined(AI_V4MAPPED)
- if (aip->ai_flags & AI_V4MAPPED)
- printf(" v4mapped");
- #endif
- #if defined(AI_ALL)
- if (aip->ai_flags & AI_ALL)
- printf(" all");
- #endif
- }
- }
- int
- main(int argc, char *argv[])
- {
- struct addrinfo *ailist, *aip;
- struct addrinfo hint;
- struct sockaddr_in *sinp;
- const char *addr;
- int err;
- char abuf[INET_ADDRSTRLEN];
- if (argc != 3)
- printf("usage: %s nodename service", argv[0]);
- hint.ai_flags = AI_CANONNAME;
- hint.ai_family = 0;
- hint.ai_socktype = 0;
- hint.ai_protocol = 0;
- hint.ai_addrlen = 0;
- hint.ai_canonname = NULL;
- hint.ai_addr = NULL;
- hint.ai_next = NULL;
- if ((err = getaddrinfo(argv[1], argv[2], &hint, &ailist)) != 0)
- printf("getaddrinfo error: %s", gai_strerror(err));
- for (aip = ailist; aip != NULL; aip = aip->ai_next) {
- print_flags(aip);
- print_family(aip);
- print_type(aip);
- print_protocol(aip);
- printf("\n\thost %s", aip->ai_canonname?aip->ai_canonname:"-");
- if (aip->ai_family == AF_INET) {
- sinp = (struct sockaddr_in *)aip->ai_addr;
- addr = inet_ntop(AF_INET, &sinp->sin_addr, abuf,INET_ADDRSTRLEN);
- printf(" address %s", addr?addr:"unknown");
- printf(" port %d", ntohs(sinp->sin_port));
- }
- printf("\n");
- }
- exit(0);
- }
代码说明:sinp = (struct sockaddr_in *)aip->ai_addr;会将struct sockaddr 变量强制转化为struct sockaddr_in 类型
inet_ntop函数用于在二进制格式与点分十进制格式表示(a.b.c.d)之间进行转换
执行结果:#./a X86-PC nfs
- root@X86-PC:/home/cheney/work-link/linux_C_Test/linux-program/fig16# ./a X86-PC nfs
- flags canon family inet type stream protocol TCP
- host X86-PC address 127.0.1.1 port 2049
- flags canon family inet type datagram protocol UDP
- host - address 127.0.1.1 port 2049
- root@X86-PC:/home/cheney/work-link/linux_C_Test/linux-program/fig16#
来自 <http://blog.chinaunix.net/uid-26583794-id-3167485.html>
IPv4中使用gethostbyname()函数完成主机名到地址解析,但是该API不允许调用者指定所需地址类型的任何信息,返回的结构只包含 了用于存储IPv4地址的空间。为了解决该问题,IPv6中引入了getaddrinfo()的新API,它是协议无关的,既可用于IPv4也可用于 IPv6。调用该函数会获得一个addrinfo结构的列表,调用的返回值是addrinfo的结构(列表)指针。
本文结合在WinowsXP和Windows2003 Server上使用该函数的经验,对getaddrinfo函数和addrinfo数据结构进行介绍,并对其参数的设置加以讨论,主要包括 nodename和servname的取值对返回值的影响,hints成员变量的设置对返回值的影响等。
可能有不完全或不准确的地方,欢迎大家讨论并指出。
1.getaddrinfo函数原型
函数
参数说明
int getaddrinfo(
const char* nodename
const char* servname,
const struct addrinfo* hints,//
struct addrinfo** res
);
nodename:节点名可以是主机名,也可以是数字地址。(IPV4的10进点分,或是IPV6的16进制)
servname:包含十进制数的端口号或服务名如(ftp,http)
hints:是一个空指针或指向一个addrinfo结构的指针,由调用者填写关于它所想返回的信息类型的线索。
res:存放返回addrinfo结构链表的指针
Getaddrinfo提供独立于协议的名称解析。
函数的前两个参数分别是节点名和服务名。节点名可以是主机名,也可以是地址串(IPv4的点分十进制数表示或IPv6的十六进制数字串)。服务名可以是十进制的端口号,也可以是已定义的服务名称,如ftp、http等。注意:其中节点名和服务名都是可选项,即节点名或服务名可以为NULL,此时调用的结果将取缺省设置,后面将详细讨论。
函数的第三个参数hints是addrinfo结构的指针,由调用者填写关于它所想返回的信息类型的线索。函数的返回值是一个指向addrinfo结构的链表指针res。
2.addrinfo结构
结构
固定的参数
typedef struct addrinfo {
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
size_t ai_addrlen;
char* ai_canonname;
struct sockaddr* ai_addr;
struct addrinfo* ai_next;
}
ai_addrlen must be zero or a null pointer
ai_canonname must be zero or a null pointer
ai_addr must be zero or a null pointer
ai_next must be zero or a null pointer
可以改动的参数
ai_flags:AI_PASSIVE,AI_CANONNAME,AI_NUMERICHOST
ai_family: AF_INET,AF_INET6
ai_socktype:SOCK_STREAM,SOCK_DGRAM
ai_protocol:IPPROTO_IP, IPPROTO_IPV4, IPPROTO_IPV6 etc.
3 参数说明
在getaddrinfo函数之前通常需要对以下6个参数进行以下设置:nodename、servname、hints的ai_flags、ai_family、ai_socktype、ai_protocol
在6项参数中,对函数影响最大的是nodename,sername和hints.ai_flag
而ai_family只是有地址为v4地址或v6地址的区别。而ai_protocol一般是为0不作改动。
其中ai_flags、ai_family、ai_socktype说明如下:
参数
取值
值
说明
ai_family
AF_INET
2
IPv4
AF_INET6
23
IPv6
AF_UNSPEC
0
协议无关
ai_protocol
IPPROTO_IP
0
IP协议
IPPROTO_IPV4
4
IPv4
IPPROTO_IPV6
41
IPv6
IPPROTO_UDP
17
UDP
IPPROTO_TCP
6
TCP
ai_socktype
SOCK_STREAM
1
流
SOCK_DGRAM
2
数据报
ai_flags
AI_PASSIVE
1
被动的,用于bind,通常用于server socket
AI_CANONNAME
2
AI_NUMERICHOST
4
地址为数字串
对于ai_flags值的说明:
AI_NUMERICHOST
AI_CANONNAME
AI_PASSIVE
0/1
0/1
0/1
如上表所示,ai_flagsde值范围为0~7,取决于程序如何设置3个标志位,比如设置ai_flags为 “AI_PASSIVE|AI_CANONNAME”,ai_flags值就为3。三个参数的含义分别为:
(1)AI_PASSIVE当此标志置位时,表示调用者将在bind()函数调用中使用返回的地址结构。当此标志不置位时,表示将在connect()函数调用中使用。
当节点名位NULL,且此标志置位,则返回的地址将是通配地址。
如果节点名NULL,且此标志不置位,则返回的地址将是回环地址。
(2)AI_CANNONAME当此标志置位时,在函数所返回的第一个addrinfo结构中的ai_cannoname成员中,应该包含一个以空字符结尾的字符串,字符串的内容是节点名的正规名。
(3)AI_NUMERICHOST当此标志置位时,此标志表示调用中的节点名必须是一个数字地址字符串。
4.实际使用的几种常用设置
一般情况下,client/server编程中,server端调用bind(如果面向连接的还需要listen),client则不用掉bind函数,解析地址后直接connect(面向连接)或直接发送数据(无连接)。因此,比较常见的情况有
(1) 通常服务器端在调用getaddrinfo之前,ai_flags设置AI_PASSIVE,用于bind;主机名nodename通常会设置为NULL,返回通配地址[::]。
(2) 客户端调用getaddrinfo时,ai_flags一般不设置AI_PASSIVE,但是主机名nodename和服务名servname(更愿意称之为端口)则应该不为空。
(3) 当然,即使不设置AI_PASSIVE,取出的地址也并非不可以被bind,很多程序中ai_flags直接设置为0,即3个标志位都不设置,这种情况下只要hostname和servname设置的没有问题就可以正确bind。
上述情况只是简单的client/server中的使用,但实际在使用getaddrinfo和参考国外开源代码的时候,曾遇到一些将servname(即端口)设为NULL的情况
(当然,此时nodename必不为NULL,否则调用getaddrinfo会报错)。以下分情况进行了测试:
(1) 如果nodename是字符串型的IPv6地址,bind的时候会分配临时端口;
(2) 如果nodename是本机名,servname为NULL,则根据操作系统的不同略有不同
注意点是: 这个函数,说起来,是get ,但是其实可以理解为creat 或者是理解为构建 。 因为你可以随意构建自己的地址结构addrinfo。
如 果本函数返回成功,那么由result参数指向的变量已被填入一个指针,它指向的是由其中的ai_next成员串联起来的addrinfo结构链表。可以 导致返回多个addrinfo结构的情形有以下2个:
1. 如果与hostname参数关联的地址有多个,那么适用于所请求地址簇的每个地址都返回一个对应的结构。
2. 如果service参数指定的服务支持多个套接口类型,那么每个套接口类型都可能返回一个对应的结构,具体取决于hints结构的ai_socktype 成员。
我们必须先分配一个hints结构,把它清零后填写需要的字段,再调用getaddrinfo然后遍历一个链表逐个尝试每个返回地 址。
getaddrinfo解决了把主机名和服务名转换成套接口地址结构的问题。
其中,如果getaddrinfo出 错,那么返回一个非0的错误值。
官方说明的链接:http://www.kernel.org/doc/man-pages/online/pages/man3/gai_strerror.3.html
很详细。
addrinfo结构体与getaddrinfo函数
最新推荐文章于 2022-03-02 22:06:10 发布