3、TCP三次握手:SYN、SYN-ACK、ACK的抓包分析、序列号与确认号的变化

TCP三次握手,说白了就是两台机器在正式传数据之前,先互相打个招呼,确认对方在线且愿意通信。我刚开始学Socket编程时,总觉得这步是浪费时间——直接发数据不就行了?直到有一次线上服务莫名其妙连不上,抓包一看,握手都没完成,我才老老实实回来补课。

你想想看,如果没有这个握手过程,服务端怎么知道客户端要连哪个端口?客户端怎么知道服务端已经准备好了?序列号又该从哪儿开始?嗯,三次握手就是来解决这些问题的。

3.1 三次握手的完整流程

先看一个标准的握手过程。我用Wireshark抓过无数次了,每次看到这三个包,心里就踏实——连接没问题。

三次握手核心流程:

  1. 第一次握手(SYN):客户端 → 服务端,发送SYN包,携带初始序列号(ISN)
  2. 第二次握手(SYN-ACK):服务端 → 客户端,回复SYN+ACK,携带自己的ISN,同时确认客户端的ISN
  3. 第三次握手(ACK):客户端 → 服务端,发送ACK包,确认服务端的ISN

我习惯在Wireshark里直接过滤 tcp.flags.syn == 1,这样能快速定位握手包。不过要注意,SYN-ACK包同时设置了SYN和ACK两个标志位,过滤时别漏了。

TCP三次握手流程图 TCP三次握手流程图 客户端 服务端 CLOSED → SYN_SENT LISTEN ① SYN (seq=x) SYN_RCVD ② SYN-ACK (seq=y, ack=x+1) ESTABLISHED ③ ACK (seq=x+1, ack=y+1) ESTABLISHED 连接建立成功

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 动手实验:自己抓一次三次握手

光看不练不行。我建议你按下面的步骤亲手抓一次:

  1. 打开Wireshark,选择正确的网卡
  2. 设置过滤条件:tcp.port == 80(或者你喜欢的端口)
  3. 在浏览器里访问一个HTTP网站
  4. 停止抓包,找到三次握手的三个包
  5. 对比Seq和Ack的变化,确认是否符合x、x+1、y、y+1的规律

做完这个实验,你对三次握手的理解会比看十遍书都深。我当年就是这么学会的。