getaddrinfo函数用法详解
getaddrinfo函数简介
- 头文件包含
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
- 函数定义
int getaddrinfo(const char * node , const char * service ,
const struct addrinfo * hints ,
struct addrinfo ** res );
void freeaddrinfo(struct addrinfo * res );
const char *gai_strerror(int errcode );
getaddrinfo函数常见使用错误
- 编译错误
warning: implicit declaration of function ‘getaddrinfo’ [-Wimplicit-function-declaration]
解决办法:包含头文件
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
getaddrinfo函数详细描述
给定标识互联网主机和服务的node和service ,getaddrinfo ()返回一个或多个addrinfo结构,每个结构都包含一个可以在对bind (2)或connect (2)的调用中指定的互联网地址。getaddrinfo ()函数将gethostbyname (3)和getservbyname (3)函数提供的功能组合到一个接口中,但与后一个函数不同,getaddrinfo ()是可重入的,并允许程序消除IPv4对IPv6的依赖性。
getaddrinfo ()使用的addrinfo结构包含以下字段:
struct addrinfo {
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
socklen_t ai_addrlen;
struct sockaddr *ai_addr;
char *ai_canonname;
struct addrinfo *ai_next;
};
hints参数指向一个addrinfo结构,该结构指定用于选择res 所指向的列表中返回的套接字地址结构的条件。如果hints不为空,则指向一个addrinfo结构,该结构的ai_family ai_socktype 和ai_protocol指定限制getaddrinfo ()所返回的套接字地址集的条件,如下所示:
- ai_family该字段为返回的地址指定所需的地址族。此字段的有效值包括AF_INET和AF_INET6 。值 AF_UNSPEC 表示getaddrinfo ()应返回可与node和service 一起使用的任何地址族(例如IPv4或IPv6)的套接字地址
- ai_socktype此字段指定首选套接字类型,例如SOCK_STREAM或SOCK_DGRAM ,在此字段中指定0表示getaddrinfo ()可以返回任何类型的套接字地址
- ai_protocol该字段指定返回套接字地址的协议。在此字段中指定0表示getaddrinfo ()可以返回任何协议的套接字地址
- ai_flags该字段指定附加选项,如下所述。多个标志是通过按位或对它们进行组合来指定的。
hints指向的结构中的所有其他字段必须包含0或空指针(视情况而定)。
将hints指定为NULL相当于将ai_socktype和ai_protocol设置为0;ai_family到AF_UNSPEC ;和ai_flags到"(AI_V4MAPPED\ |\ AI_ADDRCONFIG)" (POSIX为ai_flags ;指定了不同的默认值,请参阅注释。)node指定数字网络地址(对于IPv4,inet_aton (3);支持数字和点表示法,对于IPv6,inet_pton (3))支持十六进制字符串格式)或网络主机名,查找并解析其网络地址。如果hintsai_flags包含 AI_NUMERICHOST 标志,则node必须是数字网络地址。 AI_NUMERICHOST 标志抑制任何潜在的冗长网络主机地址查找。
如果在hintsai_flags 中指定了 AI_PASSIVE 标志,并且node为空,则返回的套接字地址将适用于bind (2)ing,这是一个将accept (2)连接的套接字。返回的套接字地址将包含“通配符地址”( INADDR_ANY表示IPv4地址,IN6ADDR_ANY_INIT表示IPv6地址)。通配符地址由打算接受任何主机网络地址上的连接的应用程序(通常是服务器)使用。如果node不为空,则 AI_PASSIVE 标志被忽略。
如果hintsai_flags 中未设置 AI_PASSIVE 标志,则返回的套接字地址将适用于connect (2) sendto (2)或sendmsg (2)。如果node为空,则网络地址将被设置为环回接口地址( INADDR_LOOPBACK表示IPv4地址,IN6ADDR_LOOPBACK_INIT表示IPv6地址);这由打算与运行在同一主机上的对等方通信的应用程序使用。
service设置每个返回地址结构中的端口。如果该参数是服务名(参见services (5))),它将被转换为相应的端口号。这个参数也可以指定为十进制数,简单地转换成二进制。如果service为空,那么返回的套接字地址的端口号将保持未初始化状态。如果hintsai_flags中指定了 AI_NUMERICSERV ,并且service不为NULL,则service必须指向包含数字端口号的字符串。此标志用于在已知不需要名称解析服务的情况下禁止调用该服务。
node或service 都可以为空,但不能同时为空。
getaddrinfo ()函数分配并初始化addrinfo结构的链表,每个网络地址对应一个与node和service 匹配的网络地址,受hints 施加的任何限制,并返回指向res 中列表开头的指针。链表中的项目由ai_next字段链接。
链表可能有一个以上的addrinfo结构有几个原因,包括:网络主机是多宿主的,可以通过多种协议访问(例如,AF_INET和AF_INET6 );或者相同的服务可以从多种套接字类型获得(例如,一个 SOCK_STREAM 地址和另一个 SOCK_DGRAM 地址)。通常,应用程序应该尝试按照返回的顺序使用地址。getaddrinfo ()中使用的排序函数在RFC\3484中定义;可以通过编辑/etc/gaiconf(从glibc 2.5开始可用)来调整特定系统的顺序。
如果hintsai_flags包含 AI_CANONNAME 标志,则返回列表中第一个addrinfo结构的ai_canonname字段被设置为指向主机的正式名称。
每个返回的addrinfo结构的其余字段初始化如下:
- ai_family ai_socktype 和ai_protocol字段返回套接字创建参数(即,这些字段与socket (2))的相应参数具有相同的含义,例如,ai_family可能返回 AF_INET 或AF_INET6 ;,ai_socktype可能返回 SOCK_DGRAM 或SOCK_STREAM ;,ai_protocol返回套接字的协议。
- 指向套接字地址的指针放在ai_addr字段中,套接字地址的长度(以字节为单位)放在ai_addrlen字段中。
如果hintsai_flags包括 AI_ADDRCONFIG 标志,则仅当本地系统至少配置了一个IPv4地址时,才会在res指向的列表中返回IPv4地址,并且仅当本地系统至少配置了一个IPv6地址时,才会返回IPv6地址。在这种情况下,环回地址不被视为与配置的地址一样有效。例如,在仅支持IPv4的系统上,此标志非常有用,可以确保getaddrinfo ()不会返回在connect (2)或bind (2)中总是失败的IPv6套接字地址
如果hintsai_flags指定了 AI_V4MAPPED 标志,并且hintsai_family被指定为AF_INET6 ,并且找不到匹配的IPv6地址,则返回res 指向的列表中IPv4映射的IPv6地址。如果hintsai_flags 中同时指定了 AI_V4MAPPED 和 AI_ALL ,则返回res 指向的列表中IPv6和IPv4映射的IPv6地址。如果没有指定 AI_V4MAPPED ,则忽略 AI_ALL 。
freeaddrinfo ()函数释放为动态分配的链表res Extensions to getaddrinfo() for Internationalized Domain Names分配的内存。从glibc 2.3.4开始,getaddrinfo ()已经扩展为选择性地允许传入和传出主机名透明地转换为国际化域名(IDN)格式(参见RFC 3490,"Internationalizing Domain Names in Applications (IDNA)" )定义了四个新标志:
- AI_IDN 如果指定了此标志,则node中给定的节点名称将在必要时转换为IDN格式。源编码是当前区域设置的编码。.IP如果输入名称包含非ASCII字符,则使用IDN编码。节点名中包含非ASCII字符的部分(由点分隔)在传递给名称解析函数之前,使用ASCII兼容编码(ACE)进行编码。
- AI_CANONIDN 成功查找名称后,如果指定了 AI_CANONNAME 标志,getaddrinfo ()将返回与传回的addrinfo结构值相对应的节点的规范名称。返回值是名称解析函数返回值的精确副本。.IP如果名称是使用ACE编码的,那么它将包含名称的一个或多个组件的xn--前缀。为了将这些组件转换成可读的形式,除了AI_CANONNAME 之外,还可以传递 AI_CANONIDN 标志,结果字符串使用当前区域设置的编码进行编码。
- AI_IDN_ALLOW_UNASSIGNED " " AI_IDN_USE_STD3_ASCII_RULES设置这些标志将分别启用IDNA_ALLOW_UNASIGNED(允许未分配的Unicode码位)和IDNA_USE_STD3_ASCII_RULES(检查输出以确保它是符合STD3的主机名)标志,以便在IDNA处理中使用。
getaddrinfo函数返回值
如果成功,getaddrinfo ()将返回0,或者返回以下非零错误代码之一:
- EAI_ADDRFAMILY 指定的网络主机在请求的地址族中没有任何网络地址。
- EAI_AGAIN 名称服务器返回临时失败指示。请稍后再试。
- EAI_BADFLAGS hintsai_flags包含无效标志;或者,hintsai_flags包含 AI_CANONNAME ,而name为空。
- EAI_FAIL 名称服务器返回永久失败指示。
- EAI_FAMILY 不支持请求的地址族。
- EAI_MEMORY 内存不足。
- EAI_NODATA 指定的网络主机存在,但没有定义任何网络地址。
- EAI_NONAME node或service未知;或者node和service都为空;或者hintsai_flags中指定了 AI_NUMERICSERV ,而service不是数字端口号字符串。
- EAI_SERVICE 请求的服务对于请求的套接字类型不可用。它可以通过另一种套接字类型获得。例如,如果service是“shell”(仅在流套接字上可用的服务),并且hintsai_protocol是IPPROTO_UDP 或hintsai_socktype是SOCK_DGRAM ;,则可能会发生此错误,或者如果service不为NULL,并且hintsai_socktype是SOCK_RAW(不支持服务概念的套接字类型),则可能会发生此错误。
- EAI_SOCKTYPE 不支持请求的套接字类型。例如,如果hintsai_socktype和hintsai_protocol不一致(例如,分别为SOCK_DGRAM和IPPROTO_TCP ),就会发生这种情况。
- EAI_SYSTEM 其他系统错误,有关详细信息,请检查errno。
gai_strerror ()函数将这些错误代码转换为人类可读的字符串,适用于错误报告。
getaddrinfo函数其他说明
getaddrinfo ()支持。用于指定IPv6作用域ID的IB地址%作用域ID表示法。
AI_ADDRCONFIG 、AI_ALL 和 AI_V4MAPPED 从glibc 2.3.3开始可用。 AI_NUMERICSERV 从glibc 2.3.4开始可用。
根据POSIX.1,将hints指定为NULL应该会导致ai_flags被假定为0。在这种情况下,GNU C库假定值为"(AI_V4MAPPED\ |\ AI_ADDRCONFIG)",因为这个值被认为是对规范的改进。
getaddrinfo函数使用举例
以下程序演示了getaddrinfo ()、gai_strerror ()、freeaddrinfo ()和getnameinfo (3)的使用。这些程序是UDP数据报的echo服务器和客户端。Server program&
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#define BUF_SIZE 500
int
main(int argc, char *argv[])
{
struct addrinfo hints;
struct addrinfo *result, *rp;
int sfd, s;
struct sockaddr_storage peer_addr;
socklen_t peer_addr_len;
ssize_t nread;
char buf[BUF_SIZE];
if (argc != 2) {
fprintf(stderr, "Usage: %s port\en", argv[0]);
exit(EXIT_FAILURE);
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
hints.ai_protocol = 0; /* Any protocol */
hints.ai_canonname = NULL;
hints.ai_addr = NULL;
hints.ai_next = NULL;
s = getaddrinfo(NULL, argv[1], &hints, &result);
if (s != 0) {
fprintf(stderr, "getaddrinfo: %s\en", gai_strerror(s));
exit(EXIT_FAILURE);
}
/* getaddrinfo() returns a list of address structures.
Try each address until we successfully bind(2).
If socket(2) (or bind(2)) fails, we (close the socket
and) try the next address. */
for (rp = result; rp != NULL; rp = rp\->ai_next) {
sfd = socket(rp\->ai_family, rp\->ai_socktype,
rp\->ai_protocol);
if (sfd == \-1)
continue;
if (bind(sfd, rp\->ai_addr, rp\->ai_addrlen) == 0)
break; /* Success */
close(sfd);
}
freeaddrinfo(result); /* No longer needed */
if (rp == NULL) { /* No address succeeded */
fprintf(stderr, "Could not bind\en");
exit(EXIT_FAILURE);
}
/* Read datagrams and echo them back to sender */
for (;;) {
peer_addr_len = sizeof(peer_addr);
nread = recvfrom(sfd, buf, BUF_SIZE, 0,
(struct sockaddr *) &peer_addr, &peer_addr_len);
if (nread == \-1)
continue; /* Ignore failed request */
char host[NI_MAXHOST], service[NI_MAXSERV];
s = getnameinfo((struct sockaddr *) &peer_addr,
peer_addr_len, host, NI_MAXHOST,
service, NI_MAXSERV, NI_NUMERICSERV);
if (s == 0)
printf("Received %zd bytes from %s:%s\en",
nread, host, service);
else
fprintf(stderr, "getnameinfo: %s\en", gai_strerror(s));
if (sendto(sfd, buf, nread, 0,
(struct sockaddr *) &peer_addr,
peer_addr_len) != nread)
fprintf(stderr, "Error sending response\en");
}
}
Client program&
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define BUF_SIZE 500
int
main(int argc, char *argv[])
{
struct addrinfo hints;
struct addrinfo *result, *rp;
int sfd, s;
size_t len;
ssize_t nread;
char buf[BUF_SIZE];
if (argc < 3) {
fprintf(stderr, "Usage: %s host port msg...\en", argv[0]);
exit(EXIT_FAILURE);
}
/* Obtain address(es) matching host/port */
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
hints.ai_flags = 0;
hints.ai_protocol = 0; /* Any protocol */
s = getaddrinfo(argv[1], argv[2], &hints, &result);
if (s != 0) {
fprintf(stderr, "getaddrinfo: %s\en", gai_strerror(s));
exit(EXIT_FAILURE);
}
/* getaddrinfo() returns a list of address structures.
Try each address until we successfully connect(2).
If socket(2) (or connect(2)) fails, we (close the socket
and) try the next address. */
for (rp = result; rp != NULL; rp = rp\->ai_next) {
sfd = socket(rp\->ai_family, rp\->ai_socktype,
rp\->ai_protocol);
if (sfd == \-1)
continue;
if (connect(sfd, rp\->ai_addr, rp\->ai_addrlen) != \-1)
break; /* Success */
close(sfd);
}
freeaddrinfo(result); /* No longer needed */
if (rp == NULL) { /* No address succeeded */
fprintf(stderr, "Could not connect\en");
exit(EXIT_FAILURE);
}
/* Send remaining command\-line arguments as separate
datagrams, and read responses from server */
for (int j = 3; j < argc; j++) {
len = strlen(argv[j]) + 1;
/* +1 for terminating null byte */
if (len > BUF_SIZE) {
fprintf(stderr,
"Ignoring long message in argument %d\en", j);
continue;
}
if (write(sfd, argv[j], len) != len) {
fprintf(stderr, "partial/failed write\en");
exit(EXIT_FAILURE);
}
nread = read(sfd, buf, BUF_SIZE);
if (nread == \-1) {
perror("read");
exit(EXIT_FAILURE);
}
printf("Received %zd bytes: %s\en", nread, buf);
}
exit(EXIT_SUCCESS);
}
评论区