一、TCP 是什么?
TCP:
Transmission Control Protocol
中文:
传输控制协议。
TCP 是一种:
面向连接
可靠传输
字节流
全双工
的传输层协议。
例如:
Client Application
│
▼
TCP
│
▼
IP
│
▼
Network
│
▼
IP
│
▼
TCP
│
▼
Server Application
TCP 在真正进行可靠的数据传输之前,通常需要:
Three-Way Handshake
三次握手
建立连接。
通信结束以后,典型正常关闭过程通常表现为:
Four-Way Termination
四次挥手
二、TCP Header 中的重要字段
理解三次握手之前,首先需要理解几个关键字段:
Source Port
Destination Port
Sequence Number
Acknowledgment Number
Flags
Window Size
...
其中三次握手最重要的是:
SEQ
ACK Number
SYN
ACK
FIN
RST
三、TCP Flags
传统教材经常介绍 6 个 TCP 控制标志:
URG
ACK
PSH
RST
SYN
FIN
分别可以简单理解:
URG
→ Urgent
→ 紧急数据相关
ACK
→ Acknowledgment
→ 确认号字段有效
PSH
→ Push
→ 提示接收端尽快把数据交给应用层
RST
→ Reset
→ 重置 / 异常终止连接
SYN
→ Synchronize
→ 同步序列号,主要用于建立连接
FIN
→ Finish
→ 发送方向对端表示“我已经没有更多数据要发送”
现代 TCP Header 还存在:
ECE
CWR
NS
等与 ECN 等机制相关的标志。
因此:
“TCP 一共只有 6 个标志位”是传统教材中的简化说法。
学习 TCP 建连与关闭时,重点掌握:
SYN
ACK
FIN
RST
即可。
四、Sequence Number
Sequence Number:
序列号
简称:
SEQ
TCP Header 中占:
32 bit
=
4 Bytes
它的核心作用是:
标识当前 TCP Segment 中数据部分第一个字节在 TCP 字节流中的序号。
TCP 本质上提供:
Byte Stream(字节流)
因此可以想象:
Byte Stream
301 302 303 304 305 ... 400
如果某 TCP Segment:
SEQ = 301
Data Length = 100 Bytes
那么它携带的数据字节编号就是:
301
~
400
所以下一个连续数据段应该从:
SEQ = 401
开始。
五、Sequence Number 是 32 位
TCP Sequence Number 是:
32-bit unsigned number
取值范围:
0
~
2^32 - 1
也就是:
0
~
4294967295
到达最大值以后按照 32 位序号空间循环。
可以概念化:
4294967294
4294967295
0
1
2
...
TCP 内部会按照其序号空间规则处理这种回绕。
六、Initial Sequence Number
建立新的 TCP 连接时,双方都会选择自己的:
ISN
即:
Initial Sequence Number
初始序列号。
例如:
Client ISN = 1000
Server ISN = 5000
非常重要:
TCP 是全双工通信,所以两个方向拥有各自独立的 Sequence Number 空间。
即:
Client → Server
使用 Client 自己的 SEQ
Server → Client
使用 Server 自己的 SEQ
不能把双方的 SEQ 当成同一个计数器。
七、Acknowledgment Number
Acknowledgment Number:
确认号
也占:
32 bit
=
4 Bytes
简称:
ACK Number
它表示:
接收方下一步期望收到的字节序号。
这是理解 TCP 最重要的一句话之一。
八、ACK 的例子
假设 A:
SEQ = 501
Data Length = 200 Bytes
那么这一段包含:
Byte 501
~
Byte 700
B 全部正确收到以后:
ACK = 701
意思不是:
“我收到了第 701 个字节。”
而是:
501~700 我已经连续收到了,现在下一步希望你发送 701。
所以:
ACK Number
=
Next Expected Sequence Number
九、TCP 使用累计确认
TCP 的 ACK 通常具有:
Cumulative Acknowledgment
即:
累计确认
的含义。
例如:
ACK = 701
表示:
在这个方向上,701 之前需要连续确认的字节已经收到,现在期望 701。
因此 TCP 不需要简单地:
每收到一个字节
↓
发送一个 ACK
而可以累计确认一段连续字节流。
十、SEQ 和 ACK 的核心关系
可以记住这个公式:
对方下一次期望的 SEQ
=
当前 ACK
例如:
A → B
SEQ = 100
Length = 50
B 收到以后:
B → A
ACK = 150
因为:
100 + 50
=
150
十一、SYN 和 FIN 是特殊的
这里有一个非常重要的规则:
SYN 和 FIN 都会各自消耗一个 Sequence Number。
即使它们通常不是一个普通应用数据字节,也要在 TCP 序号空间中占用一个位置。
因此:
SYN
SEQ = x
对方确认:
ACK = x + 1
同理:
FIN
SEQ = x
对方确认:
ACK = x + 1
这就是为什么三次握手中总是看到:
SYN
→ ACK = SEQ + 1
十二、TCP 三次握手整体过程
假设:
Client ISN = x
Server ISN = y
三次握手:
Client Server
│ │
│ SYN │
│ SEQ = x │
│ ────────────────────────────────> │
│ │
│ SYN + ACK │
│ SEQ = y │
│ ACK = x + 1 │
│ <──────────────────────────────── │
│ │
│ ACK │
│ SEQ = x + 1 │
│ ACK = y + 1 │
│ ────────────────────────────────> │
│ │
│ ESTABLISHED │
这就是:
TCP Three-Way Handshake
十三、第一次握手:Client → Server
Client 想连接服务器,例如:
192.168.1.10:50000
↓
192.168.1.20:443
Client 发送:
SYN = 1
SEQ = x
例如:
SYN = 1
SEQ = 1000
表示:
我希望与你建立 TCP 连接,我这个方向的初始序列号是 1000。
此时客户端典型状态:
SYN_SENT
十四、第二次握手:Server → Client
服务器收到:
SYN
SEQ = 1000
以后:
- 确认客户端的 SYN;
- 同时发送自己的 SYN。
所以:
SYN = 1
ACK = 1
SEQ = 5000
ACK Number = 1001
为什么:
ACK = 1001
因为客户端:
SYN SEQ = 1000
而:
SYN 消耗一个序号
所以:
1000 + 1
=
1001
服务器典型状态:
SYN_RECEIVED
十五、第三次握手:Client → Server
客户端收到:
SYN = 1
ACK = 1
SEQ = 5000
ACK = 1001
首先确认:
ACK == Client ISN + 1
然后确认服务器的 SYN。
发送:
ACK = 1
SEQ = 1001
ACK Number = 5001
为什么:
ACK = 5001
因为:
Server SYN SEQ = 5000
SYN 消耗一个序号:
5000 + 1
=
5001
十六、三次握手完成
服务器收到第三个 ACK 后:
ACK = 5001
确认:
自己的 SYN 已经被 Client 确认
于是双方进入:
ESTABLISHED
状态。
现在可以正常双向传输数据。
十七、为什么需要三次握手?
三次握手最重要的目标之一是:
让双方确认双向通信能力,并同步双方各自的初始序列号以及相关 TCP 连接状态/参数。
第一次:
Client → Server
SYN
SEQ = x
服务器知道:
Client → Server
方向可以到达
第二次:
Server → Client
SYN + ACK
SEQ = y
ACK = x + 1
客户端知道:
自己的 SYN 已经到达 Server
Server 的 SYN 也能到达 Client
第三次:
Client → Server
ACK = y + 1
服务器最终知道:
Server → Client 的 SYN
也已经被 Client 正确收到
于是:
Client
和
Server
都能确认建立连接所需要的关键状态。
十八、三次握手状态变化
完整状态可以记:
Client Server
CLOSED LISTEN
│ │
│ SYN │
▼ │
SYN_SENT ──────────────────────────> │
▼
SYN_RECEIVED
│
SYN + ACK │
│ <────────────────────────────────┘
│
│ ACK
▼
ESTABLISHED ───────────────────────> ESTABLISHED
服务器通常首先:
socket()
bind()
listen()
进入:
LISTEN
客户端:
connect()
触发主动建立连接。
十九、端口在三次握手中的作用
你原笔记写:
客户端 SYN 包产生序列号,并指定访问服务器哪个端口。
这里需要区分:
Sequence Number
和:
Port Number
它们是 TCP Header 中完全不同的字段。
例如:
Source Port = 50000
Destination Port = 443
Sequence Number = 1000
表示:
客户端端口 50000
↓
服务器端口 443
同时
客户端当前 SEQ = 1000
SEQ 并不负责指定服务器端口。
二十、TCP 连接如何唯一识别?
一个 TCP 连接通常由四元组标识:
Source IP
Source Port
Destination IP
Destination Port
即:
<Src IP, Src Port, Dst IP, Dst Port>
例如:
192.168.1.10:50000
↕
192.168.1.20:443
这是一条 TCP 连接。
因此同一个服务器:
192.168.1.20:443
可以同时接受大量客户端连接,因为客户端的:
IP
和/或
Source Port
不同。
二十一、建立连接后如何传输数据?
假设握手完成:
Client next SEQ = 1001
Server next SEQ = 5001
客户端发送:
SEQ = 1001
Data Length = 100
数据范围:
1001 ~ 1100
服务器确认:
ACK = 1101
然后客户端下一个连续数据段:
SEQ = 1101
这就是 TCP:
Sequence Number
+
Acknowledgment Number
保证可靠、有序传输的重要基础。
二十二、TCP 是全双工协议
TCP 建立以后:
Client
⇄
Server
两个方向可以同时传输数据。
所以:
Client → Server
有自己的:
SEQ / ACK 状态
同时:
Server → Client
也有自己的:
SEQ / ACK 状态
因此 TCP 不是:
一个全局 Sequence Number
而是:
两个方向
各自维护序号空间
二十三、TCP 连接关闭:四次挥手
TCP 正常关闭通常称:
Four-Way Termination
中文一般叫:
四次挥手
而不是:
四次握手。
为什么通常需要四次?
因为:
TCP 是全双工协议,两个方向的数据流可以分别关闭。
例如:
Client → Server
关闭,不代表:
Server → Client
也必须立刻关闭。
二十四、理解 FIN:我没有数据要发了
假设 Client 主动关闭。
Client 发送:
FIN = 1
SEQ = u
它真正表达的意思更接近:
我这个方向的数据已经发送完毕,之后不再发送新的数据。
但是:
Server → Client
方向仍然可以继续发送剩余数据。
这叫 TCP:
Half-Close(半关闭)
能力。
二十五、第一次挥手
假设:
Client
主动关闭连接。
发送:
FIN = 1
SEQ = u
例如:
FIN = 1
SEQ = 2000
客户端进入:
FIN_WAIT_1
二十六、第二次挥手
Server 收到:
FIN
SEQ = 2000
因为:
FIN 消耗一个 Sequence Number
所以 Server 返回:
ACK = 2001
即:
ACK = u + 1
Server 此时典型进入:
CLOSE_WAIT
Client 收到 ACK 后进入:
FIN_WAIT_2
二十七、此时连接并没有完全关闭
现在:
Client → Server
方向已经关闭。
但是:
Server → Client
仍然可以发送数据。
例如:
Client
│
│ FIN
▼
Server
Client 已经不再发送数据
但是
Server
│
│ Remaining Data
▼
Client
所以:
收到 FIN 并回复 ACK,不意味着服务器必须马上发送自己的 FIN。
二十八、第三次挥手
当 Server 自己也没有数据需要发送时:
Application close()
TCP 发送:
FIN = 1
SEQ = v
Server 进入:
LAST_ACK
注意:
Server 的 FIN Sequence Number 应该根据 Server 自己这个方向当前的序号空间决定。
不能简单理解成:
“FIN 的 Sequence Number 就等于之前 ACK 的确认号。”
因为:
ACK Number
确认的是对方方向的数据;
而:
Sequence Number
属于自己发送方向的序号空间。
这是你原笔记中一个很重要的概念错误。
二十九、第四次挥手
Client 收到:
Server FIN
SEQ = v
以后回复:
ACK = v + 1
因为:
FIN
也消耗一个 Sequence Number。
因此:
Server FIN SEQ = v
Client ACK = v + 1
三十、四次挥手完整过程
Client Server
│ │
│ FIN │
│ SEQ = u │
│ ────────────────────────────────────> │
│ │
│ ACK │
│ ACK = u + 1 │
│ <──────────────────────────────────── │
│ │
│ Server 仍可发送剩余数据 │
│ <──────────────────────────────────── │
│ │
│ FIN │
│ SEQ = v │
│ <──────────────────────────────────── │
│ │
│ ACK │
│ ACK = v + 1 │
│ ────────────────────────────────────> │
│ │
三十一、四次挥手状态变化
主动关闭方:
ESTABLISHED
↓
发送 FIN
↓
FIN_WAIT_1
↓
收到 ACK
↓
FIN_WAIT_2
↓
收到 FIN
↓
发送 ACK
↓
TIME_WAIT
↓
等待
↓
CLOSED
被动关闭方:
ESTABLISHED
↓
收到 FIN
↓
发送 ACK
↓
CLOSE_WAIT
↓
应用程序 close()
↓
发送 FIN
↓
LAST_ACK
↓
收到 ACK
↓
CLOSED
三十二、为什么通常是四次而不是三次?
建立连接时:
Server SYN
+
Client SYN 的 ACK
可以放在同一个 TCP Segment:
SYN + ACK
所以形成:
SYN
SYN+ACK
ACK
三次。
关闭时:
收到对方 FIN
只能说明:
对方没有更多数据要发送。
Server 自己可能:
还有数据没发完
所以通常:
ACK 对方 FIN
和:
发送自己的 FIN
发生在不同时间。
于是:
FIN
ACK
FIN
ACK
表现为四次挥手。
三十三、ACK 和 FIN 有时也可以合并
“四次挥手”描述的是典型过程。
在具体 TCP 交互中,如果接收 FIN 的一方恰好也准备关闭,那么:
ACK
+
FIN
有可能在协议允许的情况下组合到一个 Segment 中。
因此抓包时不应该机械认为:
“关闭 TCP 永远一定看到四个独立 TCP 包。”
更准确:
TCP 两个方向都需要完成关闭和确认,而这些控制信息在具体报文段中的组合可能有所不同。
三十四、TIME_WAIT
Client 发送最后:
ACK = v + 1
以后,主动关闭方通常不会立即进入 CLOSED。
而进入:
TIME_WAIT
等待一段时间。
经典教材通常描述为:
2 × MSL
其中:
MSL
=
Maximum Segment Lifetime
三十五、为什么需要 TIME_WAIT?
最重要的原因之一:
假设 Client 最后发送:
ACK
但 ACK 丢失:
Client
│
│ ACK
│──────X
│
Server
Server 没收到最后 ACK:
Timeout
↓
重新发送 FIN
如果 Client 还处于:
TIME_WAIT
就可以:
收到 FIN
↓
重新发送 ACK
从而帮助 Server 正确完成关闭。
三十六、TIME_WAIT 的另一个作用
网络中可能还存在:
旧连接延迟到达的 TCP Segment
TIME_WAIT 可以降低:
旧连接中的延迟报文影响后续使用相同连接标识的新连接的风险。
因此:
TIME_WAIT
不是一个毫无意义的“浪费状态”。
而是 TCP 可靠关闭机制的重要组成部分。
三十七、CLOSE_WAIT
如果服务器出现大量:
CLOSE_WAIT
通常意味着:
对端已经发送 FIN
↓
本机 TCP 已经确认
↓
本机应用程序却迟迟没有 close()
所以大量长期 CLOSE_WAIT 经常需要检查:
Application
是否正确关闭 Socket。
三十八、RST 与正常关闭
正常关闭:
FIN
表示:
我正常结束这个方向的数据发送。
而:
RST
表示:
Reset Connection
通常意味着连接被异常或立即重置。
可以简单区分:
FIN
→ 正常、有序关闭
RST
→ 重置 / 异常终止
例如连接一个没有监听服务的 TCP 端口时,常可能收到:
RST
三十九、三次握手与四次挥手放在一起
完整生命周期:
TCP Connection Lifecycle
Client Server
CLOSED LISTEN
│ │
│ SYN │
│──────────────────────────────────────────>│
│ │
│ SYN + ACK │
│<──────────────────────────────────────────│
│ │
│ ACK │
│──────────────────────────────────────────>│
│ │
ESTABLISHED ESTABLISHED
│ │
│<═══════════ Application Data ═══════════>│
│ │
│ FIN │
│──────────────────────────────────────────>│
│ │
│ ACK │
│<──────────────────────────────────────────│
│ │
│ Remaining Data │
│<──────────────────────────────────────────│
│ │
│ FIN │
│<──────────────────────────────────────────│
│ │
│ ACK │
│──────────────────────────────────────────>│
│ │
TIME_WAIT CLOSED
│
│ wait
▼
CLOSED
四十、最核心的 SEQ / ACK 计算
普通数据
如果:
SEQ = x
Data Length = N
接收方正确收到连续数据以后:
ACK = x + N
例如:
SEQ = 301
Length = 100
那么:
ACK = 401
SYN
如果:
SYN
SEQ = x
那么:
ACK = x + 1
FIN
如果:
FIN
SEQ = x
那么:
ACK = x + 1
所以可以记:
普通 Data
→ 消耗 Data Length 个序号
SYN
→ 消耗 1 个序号
FIN
→ 消耗 1 个序号
普通纯 ACK
→ 通常不消耗新的序号
这是分析 Wireshark TCP 抓包最重要的规则之一。
四十一、为什么 Wireshark 经常显示 SEQ=0?
实际 TCP 初始序列号通常不是:
0
但是 Wireshark 默认可能启用:
Relative Sequence Numbers
把真实:
Client ISN = 2847123456
显示成:
SEQ = 0
方便分析。
因此 Wireshark 中常见:
Client → Server
SYN
SEQ = 0
Server → Client
SYN, ACK
SEQ = 0
ACK = 1
Client → Server
ACK
SEQ = 1
ACK = 1
这通常是:
Relative Sequence Number
而不是双方真的随机出了 0。
四十二、用 Wireshark 看三次握手
典型抓包:
No.1
Client → Server
SYN
Seq=0
No.2
Server → Client
SYN, ACK
Seq=0
Ack=1
No.3
Client → Server
ACK
Seq=1
Ack=1
看到:
[SYN]
[SYN, ACK]
[ACK]
基本就找到了 TCP 三次握手。
四十三、用 Wireshark 看四次挥手
典型可能看到:
Client → Server
FIN, ACK
Server → Client
ACK
Server → Client
FIN, ACK
Client → Server
ACK
也就是说真实抓包经常是:
FIN,ACK
ACK
FIN,ACK
ACK
而不是只有纯:
FIN
ACK
FIN
ACK
因为 TCP 建立后,ACK Flag 在大量正常报文中都会置位。
四十四、TCP 状态机
TCP 三次握手和四次挥手本质上其实是在驱动:
TCP State Machine
常见状态:
CLOSED
LISTEN
SYN_SENT
SYN_RECEIVED
ESTABLISHED
FIN_WAIT_1
FIN_WAIT_2
CLOSE_WAIT
CLOSING
LAST_ACK
TIME_WAIT
因此真正深入理解 TCP,不应该只背:
三次握手
四次挥手
而应该理解:
TCP Segment
↓
Flags + SEQ + ACK
↓
TCP State Machine
↓
连接状态发生变化
四十五、三次握手状态机
Client Server
CLOSED CLOSED
│
│ listen()
▼
LISTEN
│
│ connect()
│ SYN
▼
SYN_SENT
│
│ SYN
│───────────────────────────────>
│
│ SYN_RECEIVED
│
│ SYN + ACK
│<───────────────────────────────
│
│ ACK
▼
ESTABLISHED
│
│───────────────────────────────>
ESTABLISHED
四十六、四次挥手状态机
主动关闭方:
ESTABLISHED
│
│ close()
│ send FIN
▼
FIN_WAIT_1
│
│ receive ACK
▼
FIN_WAIT_2
│
│ receive FIN
│ send ACK
▼
TIME_WAIT
│
│ timeout
▼
CLOSED
被动关闭方:
ESTABLISHED
│
│ receive FIN
│ send ACK
▼
CLOSE_WAIT
│
│ application close()
│ send FIN
▼
LAST_ACK
│
│ receive ACK
▼
CLOSED
四十七、Linux 中查看 TCP 状态
可以使用:
ss -ant
例如:
State Local Address Peer Address
LISTEN
ESTAB
TIME-WAIT
CLOSE-WAIT
FIN-WAIT-1
FIN-WAIT-2
因此前面学习的 TCP 状态不是纯理论。
在 Linux 服务器排错时经常直接看到:
ESTABLISHED
TIME_WAIT
CLOSE_WAIT
等状态。
四十八、TCP 三次握手最终记忆法
假设:
Client ISN = x
Server ISN = y
只需要记:
第一次:
Client → Server
SYN
SEQ = x
第二次:
Server → Client
SYN + ACK
SEQ = y
ACK = x + 1
第三次:
Client → Server
ACK
SEQ = x + 1
ACK = y + 1
即:
SYN
SEQ=x
Client ───────────────→ Server
SYN + ACK
SEQ=y
ACK=x+1
Client ←─────────────── Server
ACK
SEQ=x+1
ACK=y+1
Client ───────────────→ Server
四十九、TCP 四次挥手最终记忆法
假设:
Client 当前 SEQ = u
Server 当前 SEQ = v
典型关闭:
第一次:
Client → Server
FIN
SEQ = u
第二次:
Server → Client
ACK
ACK = u + 1
第三次:
Server → Client
FIN
SEQ = v
第四次:
Client → Server
ACK
ACK = v + 1
然后主动关闭方:
TIME_WAIT
最后:
CLOSED
五十、最容易写错的几个地方
不要写:
ACK 表示“已经收到的最后一个字节”
更准确:
ACK
=
下一步期望收到的 Sequence Number
不要写:
SEQ 指定服务器端口
正确:
Destination Port
→ 指定目标端口
Sequence Number
→ 标识 TCP 字节流中的序号
不要写:
Server FIN 的 SEQ
=
前面 ACK Number
因为:
SEQ
→ 自己发送方向的序号空间
ACK
→ 确认对方发送方向的序号空间
不要写:
四次握手断开
通常称:
TCP 四次挥手
不要写:
最后 ACK 后双方立即消失
典型情况:
主动关闭方
↓
TIME_WAIT
↓
CLOSED
五十一、最终完整模型
TCP 的整个生命周期可以浓缩为:
Application
│
│ connect()
▼
TCP Three-Way Handshake
│
├── SYN
├── SYN + ACK
└── ACK
│
▼
ESTABLISHED
│
│
│ Byte Stream
│
│ SEQ
│ ACK
│ Window
│ Retransmission
│
▼
Application close()
│
▼
TCP Four-Way Termination
│
├── FIN
├── ACK
├── FIN
└── ACK
│
▼
TIME_WAIT
│
▼
CLOSED
而贯穿整个 TCP 生命周期最重要的两个数字就是:
SEQ
=
我正在发送的数据
在“我的发送字节流”中的位置
ACK
=
你下一步应该从哪个序号继续发给我
五十二、一句话总结
TCP 是面向连接、可靠、全双工的字节流协议。建立连接时,客户端首先发送
SYN, SEQ=x,服务器返回SYN+ACK, SEQ=y, ACK=x+1,客户端再返回ACK=y+1,完成三次握手。TCP 的 Sequence Number 对字节流进行编号,而 Acknowledgment Number 表示下一步期望收到的序号。SYN 和 FIN 各自消耗一个序号。正常关闭时,由于 TCP 两个方向的数据流可以分别关闭,因此典型过程为FIN → ACK → FIN → ACK四次挥手;主动关闭方发送最后 ACK 后通常进入 TIME_WAIT,经过等待后才进入 CLOSED。
浙公网安备 33010602011771号