- 英文小册原文地址:beej.us/guide/bgnet…
- 作者:Beej
- 中文翻译地址:www.chanmufeng.com/posts/netwo…
getaddrinfo(), freeaddrinfo(), gai_strerror()
获取有关host name(主机名)以及service(服务)信息,并将结果保存在struct sockaddr
结构中。
函数原型
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *nodename, const char *servname,
const struct addrinfo *hints, struct addrinfo **res);
void freeaddrinfo(struct addrinfo *ai);
const char *gai_strerror(int ecode);
struct addrinfo {
int ai_flags; // AI_PASSIVE, AI_CANONNAME, ...
int ai_family; // AF_xxx
int ai_socktype; // SOCK_xxx
int ai_protocol; // 0 (auto) or IPPROTO_TCP, IPPROTO_UDP
socklen_t ai_addrlen; // length of ai_addr
char *ai_canonname; // canonical name for nodename
struct sockaddr *ai_addr; // binary address
struct addrinfo *ai_next; // next structure in linked list
};
说明
getaddrinfo()
是一个非常有用的函数,它会返回特定主机名的相关信息(例如其IP地址),并把信息保存在一个struct sockaddr
结构中,自动为你处理细节(比如地址是IPv4还是IPv6)。
有了这个函数,原本的 gethostbyname()
和 getservbyname()
也就可就可以退出江湖了。
接下来的描述可能会让你望而生畏,但是别担心,这个函数用起来贼简单。你可以先跳过下面的描述直接看一下例子,增强一下你的信心。
nodename
参数中保存的就是host name(主机名)。主机名可以是域名,比如“www.chanmufeng.com”,也可以是IPv4或者IPv6地址(以字符串形式传递)。如果你用了AI_PASSIVE
选项,这个参数你也可以设置为NULL
(见下文)。
servname
通常就是端口号,比如你可以以字符串格式传递“80
”;也可以是服务名,比如“http
”、“tftp
”、或者“smtp
”、“pop
”等。众所周知的服务名可以在 IANA Port List48 中找到,你本地的/etc/services
文件中也有这些信息。
然后就是核心参数——hints
。在使用addrinfo
之前,你必须先用memset()
将整个结构数据清空。接下来我们讲讲addrinfo
中的字段。
ai_flags
有很多个候选项,但是重要也就几个。(如果要同时使用多个后选项,可以使用|
运算符对他们进行按位或运算)。查看man手册获取完整的标识列表。
AI_CANONNAME
会令res
的 ai_canonname
填充为主机的canonical(real) name(规范名,或者成为真名)。
AI_PASSIVE
会令res
的IP地址被设置为 INADDR_ANY
(IPv4) 或 in6addr_any
(IPv6);这让之后在调用bind()
时,可以自动使用当前主机的IP地址来填充struct sockaddr
的IP地址。这在你写Server代码且不想写死IP地址时非常好用。
如果你使用了 AI_PASSIVE
标识,你就可以将nodename
字段设置为NULL
了(因为bind()
在之后会自动给你填上)。
接着聊参数。
你应该会想将 ai_family
设置为AF_UNSPEC
,这样 getaddrinfo()
就能同时应付IPv4和IPv6了。当然喽,你也可以用 AF_INET 或 AF_INET6 来自己指定使用IPv4还是IPv6.
另外,socktype应该被设置为 SOCK_STREAM
或 SOCK_DGRAM
,具体取决于你想使用那种socket。
最后,你可以将 ai_protocol
设置为0
,让其自动选择你的protocol type。
做了这么多,终于可以调用getaddrinfo()
了。
事情变得有趣了。res
会指向struct addrinfo
的一个链接列表,您可以通过这个链表来获得全部的地址信息(符合你通过hints
指定的address类型)。
但是你可能会获取到一些因为某些原因而无效的address,因此Linux的man手册提供的方法是循环读取链表,然后进行调用socket()
、connect()
(如果Server端程序使用了AI_PASSIVE
,那就是bind()
),直到成功为止。
最后,当你处理完链表之后,你需要调用freeaddrinfo()
来释放内存,避免内存出现泄漏。
返回值
成功返回0
,异常返回非0
。如果返回的是非0
,你可以调用gai_strerror()
获取一个文字版本的错误信息。
例子
// code for a client connecting to a server
// namely a stream socket to www.example.com on port 80 (http)
// either IPv4 or IPv6
int sockfd;
struct addrinfo hints, *servinfo, *p;
int rv;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // use AF_INET6 to force IPv6
hints.ai_socktype = SOCK_STREAM;
if ((rv = getaddrinfo("www.example.com", "http", &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
exit(1);
}
// loop through all the results and connect to the first we can
for(p = servinfo; p != NULL; p = p->ai_next) {
if ((sockfd = socket(p->ai_family, p->ai_socktype,
p->ai_protocol)) == -1) {
perror("socket");
continue;
}
if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
perror("connect");
close(sockfd);
continue;
}
break; // if we get here, we must have connected successfully
}
if (p == NULL) {
// looped off the end of the list with no connection
fprintf(stderr, "failed to connect\n");
exit(2);
}
freeaddrinfo(servinfo); // all done with this structure
// code for a server waiting for connections
// namely a stream socket on port 3490, on this host's IP
// either IPv4 or IPv6.
int sockfd;
struct addrinfo hints, *servinfo, *p;
int rv;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // use AF_INET6 to force IPv6
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // use my IP address
if ((rv = getaddrinfo(NULL, "3490", &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
exit(1);
}
// loop through all the results and bind to the first we can
for(p = servinfo; p != NULL; p = p->ai_next) {
if ((sockfd = socket(p->ai_family, p->ai_socktype,
p->ai_protocol)) == -1) {
perror("socket");
continue;
}
if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
close(sockfd);
perror("bind");
continue;
}
break; // if we get here, we must have connected successfully
}
if (p == NULL) {
// looped off the end of the list with no successful bind
fprintf(stderr, "failed to bind socket\n");
exit(2);
}
freeaddrinfo(servinfo); // all done with this structure
参阅
gethostbyname(), getnameinfo()
标签:getaddrinfo,struct,ai,strerror,int,freeaddrinfo,addrinfo,hints From: https://blog.51cto.com/u_13887950/5806583