域名和IP地址之间转换的两个函数:gethostbyname和gethostbyaddr,但是这两个函数仅仅支持IPv4。本文再介绍一个可支持 IPv4 和 IPv6 的函数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);
2、函数描述:
getaddrinfo函数根据给定的主机名和服务名,返回一个struct addrinfo结构链表,每个struct addrinfo结构都包含一个互联网地址。getaddrinfo函数将gethostbyname和getservbyname函数提供的功能组合到一个接口中,但与后一个函数不同,getaddrinfo是可重入的,可支持IPv4、IPv6。
3、函数参数:
node:一个主机名或地址串( IPv4的点分十进制数串或IPv6的十六进制数串)。如果hints.ai_flags包含AI_NUMERICHOST标志,则此参数必须是IP地址字符串;
service:一个服务名或十进制端口号数串。如果此参数被设置为一个服务名称,则会将其转换为相应的端口号。如果设置为NULL,则返回的套接字地址的端口号将保持未初始化状态。如果在hints.ai_flags中指定了AI_NUMERICSERV,并且此参数不为NULL,则此参数必须指向包含数字端口号的字符串;
hints:hints参数可以是一个空指针,也可以是一个指向某个addrinfo结构的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。hints参数中,调用者可以设置的字段有:ai_flags、ai_family、ai_socktype、ai_protocol。其中,ai_flags取值如下表:
AI_PASSIVE 套接字将用于被动打开.
AI_CANONNAME 告知getaddrinfo函数返回主机的规范名字.
AI_NUMERICHOST 防止任何类型的名字到地址映射,hostname参数必须是一个地址串。
AI_NUMERICSERV 防止任何类型的名字到服务映射, service参数必须是一个十进制端口号。AI__V4MAPPED
AI_V4MAPPED 如果同时指定ai_family成员的值为AF_INET6,那么如果没有可用的AAAA记录,就返回与A记录对应的IPv4映射的IPv6地址。
AI_ALL 如果同时指定AI_V4MAPPED标志,那么除了返回与AAAA记录对应的IPv6地址外,还返回与A记录对应的IPv4映射的IPv6地址。
AI_ADDRCONFIG 按照所在主机的配置选择返回地址类型,也就是只查找与所在主机回馈接口以外的网络接口配置的IP地址版本一致的地址。
ai_family 取值一般为AF_XXX,例如:AF_INET、AF_INET6、AF_UNSPEC(不限制IP地址协议);
ai_socktype 取值一般为SOCK_XXX,例如:SOCK_STREAM、SOCK_DGRAM。
ai_protocol 字段指定返回的套接字地址的协议。在该字段中指定0表示getaddrinfo函数可以返回具有任何协议的套接字地址。
res:传出参数,如果本函数返回成功0,则 res 参数指向的变量已被填入一个指针,它指向的是由其中的 ai_next 成员串接起来的 addrinfo 结构链表。
4、返回值:
成功返回0,失败返回非0,取值如下表:
EAI_AGAIN 名字解析中临时失败
EAI_BADFLAGS ai_flags的值无效
EAI_FAIL 名字解析中不可恢复地失败
EAI_FAMILY 不支持ai_family
EAI_MEMORY 内存分配失败
EAI_NONAME hostname或service未提供,或者不可知
EAI_OVERFTOW 用户参数缓冲区溢出(仅限getnameinfo( )函数)
EAI_SERVICE 不支持ai_socktype类型的service
EAI_SOCKTYPE 不支持ai_socktype
EAI_SYSTEM 在errno变量中有系统错误返回
struct addrinfo结构体定义在头文件 netdb.h 中,结构体声明如下:
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;
};
ai_flags:标志,在调用时使用,具体取值见上面 hints参数 的说明;
ai_family:IP协议族,一般取值有AF_INET、AF_INET6、AF_UNSPEC(不限制IP地址协议);
ai_socktype:socket类型,取值一般为SOCK_XXX,例如:SOCK_STREAM、SOCK_DGRAM;
ai_protocol:此字段指定返回的套接字地址的协议,一般有IPPROTO_UDP、IPPROTO_TCP
ai_addrlen:返回的地址结构体ai_addr的长度。一般IPv4是4,IPv6是16;
ai_addr:存储IP地址数据,一般转换成struct sockaddr_in或struct sockaddr_in6使用;
ai_canonname:正式的、标准的名称;
ai_next:用作链表结点指针,指向下一个struct addrinfo结点。
freeaddrinfo 函数介绍
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
void freeaddrinfo(struct addrinfo *res);
2、函数描述:
由getaddrinfo返回的所有存储空间都是动态获取的(譬如来自malloc调用),包括addrinfo结构、ai_addr结构和ai_canonname字符串。这些存储空间需要通过调用freeaddrinfo返还给系统。
3、参数
res:res参数应指向由getaddrinfo返回的第一个addrinfo结构。这个链表中的所有结构以及由它们指向的任何动态存储空间(譬如套接字地址结构和规范主机名)都被释放掉。
4、注意:
如果getaddrinfo成功返回后,我们为了保存返回的信息而仅仅复制了返回的addrinfo结构,在调用freeaddrinfo后,就会存在一个错误:我们前面复制的addrinfo结构中的指针指向的内存空间已在调用freeaddrinfo后返还给系统。
这种只复制结构体而没复制结构体字段指向的内容的方式称为浅复制;复制结构体又复制结构体字段指向的内容的方式称为深复制。上面例子中,如果确实要保存信息,可以使用深复制来保存。
例子
// getaddrinfo_sample.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <arpa/inet.h>
int main(int argc, char *argv[]) {
struct addrinfo hints, *result, *rp;// 定义addrinfo结构体变量
int err; // getaddrinfo函数返回值
char ipstr[INET6_ADDRSTRLEN]; // 存储IP地址字符串的缓冲区
if (argc != 2) { // 检查命令行参数数量是否正确
fprintf(stderr, "Usage: %s hostname\n", argv[0]);
return -1;
}
memset(&hints, 0, sizeof(hints)); // 初始化hints结构体
hints.ai_family = AF_UNSPEC; // 不限制IP地址版本
hints.ai_socktype = SOCK_STREAM; // 使用TCP协议
if ((err = getaddrinfo(argv[1], NULL, &hints, &result)) != 0) { // 解析主机名并将结果存储在result指针中
fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(err));
return -1;
}
printf("IP addresses for %s:\n", argv[1]);
for (rp = result; rp != NULL; rp = rp->ai_next) { // 遍历result指针中的所有套接字地址结构
void *addr;
char *ipver;
if (rp->ai_family == AF_INET) { // IPv4地址
struct sockaddr_in *ipv4 = (struct sockaddr_in *)rp->ai_addr;
addr = &(ipv4->sin_addr);
ipver = "IPv4";
} else { // IPv6地址
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)rp->ai_addr;
addr = &(ipv6->sin6_addr);
ipver = "IPv6";
}
inet_ntop(rp->ai_family, addr, ipstr, sizeof(ipstr)); // 将套接字地址结构转换为IP地址字符串
printf(" %s: %s\n", ipver, ipstr); // 打印IP地址和版本号
}
freeaddrinfo(result); // 释放由getaddrinfo函数分配的内存
return 0; // 程序正常退出
}