4. 三次握手抓包实战:用 Wireshark 捕获 TCP 三次握手全过程

理论讲完了,咱们得真刀真枪干一场。这一章,我会带你亲手抓一个 TCP 三次握手的包,把 SYN、SYN-ACK、ACK 这三步,以及 Seq 和 Ack 序号的变化,看得清清楚楚。

说实话,我刚开始学 TCP 的时候,光看 RFC 文档,总觉得 Seq 和 Ack 就是两个数字在跳,没啥感觉。直到有一次,我在客户现场排查一个“连接建立慢”的问题,抓包一看,发现第三次握手的 ACK 丢了,客户端一直在重传……嗯,从那以后,我再也不敢小看这三个包了。

4.1 准备工作:搭建一个干净的抓包环境

抓包最怕干扰。你想想看,如果本机跑着几十个后台服务,Wireshark 一开,满屏都是乱七八糟的包,你根本找不到目标。所以,我建议你按下面几步来:

  1. 选对工具:Wireshark 版本别太老,3.6 以上就行。我个人习惯用 4.0 系列,界面更清爽。
  2. 选对接口:如果你在本地抓,选 Loopback(回环接口);如果是远程抓,选对应的物理网卡。
  3. 过滤规则:别傻等。先想好你要抓哪个 IP 和端口。比如我要访问 192.168.1.100:80,过滤条件就写:
tcp.port == 80 and ip.addr == 192.168.1.100

这样一过滤,屏幕上就只剩下你关心的流量了。

我的小习惯:抓包前,我会先清空本机的 ARP 缓存(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 变化串起来,你一看就明白。

TCP 三次握手 Seq/Ack 序号流转图 TCP 三次握手 Seq/Ack 序号流转 客户端 服务器 ① SYN Seq = 0 (相对) ② SYN-ACK Seq = 0, Ack = 1 ③ ACK Seq = 1, Ack = 1 关键观察: • 第一次握手:客户端发 SYN,Seq = 0 • 第二次握手:服务器发 SYN-ACK,Ack = 客户端 Seq + 1 = 1 • 第三次握手:客户端发 ACK,Ack = 服务器 Seq + 1 = 1 • 握手完成后,双方 Seq 都变为 1,Ack 也变为 1

从图里可以清楚看到:

  • 第一次握手后,服务器记住了客户端的 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 如何验证三次握手是否成功?

抓完包,怎么判断连接建立成功了?我一般看两点:

  1. 看 Flags:三次握手必须是 SYN → SYN-ACK → ACK。如果中间出现 RST(复位标志),说明连接被拒绝了。
  2. 看时间差:正常情况下,三次握手在几毫秒内完成。如果超过 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”。