4. 三次握手抓包实战:用 Wireshark 捕获 TCP 三次握手全过程
理论讲完了,咱们得真刀真枪干一场。这一章,我会带你亲手抓一个 TCP 三次握手的包,把 SYN、SYN-ACK、ACK 这三步,以及 Seq 和 Ack 序号的变化,看得清清楚楚。
说实话,我刚开始学 TCP 的时候,光看 RFC 文档,总觉得 Seq 和 Ack 就是两个数字在跳,没啥感觉。直到有一次,我在客户现场排查一个“连接建立慢”的问题,抓包一看,发现第三次握手的 ACK 丢了,客户端一直在重传……嗯,从那以后,我再也不敢小看这三个包了。
4.1 准备工作:搭建一个干净的抓包环境
抓包最怕干扰。你想想看,如果本机跑着几十个后台服务,Wireshark 一开,满屏都是乱七八糟的包,你根本找不到目标。所以,我建议你按下面几步来:
- 选对工具:Wireshark 版本别太老,3.6 以上就行。我个人习惯用 4.0 系列,界面更清爽。
- 选对接口:如果你在本地抓,选 Loopback(回环接口);如果是远程抓,选对应的物理网卡。
- 过滤规则:别傻等。先想好你要抓哪个 IP 和端口。比如我要访问
192.168.1.100:80,过滤条件就写:
tcp.port == 80 and ip.addr == 192.168.1.100
这样一过滤,屏幕上就只剩下你关心的流量了。
arp -d),确保第一次握手不会被 ARP 请求干扰。虽然 ARP 包不影响 TCP 序号分析,但看着碍眼。
4.2 开始抓包:触发一次 TCP 连接
好,环境准备好了。现在打开 Wireshark,点击“开始捕获”。然后,在命令行或浏览器里,发起一次 HTTP 请求。比如:
curl http://192.168.1.100
请求发出去后,立刻停止抓包。别让它一直跑,否则包太多,你反而找不到关键信息。
这时候,Wireshark 里应该已经抓到几个包了。你按 tcp 过滤一下,确保只看 TCP 协议。然后,找到最前面的三个包——它们就是三次握手的报文。
4.3 逐包分析:Seq 和 Ack 是怎么变的?
咱们把这三个包拆开来看。我截取了一个典型的抓包结果,用表格展示关键字段:
| 包序号 | 方向 | Flags | Seq 序号 | Ack 序号 | 备注 |
|---|---|---|---|---|---|
| 1 | 客户端 → 服务器 | SYN | 0(相对值) | 0 | 客户端发起连接,Seq 初始为 0(相对) |
| 2 | 服务器 → 客户端 | SYN, ACK | 0(相对值) | 1 | 服务器确认客户端的 SYN,Ack = 客户端 Seq + 1 |
| 3 | 客户端 → 服务器 | ACK | 1 | 1 | 客户端确认服务器的 SYN,Ack = 服务器 Seq + 1 |
注意,Wireshark 默认显示的是相对序号(Relative Sequence Numbers),它会从 0 开始算。如果你要看真实的原始序号,可以在 Wireshark 里右键 → Protocol Preferences → 取消勾选“Relative sequence numbers”。不过,日常分析用相对序号更方便,一眼就能看出偏移量。
核心规律:
- 第一次握手:客户端发 SYN,Seq = 客户端 ISN(初始序号)。
- 第二次握手:服务器回 SYN-ACK,Seq = 服务器 ISN,Ack = 客户端 ISN + 1。
- 第三次握手:客户端发 ACK,Seq = 客户端 ISN + 1,Ack = 服务器 ISN + 1。
说白了,Ack 序号永远等于对方上一次发送的 Seq 序号 + 1(在握手阶段)。
4.4 为什么 Ack 要加 1?
你可能会问:为什么 Ack 不是等于对方的 Seq,而是要加 1?
这个问题,我当年也困惑过。其实,TCP 的 Ack 序号表示的是“期望收到的下一个字节的序号”。在握手阶段,SYN 标志位本身会消耗一个序号。所以,当客户端收到服务器的 SYN 时,它期望的下一个字节序号就是服务器 Seq + 1。
举个例子:
- 服务器发来的 SYN 包,Seq = 1000。
- 客户端收到后,知道服务器已经发送了序号 1000(SYN 本身),所以它期望下一个字节从 1001 开始。
- 因此,客户端回复的 Ack = 1001。
这个逻辑,在后续的数据传输阶段也是一样的。只不过数据包里的 Seq 和 Ack 变化会更复杂一些,因为数据本身也会消耗序号。
4.5 用 SVG 图看清三次握手的序号流转
光看表格可能还不够直观。我画了一张流程图,把三次握手的 Seq 和 Ack 变化串起来,你一看就明白。
从图里可以清楚看到:
- 第一次握手后,服务器记住了客户端的 Seq(0),并回复 Ack = 1。
- 第二次握手后,客户端记住了服务器的 Seq(0),并回复 Ack = 1。
- 第三次握手后,双方都确认了对方的初始序号,连接建立完成。
4.6 实战中可能遇到的“坑”
抓包分析看起来简单,但实际项目中,我遇到过不少让人头疼的情况。这里分享几个:
我曾经踩过的坑:
- 相对序号误导:有一次,我看到第三次握手的 Seq 是 1,Ack 是 1,觉得没问题。但后来发现,Wireshark 开了“相对序号”,实际原始序号根本不是 0 和 1。如果你在做深度分析(比如计算 RTT),一定要关掉相对序号,看原始值。
- 重传干扰:如果网络有丢包,你可能会看到多个 SYN 包。这时候别慌,看时间戳和 Seq 序号,找到第一个 SYN 和对应的 SYN-ACK,那才是真正的第一次握手。
- 防火墙截断:有些防火墙会直接丢弃 SYN 包,导致你只看到客户端在发 SYN,但服务器毫无回应。这种情况,抓包一看就知道是中间设备的问题。
4.7 如何验证三次握手是否成功?
抓完包,怎么判断连接建立成功了?我一般看两点:
- 看 Flags:三次握手必须是 SYN → SYN-ACK → ACK。如果中间出现 RST(复位标志),说明连接被拒绝了。
- 看时间差:正常情况下,三次握手在几毫秒内完成。如果超过 1 秒,说明网络延迟高或者有重传。
举个例子,有一次我帮同事排查一个“连接超时”的问题。抓包一看,客户端发了 SYN,服务器回了 SYN-ACK,但客户端没有发 ACK。等了 3 秒,客户端又重传了 SYN……这就是典型的“第三次握手丢失”。后来发现是客户端的防火墙把出站 ACK 给拦截了。
快速验证命令:如果你不想开 Wireshark,可以用 tcpdump 在服务器上抓:
tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn|tcp-ack) != 0'
这样只显示 SYN 和 ACK 标志位的包,一眼就能看出三次握手是否完整。
4.8 小结:记住这三个数字
三次握手的核心,其实就是三个数字的变化:
- Seq:自己发送的字节序号。
- Ack:期望对方下一个发送的字节序号。
- Flags:SYN 和 ACK 标志位,决定了这个包是握手包还是数据包。
你只要记住:第一次握手发 SYN,第二次握手回 SYN-ACK,第三次握手回 ACK。Seq 和 Ack 的加减规律,就是“收到对方的 Seq,然后加 1 作为自己的 Ack”。