TCP 定时器与 Keep-Alive 机制
TCP 为什么需要定时器
TCP 是面向连接的协议,但网络本身不可靠。定时器是 TCP 处理超时、探测状态、回收资源的核心机制。Linux 内核中,TCP 连接维护多个定时器,协同保障通信正确性。
重传定时器(Retransmit Timer)
已在文档 07 详细讲解,此处总结:
- 触发条件:发送数据后未在 RTO 内收到 ACK
- RTO 计算:基于 RTT 采样,Jacobson/Karels 算法
- 指数退避:每次重传 RTO 翻倍,上限通常 120s
- 快速重传:3 个重复 ACK 立即重传,不等待 RTO
坚持定时器(Persist Timer)
问题场景
接收方通告窗口 = 0(缓冲区满),发送方停止发送。之后接收方空出缓冲区,发送 ACK 通告新窗口。但如果这个 ACK 丢失:
- 发送方认为窗口仍为 0,永远等待
- 接收方认为已通告窗口,永远等待数据
- 死锁!
解决方案
发送方启动坚持定时器,周期性地发送窗口探测报文(1 字节数据):
定时器行为
- 首次超时:RTO 后发送第一个探测
- 后续探测:指数退避(RTO × 2, ×4, ×8...),上限 60s
- 探测次数:通常 10~15 次后放弃连接
- 探测内容:1 字节数据(必须被接收方确认,即使窗口为 0)
# Linux 相关参数
sysctl net.ipv4.tcp_retries2 # 放弃连接前的重传次数(默认 15)
保活定时器(Keep-Alive Timer)
为什么需要 Keep-Alive
TCP 连接可能因以下原因"静默死亡":
- 对端主机崩溃(未发送 FIN)
- 中间路由器故障
- 网络分区
- 对端应用死锁但未关闭连接
发送方可能永远等待,连接资源被占用。
Keep-Alive 机制
空闲一段时间后,发送探测报文(Keep-Alive Probe):
Linux 参数
# 空闲多久开始探测(秒,默认 7200 = 2 小时)
sysctl net.ipv4.tcp_keepalive_time=7200
# 探测间隔(秒,默认 75)
sysctl net.ipv4.tcp_keepalive_intvl=75
# 探测次数(默认 9)
sysctl net.ipv4.tcp_keepalive_probes=9
总超时时间 = keepalive_time + keepalive_intvl × keepalive_probes 默认 = 7200 + 75 × 9 = 7875 秒 ≈ 2.2 小时
应用层替代方案
大多数应用(如 WebSocket、MQTT、数据库连接池)使用应用层心跳:
- 更灵活(间隔可配置为秒级)
- 可携带业务状态
- 避免内核参数全局影响
TIME_WAIT 定时器
已在文档 04 详细讲解,此处总结:
- 触发:主动关闭方发送最后一个 ACK 后进入 TIME_WAIT
- 持续时间:2MSL(Linux 默认 60s,MSL=30s)
- 目的:
- 确保最后一个 ACK 被接收(若丢失可重传)
- 防止旧连接的延迟报文干扰新连接
- 副作用:大量短连接导致端口/内存耗尽
延迟 ACK 定时器
已在文档 16 提及,此处补充内核机制:
- 触发:收到数据后启动 40ms 定时器
- 行为:
- 40ms 内无数据要发送 → 发送纯 ACK
- 40ms 内有数据要发送 → 捎带 ACK
- 例外:收到乱序数据立即回复 ACK(用于快速重传)
# Linux 快速 ACK 模式(禁用延迟 ACK)
setsockopt(sockfd, IPPROTO_TCP, TCP_QUICKACK, &on, sizeof(on));
SYN-ACK 重传定时器
三次握手中,服务端发送 SYN-ACK 后等待客户端 ACK:
Linux 参数 tcp_synack_retries 控制重传次数(默认 5)。
定时器参数速查表
| 定时器 | 内核参数 | 默认值 | 作用 |
|---|---|---|---|
| 重传 | tcp_retries2 | 15 | 放弃连接前的重传次数 |
| 坚持 | (无独立参数) | RTO 退避 | 窗口探测间隔 |
| 保活 | tcp_keepalive_time | 7200s | 空闲多久开始探测 |
| 保活 | tcp_keepalive_intvl | 75s | 探测间隔 |
| 保活 | tcp_keepalive_probes | 9 | 探测次数 |
| TIME_WAIT | tcp_fin_timeout | 60s | TIME_WAIT 持续时间 |
| SYN-ACK | tcp_synack_retries | 5 | SYN-ACK 重传次数 |
| SYN | tcp_syn_retries | 6 | SYN 重传次数 |
本篇小结
- 重传定时器:RTO 动态计算,指数退避,上限 120s
- 坚持定时器:窗口=0 时周期性探测,防止死锁
- 保活定时器:空闲 2 小时后探测对端存活,默认 9 次
- TIME_WAIT 定时器:2MSL 确保 ACK 到达、防止旧报文干扰
- 延迟 ACK 定时器:40ms 等待捎带,乱序数据立即 ACK
- SYN-ACK 定时器:指数退避重传,默认 5 次
- 应用层心跳优于 TCP Keep-Alive:更灵活、间隔更短
动手实践
查看本机所有 TCP 定时器参数:
sysctl -a | grep -E "keepalive|retries|timeout|syn"缩短 Keep-Alive 时间(测试环境):
sudo sysctl -w net.ipv4.tcp_keepalive_time=60 sudo sysctl -w net.ipv4.tcp_keepalive_intvl=10 sudo sysctl -w net.ipv4.tcp_keepalive_probes=3 # 总超时 = 60 + 10×3 = 90 秒用 Wireshark 观察 Keep-Alive 探测:
- 建立 SSH 连接,空闲等待
- 过滤
tcp.keep_alive或观察 Seq=NextSeq-1 的报文
观察窗口探测(Persist Timer):
- 用
nc建立连接 - 接收方停止读取,发送方继续发送直到窗口=0
- 抓包观察 1 字节的窗口探测报文
- 用
思考:为什么坚持定时器发送 1 字节数据而不是纯 ACK?如果发送纯 ACK,接收方如何响应?