2、TCP协议详解:TCP报文格式、三次握手、四次挥手、状态转换图
聊到网络编程,TCP 是绕不开的核心。我刚开始做网络开发时,总觉得 TCP 就是个可靠传输协议,没什么好深究的。直到有一次线上服务出现大量连接超时,排查了整整两天,最后发现是三次握手阶段出了问题。嗯,从那以后,我再也不敢小看 TCP 的细节了。
说白了,TCP 协议就是两台机器之间的一条「虚拟管道」。它保证数据按顺序到达,不丢包,不重复。但这条管道是怎么建立的?怎么关闭的?中间经历了哪些状态?咱们今天一次性说清楚。
核心要点:TCP 是面向连接的、可靠的、基于字节流的传输层协议。它的可靠性建立在三次握手、四次挥手、滑动窗口、拥塞控制等机制之上。
2.1 TCP 报文格式
先看报文格式。我习惯把 TCP 报文头想象成一个「快递面单」——它不装货物,但告诉接收方怎么处理货物。
TCP 报文头最少 20 字节,最多 60 字节。关键字段如下:
| 字段 | 长度 | 说明 |
|---|---|---|
| 源端口 / 目的端口 | 各 2 字节 | 标识通信的应用程序 |
| 序列号 (SEQ) | 4 字节 | 本报文段数据的第一个字节序号 |
| 确认号 (ACK) | 4 字节 | 期望收到对方下一个报文段的序号 |
| 数据偏移 | 4 位 | TCP 头部的长度(单位 4 字节) |
| 标志位 | 6 位 | URG、ACK、PSH、RST、SYN、FIN |
| 窗口大小 | 2 字节 | 接收方的可用缓冲区大小 |
| 校验和 | 2 字节 | 校验头部和数据部分 |
| 紧急指针 | 2 字节 | 紧急数据的末尾位置 |
这里我特别想强调一下标志位。6 个标志位里,SYN、ACK、FIN 是握手和挥手的主角。你想想看,三次握手其实就是 SYN 和 ACK 的「你来我往」。RST 标志位也很重要——我在项目中遇到过客户端突然崩溃,服务端收到 RST 后直接重置连接,省去了等待超时的开销。
个人经验:抓包分析时,我习惯先看 SYN 和 ACK 标志位。如果 SYN 发出去没收到 SYN+ACK,大概率是防火墙拦截了,或者对端端口没开。
2.2 三次握手
三次握手,说白了就是建立连接前的「互相确认」过程。为什么需要三次?两次行不行?我当年面试时也被问过这个问题。
先看流程:
- 第一次握手:客户端发送 SYN=1,SEQ=x。客户端进入 SYN_SENT 状态。
- 第二次握手:服务端回复 SYN=1,ACK=1,SEQ=y,ACK_NUM=x+1。服务端进入 SYN_RCVD 状态。
- 第三次握手:客户端发送 ACK=1,SEQ=x+1,ACK_NUM=y+1。客户端进入 ESTABLISHED 状态,服务端收到后也进入 ESTABLISHED。
为什么是三次?我举个例子你就明白了。假设只有两次握手:客户端发 SYN,服务端回 SYN+ACK,然后服务端就认为连接建立了。但如果这个 SYN 是网络延迟重发的旧包呢?服务端会白白浪费资源维护一个无效连接。三次握手的关键在于——客户端收到服务端的 SYN+ACK 后,再发一次 ACK,告诉服务端「我确实在跟你通信,不是旧包乱入」。
避坑指南:我曾经遇到过 SYN 洪水攻击。攻击者只发 SYN,不回复第三次握手。服务端维护的半连接队列很快被填满,正常用户无法建立连接。解决方案是开启 SYN Cookie 或者调整 tcp_syncookies 内核参数。
2.3 四次挥手
断开连接比建立连接复杂一些。因为 TCP 是双工通信,双方都需要关闭自己的发送通道。
流程如下:
- 第一次挥手:客户端发送 FIN=1,SEQ=u。客户端进入 FIN_WAIT_1 状态。
- 第二次挥手:服务端回复 ACK=1,SEQ=v,ACK_NUM=u+1。服务端进入 CLOSE_WAIT 状态。客户端收到后进入 FIN_WAIT_2。
- 第三次挥手:服务端发送 FIN=1,ACK=1,SEQ=w,ACK_NUM=u+1。服务端进入 LAST_ACK 状态。
- 第四次挥手:客户端发送 ACK=1,SEQ=u+1,ACK_NUM=w+1。客户端进入 TIME_WAIT 状态,等待 2MSL 后关闭。
这里有个关键点:第二次和第三次挥手之间,服务端可能还有数据要发送。所以挥手是四次,不是三次。你想想看,如果服务端在收到 FIN 时立刻也发 FIN,那就变成三次了——但这种情况很少见,因为服务端通常需要时间处理剩余数据。
注意:TIME_WAIT 状态持续 2MSL(约 2 分钟)。我见过很多高并发服务因为 TIME_WAIT 过多导致端口耗尽。解决方案是开启 tcp_tw_reuse 和 tcp_tw_recycle(但要注意 NAT 环境下的问题)。
2.4 状态转换图
下面这张图是我自己整理的 TCP 状态转换流程。它把三次握手和四次挥手串在一起,看起来更直观。
这张图里,蓝色箭头是三次握手,紫色箭头是四次挥手。你注意看服务端多了一个 LISTEN 状态——这是服务端特有的,表示它在监听端口等待连接。客户端没有 LISTEN,它直接从 CLOSED 跳到 SYN_SENT。
还有一个容易被忽略的状态:CLOSE_WAIT。我排查过一个问题,服务端大量连接处于 CLOSE_WAIT,导致文件描述符耗尽。原因很简单——服务端收到客户端的 FIN 后,没有调用 close() 关闭 socket。嗯,写代码时一定要记得在收到 FIN 后及时关闭连接。
调试技巧:用 netstat -anp | grep TIME_WAIT 可以查看 TIME_WAIT 数量。如果超过几万个,建议优化内核参数。用 ss -s 可以快速查看所有 TCP 状态的统计信息。
好了,TCP 协议的核心内容就这些。报文格式是基础,三次握手和四次挥手是灵魂,状态转换图是调试利器。我个人建议你把这张图记在脑子里——排查网络问题时,90% 的问题都能通过状态转换找到线索。
可可精品资料库