本文档将模拟一次最简单的 TCP 通信:客户端向服务器发送一条短消息 "HelloWorld"
。我们将追踪从连接建立、数据传输到连接断开的每一步,并详细解释每个 TCP 报文段中关键字段(标志位、序号、确认号)的含义和变化。
初始约定
-
客户端 C:初始序列号 (ISN) = 1000
-
服务器 S:初始序列号 (ISN) = 2000
-
传输数据 :客户端 C 发送 10 字节的
"HelloWorld"
给服务器 S -
TCP 头部:为简化,假设每次报文无选项字段,头部固定为 20 字节
-
窗口大小:双方初始接收窗口均为 8192 字节
1. 三次握手 (建立连接)
① C 发送 SYN
报文内容:
C → S
- Flags: SYN=1 (→ 标志位:设置 SYN,表示这是一个连接请求)
- seq=1000, ack=0 (→ 序号:我的初始序号是 1000;确认号:暂时没有需要确认的)
- Win=8192 (→ 窗口:我的接收窗口大小是 8192)
- 客户端状态 :
SYN_SENT
(已发送同步请求)
② S 回复 SYN+ACK
报文内容:
S → C
- Flags: SYN=1, ACK=1 (→ 标志位:设置 SYN 和 ACK,表示同意连接并确认收到)
- seq=2000, ack=1001 (→ 序号:我的初始序号是 2000;确认号:你的 1000 我收到了,期待 1001)
- Win=8192 (→ 窗口:我的接收窗口大小是 8192)
-
服务器状态 :
SYN_RCVD
(已接收同步请求) -
客户端收到后状态 :
ESTABLISHED
(连接已建立)
③ C 发送 ACK (握手结束)
报文内容:
C → S
- Flags: ACK=1 (→ 标志位:设置 ACK,表示确认)
- seq=1001, ack=2001 (→ 序号:我的序号是 1001;确认号:你的 2000 我收到了,期待 2001)
- Win=8192 (→ 窗口:我的接收窗口大小是 8192)
-
说明:这是一个纯确认报文,不携带数据,不消耗序列号。
-
服务器收到后状态 :
ESTABLISHED
(连接已建立)
2. 数据传输
④ C 发送 10 字节数据
报文内容:
C → S
- Flags: ACK=1 (→ 标志位:这是一个确认报文,捎带了数据)
- seq=1001, ack=2001 (→ 序号:从 1001 开始;确认号:继续确认对方的 2001)
- Len=10 (→ 数据长度:10 字节,内容为 "HelloWorld")
- 说明 :这次传输占用了
1001
到1010
共 10 个序列号。
⑤ S 确认收到数据
报文内容:
S → C
- Flags: ACK=1 (→ 标志位:这是一个纯确认报文)
- seq=2001, ack=1011 (→ 序号:我的序号不变;确认号:你 1010 为止的数据都收到了,期待 1011)
- Win=8192 (→ 窗口:我的接收窗口大小不变)
- 说明:服务器确认收到了 10 字节数据。
3. 四次挥手 (断开连接)
⑥ C 发起第一次挥手 (FIN)
报文内容:
C → S
- Flags: FIN=1, ACK=1 (→ 标志位:设置 FIN 和 ACK,表示我没数据发了,要关闭)
- seq=1011, ack=2001 (→ 序号:我的新序号是 1011;确认号:继续确认对方的 2001)
-
说明:FIN 标志本身会消耗一个序列号。
-
客户端状态 :
FIN_WAIT_1
⑦ S 回复第二次挥手 (ACK)
报文内容:
S → C
- Flags: ACK=1 (→ 标志位:纯确认)
- seq=2001, ack=1012 (→ 序号:我的序号不变;确认号:你的 FIN(1011) 收到了,期待 1012)
-
服务器状态 :
CLOSE_WAIT
(等待应用层关闭) -
客户端收到后状态 :
FIN_WAIT_2
⑧ S 发起第三次挥手 (FIN)
报文内容:
S → C
- Flags: FIN=1, ACK=1 (→ 标志位:我也没数据发了,准备关闭)
- seq=2001, ack=1012 (→ 序号:我的序号是 2001;确认号:继续确认对方的 1012)
- 服务器状态 :
LAST_ACK
(等待最后的确认)
⑨ C 回复第四次挥手 (ACK)
报文内容:
C → S
- Flags: ACK=1 (→ 标志位:最后的确认)
- seq=1012, ack=2002 (→ 序号:我的序号是 1012;确认号:你的 FIN(2001) 收到了,期待 2002)
-
客户端状态 :
TIME_WAIT
(等待 2*MSL 时间后进入CLOSED
) -
服务器收到后状态 :
CLOSED
(立即关闭)
例子(挂电话):
-
目录
[1. 三次握手 (建立连接)](#1. 三次握手 (建立连接))
[① C 发送 SYN](#① C 发送 SYN)
[② S 回复 SYN+ACK](#② S 回复 SYN+ACK)
[③ C 发送 ACK (握手结束)](#③ C 发送 ACK (握手结束))
[2. 数据传输](#2. 数据传输)
[④ C 发送 10 字节数据](#④ C 发送 10 字节数据)
[⑤ S 确认收到数据](#⑤ S 确认收到数据)
[3. 四次挥手 (断开连接)](#3. 四次挥手 (断开连接))
[⑥ C 发起第一次挥手 (FIN)](#⑥ C 发起第一次挥手 (FIN))
[⑦ S 回复第二次挥手 (ACK)](#⑦ S 回复第二次挥手 (ACK))
[⑧ S 发起第三次挥手 (FIN)](#⑧ S 发起第三次挥手 (FIN))
[⑨ C 回复第四次挥手 (ACK)](#⑨ C 回复第四次挥手 (ACK))
一条数据的一生总结
整个过程的报文交互可以简化为: SYN
→ SYN+ACK
→ ACK
→ [数据] → ACK
→ FIN
→ ACK
→ FIN
→ ACK
序号和确认号严格按照"发了多少数据就加多少,SYN/FIN 标志算 1"的规则进行递增,每一步都环环相扣,逻辑严谨,这就是真实 TCP 的完整对话。