启用tcp_keepalive需设置tcp_keepidle、tcp_keepintvl、tcp_keepcnt三个参数,分别控制空闲时间、探测间隔和失败次数;仅开启so_keepalive开关无效。

setsockopt启用TCP_KEEPALIVE到底要设哪些参数
Linux下直接用setsockopt开TCP保活,不是设个开关就完事。核心是三个参数:空闲时间、重试间隔、重试次数,它们决定心跳什么时候发、发几次、失败后多久断连。
常见错误是只设TCP_KEEPALIVE为1,结果连接空闲5分钟才触发第一次探测,而业务要求2分钟内必须感知断连——这就得调TCP_KEEPIDLE。
-
TCP_KEEPIDLE:连接空闲多少秒后开始发第一个探测包(默认7200秒,太长) -
TCP_KEEPINTVL:两次探测之间间隔几秒(默认75秒) -
TCP_KEEPCNT:连续失败几次后关闭连接(默认9次)
示例(服务端socket fd为sockfd):
int idle = 120; // 2分钟空闲后开始心跳 int interval = 30; // 每30秒探一次 int count = 3; // 连续3次失败就断 setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle)); setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval)); setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count));
Windows上SO_KEEPALIVE和Linux行为不一致?
Windows的SO_KEEPALIVE只开开关,没有暴露TCP_KEEPIDLE这类参数。它的默认空闲时间是2小时,远超大多数业务容忍阈值。不能靠它做精细控制。
立即学习“C++免费学习笔记(深入)”;
真实场景里,比如跨公网的IoT设备连接,2小时才发现断连等于不可用。这时候必须自己实现应用层心跳,或者用WSAIoctl配SIO_KEEPALIVE_VALS(仅WinXP+支持)。
- 调
setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on))只是启用底层机制 - 想改超时逻辑,必须用
WSAIoctl传tcp_keepalive结构体,否则参数全走系统默认 - 注意
tcp_keepalive里的keepalivetime单位是毫秒,别和Linux秒级搞混
为什么开了TCP_KEEPALIVE还是收不到RST或ETIMEDOUT
保活探测发出去,对方没响应,不代表你立刻收到错误。TCP栈会默默重试,直到TCP_KEEPCNT耗尽才通知应用层——这中间可能拖几十秒甚至几分钟。
更麻烦的是:对方进程崩溃但内核还在,或者NAT设备静默丢包,探测包根本到不了对端,本地TCP只能等超时,期间read()和write()都正常返回,你完全感知不到异常。
- 用
select()或poll()监听socket可读,不一定能捕获断连;得结合recv(fd, buf, 1, MSG_PEEK | MSG_DONTWAIT)试探性读 - 真正可靠的做法是加应用层心跳:固定间隔发
PING包,对方回PONG,超时未回就主动close - 别依赖
errno == ETIMEDOUT——多数情况断连表现为read()返回0(对端关闭)或-1+errno == ECONNRESET
应用层心跳该放协议哪一层
在传输层之上、业务逻辑之下加心跳帧,最稳妥。不要塞进业务包里混用,否则心跳逻辑和业务耦合,升级协议时容易漏处理。
典型做法是定义独立的心跳消息类型,比如Protobuf中加message Ping { int64 ts = 1; }和message Pong { int64 ts = 1; },用同一个连接复用收发。
- 心跳包体积尽量小(
sizeof(uint8_t)级),避免带大字段或序列化开销 - 发送间隔建议设为保活空闲时间的1/3,比如TCP空闲设120秒,应用心跳就每40秒发一次
- 服务端收到
Ping必须立即回Pong,不能排队;客户端收到Pong要校验时间戳防延迟抖动误判
复杂点在于:心跳超时判定不能只看单次往返,得用滑动窗口统计最近3–5次RTT,否则网络抖动会频繁误杀连接。










