网络基础回顾:TCP三次握手与四次挥手、TCP状态变迁图、MTU与MSS、网络延迟与丢包

做网络排障这么多年,我有个习惯——不管问题多复杂,先回到TCP本身。说白了,连接超时绝大多数时候都跟TCP的握手、挥手、状态机或者报文分片有关。这一节咱们就把这些基础再过一遍,但我会结合实战中踩过的坑来讲。

TCP三次握手:不只是“你好你好你好”

三次握手的流程大家应该都熟:客户端发SYN,服务端回SYN+ACK,客户端再回ACK。但你真的理解每一步可能出什么问题吗?

核心流程:

  1. 客户端 → 服务端:SYN(seq=x)—— 客户端进入SYN_SENT状态
  2. 服务端 → 客户端:SYN+ACK(seq=y, ack=x+1)—— 服务端进入SYN_RCVD状态
  3. 客户端 → 服务端:ACK(seq=x+1, ack=y+1)—— 双方进入ESTABLISHED状态

我在项目中遇到过一个问题:客户端发完SYN后一直收不到SYN+ACK。当时抓包一看,SYN报文到了服务端,但服务端的回包被中间防火墙丢了。嗯,这里要注意——很多连接超时不是真的“连不上”,而是中间设备把SYN+ACK给过滤了。

实战技巧:排查连接超时时,我习惯先在客户端和服务端分别抓包。如果客户端看到SYN_SENT但没收到SYN+ACK,那问题大概率在中间链路或服务端没监听端口。

四次挥手:优雅关闭与暴力关闭

四次挥手比握手复杂一些,因为涉及半关闭状态。流程是这样的:

  1. 主动关闭方 → 被动关闭方:FIN(seq=u)—— 进入FIN_WAIT_1
  2. 被动关闭方 → 主动关闭方:ACK(seq=v, ack=u+1)—— 进入CLOSE_WAIT,主动方进入FIN_WAIT_2
  3. 被动关闭方 → 主动关闭方:FIN(seq=w, ack=u+1)—— 进入LAST_ACK
  4. 主动关闭方 → 被动关闭方:ACK(seq=u+1, ack=w+1)—— 主动方进入TIME_WAIT

你想想看,为什么要有TIME_WAIT?说白了,就是为了保证最后一个ACK能到达对方。如果ACK丢了,被动方会重发FIN,TIME_WAIT期间还能处理这个重传。TIME_WAIT默认持续2MSL(约1-4分钟),这也是很多高并发服务端出现端口耗尽的原因之一。

避坑指南:我曾经调优过一个短连接服务,发现大量TIME_WAIT堆积导致新连接无法建立。解决方案不是盲目调小TIME_WAIT,而是改用长连接或开启tcp_tw_reuse(Linux 4.12+已废弃该参数,需注意内核版本)。

TCP状态变迁图:排障的“地图”

TCP状态机是排查连接问题的核心工具。我每次遇到诡异的连接问题,第一件事就是画状态变迁图。下面这张图是我自己总结的,涵盖了所有关键状态和转换路径。

TCP状态变迁图 TCP状态变迁图 CLOSED SYN_SENT SYN_RCVD ESTABLISHED FIN_WAIT_1 CLOSE_WAIT FIN_WAIT_2 LAST_ACK TIME_WAIT 主动打开 被动打开 收到SYN+ACK 收到ACK 主动关闭 收到FIN 收到ACK 收到FIN+ACK 关闭 收到FIN 收到ACK 2MSL超时 说明: • 实线表示正常状态转换路径 • 虚线表示特殊路径(同时收到FIN+ACK时直接进入TIME_WAIT) • 排障时重点关注SYN_RCVD、CLOSE_WAIT、TIME_WAIT这三个状态

这张图我建议你保存下来。每次排查连接问题时,对着图看当前连接在哪个状态,基本就能定位到问题环节。比如,服务端大量CLOSE_WAIT,说明应用程序没调用close()——这是代码bug,不是网络问题。

MTU与MSS:报文分片那些事

MTU(最大传输单元)是链路层对单次传输数据大小的限制。以太网标准MTU是1500字节。MSS(最大报文段长度)是TCP层在握手时协商的,通常等于MTU减去IP头(20字节)和TCP头(20字节),也就是1460字节。

概念 层级 典型值 作用
MTU 链路层 1500(以太网) 限制单次传输的总大小
MSS 传输层 1460(以太网) 限制TCP数据段大小

避坑指南:我曾经遇到一个诡异的问题——某些大文件传输总是超时重传。排查发现中间有个VPN隧道MTU只有1400,但客户端和服务端协商的MSS还是1460。结果报文在中间被分片,分片丢失导致重传。解决方案是手动调小MSS或开启PMTUD(路径MTU发现)。

PMTUD的工作原理很简单:发送端设置DF(不分片)标志,如果中间链路MTU更小,路由器会返回ICMP“需要分片”消息。但很多防火墙会丢弃ICMP,导致PMTUD失效。这时候就需要手动设置MSS了。

调优建议:在不确定中间链路MTU的情况下,我习惯在服务端iptables中设置MSS钳制:iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu。这能自动适配路径MTU。

网络延迟与丢包:连接超时的两大元凶

延迟和丢包是导致连接超时的两个最直接原因。但很多人分不清它们各自的表现。

延迟(Latency):数据从A到B的单向时间。高延迟会导致握手变慢,但不会丢数据。比如从北京到纽约的RTT(往返时间)大约200ms,三次握手就需要600ms。

丢包(Packet Loss):数据在传输过程中丢失。TCP通过超时重传和快速重传机制来恢复。丢包率超过2%时,TCP性能会急剧下降。

指标 正常范围 异常表现 对连接超时的影响
RTT < 50ms(同城) > 200ms 握手时间延长,但不会超时
丢包率 < 0.1% > 1% 触发重传,可能导致连接超时
抖动 < 10ms > 50ms 导致RTO估算不准,过早超时

我排查连接超时问题时,第一步就是用ping和mtr看延迟和丢包。如果ping正常但TCP连接超时,那问题大概率在应用层或中间设备对TCP报文的特殊处理上。

排障命令示例:

# 查看延迟和丢包
ping -c 10 target_ip

# 查看路径中每一跳的延迟和丢包
mtr -r target_ip

# 查看TCP连接状态
ss -tna | grep target_ip

# 抓包分析握手过程
tcpdump -i eth0 host target_ip and port 80 -w handshake.pcap

嗯,这里要注意——ping不通不代表TCP一定连不上。有些网络设备会禁ping但放行TCP。反过来,ping通也不代表TCP没问题,因为TCP有状态,中间设备可能对TCP报文做特殊处理(比如SYN flood防护)。

我的习惯:排查连接超时,我从来不用单一工具。ping + telnet + tcpdump三件套一起上,基本能覆盖90%的场景。如果还不行,就用tcptraceroute看TCP路径。

最后说一句,这些基础概念看似简单,但每次遇到棘手的连接超时问题,回头一看,根因往往就在这些基础环节里。把TCP状态机、MTU/MSS、延迟丢包这些吃透了,排障时心里就有底了。