协程异步dns解析必须绕开getaddrinfo,因其为阻塞调用,直接使用会挂起整个协程调度器;linux应采用getaddrinfo_a或libuv等异步dns模块,windows需用getaddrinfoex;超时与清理需解耦生命周期、绑定唯一request_id并定时扫描ttl过期项。

协程异步DNS解析必须绕开getaddrinfo
标准getaddrinfo是阻塞调用,直接在协程里用会挂起整个调度器。Linux上得用getaddrinfo_a(GNU扩展)或自己封装libuv/libevent的DNS模块;Windows只能走GetAddrInfoEx异步API。别试图用线程池+co_await包装getaddrinfo——线程切换开销大,且无法真正利用协程轻量性。
推荐方案:用uvw(基于libuv)或cpp-httplib自带的异步DNS(底层调用getaddrinfo_a)。例如uvw中:
auto dns = loop.resource<:getaddrinforeq>();
dns->once<:getaddrinfoevent>([&](const auto& e) {
// 解析结果在e.hints里
});
dns->req("example.com", "80", uvw::GetAddrInfoReq::Hints{});</:getaddrinfoevent></:getaddrinforeq>
如何给每个DNS请求绑定独立超时与过期清理
协程生命周期和DNS请求生命周期必须解耦。不能靠co_await后自动销毁——网络延迟可能让协程早已结束,但DNS响应还在路上,导致use-after-free。
- 每个DNS请求分配唯一
request_id,存入全局std::unordered_map<uint64_t std::shared_ptr>></uint64_t> - 协程启动时生成
request_id,传给DNS请求对象,并注册到map中 - 超时用
uvw::TimerHandle或std::chrono::steady_clock+ 调度器定时检查,触发时从map中erase并cancel未完成请求 -
DnsEntry持有std::weak_ptr指向原始协程promise,回调时先lock再resume,避免协程已销毁
协程返回值设计要区分“无响应”和“解析失败”
异步DNS常见三种结果:成功、超时、系统级错误(如EAI_NONAME)。如果统一返回std::optional<:vector>></:vector>,就丢失了错误类型信息,上层无法判断是该重试还是该报错。
建议用变体类型:
struct DnsResult {
enum class Status { OK, TIMEOUT, ERROR };
Status status;
std::vector<sockaddr_storage> addrs;
int sys_errno = 0; // 如EAI_AGAIN
};</sockaddr_storage>协程函数签名应为task<dnsresult> resolve_async(const std::string& host)</dnsresult>,调用方能按status分支处理。
缓存清理不能只靠LRU,得加TTL校验
即使用了std::lru_cache,也得在每次get前检查记录的std::chrono::steady_clock::time_point是否过期。DNS记录TTL通常几秒到几小时不等,硬编码固定过期时间(比如5分钟)会导致缓存污染或重复查询。
- 解析成功后,从
addrinfo.ai_flags或响应包里提取TTL(需用ldns或libresolv解析原始响应) - 更实际的做法:用
getaddrinfo_a时无法获取TTL,所以退而求其次,在缓存entry里存一个保守值(如60秒),并允许用户配置默认TTL - 清理任务不要用单独线程轮询——用调度器的
repeat_timer每10秒扫描一次缓存map,erase过期项
最麻烦的是并发读写缓存map:插入/查询/清理三者同时发生。别用std::shared_mutex——它在高并发下性能差;改用分段锁(sharded hash map)或folly::ConcurrentHashMap(如果项目允许引入folly)。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











