网络基础回顾:TCP三次握手与四次挥手、TCP状态变迁图、MTU与MSS、网络延迟与丢包
做网络排障这么多年,我有个习惯——不管问题多复杂,先回到TCP本身。说白了,连接超时绝大多数时候都跟TCP的握手、挥手、状态机或者报文分片有关。这一节咱们就把这些基础再过一遍,但我会结合实战中踩过的坑来讲。
TCP三次握手:不只是“你好你好你好”
三次握手的流程大家应该都熟:客户端发SYN,服务端回SYN+ACK,客户端再回ACK。但你真的理解每一步可能出什么问题吗?
核心流程:
- 客户端 → 服务端:SYN(seq=x)—— 客户端进入SYN_SENT状态
- 服务端 → 客户端:SYN+ACK(seq=y, ack=x+1)—— 服务端进入SYN_RCVD状态
- 客户端 → 服务端:ACK(seq=x+1, ack=y+1)—— 双方进入ESTABLISHED状态
我在项目中遇到过一个问题:客户端发完SYN后一直收不到SYN+ACK。当时抓包一看,SYN报文到了服务端,但服务端的回包被中间防火墙丢了。嗯,这里要注意——很多连接超时不是真的“连不上”,而是中间设备把SYN+ACK给过滤了。
实战技巧:排查连接超时时,我习惯先在客户端和服务端分别抓包。如果客户端看到SYN_SENT但没收到SYN+ACK,那问题大概率在中间链路或服务端没监听端口。
四次挥手:优雅关闭与暴力关闭
四次挥手比握手复杂一些,因为涉及半关闭状态。流程是这样的:
- 主动关闭方 → 被动关闭方:FIN(seq=u)—— 进入FIN_WAIT_1
- 被动关闭方 → 主动关闭方:ACK(seq=v, ack=u+1)—— 进入CLOSE_WAIT,主动方进入FIN_WAIT_2
- 被动关闭方 → 主动关闭方:FIN(seq=w, ack=u+1)—— 进入LAST_ACK
- 主动关闭方 → 被动关闭方: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状态机是排查连接问题的核心工具。我每次遇到诡异的连接问题,第一件事就是画状态变迁图。下面这张图是我自己总结的,涵盖了所有关键状态和转换路径。
这张图我建议你保存下来。每次排查连接问题时,对着图看当前连接在哪个状态,基本就能定位到问题环节。比如,服务端大量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、延迟丢包这些吃透了,排障时心里就有底了。