心跳包应发送空字节或固定字符串(如"heartbeat"),禁用json等序列化格式;客户端宜集成进事件循环而非多线程发送;服务端需结合last_heartbeat_time与超时阈值(如90秒)判断掉线,并在recv返回0或异常errno时立即断连。

心跳包该用什么数据格式发送
直接发空字节或固定字符串最稳妥,比如 "HEARTBEAT" 或单字节 0x01。别用 JSON、Protobuf 等序列化格式——心跳必须轻量,序列化开销和解析失败风险都得避开。服务端收到后只校验长度或内容是否匹配预期,不解析结构。
客户端怎么定时发心跳而不阻塞主线程
用 std::thread + std::chrono::sleep_for 单独起心跳线程,但必须加锁保护 socket 句柄(send() 不是线程安全的)。更稳妥的做法是把心跳逻辑集成进事件循环,比如用 epoll(Linux)或 select() 配合超时控制,在主循环里统一处理读写和心跳触发。否则多线程操作同一 socket 容易导致 send() 返回 EAGAIN 或直接失败。
- 心跳间隔建议设为 30 秒,比 TCP keepalive 默认的 2 小时短得多,能更快发现断连
- 发送前先检查 socket 是否仍有效(
getsockopt(..., SO_ERROR)),避免向已关闭连接发包 - 不要依赖
SO_KEEPALIVE选项替代应用层心跳——它无法感知 NAT 超时、防火墙静默丢包等场景
服务端如何判断客户端掉线
不能只看有没有收到心跳包,要结合「最近一次成功接收时间」+「超时阈值」做判断。比如设定 90 秒无心跳即断连:每次收到心跳就更新该 client 的 last_heartbeat_time;主循环中定期遍历所有连接,对超时的调用 close() 并清理资源。注意这个检查频率别太高(如每 5 秒一次),否则遍历开销大;也别太低(如每分钟一次),否则掉线感知延迟过高。
- 务必在
recv()返回 0 时立即标记连接断开,这是 TCP FIN 的明确信号,比等心跳超时更及时 - 如果用
epoll,记得对每个 socket 开启EPOLLET模式并正确处理边缘触发,否则可能漏掉最后一次心跳后的 EOF - 别在心跳超时后立刻重连——客户端可能只是临时卡顿,服务端只需清理连接,由客户端自行决定是否重连
为什么 recv() 返回 -1 但 errno 不是 EAGAIN 就要断连
recv() 返回 -1 且 errno 是 ECONNRESET、ETIMEDOUT 或 ENETDOWN 这类错误,说明链路已异常中断,不是暂时没数据。这时候再等心跳超时就晚了——连接实际已经不可用。必须立刻关闭 socket,释放 fd 和内存,否则会堆积大量僵尸连接。
- Linux 下可配合
SO_RCVTIMEO设置 recv 超时,避免线程永久阻塞,但注意它只对阻塞 socket 有效 - 非阻塞 socket 下,
recv()返回 -1 且errno == EAGAIN || errno == EWOULDBLOCK才代表正常无数据,其余 errno 均需按错误处理 - Windows 上对应的是
WSAEWOULDBLOCK,别硬写EAGAIN判断
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











