需先调用setsockopt启用SO_KEEPALIVE,再分别设置IPPROTO_TCP级别的TCP_KEEPIDLE、TCP_KEEPINTVL、TCP_KEEPCNT三参数;注意跨平台差异及内核兼容性,且必须验证实际抓包效果而非仅看ss输出。

怎么用 setsockopt 为单个 socket 启用并调优 keepalive
不是所有连接都该用系统级默认值,尤其在 NAT 环境下——默认 2 小时才发第一个探测包(tcp_keepalive_time=7200),早被中间设备回收了。必须在代码里对关键 socket 主动配置。
启用 + 定制三参数是两步操作:先开 SO_KEEPALIVE,再设 TCP_KEEPIDLE/TCP_KEEPINTVL/TCP_KEEPCNT。注意:后三个是 TCP 层选项(level=IPPROTO_TCP),不是 socket 层的。
-
int keepalive = 1;→ 调用setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive)) -
int idle = 300;(5 分钟后开始探测)→setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle)) -
int interval = 30;(每次重试间隔 30 秒)→setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval)) -
int probes = 3;(最多探 3 次)→setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, &probes, sizeof(probes))
⚠️ 坑点:这些 TCP 选项在某些旧内核(如 2.6.32 之前)或 musl libc(Alpine)上可能不支持,调用失败会返回 ENOPROTOOPT,务必检查返回值;macOS 用的是 TCP_KEEPALIVE 而非 TCP_KEEPIDLE,跨平台需条件编译。
为什么改系统级参数(sysctl)往往不如代码里设单个 socket
全局改 net.ipv4.tcp_keepalive_time 影响所有 TCP 连接,包括你根本不想保活的短连接(比如 HTTP/1.1 的临时上传)、监控探针、甚至 SSH 登录——反而增加无效流量和内核负担。
典型场景:你的服务既要维持 WebSocket 长连接(需 300s 探测),又要处理大量秒级完成的 API 请求(完全不需要 keepalive)。全局调参等于一刀切,得不偿失。
- 改
/etc/sysctl.conf适合:全服务器统一策略,且无精细控制需求(如嵌入式网关) - 代码中调
setsockopt适合:高并发服务(如 Nginx、Netty、自研代理),按连接类型差异化保活 - 若必须改全局,推荐只调严:比如
tcp_keepalive_time=600(10 分钟)、tcp_keepalive_intvl=20、tcp_keepalive_probes=3,总超时约 660 秒,比默认 7875 秒更适应公网
SSH 和 HTTP 长连接保活,别混用 TCP keepalive 和应用层心跳
很多人以为开了 TCPKeepAlive yes 就万事大吉,结果跨国隧道还是断——因为 TCP keepalive 包可能被防火墙静默丢弃(尤其带 tcp_timestamps=1 时),而应用层心跳(如 HTTP Connection: keep-alive 或 SSH 的 ServerAliveInterval)是明文数据包,更容易穿透。
- SSH:服务端配
TCPKeepAlive yes+ClientAliveInterval 60(单位秒),客户端配ServerAliveInterval 60;禁用tcp_tw_recycle(已废弃,且在 NAT 下必出问题) - HTTP:Nginx 用
keepalive_timeout 60s,但这是连接复用时限,不是保活;真正防 NAT 断连得靠上游主动发OPTIONS或空GET,或反向代理加健康检查 - 关键区别:
TCP keepalive是内核发的纯 ACK 探测段(无 payload),ServerAliveInterval是 sshd 发的加密应用层消息,后者更可靠
验证 keepalive 是否生效,别只看 ss -i
ss -i 显示的 keepalive 字样只表示 socket 开了 SO_KEEPALIVE,**不反映实际参数值**。你设了 TCP_KEEPIDLE=300,它照样可能显示 keepalive 而没数字——这很容易误判。
- 真验证方法:用
tcpdump抓包,过滤tcp[tcpflags] & (tcp-rst|tcp-ack) == tcp-ack and tcp len == 0,看空 ACK 是否在预期时间发出 - 查当前连接实际参数:Linux 4.15+ 可读
/proc/net/tcp第 10 列(keepalive标志位)+ 第 11–13 列(idle/intvl/probes,十六进制),但需结合ss -i的 inode 找对应行 - 最简单粗暴:写个测试 client,
connect后sleep超过idle时间,用strace -e trace=sendto,recvfrom看内核是否触发探测
真正难的从来不是“怎么设”,而是确认它在真实网络路径上起作用——中间设备是否转发探测包、对方是否响应、丢包时重传逻辑是否符合预期,这些都得实测,不能只信配置文件。


















