1. TCP协议基础回顾:头部结构、三次握手与四次挥手、状态转换图
各位同学,咱们今天正式开讲《TCP重传定位与端口复用实战》的第一章。说实话,TCP协议这东西,很多搞了几年网络的老手都未必能把它吃透。我见过太多人一遇到TCP重传就抓瞎,端口复用更是稀里糊涂。所以,咱们先把地基打牢。
这一章,我带你过一遍TCP协议最核心的三个东西:头部结构、三次握手与四次挥手、状态转换图。别嫌基础,后面所有实战技巧都建立在这上面。
1.1 TCP头部结构:每一比特都有用
TCP头部,说白了就是20字节的固定部分,后面还能跟选项。我刚开始学的时候,总觉得这20字节记不住。后来在项目里抓包分析问题,才发现每一比特都可能是关键线索。
| 字段 | 长度(位) | 说明 |
|---|---|---|
| 源端口 | 16 | 发送方端口号 |
| 目的端口 | 16 | 接收方端口号 |
| 序列号 | 32 | 本报文段数据的第一个字节序号 |
| 确认号 | 32 | 期望收到的下一个字节序号 |
| 数据偏移 | 4 | 头部长度,单位是4字节 |
| 保留 | 3 | 保留位,必须为0 |
| 标志位 | 9 | URG、ACK、PSH、RST、SYN、FIN等 |
| 窗口大小 | 16 | 接收窗口,用于流量控制 |
| 校验和 | 16 | 覆盖头部和数据 |
| 紧急指针 | 16 | URG标志位为1时有效 |
重点记忆:序列号和确认号是TCP可靠传输的命根子。重传定位时,你盯着这两个字段看,基本就能判断出问题出在哪一端。
嗯,这里要注意一下标志位。9个标志位里,我们最常用的是SYN、ACK、FIN、RST这四个。SYN用来发起连接,ACK用来确认,FIN用来关闭连接,RST用来异常复位。我在一次线上故障排查中,就是靠RST标志位锁定了对端服务异常关闭的问题。
1.2 三次握手:建立连接的三步舞
三次握手,说白了就是客户端和服务端互相确认对方能收能发的过程。你想想看,如果只有两次握手,服务端怎么知道客户端已经收到了自己的SYN-ACK?
- 第一次握手:客户端发送SYN包,序列号设为x,进入SYN_SENT状态。
- 第二次握手:服务端收到后,回复SYN-ACK包,序列号设为y,确认号为x+1,进入SYN_RCVD状态。
- 第三次握手:客户端收到后,发送ACK包,确认号为y+1,进入ESTABLISHED状态。
个人经验:我曾经遇到过一个问题,客户端一直卡在SYN_SENT状态。抓包一看,服务端根本没回SYN-ACK。后来发现是防火墙把SYN包给丢了。所以,三次握手失败,先查中间设备。
这里有个坑,我提醒一下大家。三次握手中的序列号不是从0开始的,而是随机生成的。为什么?为了防止历史连接干扰。我记得有一次,一个老系统升级后频繁断连,查了半天才发现是序列号生成算法有bug,导致新旧连接混淆。
1.3 四次挥手:优雅地告别
四次挥手比三次握手复杂一些。为什么?因为TCP连接是全双工的,每一端都要单独关闭自己的数据流。
- 第一次挥手:主动关闭方发送FIN包,序列号为u,进入FIN_WAIT_1状态。
- 第二次挥手:被动关闭方回复ACK包,确认号为u+1,进入CLOSE_WAIT状态。主动方收到后进入FIN_WAIT_2状态。
- 第三次挥手:被动关闭方发送FIN包,序列号为v,进入LAST_ACK状态。
- 第四次挥手:主动关闭方回复ACK包,确认号为v+1,进入TIME_WAIT状态。
避坑指南:我曾经在端口复用时踩过TIME_WAIT的坑。主动关闭方在发送最后一个ACK后,会进入TIME_WAIT状态,持续2MSL(约2分钟)。这段时间内,该端口不能被重用。如果你在写高并发服务器,一定要考虑这个因素。
1.4 状态转换图:TCP的完整生命周期
下面这张图,是我自己整理的TCP状态转换图。你把它记熟了,以后排查连接问题会快很多。
这张图里,我特别标出了客户端和服务端的状态路径。你注意看,客户端在主动关闭后会进入TIME_WAIT,而服务端不会。这就是为什么高并发服务器通常设计为被动关闭方,避免TIME_WAIT堆积。
1.5 状态转换中的关键点
我个人习惯把状态转换图分成三个阶段来记:连接建立阶段、数据传输阶段、连接关闭阶段。每个阶段都有几个关键状态需要关注。
核心要点:
- SYN_SENT和SYN_RCVD:这两个状态持续时间很短,如果长时间停留,说明网络有问题。
- CLOSE_WAIT:服务端收到FIN后进入此状态。如果大量连接卡在CLOSE_WAIT,说明应用程序没有正确调用close()。
- TIME_WAIT:主动关闭方最后的等待状态。端口复用时,这个状态是最大的障碍。
我记得有一次,一个同事跑来跟我说,他的服务端程序跑着跑着就报"Too many open files"。我让他用netstat -tan | grep CLOSE_WAIT一看,好家伙,几千个CLOSE_WAIT连接。说白了,就是他的代码里没有正确处理对端关闭后的清理逻辑。
实用命令:排查TCP状态问题,我常用这三个命令:
# 查看所有TCP连接状态统计
netstat -tan | awk '{print $6}' | sort | uniq -c
# 查看特定端口的连接状态
ss -tan state time-wait | grep :8080
# 实时监控连接状态变化
watch -n 1 'netstat -tan | grep ESTABLISHED | wc -l'
好了,这一章的内容就到这里。TCP头部结构、三次握手、四次挥手、状态转换图,这四个东西你吃透了,后面讲重传定位和端口复用的时候,你就能跟上我的节奏。记住,网络问题排查,90%的线索都藏在TCP的状态和头部字段里。