这是《计算机网络 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 TCP 提供面向连接、可靠、有序的字节流服务。连接管理的核心包括三次握手、序列号同步、选项协商、数据传输、四次挥手、半关闭和异常复位。生产中大量“连接超时”“连接重置”“TIME_WAIT 过多”问题都与这一层有关。
8.1 三次握手
Client Server
| --- SYN, Seq=x ------------> |
| <-- SYN+ACK, Seq=y, ACK=x+1 |
| --- ACK, ACK=y+1 ----------> |
为什么需要三次?
- 双方同步初始序列号;
- 双方确认发送和接收能力;
- 防止历史重复连接请求造成错误状态;
- 协商窗口、MSS、窗口缩放、SACK 等选项。
握手耗时至少 1 RTT。跨城或跨洲请求中,连接建立本身就是明显延迟来源,因此长连接和会话复用很重要。
8.2 初始序列号
TCP 用序列号标识字节流位置:
SYN 消耗一个序列号
数据字节逐字节递增
FIN 消耗一个序列号
初始序列号不能从固定值开始,否则旧连接的重复包可能被误认为新连接数据。现代系统会使用随机初始序列号。
8.3 TCP 选项
| 选项 | 作用 |
|---|---|
| MSS | 最大段大小 |
| Window Scale | 扩大窗口 |
| SACK Permitted | 选择性确认 |
| Timestamps | RTT 测量和序号回绕 |
| ALPN | TLS 应用协议协商,位于 TLS 扩展而非 TCP 选项 |
查看握手:
tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn|tcp-ack) != 0' -nn
8.4 半连接与全连接队列
Linux 中服务端有两个关键队列:
SYN Queue:收到 SYN,未完成握手
Accept Queue:握手完成,等待应用 accept()
观察:
ss -lnt
netstat -s | grep -E 'SYN|listen|overflow|drop'
ss -lnt 示例:
State Recv-Q Send-Q Local Address:Port
LISTEN 0 1024 0.0.0.0:8080
在 LISTEN 状态,Send-Q 表示 accept 队列上限,Recv-Q 表示当前完成握手但尚未被应用取走的连接数。
溢出表现:
- 客户端连接超时或重置;
- 服务端 CPU 不高但建连失败;
- 监听队列溢出计数增长;
- SYN 重传;
- 应用 accept 速度不足。
处理:
- 增大监听 backlog;
- 优化应用 accept 和 IO 模型;
- 调整线程池;
- 前置负载均衡;
- 限制异常连接速率;
- 检查 SYN Flood 防护。
8.5 四次挥手
主动关闭方 被动关闭方
| --- FIN, Seq=u -------------> |
| <-- ACK, ACK=u+1 ------------ |
| <-- FIN, Seq=v, ACK=u+1 ----- |
| --- ACK, ACK=v+1 -----------> |
为什么是四次?
TCP 全双工,两个方向分别关闭。被动方收到 FIN 后可能还有数据要发,因此 ACK 和 FIN 可以分开发送,也可以合并。
8.6 TCP 状态
LISTEN
SYN_RECV
ESTABLISHED
FIN_WAIT_1
FIN_WAIT_2
TIME_WAIT
CLOSE_WAIT
LAST_ACK
CLOSING
CLOSED
查看:
ss -ant
ss -ant state time-wait
ss -ant state close-wait
TIME_WAIT
出现在主动关闭方,通常持续 2MSL。
作用:
- 让旧连接重复包在网络中消失;
- 保证最后一个 ACK 可被对方收到;
- 避免新旧连接序列号冲突。
大量 TIME_WAIT 的常见原因:
- 客户端频繁新建短连接;
- 每次请求都关闭连接;
- 连接池配置过小;
- 服务端主动关闭导致客户端侧堆积;
- 压测工具并发模式造成。
优先方案是连接复用和长连接,而不是盲目开启端口重用。
CLOSE_WAIT
出现在被动关闭方,表示对方已关闭,本方应用还没调用 close。
大量 CLOSE_WAIT 常见原因:
- 应用连接泄漏;
- 未正确关闭响应体或 socket;
- 异常路径缺少 finally close;
- 线程阻塞;
- 连接池生命周期错误。
排查方向在应用代码,而不是网络设备。
8.7 RST 与异常关闭
触发 RST 的常见场景:
- 访问未监听端口;
- 队列溢出或策略拒绝;
- 应用崩溃;
- 强制关闭 socket;
- 防火墙或 LB 断开;
- 请求已超时后被服务端丢弃;
- keepalive 探测失败。
表现:
Connection reset by peer
ECONNRESET
排查:
ss -lntp
tcpdump -i eth0 tcp port 8080 and 'tcp[tcpflags] & tcp-rst != 0' -nn
8.8 Keepalive 与应用心跳
TCP keepalive 用于检测死连接:
sysctl net.ipv4.tcp_keepalive_time
sysctl net.ipv4.tcp_keepalive_intvl
sysctl net.ipv4.tcp_keepalive_probes
局限:
- 默认探测周期较长;
- 只能发现连接死活,不能证明服务可用;
- 中间设备可能提前断开空闲连接;
- 应用层仍需业务心跳和超时。
生产长连接应使用应用层心跳:
客户端定期 PING
服务端超时未收到则断开
连续失败后客户端重连
心跳间隔小于中间设备空闲超时
8.9 连接管理实践
客户端:
- 使用连接池;
- 设置连接建立超时;
- 控制最大连接数;
- 重试必须幂等;
- 借出前检测连接健康;
- 空闲时间小于 LB 超时;
- 关闭响应体和流。
服务端:
- 合理设置 backlog;
- accept 后尽快读取;
- 限制单 IP 连接;
- 设置读写空闲超时;
- 优雅关闭;
- 监控连接状态分布;
- 对异常 RST 抓包。
本章小结
TCP 通过三次握手同步序列号并协商选项,通过四次挥手关闭两个方向。Linux 的 SYN 队列和 accept 队列决定突发建连能力;TIME_WAIT 多与短连接有关,CLOSE_WAIT 多与应用连接泄漏有关。RST、keepalive、心跳和连接池是连接治理的关键点。
思考题
- 三次握手为什么不能简化为两次?
- SYN Queue 和 Accept Queue 分别在什么阶段?
- TIME_WAIT 和 CLOSE_WAIT 分别出现在哪一方?
- 大量 CLOSE_WAIT 应优先排查网络还是应用?
- 设计一套后端服务长连接心跳和重连策略。