因为getaddrinfo是阻塞系统调用,协程无法接管其内核态等待,强行包装仅是伪异步;真正可行方案只有使用boost.asio等支持异步i/o的dns库,或自行封装平台原生异步api(如linux的getaddrinfo_a、windows的getaddrinfoex)。

为什么不能直接用 getaddrinfo 配合 std::coroutine 做异步 DNS?
因为 getaddrinfo 是阻塞系统调用,即使挂起协程,底层线程仍会被卡住;协程调度器无法接管其内核态等待。强行包装会导致线程池资源浪费或伪异步——看着像协程,实则没释放执行权。
真正可行的路径只有两条:用支持异步 I/O 的 DNS 库(如 libuv、Boost.ASIO),或自己封装 epoll/kqueue + getaddrinfo_a(Linux)或 dns_async(macOS)。Windows 下需走 GetAddrInfoEx + OVERLAPPED。
-
getaddrinfo_a是 GNU libc 提供的异步版本,但仅 Linux 支持,且需手动管理信号或轮询aio_error -
Boost.ASIO的resolver.async_resolve封装了平台差异,是目前最稳妥的选择 - 别试图用
std::jthread+std::async包一层就叫“协程化”——那只是线程池调度,不是协程语义
如何用 Boost.ASIO 实现可挂起的 DNS 解析协程?
关键不是“怎么写协程”,而是让 resolver 调用天然支持 awaitable。从 Boost 1.79 开始,asio::ip::tcp::resolver::async_resolve 已默认返回 awaitable(需启用 BOOST_ASIO_ENABLE_WAITABLE_TIMER 和 C++20 编译模式)。
co_await resolver.async_resolve(
asio::ip::tcp::resolver::query("github.com", "https"));
注意三点:
- 必须用
asio::io_context驱动,且该 context 不能被多个协程并发调用 resolver(推荐每个协程绑定独立strand或复用同一io_context+co_spawn) - 返回的是
asio::ip::tcp::resolver::results_type,不是单个地址;需遍历或取.begin()->endpoint().address() - 超时需单独套一层
asio::steady_timer+co_await timer.async_wait,ASIO resolver 本身不内置 timeout
DNS 缓存该缓什么、怎么失效?
缓存目标不是原始字符串,而是 (host, port, hints) 三元组对应的解析结果集合 + TTL。TTL 来自 DNS 响应中的 RR 记录字段,但 Boost.ASIO 不暴露该信息——它只做解析,不抓包。
所以实际缓存策略只能退而求其次:
- 用固定 TTL(如 30s)覆盖所有请求,适合内部服务发现场景
- 对接
dnsmasq或systemd-resolved的本地 socket 接口,它们会返回带 TTL 的 JSON/DBUS 响应 - 若必须精确 TTL,得换用
ldns或libunbound自行发 DNS 查询,再解析EDNS0字段
缓存结构建议用 std::unordered_map<:string std::pair>, std::chrono::steady_clock::time_point>></:string>,key 是 host + ":" + port,避免因 hints 差异导致缓存污染。
协程 DNS 缓存容易踩的线程安全坑
缓存读写本身不是问题,问题是:协程可能跨线程调度,而 asio::io_context 默认不是线程安全的。如果你在协程里直接操作全局缓存 map,又没加锁或 strand 绑定,就会触发数据竞争。
- 不要在
co_await前后直接读写裸std::unordered_map—— 协程恢复点可能切换线程 - 用
asio::post(strand, [&] { cache[key] = value; });确保缓存操作串行化 - 或者改用
absl::flat_hash_map+absl::Mutex,比std::shared_mutex更轻量(尤其读多写少) - 更隐蔽的问题:缓存清理定时器如果用
co_spawn(io_ctx, ...)启动,其内部也需绑定 strand,否则清理逻辑和解析逻辑可能并发修改同一 key
缓存失效逻辑和解析逻辑共享同一把锁或 strand,是最小改动、最不易出错的方式。别为了“无锁”提前引入 RCU 或 epoch-based reclamation——DNS 缓存更新频率远低于网络吞吐,锁开销可忽略。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











