《Linux 网络编程》深入理解 TCP 协议(二):序号、确认应答与流量控制机制详解

🔥小叶-duck个人主页

❄️个人专栏《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》

《Linux系统从入门到实践》《Linux网络从入门到实践》

《Qt 方寸极境》 《MySQL》

未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游


目录

前言

[一、TCP 核心知识点回顾](#一、TCP 核心知识点回顾)

[二、 TCP 发送数据的两种工作模式](#二、 TCP 发送数据的两种工作模式)

[2.1 串行发送模式(停等协议 Stop-and-Wait)](#2.1 串行发送模式(停等协议 Stop-and-Wait))

[2.2 并行发送模式(流水线模式 Pipelining)](#2.2 并行发送模式(流水线模式 Pipelining))

[三、TCP 序号与确认序号机制](#三、TCP 序号与确认序号机制)

[3.1 序号的本质](#3.1 序号的本质)

[3.2 确认序号定义与累积确认机制](#3.2 确认序号定义与累积确认机制)

[3.3 序号的三大核心功能](#3.3 序号的三大核心功能)

[3.4 缓冲区与序号的逻辑关系](#3.4 缓冲区与序号的逻辑关系)

[3.5 经典场景:中间报文丢包的处理](#3.5 经典场景:中间报文丢包的处理)

[3.5.1 前置基础定义](#3.5.1 前置基础定义)

[3.5.2 正常无丢包场景的序号流转](#3.5.2 正常无丢包场景的序号流转)

[3.5.3 报文 3 丢失、报文 4 乱序提前到达场景](#3.5.3 报文 3 丢失、报文 4 乱序提前到达场景)

[3.5.4 补齐缺失报文后的确认应答](#3.5.4 补齐缺失报文后的确认应答)

[3.5.5 核心结论](#3.5.5 核心结论)

[3.6 问题:为什么同时需要序号和确认序号?](#3.6 问题:为什么同时需要序号和确认序号?)

[3.6.1 全双工通信要求双方都能发送和确认](#3.6.1 全双工通信要求双方都能发送和确认)

[3.6.2 捎带应答:一个报文同时承载数据和 ACK](#3.6.2 捎带应答:一个报文同时承载数据和 ACK)

[3.6.3 区分:捎带应答&&纯ACK应答](#3.6.3 区分:捎带应答&&纯ACK应答)

[3.6.4 小结](#3.6.4 小结)

[四、TCP 16 位窗口大小与流量控制](#四、TCP 16 位窗口大小与流量控制)

[4.1 流量控制产生背景](#4.1 流量控制产生背景)

[4.2 接收方接收能力的量化标准](#4.2 接收方接收能力的量化标准)

[4.3 16 位窗口大小字段含义](#4.3 16 位窗口大小字段含义)

[4.4 流量控制的本质:提高效率](#4.4 流量控制的本质:提高效率)

[4.5 窗口探测机制(零窗口死锁规避)](#4.5 窗口探测机制(零窗口死锁规避))

[4.5.1 窗口探测视角:理解TCP面向字节流](#4.5.1 窗口探测视角:理解TCP面向字节流)

[4.6 核心总结](#4.6 核心总结)

结束语


前言

在上一篇文章中,我们深度解析了 TCP 报头中 4 位首部长度字段的设计精髓,以及可靠性最底层的两大基石 ------ 确认应答与超时重传机制。但 TCP 的复杂之处远不止于此:为了提高传输效率,TCP 不会像停等协议那样发一个等一个,而是支持并行发送多个报文;面对多个报文同时发送带来的丢包、乱序、重复等问题,TCP 依靠序号与确认序号这套字节级编号体系来化解;为了避免发送方发送过快导致接收方缓冲区溢出,TCP 引入了流量控制机制,并借助 16 位窗口大小字段实时同步双方的接收能力。

本文将继续沿着 TCP 协议的设计思路往下走,先从数据发送的两种工作模式讲起,厘清停等协议与流水线模式的取舍;再深度拆解序号与确认序号的核心作用,还原丢包、乱序场景下 TCP 的真实处理逻辑;随后详解流量控制与窗口探测机制。所有内容均严格基于 TCP 协议规范和 Linux 内核实现,力求做到理论与实践相结合。

一、TCP 核心知识点回顾

在开始本篇文章新内容之前,我们先梳理上篇文章中TCP的基础核心结论,作为后续内容理解的前提:

  1. **报文交互规则:**TCP 通信双方交互的基本单元是完整 TCP 报文。发送方传输业务数据时,数据会封装成 TCP 报文;接收方返回的应答报文,就算不携带任何应用层数据,也必须保留完整 TCP 报头,不会单独只发送标志位。
  2. **确认应答核心规则:**TCP 属于全双工可靠传输协议,确认应答是实现可靠性的基础。应答报文由接收端操作系统内核的 TCP 协议栈自动生成并回复,不需要应用层参与。 通信规则:A 向 B 发送 TCP 数据,B 收到后自动返回 ACK 应答;应答报文本身不再需要二次应答,否则会形成无限循环,严重降低传输效率。依托这套机制,通信双向都可以保障数据传输可靠。
  3. 超时重传判定逻辑: 发送方收到 ACK 应答,就可以 100% 确认接收方完整收到数据,本次传输可靠。 如果发送方没有收到应答,无法区分是原始数据丢包,还是 ACK 应答报文在路上丢失。TCP 统一处理:超时时间内没有收到对应应答,就判定传输异常,自动触发报文重传
  4. TCP 可靠性的真正定义: TCP 的可靠,不等于强制保证数据一定送达接收方。可靠性的核心本质是:无论传输最终成功还是失败,发送方一定能够感知最终结果。收到 ACK 代表数据交付成功;超时无应答,则判定传输失败并重传。就算遇到网线断开、网络中断这类场景,TCP 也可以精准感知传输失败的状态。
  5. **全双工特性:**TCP 是全双工协议,通信两端能够同时收发数据。这个特性深刻影响 TCP 的诸多设计,捎带应答、三次握手等机制,都建立在全双工的基础之上。

二、 TCP 发送数据的两种工作模式

TCP 为适配不同的数据传输场景,设计了两套发送工作模式:串行发送并行流水线发送模式。

2.1 串行发送模式(停等协议 Stop-and-Wait)

工作逻辑:发送方每发出 1 个报文之后,就暂停发送,原地等待接收方返回对应的 ACK 应答。必须收到应答,才可以继续发送下一份报文

  • 优点:逻辑简单,天然不会出现报文乱序、重复接收的问题,实现成本低。
  • 缺点:传输效率很低。在等待 ACK 的 RTT 往返时间内,网络链路处于空闲状态,带宽资源被浪费。
  • 适用场景:只适合少量数据传输场景,在真实 TCP 通信里很少单独使用。
bash 复制代码
主机A                主机B
   |---- 数据1 ------>|
   |                  |
   |<---- ACK1 -------|
   |                  |
   |---- 数据2 ------>|
   |                  |
   |<---- ACK2 -------|

2.2 并行发送模式(流水线模式 Pipelining)

这是 TCP 实际通信里主流默认使用 的发送模式。

工作逻辑:发送方不需要等待上一个报文的 ACK 应答,就可以连续向外发送多个报文,不需要等待一轮 RTT。多个报文的收发时间可以相互重叠,充分利用链路带宽,大幅提升网络吞吐与传输效率。

bash 复制代码
主机A               主机B
|------ 数据1 ------>|
|------ 数据2 ------>|
|------ 数据3 ------>|
|<----- ACK1  -------|
|<----- ACK2  -------|
|------ 数据4 ------>|
|<----- ACK3  -------|

并行流水线模式虽然解决了性能瓶颈,但同时引入了新难题:

  1. 如果多个报文连续发出,中间某个报文丢失,发送方如何定位到底是哪一段数据丢包?
  2. 报文在网络路由转发时乱序到达,接收端怎么还原出原始数据流顺序?
  3. 超时重传带来重复报文,接收端如何识别并丢弃重复数据?

为了解决并行流水线带来的丢包识别报文排序去重 等一系列问题,TCP 协议在报头中定义了两个核心字段:32 位序号(Sequence Number)32 位确认序号(Acknowledgment Number)

三、TCP 序号与确认序号机制

序号与确认序号是 TCP 实现可靠传输与流水线高效传输的核心,TCP 绝大多数可靠性机制都建立在这两个字段之上。

3.1 序号的本质

操作系统内核维护发送缓冲区与接收缓冲区,底层依靠sk_buff存储报文。逻辑层面,TCP 把缓冲区中的数据流视为一个巨大连续字节数组,序号就是这个字节数组的下标

TCP 不会逐字节发送数据,而是将数据分批封装成 TCP 数据段(Segment)进行传输。

举例:

  • 报文承载第 1~1000 字节数据,该报文的序号 = 1
  • 下一段承载 1001~2000 字节,序号 = 1001
  • 再下一段承载 2001~3000 字节,序号 = 2001

3.2 确认序号定义与累积确认机制

确认序号计算公式:确认序号 = 收到的最后一个完整字节的序号 + 1 含义:确认序号 N,代表 N 之前所有字节全部接收完毕,下一次期望从序号 N 开始接收数据

示例:

  • 主机 B 收到 1~1000 字节的数据段,回复确认序号 1001;
  • 主机 B 收到 1001~2000 字节的数据段,回复确认序号 2001。

TCP 采用累积确认,这是非常关键的特性: 如果主机 B 已经收到 1~1000、2001~3000,但中间 1001~2000 还未收到,此时只能回复确认序号 1001。 哪怕后面的字节提前到达,也不会确认后面的数据,以此保障 TCP 有序交付。

累积确认优势:容错能力强,中间部分 ACK 报文在网络丢失也不会造成严重影响,只要后续 ACK 抵达,发送方就能确认数据。

3.3 序号的三大核心功能

正是依靠序号和确认序号,流水线并行发送带来的各类难题得以解决:

  • **保障传输可靠性:**接收方通过确认序号反馈接收进度,发送方能精准判断哪些数据成功送达、哪些报文丢失,触发超时重传。
  • **报文去重:**超时重传场景下接收端可能收到重复报文,依靠序号识别重复数据并丢弃。
  • **实现乱序重组:**网络传输时报文可能乱序抵达,接收缓冲区利用序号重新排序,整理成有序数据流再交付应用层。

3.4 缓冲区与序号的逻辑关系

发送缓冲区、接收缓冲区在内核中以 sk_buff 队列物理存储。逻辑上等效为连续字节数组 ,缓冲区里的每一字节数据,都对应唯一序号下标 。 TCP 数据段发送时,按字节范围打包。

例如一段报文包含 1000 字节,起始序号 1,覆盖字节 1~1000;下一段就从 1001 开始。不同数据段的序号自然不连续,根源是每个 TCP 段携带的数据长度不一样。

3.5 经典场景:中间报文丢包的处理

场景:发送方依次发送序号 100、200、300、400 四段报文;序号 300 的报文在传输途中丢失,400 报文先抵达接收端。

此时接收方不能直接应答 400 ,仍然持续回复确认序号 300。

即便收到 400 这一段,接收端也会告知发送方:300 之前的数据已经全部收到,请从 300 继续发送。只有 300 号报文补齐之后,确认序号才会继续向后推进。

面试小结:确认序号只确认连续的前置字节,后面提前到达的数据不会单独确认,这就是累积确认。

3.5.1 前置基础定义

TCP 是面向字节流的协议,全部数据都会按字节分配连续序号,序号对应内核接收缓冲区的字节下标。

  • 报文起始序号:该报文携带的第一个字节的下标
  • 报文结束序号:该报文携带的最后一个字节的下标 = 起始序号 + 报文长度 - 1
  • 确认序号 ACK:接收方返回期望收到的下一个字节下标 ,含义:我已经完整收到 ACK 序号之前的全部字节,这就是 TCP 累积确认的核心规则。

3.5.2 正常无丢包场景的序号流转

连续发送 4 个报文,每个报文固定承载 100 字节数据:

报文编号 起始序号 携带字节范围 结束序号 下一个报文起始序号
报文 1 1 1~100 100 101
报文 2 101 101~200 200 201
报文 3 201 201~300 300 301
报文 4 301 301~400 400 401

网络正常无丢包、不乱序时,接收方收到报文后回复对应的确认序号:

  • 收到报文 1 → 返回 ACK=101:1~100 字节已全部接收,下次从 101 开始发送
  • 收到报文 2 → 返回 ACK=201:1~200 字节已全部接收,下次从 201 开始发送
  • 收到报文 3 → 返回 ACK=301:1~300 字节已全部接收,下次从 301 开始发送
  • 收到报文 4 → 返回 ACK=401:1~400 字节已全部接收,下次从 401 开始发送

3.5.3 报文 3 丢失、报文 4 乱序提前到达场景

发送方按顺序发送:报文 1 → 报文 2 → 报文 3 → 报文 4。 网络传输发生异常:报文 1、报文 2 正常抵达接收端;报文 3(201~300 字节)在传输途中丢失;报文 4(301~400 字节)绕过路由,先于报文 3 到达接收方

接收方缓冲区当前状态:

  • ✅ 连续完整收到:1~200 字节(报文 1、报文 2)
  • ✅ 提前零散收到:301~400 字节(报文 4,临时存放在接收缓冲区)
  • ❌ 缺失空缺段:201~300 字节(报文 3)

重点说明 : 虽然报文 4 已经提前到达并存入接收缓冲区,接收方依然只能回复 ACK=201,不能返回 ACK=401

累积确认有硬性约束 :确认序号只能标记已经连续收到的最大字节的下一位不能跳过中间缺失的字节

如果返回 ACK=401,会误导发送方,让发送方认为 1~400 全部收到,不会重传丢失的 201~300 这一段,最终造成数据永久丢失。

接收端只会暂存提前抵达的报文 4 ,但不会更新确认序号,必须等空缺的 201~300 字节补齐之后,确认序号才会向后推进。
👉 悬念:此时发送方收到 ACK=201 重复应答,如何判断是报文 3 发生丢失、需要重传报文 3?该部分依赖滑动窗口快速重传机制,我们留到后面章节详细讲解。

3.5.4 补齐缺失报文后的确认应答

发送方触发重传,重新发送报文 3(201~300 字节)。接收端收到重传的报文 3 后,缓冲区 1~400 字节全部补齐,此时接收方返回 ACK=401。 ACK=401 属于累积确认,它的含义是:1~400 的所有字节我都完整收到。

  • 中间的 ACK=201、ACK=301 应答报文就算在网络中丢失也没关系
  • 只要发送方收到最高确认序号 ACK=401,就代表前置全部字节送达
  • 不需要重复重传任何报文,直接从 401 序号继续传输后续数据

3.5.5 核心结论

  1. TCP 序号是字节级编号,不是报文编号,报文之间序号不连续,是因为每个报文承载多个字节。
  2. 确认序号永远等于:已连续接收的最后一个字节序号 + 1。
  3. 乱序提前到达的报文会被接收缓冲区暂存,但确认序号不会向前推进,直到空缺字节补齐(重点理解)
  4. 累积确认自带容错能力,中间 ACK 应答丢失不影响传输,只要收到最高序号的确认,就代表前面所有数据全部接收成功。

3.6 问题:为什么同时需要序号和确认序号?

在前面的例子里,我们看到确认序号可以表示 "我已经收到了哪些字节"。

于是有人会问:既然确认序号已经能说明接收进度,为什么不直接只用一个32位序号字段?应答时把序号加 1 再添到32位序号中返回不就行了?

这个问题的关键在于:TCP 是全双工协议,通信双方可以同时发送数据。 在这种情况下,发送方不仅要标识 "自己发的数据到了哪里",还要标识 "自己确认收到了对方哪些数据"。

3.6.1 全双工通信要求双方都能发送和确认

TCP 允许两端同时传输数据。主机 A 可以向主机 B 发送数据,主机 B 也可以同时向主机 A 发送数据。这意味着:

  • 每一端都需要一个 "发送序号",用来标记自己发送数据的字节位置;
  • 每一端也需要一个 "确认序号",用来告诉对方:我已经收到了你的哪些数据。

如果只有一个序号字段,就无法同时区分:

  • 当前报文是在描述 "我发送到哪里";
  • 还是在描述 "我确认收到了哪里"。

因此,序号和确认序号是两个独立职责:

字段 作用
序号 标识本报文携带的数据在发送方字节流中的位置
确认序号 标识接收方已经连续收到的最大字节位置

3.6.2 捎带应答:一个报文同时承载数据和 ACK

TCP 的全双工特性带来了一个重要优化:捎带应答

当主机 B 需要向主机 A 发送数据时,它不必单独发送一条空的 ACK 报文,而是可以把对主机 A 的确认信息,直接放到自己的业务数据报文中一起发送。

例如:

  1. 主机 A 先向主机 B 发送数据,字节范围是 1~1000;
  2. 主机 B 收到后,本来可以返回一个 ACK=1001;
  3. 但如果此时主机 B 也有数据要发给主机 A,比如字节范围是 5001~6000;
  4. 那么主机 B 可以直接发送一个同时包含数据的报文:
    自己的发送序号:5001;
    对主机 A 的确认序号:1001。

这样,这个报文既携带了主机 B 的业务数据,又完成了对主机 A 的确认,减少了单独 ACK 报文的数量,提高了传输效率。

3.6.3 区分:捎带应答&&纯ACK应答

引出问题:

3.6.4 小结

TCP 之所以同时需要序号和确认序号,根本原因有两个:

  1. TCP 是全双工协议,两端可以同时发送数据,必须分别跟踪各自的发送进度;
  2. TCP 支持捎带应答,一个报文可以同时承载业务数据和确认信息,因此需要同时标识 "我发的数据" 和 "我确认的数据"。

这也进一步说明:序号和确认序号并不是两个孤立的数字,而是 TCP 可靠性、顺序控制和流量控制的重要基础 。后续讲解滑动窗口时,还会看到这两个字段如何配合窗口大小,共同决定发送方可以连续发送多少数据。

四、TCP 16 位窗口大小与流量控制

4.1 流量控制产生背景

即便网络带宽充足,发送方也不能无限制持续发送数据。接收主机的处理能力存在上限,如果发送速率过快,内核接收缓冲区会被迅速填满,后续到达的数据会直接丢弃。

数据包一旦丢失,会触发 TCP 超时重传,反复重传会浪费网络带宽、主机 CPU 以及内存资源,传输效率大幅下降。TCP 引入流量控制 解决这个问题,而 TCP 头部的16 位窗口大小字段就是流量控制的核心载体。

4.2 接收方接收能力的量化标准

接收方的接收能力,由内核接收缓冲区的剩余空闲空间决定:

  • 接收缓冲区的剩余空间越大,代表接收能力越强,可以接收更多数据;
  • 接收缓冲区的剩余空间越小,接收能力越弱;
  • 接收缓冲区的剩余空间为 0,缓冲区已满,无法接收新数据。

4.3 16 位窗口大小字段含义

窗口大小字段表示接收方当前接收缓冲区剩余空闲字节数量单位是字节。 接收方在回复 ACK 应答报文时,会把自身接收缓冲区剩余空间写入窗口大小字段,告诉发送方当前还能接收多少字节的数据。 发送方读取 ACK 中的窗口值,动态调整发送速度:

  • 窗口大:接收方处理压力小,发送方可以持续发送较多数据;
  • 窗口小:接收方缓冲区剩余空间紧张,发送方需要降低发送速率;
  • 窗口为 0:接收缓冲区已满,发送方必须暂停发送业务数据。

计算公式:窗口大小 = 接收缓冲区总大小 - 缓冲区已使用字节数

4.4 流量控制的本质:提高效率

4.5 窗口探测机制(零窗口死锁规避)

当接收方窗口等于 0,发送方停止发送业务数据。 这里存在一个风险:接收方后续窗口更新的 ACK 报文,如果在网络传输中丢失,发送方会一直阻塞等待,双方陷入死锁。

TCP 引入窗口探测机制 解决该问题: 发送方收到窗口为 0 的 ACK 后,会周期性发送仅携带 1 字节数据的窗口探测报文,用来询问接收方当前最新窗口大小。

  • 如果接收方窗口已经恢复,返回携带新窗口值的 ACK,发送方恢复数据传输;
  • 如果窗口依旧为 0,接收方回复窗口 0,发送方继续等待,下一次周期再探测。

4.5.1 窗口探测视角:理解TCP面向字节流

4.6 核心总结

  1. 流量控制 解决发送方和接收方主机处理能力不匹配的问题,和网络拥塞不是一回事(拥塞控制后续讲解);
  2. 16 位窗口大小由接收方填充,反映接收缓冲区剩余空间,用于告知发送方接收上限
  3. 窗口为 0 时发送方暂停发送,依靠窗口探测报文,防止窗口更新报文丢失带来的死锁问题;
  4. 流量控制全程动态交互,让发送速率跟随接收方处理能力自适应变化,减少丢包与不必要的重传。

结束语

回顾本篇,我们承接上一篇文章对确认应答与超时重传的讲解,把视角从 "数据怎么保证送达" 推进到 "数据怎么传得又快又稳"。我们先是理清了停等协议与流水线两种发送模式的区别,明白 TCP 之所以默认采用并行发送,是为了消除等待应答的往返空档,充分榨取链路带宽。

随之而来的丢包定位、乱序重组、重复报文识别,则依靠序号与确认序号这套字节级编号体系解决。通过丢包场景的推演,我们看到累积确认如何保证有序交付,也理解了为什么序号和确认序号两个字段缺一不可。最后,流量控制机制借助 16 位窗口字段动态调节发送速率,解决了接收方缓冲区溢出的隐患,窗口探测则化解了零窗口下的死锁风险。

值得注意的是,流量控制针对的是收发两端处理能力不匹配的问题,与后续要讲的拥塞控制并非同一概念。序号、窗口、重传这些机制环环相扣,共同撑起 TCP 复杂而严谨的传输控制体系,也为下一部分学习三次握手、四次挥手与连接管理打下了坚实基础。

相关推荐
北京盛世宏博1 小时前
物联网传感器通信协议:帧字段规划、CRC 校验、异常包过滤方案
网络·物联网·php
楚疏笃1 小时前
linux下给OpenCloudOS根目录扩容
linux·运维·服务器
dog2501 小时前
网络的时延抖动
linux·运维·网络
阿钱真强道1 小时前
01 嵌入式操作系统 | 课程导论与虚拟机安装 Ubuntu
linux·ubuntu·嵌入式·虚拟机
devpotato1 小时前
高可用系统如何定义可用性
服务器·网络·可用性测试
程序员-Benothing1 小时前
Linux 软件包管理:yum / apt / dnf 命令使用全解析
linux·运维·服务器
奇牙coding1231 小时前
GPT-5.5 升级 GPT-5.6 接入指南:流式调用配置与常见问题
java·网络·gpt·ai
GeW1 小时前
拿RHCE证书只是起点:这样备考,顺便把真实运维能力也练了
linux
stellanke2 小时前
Linux网络编程实战2:TCP Socket 基础与实践
linux·网络·c++