一、网络地址解析的必要性
在网络通信中,应用程序通常需要通过域名(如www.example.com)而非直接使用 IP 地址(如93.184.216.34)来定位目标主机,同时需要通过服务名(如http)而非直接使用端口号(如80)来指定通信端口。这种抽象带来了以下核心需求:
用户友好性:人类更容易记忆域名而非数字 IP
协议兼容性:需同时支持 IPv4 与 IPv6,避免硬编码地址类型
服务灵活性:服务端口可能动态分配,通过服务名解析更可靠
网络拓扑适应:同一域名可能对应多个 IP(负载均衡场景),需支持多地址选择
传统地址解析方法(如gethostbyname
、getservbyname
)存在明显局限性:仅支持 IPv4、无法统一处理主机名与服务名、线程安全性差。getaddrinfo
作为 POSIX 标准接口,完美解决了这些问题,是现代 C++ 网络编程的首选地址解析方案。
二、getaddrinfo
函数
getaddrinfo
是一个统一的地址解析接口,可同时处理主机名→IP 地址、服务名→端口号的解析,并返回可直接用于socket调用的地址结构。
2.1 函数原型
1 2 3 4 5 6 7 8
| #include <sys/types.h> #include <sys/socket.h> #include <netdb.h>
int `getaddrinfo`(const char *node, // 主机名/IP地址字符串 const char *service, // 服务名/端口号字符串 const struct addrinfo *hints, // 解析参数提示 struct addrinfo **res); // 输出:解析结果链表
|
2.2 参数详解
2.2.1 输入参数
参数名 |
含义与取值 |
典型用法 |
node |
目标主机标识:- 域名(如"www.baidu.com")- IPv4 地址(如"192.168.1.1")- IPv6 地址(如"2001:0db8:85a3:0000:0000:8a2e:0370:7334")- NULL(服务器端绑定场景,配合AI_PASSIVE) |
客户端:"www.example.com"服务器端:NULL |
service |
目标服务标识:- 服务名(如"http"、"ssh",需系统服务数据库支持)- 端口号字符串(如"80"、"22")- NULL(需手动指定端口) |
客户端:"http"或"8080"服务器端:"80" |
hints |
解析策略提示(结构体指针):- 为NULL时,返回所有可能的地址类型- 非NULL时,按结构体字段过滤结果 |
见 2.2.3 小节 |
2.2.2 输出参数
res:指向struct addrinfo
结构体链表的指针,每个节点包含一个可用的网络地址信息。结构体定义如下:
1 2 3 4 5 6 7 8 9 10
| struct addrinfo { int ai_flags; // 标志位(如AI_PASSIVE、AI_CANONNAME) int ai_family; // 地址族(AF_INET=IPv4,AF_INET6=IPv6,AF_UNSPEC=任意) int ai_socktype; // 套接字类型(SOCK_STREAM=TCP,SOCK_DGRAM=UDP) int ai_protocol; // 协议类型(IPPROTO_TCP=TCP,IPPROTO_UDP=UDP,0=任意) socklen_t ai_addrlen; // ai_addr指向的地址结构体长度 char *ai_canonname; // 主机的规范域名(仅当指定AI_CANONNAME时有效) struct sockaddr *ai_addr; // 指向sockaddr_in(IPv4)或sockaddr_in6(IPv6)的指针 struct addrinfo *ai_next; // 指向下一个结果节点的指针(链表结构) };
|
2.2.3 hints 关键字段配置
hints是控制解析行为的核心,常见配置组合如下:
字段 |
取值与含义 |
ai_family |
- AF_INET:仅返回 IPv4 地址- AF_INET6:仅返回 IPv6 地址- AF_UNSPEC:返回所有兼容地址(默认) |
ai_socktype |
- SOCK_STREAM:仅返回 TCP 相关地址- SOCK_DGRAM:仅返回 UDP 相关地址- 0:返回所有类型(默认) |
ai_protocol |
- IPPROTO_TCP:强制 TCP 协议- IPPROTO_UDP:强制 UDP 协议- 0:自动匹配(默认) |
ai_flags |
- AI_PASSIVE:返回适用于服务器绑定(bind)的地址(如0.0.0.0)- AI_CANONNAME:获取主机规范域名- AI_NUMERICHOST:强制node为 IP 地址(不触发 DNS 解析)- AI_NUMERICSERV:强制service为端口号(不查服务数据库) |
2.3 返回值处理
三、完整示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| #include <cstring> #include <netdb.h> #include <arpa/inet.h> using std::cout; using std::endl; using std::cin;
void getAddrInfo(const char* domain) { struct addrinfo hints, *res; // 初始化hints结构体,避免随机值影响 memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; // 同时支持IPv4和IPv6 hints.ai_socktype = SOCK_STREAM;
int ret = getaddrinfo(domain, NULL, &hints, &res); // 处理getaddrinfo调用失败的情况 if (ret != 0) { cout << "域名解析失败: " << gai_strerror(ret) << endl; return; }
bool found = false; struct addrinfo *p = res; for (; p != nullptr; p = p->ai_next) { char ipstr[INET6_ADDRSTRLEN]; void* addr; const char* ipVersion;
if (p->ai_family == AF_INET) { struct sockaddr_in* ipv4 = (struct sockaddr_in*)p->ai_addr; addr = &(ipv4->sin_addr); ipVersion = "IPv4"; } else { struct sockaddr_in6* ipv6 = (struct sockaddr_in6*)p->ai_addr; addr = &(ipv6->sin6_addr); ipVersion = "IPv6"; } // 将二进制IP地址转换为字符串 inet_ntop(p->ai_family, addr, ipstr, sizeof(ipstr)); cout << " " << ipVersion << ": " << ipstr << endl; found = true; }
freeaddrinfo(res); // 释放内存
if (!found) { cout << " 未找到该域名的IP地址" << endl; } }
int main(int argc, char* argv[]) { cout << "请输入你想查询的网址" << endl;
char domain[1024] = {0};
while (cin >> domain) { getAddrInfo(domain); memset(domain, 0, sizeof(domain)); cout << "请输入你想查询的网址" << endl; }
return 0; }
|