3、TCP三次握手:从协议头到抓包实战

TCP三次握手,说白了就是两台机器建立连接前的「打招呼」仪式。我经常跟团队里的新人说,搞懂了这个握手过程,你就能解决一半的网络连接问题。

咱们先看协议头部,再看握手细节,最后用Wireshark抓包验证。这样你以后遇到连接超时、握手失败的问题,心里就有底了。

3.1 TCP协议头部结构

TCP头部最少20字节,最多60字节。我习惯把它分成两部分记:固定部分和选项部分。

字段 长度(位) 说明
源端口 16 发送方端口号
目的端口 16 接收方端口号
序列号(SEQ) 32 本报文段数据的第一个字节序号
确认号(ACK) 32 期望收到的下一个字节序号
数据偏移 4 头部长度,单位是4字节
标志位 9 SYN、ACK、FIN等控制位
窗口大小 16 接收窗口,用于流量控制
校验和 16 覆盖头部和数据
紧急指针 16 仅URG标志位为1时有效

这里我重点说三个字段,因为三次握手全靠它们:

  • 序列号(SEQ):每个TCP报文都有一个序列号。第一次握手时,客户端会随机生成一个初始序列号(ISN)。
  • 确认号(ACK):表示「我已经收到了你发的数据,下一个该发这个序号了」。比如收到SEQ=1000的报文,ACK就填1001。
  • 标志位:SYN表示发起连接,ACK表示确认,FIN表示关闭连接。三次握手主要用SYN和ACK。
我的经验: 很多人搞混SEQ和ACK的关系。你记住一句话:ACK = 收到的SEQ + 1。这个规律在三次握手里百试百灵。

3.2 三次握手详细过程

握手过程其实就三步。我画个图,你一看就明白。

TCP三次握手流程图 客户端 服务端 CLOSED LISTEN ① SYN=1, SEQ=x SYN_SENT SYN_RCVD ② SYN=1, ACK=1, SEQ=y, ACK=x+1 ③ ACK=1, SEQ=x+1, ACK=y+1 ESTABLISHED ESTABLISHED 核心规律 ACK = 收到的SEQ + 1 每次握手都携带自己的SEQ,同时确认对方的SEQ

好,咱们一步步拆解:

第一步:客户端发SYN

客户端主动打开连接,发送一个SYN报文。这个报文里:

  • SYN标志位 = 1
  • 序列号SEQ = x(随机生成的初始序列号)
  • 不携带数据

发送后,客户端进入SYN_SENT状态。

第二步:服务端回复SYN-ACK

服务端收到SYN后,如果同意连接,就回复SYN-ACK报文:

  • SYN标志位 = 1
  • ACK标志位 = 1
  • 序列号SEQ = y(服务端自己的初始序列号)
  • 确认号ACK = x + 1(确认收到了客户端的SYN)

服务端进入SYN_RCVD状态。

第三步:客户端发ACK确认

客户端收到SYN-ACK后,回复一个ACK报文:

  • ACK标志位 = 1
  • 序列号SEQ = x + 1
  • 确认号ACK = y + 1

客户端进入ESTABLISHED状态。服务端收到这个ACK后,也进入ESTABLISHED状态。

关键点: 三次握手完成后,双方都确认了对方的接收能力和发送能力。客户端知道「服务端能收能发」,服务端也知道「客户端能收能发」。这就是为什么需要三次,而不是两次。

避坑指南: 我曾经遇到一个线上问题,客户端一直卡在SYN_SENT状态。抓包发现服务端回了SYN-ACK,但客户端没收到。最后排查是中间防火墙把SYN-ACK报文给丢了。所以遇到握手失败,别光看客户端,也要看服务端有没有回包。

3.3 用Wireshark观察三次握手

理论说完了,咱们实战。我习惯用Wireshark抓一个HTTP请求,然后过滤出三次握手报文。

抓包步骤

  1. 打开Wireshark,选择网卡开始抓包
  2. 在浏览器访问一个网站(比如 http://example.com)
  3. 停止抓包,在过滤栏输入 tcp.flags.syn == 1
  4. 找到第一个SYN报文,右键 → Follow → TCP Stream

你会看到类似这样的三个报文:

# 报文1:客户端 → 服务端
Frame 1: 74 bytes on wire (592 bits)
Ethernet II, Src: Client_MAC, Dst: Server_MAC
Internet Protocol Version 4, Src: 192.168.1.100, Dst: 93.184.216.34
Transmission Control Protocol, Src Port: 54321, Dst Port: 80
    Flags: 0x002 (SYN)
    Sequence Number: 0 (relative)
    Acknowledgment Number: 0
    Window Size: 65535

# 报文2:服务端 → 客户端
Frame 2: 74 bytes on wire (592 bits)
Transmission Control Protocol, Src Port: 80, Dst Port: 54321
    Flags: 0x012 (SYN, ACK)
    Sequence Number: 0 (relative)
    Acknowledgment Number: 1
    Window Size: 65535

# 报文3:客户端 → 服务端
Frame 3: 66 bytes on wire (528 bits)
Transmission Control Protocol, Src Port: 54321, Dst Port: 80
    Flags: 0x010 (ACK)
    Sequence Number: 1 (relative)
    Acknowledgment Number: 1
    Window Size: 65535

我的小技巧: Wireshark默认显示的是「相对序列号」,从0开始。这样看起来更直观。如果你想看真实的绝对序列号,可以在Wireshark里右键 → Protocol Preferences → TCP → 取消勾选「Relative sequence numbers」。

怎么看懂这些报文?

我教你一个快速判断的方法:

  • 第一次握手:只有SYN标志位,没有ACK。SEQ是0(相对值),ACK是0。
  • 第二次握手:SYN和ACK同时为1。SEQ是0(服务端的相对值),ACK是1(客户端的SEQ+1)。
  • 第三次握手:只有ACK标志位。SEQ是1(客户端的SEQ+1),ACK是1(服务端的SEQ+1)。

你想想看,如果第三次握手丢了会怎样?服务端收不到ACK,会一直重发SYN-ACK。默认重试次数是5次,间隔时间指数增长。我在项目里遇到过这种情况,客户端发完ACK后直接开始发数据,但服务端还在SYN_RCVD状态,数据就被丢了。

3.4 常见问题与排查思路

现象 可能原因 排查方法
客户端一直SYN_SENT 服务端没回SYN-ACK 检查服务端端口是否监听、防火墙是否拦截
服务端一直SYN_RCVD 客户端没回第三次ACK 检查客户端是否收到SYN-ACK、路由是否正常
握手成功但连接异常 中间设备篡改SEQ/ACK 对比两端抓包,看SEQ是否一致
握手延迟高 网络延迟或SYN Flood攻击 用ping测延迟,用netstat看半连接数

重要提醒: 千万不要在生产环境直接抓包!我见过有人直接在线上服务器抓包,结果磁盘写满导致服务挂了。正确的做法是:用tcpdump抓少量包,或者用镜像端口抓包。

嗯,三次握手的内容就这些。你只要记住SEQ和ACK的关系,再配合Wireshark看一遍实际报文,这个知识点就彻底拿下了。