Computer NetworkNotes

第 08 章:TCP 连接管理

zjc 于 2026-01-08 发布

这是《计算机网络 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 TCP 提供面向连接、可靠、有序的字节流服务。连接管理的核心包括三次握手、序列号同步、选项协商、数据传输、四次挥手、半关闭和异常复位。生产中大量“连接超时”“连接重置”“TIME_WAIT 过多”问题都与这一层有关。

8.1 三次握手

Client                         Server
  | --- SYN, Seq=x ------------> |
  | <-- SYN+ACK, Seq=y, ACK=x+1 |
  | --- ACK, ACK=y+1 ----------> |

为什么需要三次?

  1. 双方同步初始序列号;
  2. 双方确认发送和接收能力;
  3. 防止历史重复连接请求造成错误状态;
  4. 协商窗口、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 表示当前完成握手但尚未被应用取走的连接数。

溢出表现:

  1. 客户端连接超时或重置;
  2. 服务端 CPU 不高但建连失败;
  3. 监听队列溢出计数增长;
  4. SYN 重传;
  5. 应用 accept 速度不足。

处理:

  1. 增大监听 backlog;
  2. 优化应用 accept 和 IO 模型;
  3. 调整线程池;
  4. 前置负载均衡;
  5. 限制异常连接速率;
  6. 检查 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

作用:

  1. 让旧连接重复包在网络中消失;
  2. 保证最后一个 ACK 可被对方收到;
  3. 避免新旧连接序列号冲突。

大量 TIME_WAIT 的常见原因:

  1. 客户端频繁新建短连接;
  2. 每次请求都关闭连接;
  3. 连接池配置过小;
  4. 服务端主动关闭导致客户端侧堆积;
  5. 压测工具并发模式造成。

优先方案是连接复用和长连接,而不是盲目开启端口重用。

CLOSE_WAIT

出现在被动关闭方,表示对方已关闭,本方应用还没调用 close。

大量 CLOSE_WAIT 常见原因:

  1. 应用连接泄漏;
  2. 未正确关闭响应体或 socket;
  3. 异常路径缺少 finally close;
  4. 线程阻塞;
  5. 连接池生命周期错误。

排查方向在应用代码,而不是网络设备。

8.7 RST 与异常关闭

触发 RST 的常见场景:

  1. 访问未监听端口;
  2. 队列溢出或策略拒绝;
  3. 应用崩溃;
  4. 强制关闭 socket;
  5. 防火墙或 LB 断开;
  6. 请求已超时后被服务端丢弃;
  7. 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

局限:

  1. 默认探测周期较长;
  2. 只能发现连接死活,不能证明服务可用;
  3. 中间设备可能提前断开空闲连接;
  4. 应用层仍需业务心跳和超时。

生产长连接应使用应用层心跳:

客户端定期 PING
服务端超时未收到则断开
连续失败后客户端重连
心跳间隔小于中间设备空闲超时

8.9 连接管理实践

客户端:

  1. 使用连接池;
  2. 设置连接建立超时;
  3. 控制最大连接数;
  4. 重试必须幂等;
  5. 借出前检测连接健康;
  6. 空闲时间小于 LB 超时;
  7. 关闭响应体和流。

服务端:

  1. 合理设置 backlog;
  2. accept 后尽快读取;
  3. 限制单 IP 连接;
  4. 设置读写空闲超时;
  5. 优雅关闭;
  6. 监控连接状态分布;
  7. 对异常 RST 抓包。

本章小结

TCP 通过三次握手同步序列号并协商选项,通过四次挥手关闭两个方向。Linux 的 SYN 队列和 accept 队列决定突发建连能力;TIME_WAIT 多与短连接有关,CLOSE_WAIT 多与应用连接泄漏有关。RST、keepalive、心跳和连接池是连接治理的关键点。

思考题

  1. 三次握手为什么不能简化为两次?
  2. SYN Queue 和 Accept Queue 分别在什么阶段?
  3. TIME_WAIT 和 CLOSE_WAIT 分别出现在哪一方?
  4. 大量 CLOSE_WAIT 应优先排查网络还是应用?
  5. 设计一套后端服务长连接心跳和重连策略。