应采用「一个i/o线程+一个心跳监控线程」模型:i/o线程处理收发与心跳响应,心跳线程定时检查last_heard_time(用steady_clock+relaxed内存序),二者解耦避免阻塞导致超时误判。

心跳包检测该用什么线程模型
单连接单线程(每个 socket 独占一个 std::thread)最直观,但资源开销大、难以管理;更合理的做法是「一个 I/O 线程 + 一个心跳监控线程」配合使用。I/O 线程负责收发业务数据和心跳响应,心跳监控线程则定时检查每个连接的最后活动时间——它不碰 socket,只读共享状态,避免锁竞争。
关键点在于:心跳超时判断必须和网络 I/O 解耦。否则在 recv() 阻塞或 epoll_wait() 中等待时,无法及时发现对端失联。
- 不要在业务线程里用
setsockopt(..., SO_RCVTIMEO, ...)做心跳超时:这只能控制单次recv(),无法覆盖对端静默断连场景 - 避免为每个连接起一个独立心跳线程:1000 个连接 = 1000 个线程,调度开销爆炸
- 推荐用
std::shared_mutex保护连接元数据(如last_heard_time),读多写少时比std::mutex更高效
怎么安全更新和检查 last_heard_time
每次成功收到对端心跳包(或任意有效业务包)时,必须原子更新该连接的最后活跃时间戳。不能只靠 time(nullptr),要统一用高精度时钟源,比如 std::chrono::steady_clock::now(),避免系统时间被手动调整导致误判。
示例更新逻辑:
auto now = std::chrono::steady_clock::now(); connection->last_heard_time.store(now, std::memory_order_relaxed);
心跳监控线程中检查时,也用相同时钟做差值计算:
auto elapsed = std::chrono::steady_clock::now() - conn->last_heard_time.load(std::memory_order_relaxed);
if (elapsed > std::chrono::seconds(30)) {
// 触发断连
}
-
std::memory_order_relaxed足够:这里不要求与其他变量同步,只要时间戳本身不乱序写入即可 - 别用
std::time_t或gettimeofday():它们可能回跳,导致elapsed为负,进而立即触发误断连 - 所有连接共享同一个时钟源实例,避免不同线程调用
now()产生微小偏差累积
发送心跳包时要注意 socket 状态和 EAGAIN
心跳包应由 I/O 线程在空闲时主动发送(例如每次 poll 返回后、无业务数据待发时),而不是另起定时器强制发。发送前必须确认 socket 处于可写状态,否则直接 send() 可能阻塞(阻塞模式)或返回 EAGAIN/EWOULDBLOCK(非阻塞模式)。
- 非阻塞 socket 下,
send()返回 -1 且errno == EAGAIN是正常现象,应把心跳包暂存到连接的待发缓冲区,等下次epoll报EPOLLOUT再重试 - 不要在心跳线程里调用
send():它没有 socket 文件描述符上下文,也无法处理写事件就绪通知 - 心跳包内容建议用固定长度(如 4 字节
"PING"),避免粘包解析成本;服务端收到后只需原样回"PONG",不涉及业务逻辑
断连清理时最容易漏掉的资源
检测到心跳超时后,I/O 线程需要安全关闭对应 socket 并从管理容器(如 std::unordered_map<int connectionptr></int>)中移除。但有三个常被忽略的点:
- epoll 实例里必须调用
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, nullptr):否则该 fd 仍留在内核 epoll 表中,后续可能触发无效事件 - 如果用了
std::shared_ptr<connection></connection>,确保没有循环引用(比如 Connection 持有自身shared_ptr的回调),否则对象永远不会析构 - 关闭 socket 后,立刻调用
shutdown(fd, SHUT_RDWR)再close(fd):仅close()可能导致 FIN 包延迟发送,影响对端快速感知断连
真正麻烦的是:I/O 线程正在处理该连接的 recv,而心跳线程同时判定超时并试图 close —— 这要求你在 close 前加一层「标记为待关闭」的原子状态,并在 I/O 循环末尾统一清理。否则容易出现 double-close 或 use-after-close。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











