getaddrinfo是posix标准中唯一推荐的跨协议dns解析接口,支持ipv4/ipv6双栈且线程安全;它替代已废弃的gethostbyname,后者不支持ipv6且非线程安全;正确使用需设ai_addrconfig标志、用gai_strerror处理错误码,并通过独立线程或future实现非阻塞调用。

用 getaddrinfo 实现跨协议 DNS 解析,别碰 gethostbyname
直接上结论:getaddrinfo 是 C++(实际是 POSIX)里唯一推荐的、支持 IPv4/IPv6 双栈且线程安全的 DNS 解析接口。它天然异步友好——只要不阻塞在调用上就行。而 gethostbyname 已废弃,不支持 IPv6,且内部可能用静态缓冲区,多线程下会踩内存。
常见错误是以为“异步”必须用回调或 std::async 包一层——其实核心是避免在主线程同步等 DNS 响应。你完全可以开一个独立线程调用 getaddrinfo,它本身是阻塞的,但阻塞范围可控、无副作用。
-
getaddrinfo返回的是addrinfo*链表,每个节点含地址族(AF_INET或AF_INET6)、端口、二进制地址,可直接用于后续socket/connect - 必须传
hints.ai_flags = AI_ADDRCONFIG,否则在仅 IPv4 网络上调用可能卡住(试图查 AAAA 记录但无响应) - 错误码不是
errno,而是getaddrinfo返回值(如EAI_NONAME、EAI_AGAIN),要用gai_strerror转成字符串
如何封装成非阻塞式请求器:线程 + 回调 + 超时控制
真正的难点不在解析逻辑,而在怎么不让用户线程挂起。最简方案是用 std::thread + std::promise,但要注意资源清理和超时取消——getaddrinfo 本身不支持中断,只能靠线程 join 超时 + 主动 detach(不推荐)或共享 flag(推荐)。
典型使用场景:HTTP 客户端发起请求前需解析域名;爬虫批量查多个域名;游戏客户端连接服务器前预检 IP。
- 启动线程前设一个
std::atomic<bool> cancelled{false}</bool>,在线程内循环检查是否被取消(虽然getaddrinfo不响应,但可缩短等待窗口) - 用
std::future<:vector>></:vector>持有结果,成功时存所有解析出的 IP 字符串(inet_ntop转);失败时用std::exception_ptr传递错误信息 - 超时建议设为 5–10 秒,DNS 协议本身重试机制已包含 2–3 次 UDP 查询,再长大概率是网络问题而非 DNS 慢
// 示例片段:关键参数设置
struct addrinfo hints{};
hints.ai_family = AF_UNSPEC; // 同时接受 IPv4 和 IPv6
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED; // 重要:避免 IPv6-only 环境查不到
hints.ai_protocol = IPPROTO_TCP;
IPv4/IPv6 结果分离与优先级处理
同一个域名返回的 addrinfo 链表里,IPv6 地址不一定排前面——取决于系统 resolver 配置(如 /etc/gai.conf)。如果你要模拟浏览器行为(先试 IPv6,失败降级 IPv4),不能只按链表顺序用,得自己分类再排序。
容易踩的坑:直接对所有地址轮询 connect,但 IPv6 地址在双栈主机上可能因路由缺失导致超时(比如本地没配置 IPv6 网关),拖慢整体速度。
- 遍历
addrinfo链表时,用ai_family == AF_INET或ai_family == AF_INET6分离两类地址 - 优先尝试 IPv6 时,注意检查
IN6_IS_ADDR_LINKLOCAL或IN6_IS_ADDR_SITELOCAL—— 这类地址不可路由,应跳过 - 如果业务明确只需 IPv4(如嵌入式设备无 IPv6 栈),把
hints.ai_family改成AF_INET,性能更好,也避免getaddrinfo内部做 AAAA 查询
Windows 上的兼容性陷阱:getaddrinfo 不是默认可用
Windows Vista+ 才原生支持 getaddrinfo,但旧项目若链接了 ws2_32.lib 而没初始化 Winsock,调用会直接 crash。更隐蔽的问题是:Windows 的 getaddrinfo 默认行为和 Linux 不同——它可能忽略 AI_ADDRCONFIG,强行查 AAAA 记录,导致在纯 IPv4 网络下延迟飙升。
- 务必在首次调用前执行
WSAStartup(MAKEWORD(2,2), &wsaData) - 测试时用
nslookup -type=A example.com和nslookup -type=AAAA example.com对比,确认系统 resolver 是否真返回双记录 - 若发现 Windows 下始终超时,临时改用
hints.ai_flags = AI_PASSIVE+hints.ai_family = AF_INET锁死 IPv4,快速验证是否为 IPv6 相关问题
复杂点在于:DNS 解析本身不提供权威答案缓存,每次调用都是真实查询。如果你需要高频查相同域名,必须自己加一层 LRU cache(key 是域名+family,value 是 addrinfo* 链表深拷贝),否则性能会断崖下跌。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











