TCP 三次握手彻底解析:SYN、ACK、SEQ、确认号与状态迁移

文章目录

  • [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 --------------> |
   |                              |

但一旦涉及以下细节,就很容易出现混淆:

  • SYNACK 到底是标志位还是字段?
  • 为什么第二次握手的确认号是 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 后,会:

  1. 为连接请求保留一定状态;
  2. 发送 SYN-ACK;
  3. 进入 SYN-RCVD
  4. 等待客户端的第三次 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


十七、客户端和服务器分别维护独立序号空间

三次握手中最容易出现的错误之一,是把 XY 混在一起。

实际上:

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 是"我下一次希望你从哪里开始发送"

三次握手中的所有 XYX+1Y+1 就不再需要死记硬背,而是可以根据 TCP 序号空间自然推导出来。

相关推荐
2501_915106321 小时前
TraceEagle 代理抓包教程 本机和手机的 HTTPS 抓包方法
网络协议·计算机网络·网络安全·ios·adb·https·udp
小心我捶你啊2 小时前
数据采集和Web解锁不是一回事,从用途到规则区分
前端·爬虫·网络协议
DLYSB_3 小时前
基于 HTTP API 与 Modbus 协议的多源监控声光语音联动告警系统设计与实现
网络·网络协议·http·报警灯
Bobolink_3 小时前
手机端和电脑端的网络环境配置有什么不同?
网络·智能手机·电脑·网络环境
星恒讯工业路由器4 小时前
三大工业以太网协议选型对比
网络·物联网·ethercat·profinet·ethernet/ip·工业以太网协议
春卷同学4 小时前
HarmonyOS掌上记账APP开发实践第25篇:Image 组件高级用法 — 资源引用、网络图片与首字符占位方案
网络·华为·harmonyos
mounter6254 小时前
从内存隔离到运行时防线:透视 Linux 内核安全强化的博弈与演进
linux·网络·安全·linux kernel·kernel
Bug退散师7 小时前
多路IO复用[select版TCP服务器与poll版TCP服务器]与常用网络编程函数详解
linux·服务器·c语言·网络·驱动开发·tcp/ip
Acrellea8 小时前
固态变压器(SST)热管理破局:安科瑞交直流专属测温方案护航无人值守运维
运维·网络