文章目录
- [TCP 三次握手彻底解析:SYN、ACK、SEQ、确认号与状态迁移](#TCP 三次握手彻底解析:SYN、ACK、SEQ、确认号与状态迁移)
- 一、先区分四个容易混淆的概念
- [二、SEQ 序号到底表示什么](#二、SEQ 序号到底表示什么)
- [三、ack 确认号到底表示什么](#三、ack 确认号到底表示什么)
- [四、ACK 标志位与确认号字段的关系](#四、ACK 标志位与确认号字段的关系)
- [五、为什么 SYN 会占用一个序号](#五、为什么 SYN 会占用一个序号)
- [六、TCP 三次握手完整过程](#六、TCP 三次握手完整过程)
- [七、第一次握手:客户端发送 SYN](#七、第一次握手:客户端发送 SYN)
-
- [1. 报文方向](#1. 报文方向)
- [2. 标志位](#2. 标志位)
- [3. 序号](#3. 序号)
- [4. 状态变化](#4. 状态变化)
- [5. 第一次握手表达的含义](#5. 第一次握手表达的含义)
- [八、第二次握手:服务器发送 SYN 和 ACK](#八、第二次握手:服务器发送 SYN 和 ACK)
-
- [1. 报文方向](#1. 报文方向)
- [2. 标志位](#2. 标志位)
- [3. 服务器序号](#3. 服务器序号)
- [4. 确认号](#4. 确认号)
- [5. 状态变化](#5. 状态变化)
- [6. 第二次握手表达的含义](#6. 第二次握手表达的含义)
- [九、第三次握手:客户端发送最终 ACK](#九、第三次握手:客户端发送最终 ACK)
-
- [1. 报文方向](#1. 报文方向)
- [2. 标志位](#2. 标志位)
- [3. 客户端序号](#3. 客户端序号)
- [4. 确认号](#4. 确认号)
- [5. 状态变化](#5. 状态变化)
- 十、三次握手报文汇总
- 十一、代入具体数值理解
- 十二、为什么必须进行三次握手
-
- [1. TCP 需要同步两个方向的初始序号](#1. TCP 需要同步两个方向的初始序号)
- [2. 两次握手无法确认服务器的 SYN 是否到达客户端](#2. 两次握手无法确认服务器的 SYN 是否到达客户端)
- [3. 三次握手可以降低旧连接报文造成的混乱](#3. 三次握手可以降低旧连接报文造成的混乱)
- [十三、三次握手和 SYN Flood 的关系](#十三、三次握手和 SYN Flood 的关系)
- 十四、第三次握手能否携带数据
- [十五、普通 ACK 为什么不消耗序号](#十五、普通 ACK 为什么不消耗序号)
- [十六、SEQ 和 ack 的通用计算方法](#十六、SEQ 和 ack 的通用计算方法)
-
- [只有 SYN](#只有 SYN)
- [只有 10 字节数据](#只有 10 字节数据)
- [10 字节数据加 FIN](#10 字节数据加 FIN)
- 十七、客户端和服务器分别维护独立序号空间
- 十八、三次握手中的状态迁移
- 十九、常见错误逐项纠正
-
- [错误一:第一次握手是 SYN=1、ACK=1](#错误一:第一次握手是 SYN=1、ACK=1)
- 错误二:第一次握手没有确认号字段
- [错误三:第二次握手的 SEQ 是 Y+1](#错误三:第二次握手的 SEQ 是 Y+1)
- [错误四:第三次握手仍然设置 SYN=1](#错误四:第三次握手仍然设置 SYN=1)
- [错误五:第三次握手的 SEQ 是 Y](#错误五:第三次握手的 SEQ 是 Y)
- [错误六:确认号永远等于对方 SEQ+1](#错误六:确认号永远等于对方 SEQ+1)
- [错误七:SYN 相当于一个虚构的数据字节](#错误七:SYN 相当于一个虚构的数据字节)
- [错误八:两次握手必然导致大量 SYN Flood 僵尸连接](#错误八:两次握手必然导致大量 SYN Flood 僵尸连接)
- 二十、最终结论
TCP 三次握手彻底解析:SYN、ACK、SEQ、确认号与状态迁移
前言
TCP 是一种面向连接、可靠、基于字节流的传输层协议。
在客户端和服务器正式传输应用数据之前,双方需要通过三次报文交换完成连接建立。这个过程通常被称为:
TCP 三次握手,Three-Way Handshake。
很多资料能够正确画出三次握手的基本结构:
text
客户端 服务器
| |
| -------- SYN --------------> |
| <------ SYN + ACK ---------- |
| -------- ACK --------------> |
| |
但一旦涉及以下细节,就很容易出现混淆:
SYN和ACK到底是标志位还是字段?- 为什么第二次握手的确认号是
X + 1? - 为什么第三次握手的序号是
X + 1? - 为什么不能只握手两次?
- 三次握手与 SYN Flood 有什么关系?
- 第三次握手能不能携带应用数据?
本文将使用客户端初始序号 X 和服务器初始序号 Y,逐字段拆解整个过程。
一、先区分四个容易混淆的概念
分析三次握手之前,必须先区分:
SYN标志位ACK标志位SEQ序号字段ACK Number确认号字段
为了避免把 ACK 标志位和确认号字段混在一起,本文采用以下写法:
text
SYN 表示 SYN 标志位
ACK 表示 ACK 标志位
SEQ 表示序号字段
ack 表示确认号字段
也就是说:
text
ACK = 1
表示 TCP 首部中的 ACK 标志位被置为 1。
而:
text
ack = X + 1
表示 TCP 首部中的确认号字段值为 X + 1。
这两个概念不能混为一谈。
二、SEQ 序号到底表示什么
TCP 不直接给每个报文编号,而是给整个连接中的每一个数据字节编号。
假设某个 TCP 报文段携带 100 字节数据,并且:
text
SEQ = 1000
那么这段数据占用的序号范围就是:
text
1000 ~ 1099
发送端下一次发送新数据时,序号通常从:
text
1100
开始。
因此,更准确地说:
SEQ表示该报文段中第一个数据字节在 TCP 字节流中的序号。
对于不携带数据的纯 ACK 报文,SEQ 通常表示发送端当前下一个准备使用的序号。
对于携带 SYN 的报文,SEQ 字段存放本端的初始序号 ISN。SYN 本身会占用一个序号,因此第一个实际数据字节通常从 ISN + 1 开始。RFC 9293 给出的基本三次握手示例同样展示了 SYN 占用一个序号的行为。
三、ack 确认号到底表示什么
确认号不是简单地表示:
text
我收到了哪个序号
它真正表达的是:
我已经按序收到了该确认号之前的所有字节,下一次希望收到的序号是 ack。
例如,服务器发送:
text
ack = 1001
表示:
text
序号 1000 及之前需要确认的内容已经收到,
下一次期望客户端从序号 1001 开始发送。
因此,TCP 的 ACK 是一种累计确认。
对于按序到达的普通数据段,可以近似理解为:
text
ack = 对方的 SEQ + 数据长度
但是更加准确的表达应该是:
text
ack = 接收端下一次期望收到的序号
这两种表达在数据按序到达时结果通常相同,但后者才是 TCP 确认号的本质定义。
四、ACK 标志位与确认号字段的关系
TCP 首部中始终存在确认号字段。
但是:
只有当 ACK 标志位为 1 时,确认号字段才具有协议意义。
因此,第一次握手中并不是"确认号字段不存在",而是:
text
ACK = 0
所以确认号字段不生效,接收方应当忽略它。
某些抓包工具可能仍然显示该字段的数值,但这个数值不能被解释为有效确认号。
五、为什么 SYN 会占用一个序号
普通 TCP 数据每发送一个字节,就会消耗一个序号。
例如:
text
SEQ = 100
数据长度 = 10 字节
那么序号范围为:
text
100 ~ 109
下一个序号是:
text
110
SYN 虽然通常不属于应用层数据,但它也会占用一个 TCP 序号。
因此:
text
SYN 的 SEQ = X
对端确认时就必须发送:
text
ack = X + 1
FIN 也具有相同特性:
text
SYN 消耗一个序号
FIN 消耗一个序号
但普通纯 ACK 报文不会额外消耗序号。RFC 对 TCP 序号空间的定义明确规定,SYN 和 FIN 都占据序号空间。
六、TCP 三次握手完整过程
下面分析最常见的连接建立方式:
- 客户端执行主动打开
- 服务器执行被动打开
- SYN 报文不携带应用数据
- 不考虑同时打开等特殊情况
假设:
text
客户端初始序号:X
服务器初始序号:Y
完整过程如下:
text
客户端 服务器
CLOSED LISTEN
| |
| SYN=1, ACK=0 |
| SEQ=X |
| --------------------------------------------------> |
| |
SYN-SENT SYN-RCVD
| |
| SYN=1, ACK=1 |
| SEQ=Y, ack=X+1 |
| <-------------------------------------------------- |
| |
| SYN=0, ACK=1 |
| SEQ=X+1, ack=Y+1 |
| --------------------------------------------------> |
| |
ESTABLISHED ESTABLISHED
七、第一次握手:客户端发送 SYN
1. 报文方向
text
客户端 → 服务器
客户端主动发起连接。
在 Socket 编程中,这通常对应客户端调用:
c
connect(sockfd, ...);
之后,客户端 TCP 协议栈开始发送连接请求。
2. 标志位
text
SYN = 1
ACK = 0
SYN = 1 表示该报文用于同步初始序号。
第一次握手时,客户端还没有收到服务器的初始序号,因此没有可确认的服务器序号:
text
ACK = 0
需要注意,准确说法是:
ACK 标志位为 0,确认号字段无效。
而不是:
TCP 首部中不存在确认号字段。
3. 序号
text
SEQ = X
这里的 X 是客户端生成的初始序号,也称为:
text
ISN:Initial Sequence Number
不能简单地认为初始序号一定是一个纯随机数。实际操作系统会按照自身算法生成初始序号,并考虑安全性以及旧连接报文混入新连接等问题。
为了便于理解,假设:
text
X = 100
那么客户端发送:
text
SYN=1, ACK=0, SEQ=100
4. 状态变化
客户端状态从:
text
CLOSED
进入:
text
SYN-SENT
含义是:
text
同步请求已经发送,正在等待服务器确认。
5. 第一次握手表达的含义
客户端实际上是在告诉服务器:
text
我希望与你建立 TCP 连接。
我的初始序号是 X。
如果你收到了这个 SYN,
请通过确认号 X+1 对它进行确认。
八、第二次握手:服务器发送 SYN 和 ACK
服务器收到客户端的 SYN 后,如果端口处于监听状态并且允许建立连接,就会发送第二个报文段。
1. 报文方向
text
服务器 → 客户端
2. 标志位
text
SYN = 1
ACK = 1
其中:
text
SYN = 1
表示服务器也需要向客户端同步自己的初始序号。
text
ACK = 1
表示确认号字段有效,服务器正在确认客户端发送的 SYN。
因此,第二次握手通常称为:
text
SYN-ACK
或者:
text
SYN + ACK
3. 服务器序号
text
SEQ = Y
服务器也会生成自己的初始序号 Y。
假设:
text
Y = 200
那么第二次握手的序号为:
text
SEQ = 200
这里不能写成:
text
SEQ = Y + 1
因为服务器的 SYN 当前正占用序号 Y。
只有这个 SYN 被客户端确认后,服务器后续发送的第一个普通数据字节才会从:
text
Y + 1
开始。
4. 确认号
text
ack = X + 1
客户端第一次握手发送的是:
text
SEQ = X
SYN = 1
由于 SYN 消耗一个序号,服务器接下来期望客户端使用:
text
X + 1
所以服务器返回:
text
ack = X + 1
假设:
text
X = 100
那么:
text
ack = 101
它表达的含义是:
text
序号为 100 的 SYN 已经收到,
下一次希望你从序号 101 开始发送。
5. 状态变化
服务器从:
text
LISTEN
进入:
text
SYN-RCVD
也常写为:
text
SYN-RECEIVED
表示:
text
已经收到对方的 SYN,
也已经发送自己的 SYN 和 ACK,
现在等待客户端的最终确认。
此时连接还没有在服务器一侧完全建立。
6. 第二次握手表达的含义
服务器是在告诉客户端:
text
你的 SYN 我已经收到。
你的初始序号是 X,
因此下一次请从 X+1 开始发送。
同时,我的初始序号是 Y,
请你对 Y 进行确认。
完整字段为:
text
SYN = 1
ACK = 1
SEQ = Y
ack = X + 1
九、第三次握手:客户端发送最终 ACK
客户端收到服务器的 SYN-ACK 后,需要确认服务器的初始序号。
1. 报文方向
text
客户端 → 服务器
2. 标志位
在普通的主动打开与被动打开场景中:
text
SYN = 0
ACK = 1
客户端已经在第一次握手中发送过 SYN,因此第三次握手不需要再次设置 SYN。
第三次握手的主要任务是确认服务器的 SYN,所以:
text
ACK = 1
3. 客户端序号
text
SEQ = X + 1
第一次握手中的 SYN 已经占用了客户端的序号 X。
因此客户端下一个可使用的序号为:
text
X + 1
例如:
text
X = 100
那么第三次握手:
text
SEQ = 101
第三次握手的序号不能写成 Y,因为:
text
X 属于客户端的发送序号空间
Y 属于服务器的发送序号空间
TCP 是全双工协议,两个方向分别维护独立的序号空间。
客户端不能把服务器的序号 Y 当作自己的 SEQ 使用。
4. 确认号
text
ack = Y + 1
服务器第二次握手发送的是:
text
SEQ = Y
SYN = 1
因为服务器的 SYN 消耗一个序号,所以客户端下一次期望服务器使用:
text
Y + 1
于是第三次握手发送:
text
ack = Y + 1
例如:
text
Y = 200
则:
text
ack = 201
它表示:
text
服务器序号为 200 的 SYN 已经收到,
下一次希望服务器从序号 201 开始发送。
5. 状态变化
客户端成功处理服务器的 SYN-ACK,并发送最终 ACK 后,从:
text
SYN-SENT
进入:
text
ESTABLISHED
服务器收到这个最终 ACK 后,从:
text
SYN-RCVD
进入:
text
ESTABLISHED
此时双方都确认了对方的初始序号,连接正式建立。
RFC 9293 将该过程定义为 TCP 建立连接时用于同步双方序号空间的基本三次握手。
十、三次握手报文汇总
| 握手 | 方向 | SYN | ACK | SEQ | ack |
|---|---|---|---|---|---|
| 第一次 | 客户端 → 服务器 | 1 | 0 | X |
无效 |
| 第二次 | 服务器 → 客户端 | 1 | 1 | Y |
X + 1 |
| 第三次 | 客户端 → 服务器 | 0 | 1 | X + 1 |
Y + 1 |
用一行形式表示:
text
客户端 → 服务器:
SYN=1, ACK=0, SEQ=X
服务器 → 客户端:
SYN=1, ACK=1, SEQ=Y, ack=X+1
客户端 → 服务器:
SYN=0, ACK=1, SEQ=X+1, ack=Y+1
十一、代入具体数值理解
假设:
text
X = 100
Y = 200
那么三次握手就是:
第一次握手
text
客户端 → 服务器
SYN = 1
ACK = 0
SEQ = 100
ack = 无效
服务器收到后,知道客户端的 SYN 占用了序号 100,因此下一次期望:
text
101
第二次握手
text
服务器 → 客户端
SYN = 1
ACK = 1
SEQ = 200
ack = 101
客户端收到后,可以确认:
text
服务器已经收到客户端序号为 100 的 SYN。
服务器自己的初始序号是 200。
第三次握手
text
客户端 → 服务器
SYN = 0
ACK = 1
SEQ = 101
ack = 201
服务器收到后,可以确认:
text
客户端已经收到服务器序号为 200 的 SYN。
最终双方的数据起始序号分别为:
text
客户端下一次发送:SEQ = 101
服务器下一次发送:SEQ = 201
十二、为什么必须进行三次握手
1. TCP 需要同步两个方向的初始序号
TCP 是全双工协议:
text
客户端可以向服务器发送数据
服务器也可以向客户端发送数据
因此,双方各自拥有独立的发送序号空间。
连接建立时,需要完成四件事:
text
1. 客户端把自己的初始序号 X 告诉服务器
2. 服务器确认客户端的 X
3. 服务器把自己的初始序号 Y 告诉客户端
4. 客户端确认服务器的 Y
理论上可以拆成四个报文:
text
客户端 → 服务器:SYN,SEQ=X
服务器 → 客户端:ACK,ack=X+1
服务器 → 客户端:SYN,SEQ=Y
客户端 → 服务器:ACK,ack=Y+1
但是服务器对客户端的确认和服务器自己的 SYN 可以合并成同一个报文:
text
SYN + ACK
于是四个逻辑动作被合并为三个 TCP 报文,这就是"三次握手"。
2. 两次握手无法确认服务器的 SYN 是否到达客户端
假设只进行两次握手:
text
客户端 → 服务器:SYN
服务器 → 客户端:SYN + ACK
服务器发送完第二个报文后,无法知道:
text
客户端是否真正收到了服务器的 SYN
只有客户端发送第三个 ACK,服务器才能确认:
text
客户端已经收到服务器的初始序号 Y。
因此,第三次握手不是多余的。
它完成了服务器发送方向的序号确认。
3. 三次握手可以降低旧连接报文造成的混乱
网络中的报文可能发生:
- 延迟
- 重复
- 乱序
- 重传
假设某个旧连接中的 SYN 报文在网络中滞留,过了一段时间后才到达服务器。
服务器可能暂时把它当作新的连接请求,并返回 SYN-ACK。
但是原客户端当前并没有建立这个连接,因此不会正常完成第三次握手,或者会直接返回 RST。
服务器不会仅凭一个旧 SYN 就把连接无条件视为完全建立。
经典 TCP 规范在解释三次握手必要性时,也强调了初始序号同步以及对网络中旧重复报文的处理问题。
十三、三次握手和 SYN Flood 的关系
SYN Flood 是一种针对 TCP 连接建立阶段的拒绝服务攻击。
攻击者向服务器发送大量 SYN:
text
攻击者 → 服务器:大量 SYN
服务器收到 SYN 后,会:
- 为连接请求保留一定状态;
- 发送 SYN-ACK;
- 进入
SYN-RCVD; - 等待客户端的第三次 ACK。
如果攻击者使用伪造源地址,或者故意不返回第三次 ACK,服务器就会保留大量未完成连接。
这些连接通常被称为:
text
半连接
当半连接队列或相关资源被大量占用后,正常客户端可能无法建立新连接。
因此,SYN Flood 的核心并不是:
网络中延迟的第二次握手不断产生僵尸连接。
更准确的描述是:
攻击者发送大量不能完成三次握手的 SYN,使服务器为大量半连接保留状态,从而耗尽连接队列或系统资源。
RFC 4987 专门描述了 SYN Flood 如何利用服务器为伪造或不完整连接保留状态,并讨论了 SYN Cookie、增加队列容量和缩短超时等缓解方式。
十四、第三次握手能否携带数据
在最基础的教学模型中,第三次握手通常被画成一个纯 ACK:
text
SYN = 0
ACK = 1
SEQ = X + 1
ack = Y + 1
数据长度 = 0
这种情况下,第三次握手不会消耗新的数据序号。
但是从 TCP 报文结构上看,第三次握手可以同时携带应用数据。
假设客户端第三次握手携带 10 字节数据:
text
SEQ = X + 1
ack = Y + 1
数据长度 = 10
那么这些数据占用的序号范围为:
text
X + 1 ~ X + 10
客户端下一次发送数据时,序号变为:
text
X + 11
因此应当区分:
text
第三次握手通常不设置 SYN
和:
text
第三次握手一定不能携带数据
前者在普通三次握手中成立,后者并不准确。
十五、普通 ACK 为什么不消耗序号
假设客户端发送纯 ACK:
text
SEQ = 101
ACK = 1
ack = 201
数据长度 = 0
由于:
text
没有 SYN
没有 FIN
没有应用数据
所以该报文的序号空间长度为 0。
客户端下一次发送数据时,仍然可以使用:
text
SEQ = 101
也就是说:
纯 ACK 报文可以带有 SEQ 字段,但它本身不会推动发送序号向前移动。
会消耗序号的主要内容是:
text
应用数据:按字节数消耗
SYN:消耗 1
FIN:消耗 1
十六、SEQ 和 ack 的通用计算方法
对于一个正常按序到达的报文段,可以使用下面的思路计算确认号。
定义:
text
序号空间长度 =
数据长度
+ SYN 是否置位
+ FIN 是否置位
写成公式:
text
SEG.LEN = DataLength + SYN + FIN
其中:
text
SYN=1 时计入 1,否则计入 0
FIN=1 时计入 1,否则计入 0
对方期望的下一个序号通常为:
text
ack = SEQ + SEG.LEN
例如:
只有 SYN
text
SEQ = 100
SYN = 1
FIN = 0
数据长度 = 0
ack = 100 + 1 = 101
只有 10 字节数据
text
SEQ = 100
SYN = 0
FIN = 0
数据长度 = 10
ack = 100 + 10 = 110
10 字节数据加 FIN
text
SEQ = 100
SYN = 0
FIN = 1
数据长度 = 10
ack = 100 + 10 + 1 = 111
不过需要注意,这个公式适用于报文按序到达并能够推进接收端期望序号的场景。
TCP 确认号的根本含义仍然是:
text
接收端下一次期望收到的序号
如果报文乱序到达,确认号不一定直接等于刚收到报文的 SEQ + SEG.LEN。
十七、客户端和服务器分别维护独立序号空间
三次握手中最容易出现的错误之一,是把 X 和 Y 混在一起。
实际上:
text
X 属于客户端 → 服务器方向
Y 属于服务器 → 客户端方向
可以理解为双方各有一本独立的账本。
客户端发送方向
text
客户端 SYN:SEQ = X
客户端第一个数据字节:SEQ = X + 1
服务器通过:
text
ack = X + 1
确认客户端的发送进度。
服务器发送方向
text
服务器 SYN:SEQ = Y
服务器第一个数据字节:SEQ = Y + 1
客户端通过:
text
ack = Y + 1
确认服务器的发送进度。
因此,第三次握手绝对不能写成:
text
客户端 SEQ = Y
因为 Y 属于服务器的发送序号空间。
十八、三次握手中的状态迁移
客户端状态
text
CLOSED
|
| 主动发起连接,发送 SYN
v
SYN-SENT
|
| 收到合法 SYN-ACK,发送 ACK
v
ESTABLISHED
服务器状态
text
CLOSED
|
| bind、listen
v
LISTEN
|
| 收到 SYN,发送 SYN-ACK
v
SYN-RCVD
|
| 收到最终 ACK
v
ESTABLISHED
完整状态变化:
| 阶段 | 客户端状态 | 服务器状态 |
|---|---|---|
| 初始 | CLOSED |
LISTEN |
| 第一次握手后 | SYN-SENT |
收到 SYN 前仍为 LISTEN |
| 第二次握手后 | 等待并处理 SYN-ACK | SYN-RCVD |
| 第三次握手后 | ESTABLISHED |
收到 ACK 后进入 ESTABLISHED |
在典型 Socket API 中:
- 客户端的
connect()通常在连接建立成功后返回; - 服务器的
accept()通常从已完成连接队列中取得已经完成握手的连接; - 监听套接字继续负责接收新的连接请求;
accept()返回的新套接字负责与具体客户端通信。
十九、常见错误逐项纠正
错误一:第一次握手是 SYN=1、ACK=1
错误写法:
text
第一次握手:
SYN = 1
ACK = 1
正确写法:
text
第一次握手:
SYN = 1
ACK = 0
因为客户端还没有收到服务器的初始序号,没有需要确认的内容。
错误二:第一次握手没有确认号字段
不准确写法:
text
第一次握手不存在确认号字段。
准确写法:
text
确认号字段始终存在于 TCP 首部中,
但由于 ACK=0,该字段没有协议意义。
错误三:第二次握手的 SEQ 是 Y+1
错误写法:
text
SEQ = Y + 1
正确写法:
text
SEQ = Y
服务器当前发送的 SYN 正在占用序号 Y。
服务器后续第一个普通数据字节才使用 Y + 1。
错误四:第三次握手仍然设置 SYN=1
普通三次握手中的错误写法:
text
SYN = 1
ACK = 1
正确写法:
text
SYN = 0
ACK = 1
客户端已经在第一次握手中发送过 SYN。
不过需要注意,同时打开是 TCP 支持的一种特殊场景。两个端点都主动发送 SYN 时,报文交换过程可能与普通客户端---服务器模型不同。本文讨论的是最常见的主动打开与被动打开过程。RFC 9293 明确说明三次握手同样支持双方同时主动打开。
错误五:第三次握手的 SEQ 是 Y
错误写法:
text
客户端第三次握手:
SEQ = Y
正确写法:
text
客户端第三次握手:
SEQ = X + 1
因为客户端必须使用自己的发送序号空间。
Y 是服务器的初始序号,只能出现在服务器发送报文的 SEQ 字段,或者被客户端用于计算确认号:
text
ack = Y + 1
错误六:确认号永远等于对方 SEQ+1
这个说法只适用于:
text
对方发送了一个不携带数据的 SYN
因为 SYN 消耗一个序号。
对于普通数据:
text
ack = SEQ + 数据长度
对于携带 FIN 的数据:
text
ack = SEQ + 数据长度 + 1
更加通用且准确的说法是:
确认号表示接收方下一次期望收到的序号。
错误七:SYN 相当于一个虚构的数据字节
这种说法适合辅助理解,但不够严谨。
更准确的说法是:
SYN 不一定是应用层数据字节,但它会占用 TCP 序号空间中的一个位置。
因此:
text
SYN 的 SEQ = X
被确认后:
text
ack = X + 1
错误八:两次握手必然导致大量 SYN Flood 僵尸连接
三次握手的主要作用是:
- 同步双方初始序号;
- 确认两个方向都具备收发能力;
- 减少旧重复连接请求造成的混乱。
SYN Flood 则是攻击者利用服务器在 SYN-RCVD 状态保存半连接资源的行为。
两者有关联,但不能把"三次握手存在的全部原因"简单归结为防御 SYN Flood。
事实上,即使 TCP 使用三次握手,仍然可能遭受 SYN Flood,因此操作系统还需要使用:
- SYN Cookie
- 半连接队列管理
- 超时与重传限制
- 速率限制
- 防火墙与负载均衡防护
等额外机制。
二十、最终结论
标准的 TCP 三次握手可以概括为:
text
第一次握手:
客户端 → 服务器
SYN=1, ACK=0, SEQ=X
第二次握手:
服务器 → 客户端
SYN=1, ACK=1, SEQ=Y, ack=X+1
第三次握手:
客户端 → 服务器
SYN=0, ACK=1, SEQ=X+1, ack=Y+1
需要牢牢记住以下规则:
text
1. ACK 标志位和确认号字段不是同一个概念。
2. ACK=0 时,确认号字段仍然存在,但没有协议意义。
3. SYN 和 FIN 各自消耗一个序号。
4. 普通纯 ACK 不消耗新的序号。
5. ack 表示下一次期望收到的序号。
6. 客户端和服务器分别维护独立的发送序号空间。
7. X 属于客户端发送方向,Y 属于服务器发送方向。
8. 普通三次握手的第三个报文是 ACK,而不是再次发送 SYN。
9. 三次握手的核心是同步并确认双方的初始序号。
10. SYN Flood 利用了服务器保存半连接状态的机制,但防御 SYN Flood 不是三次握手存在的唯一原因。
只要理解了:
text
SEQ 是"我这次从哪里开始发送"
ack 是"我下一次希望你从哪里开始发送"
三次握手中的所有 X、Y、X+1 和 Y+1 就不再需要死记硬背,而是可以根据 TCP 序号空间自然推导出来。