TCP协议回顾:三次握手与四次挥手、状态转换、序列号与确认号
说实话,做网络抓包这么多年,我见过太多人一上来就盯着应用层数据猛看。结果呢?连最基本的TCP握手都没搞明白,抓到的包全是乱序重传,还以为是服务器出了问题。
TCP协议,说白了就是两台机器之间的一条「虚拟管道」。这条管道怎么建、怎么拆、中间怎么保证数据不乱——这就是我们今天要聊的核心。
一、TCP三次握手:建立连接的过程
我记得刚入行那会儿,带我的老工程师跟我说:「TCP握手就像打电话——你拨号,对方说听到了,你再回一句好的,这才算通。」这个比喻我一直记着。
三次握手的本质:客户端和服务器互相确认对方的接收能力和发送能力都正常。
- 第一次握手(SYN):客户端发送SYN包,序列号设为x。客户端进入SYN_SENT状态。
- 第二次握手(SYN+ACK):服务器收到后,回复SYN+ACK包,确认号设为x+1,自己的序列号设为y。服务器进入SYN_RCVD状态。
- 第三次握手(ACK):客户端收到后,发送ACK包,确认号设为y+1。客户端和服务器都进入ESTABLISHED状态。
你想想看,如果只有两次握手会怎样?服务器没法确认客户端是否收到了自己的SYN+ACK。万一那个包丢了,服务器白白等在那里,资源就浪费了。
实战小技巧:我在排查连接超时问题时,经常先看三次握手是否完成。如果只看到SYN包没有SYN+ACK,八成是防火墙拦截了。如果SYN+ACK发出去了但没收到最后的ACK,那可能是客户端半连接队列满了。
二、TCP四次挥手:优雅地断开连接
断开连接比建立连接复杂一些。为什么?因为TCP是全双工的,两边都要说「再见」才行。
- 第一次挥手(FIN):主动关闭方发送FIN包,序列号为u。进入FIN_WAIT_1状态。
- 第二次挥手(ACK):被动关闭方回复ACK,确认号为u+1。进入CLOSE_WAIT状态。主动方收到后进入FIN_WAIT_2。
- 第三次挥手(FIN):被动关闭方发送自己的FIN包,序列号为v。进入LAST_ACK状态。
- 第四次挥手(ACK):主动关闭方回复ACK,确认号为v+1。进入TIME_WAIT状态,等待2MSL后关闭。
注意:我曾经遇到过一个线上问题——服务器上TIME_WAIT连接堆积到几万个,导致新连接无法建立。后来发现是短连接太多,TIME_WAIT的2MSL等待时间太长。解决方案是用端口复用或者调整tcp_tw_reuse参数。
三、TCP状态转换图
状态转换是TCP协议里最容易让人头晕的部分。我建议你把它当成一张地图——每次抓包看到某个状态,就想想它下一步会去哪。
这张图里,我特意把客户端和服务器分成了两列。你抓包的时候,对照着看状态变化,会清晰很多。
四、序列号与确认号:TCP的「快递单号」
TCP能保证数据不乱、不丢、不重,靠的就是序列号和确认号。我习惯把它们比作快递包裹上的单号——每个包裹都有唯一编号,收件人收到后告诉你「我收到第几号了,下一个发第几号」。
| 概念 | 含义 | 抓包中的表现 |
|---|---|---|
| 序列号(SEQ) | 当前数据包的第一个字节的编号 | Seq=1 表示这是从字节1开始的数据 |
| 确认号(ACK) | 期望收到的下一个序列号 | Ack=1001 表示已收到前1000字节 |
| 初始序列号(ISN) | 连接建立时随机生成的起始序列号 | 三次握手中的SYN包携带ISN |
抓包小技巧:我每次看TCP流,第一件事就是看初始序列号。如果发现ISN是0或者规律递增,那八成是某些嵌入式设备或者老系统。正常的操作系统会用随机ISN防止攻击。
五、实战中的常见问题
说了这么多理论,咱们聊聊实际抓包中经常碰到的情况。
- 半连接攻击:只发SYN不完成握手。服务器资源被耗尽。我见过一个案例,某电商平台大促前被打了半连接攻击,nginx直接挂掉。
- CLOSE_WAIT堆积:应用层没调用close()。我曾经排查过一个Java应用,发现数据库连接池没释放,CLOSE_WAIT堆到上千个。
- TIME_WAIT过多:短连接场景下常见。可以用端口复用或者长连接来缓解。
- 序列号回绕:高速传输时序列号可能溢出。TCP用时间戳选项来解决这个问题。
避坑指南:我曾经在排查一个「连接卡死」的问题时,抓包发现三次握手都完成了,但数据就是发不出去。后来发现是中间防火墙把某些TCP选项给干掉了——比如窗口缩放因子。从那以后,我每次抓包都会先看TCP选项字段。
嗯,TCP协议这块内容确实不少。但只要你把三次握手、四次挥手、状态转换、序列号确认号这四块吃透了,后面讲端口复用的时候就会轻松很多。毕竟,端口复用本质上就是在TCP连接的生命周期里做文章。