3、TCP三次握手:SYN、SYN-ACK、ACK的抓包分析、序列号与确认号的变化
TCP三次握手,说白了就是两台机器在正式传数据之前,先互相打个招呼,确认对方在线且愿意通信。我刚开始学Socket编程时,总觉得这步是浪费时间——直接发数据不就行了?直到有一次线上服务莫名其妙连不上,抓包一看,握手都没完成,我才老老实实回来补课。
你想想看,如果没有这个握手过程,服务端怎么知道客户端要连哪个端口?客户端怎么知道服务端已经准备好了?序列号又该从哪儿开始?嗯,三次握手就是来解决这些问题的。
3.1 三次握手的完整流程
先看一个标准的握手过程。我用Wireshark抓过无数次了,每次看到这三个包,心里就踏实——连接没问题。
三次握手核心流程:
- 第一次握手(SYN):客户端 → 服务端,发送SYN包,携带初始序列号(ISN)
- 第二次握手(SYN-ACK):服务端 → 客户端,回复SYN+ACK,携带自己的ISN,同时确认客户端的ISN
- 第三次握手(ACK):客户端 → 服务端,发送ACK包,确认服务端的ISN
我习惯在Wireshark里直接过滤 tcp.flags.syn == 1,这样能快速定位握手包。不过要注意,SYN-ACK包同时设置了SYN和ACK两个标志位,过滤时别漏了。
3.2 序列号与确认号的变化规律
很多初学者搞不清序列号(Seq)和确认号(Ack)到底怎么变。我当年也迷糊过,后来自己抓包对比了几次,才彻底明白。其实规律很简单:
| 握手阶段 | 方向 | Seq(序列号) | Ack(确认号) | 含义 |
|---|---|---|---|---|
| 第一次(SYN) | 客户端 → 服务端 | x(随机ISN) | 0(无效) | 客户端发起连接,告知自己的初始序列号 |
| 第二次(SYN-ACK) | 服务端 → 客户端 | y(随机ISN) | x+1 | 服务端确认收到客户端的SYN,并告知自己的初始序列号 |
| 第三次(ACK) | 客户端 → 服务端 | x+1 | y+1 | 客户端确认收到服务端的SYN-ACK |
我的小技巧:在Wireshark里看三次握手,直接看Seq和Ack两列。第一次握手Ack=0,第二次握手Ack=x+1,第三次握手Ack=y+1。如果发现Ack值不对,那肯定有问题。
为什么会是x+1和y+1?因为SYN包本身要消耗一个序列号。你想想看,SYN标志位占了一个字节的序列号空间,所以确认号要加1。这个细节我踩过坑——有次写程序自己构造TCP包,忘了给SYN加1,结果服务端一直不响应。
3.3 Wireshark抓包实战分析
光讲理论没意思,咱们直接看抓包。我打开Wireshark,过滤 tcp.port == 8080,然后启动一个Socket连接。你会看到这样的输出:
No. Time Source Destination Protocol Info
1 0.000000 192.168.1.100 192.168.1.200 TCP 8080 → 80 [SYN] Seq=0 Win=65535
2 0.000123 192.168.1.200 192.168.1.100 TCP 80 → 8080 [SYN, ACK] Seq=0 Ack=1 Win=65535
3 0.000245 192.168.1.100 192.168.1.200 TCP 8080 → 80 [ACK] Seq=1 Ack=1 Win=65535
注意看,Wireshark默认把相对序列号显示为0,实际抓到的原始序列号是随机的。你可以右键点击列头,选择「Protocol Preferences」→「Relative sequence numbers」来切换显示方式。我个人习惯看相对序列号,更直观。
我曾经踩过的坑:有次排查一个连接超时问题,抓包发现只有SYN包,没有SYN-ACK。一开始以为是服务端没启动,后来才发现是防火墙把SYN包丢了。所以看到只有第一个包,别急着怀疑代码,先看看中间有没有网络设备在捣乱。
3.4 常见异常场景分析
三次握手不是每次都顺利。我整理了几种常见的异常情况,你在调试时可能会遇到:
- 只有SYN,没有SYN-ACK:服务端没启动,或者防火墙拦截了入站SYN包。用
tcp.flags.syn == 1 and tcp.flags.ack == 0过滤看看。 - 有SYN和SYN-ACK,没有最后的ACK:客户端收到SYN-ACK后没回复。可能是客户端程序挂了,或者半连接队列满了。我遇到过一种情况——客户端收到SYN-ACK后直接发RST,那是端口被占用了。
- SYN重传:客户端发了SYN没收到回复,会重传。默认重传次数是3次(Linux下),每次间隔指数增长。看到SYN重传,基本可以确定网络不通。
快速定位问题的方法:
在Wireshark里用 tcp.analysis.flags 过滤,能看到TCP分析器标记的异常包。比如「TCP Retransmission」表示重传,「TCP Previous segment not captured」表示丢包。这些标记比你自己翻包快多了。
3.5 序列号随机化与安全
你可能注意到了,每次握手的初始序列号(ISN)都是随机的。这不是随便选的,而是为了防止TCP序列号预测攻击。我记得早期Linux内核的ISN生成算法有漏洞,攻击者可以预测序列号,伪造TCP包。现在内核用了一种加密算法,基本没法预测了。
在Wireshark里看ISN,把相对序列号关掉就行。你会看到类似 Seq=1234567890 这样的值,每次连接都不一样。嗯,这个细节在面试时经常被问到,你可以留意一下。
3.6 动手实验:自己抓一次三次握手
光看不练不行。我建议你按下面的步骤亲手抓一次:
- 打开Wireshark,选择正确的网卡
- 设置过滤条件:
tcp.port == 80(或者你喜欢的端口) - 在浏览器里访问一个HTTP网站
- 停止抓包,找到三次握手的三个包
- 对比Seq和Ack的变化,确认是否符合x、x+1、y、y+1的规律
做完这个实验,你对三次握手的理解会比看十遍书都深。我当年就是这么学会的。