【Java EE】TCP—滑动窗口

滑动窗口

滑动窗口

什么是滑动窗口?

窗口大小 定义为:不需要等待确认应答,能够连续发送的最大数据量。

窗口的单位通常是字节(或数据包个数)。发送方在窗口范围内可以"一口气"发出多个数据段,然后等待这些数据段的 ACK。
发送缓冲区
已发送已确认
已发送未确认
可发送未发送
不可发送

上图中,黄色部分为"已发送但未收到 ACK"的数据,蓝色部分为"窗口内待发送的数据"。随着 ACK 的到达,窗口会向右滑动,新的数据进入窗口并被发送。

发送策略:等所有 ACK 回来还是收到一个就发?

初学者常会疑惑:发送方应该等窗口内所有 ACK 都收到后再发下一批,还是每收到一个 ACK 就立即发一个新的?

答案是后者 。TCP 采用的是流水线滑动窗口 机制:发送方可以连续发出窗口允许的多个数据段,而不必等前一个确认返回。每收到一个累计 ACK,窗口便向右滑动,腾出的空间立即允许发送新的数据段

下面以窗口大小为 3 个段(每个段 1000 字节)为例,演示这一过程:
主机B(接收方) 主机A(发送方) 主机B(接收方) 主机A(发送方) 发送窗口 1\~3000 窗口用尽,等待确认 收到 1~3000 窗口右滑 → 3001\~6000 立即发送 3001~4000 收到 1~4000 窗口再次右滑 → 4001\~7000 立即发送 4001~5000 数据包 1~1000 数据包 1001~2000 数据包 2001~3000 ACK 3001(确认收到 1~3000) 数据包 3001~4000 ACK 4001(确认收到 1~4000) 数据包 4001~5000

图解说明:

  • 主机 A 一开始便连发 3 个数据段,把窗口填满。
  • 收到第一个 ACK(累计确认 1~3000)后,窗口右移3001~6000,A 立即将 3001~4000 发送出去。
  • 收到第二个 ACK(累计确认 1~4000)时,窗口继续右移,又可发送下一个段。

整个过程无需等待所有 ACK 返回,一个 ACK 就能推动窗口滑动并触发一次新发送,传输链路始终保持忙碌,大大减少了空闲等待时间,这就是滑动窗口流水线的高效所在。

累计确认:ACK丢失问题⭐

网络传输中,ACK 本身也可能丢失。假设主机B收到了数据包1~5000,但返回给主机A的某些ACK在途中丢失了。此时需要重传吗?

不需要 。TCP 使用累计确认机制:ACK 携带的是"下一个期望收到的字节序号"。例如:

  • 收到1~1000后,回复 ACK: 下一个是1001
  • 又收到1001~2000,回复 ACK: 下一个是2001
  • 如果 ACK(2001) 丢失,但随后收到3001~4000,则接收方会回复 ACK: 下一个是4001

这个 ACK(4001) 隐含地确认了4001之前的所有数据(包括1~3000)。因此,即使中间的ACK丢失,只要后续有更高序号的ACK到达,发送方就知道之前的数据已被成功接收。
主机B 主机A 主机B 主机A ACK 2001 在传输中丢失 主机A收到ACK 4001 就知道1~4000已全部确认 即使ACK 2001丢了也无需重传 发送数据 1~1000 ACK 1001 (确认收到1~1000) 发送数据 1001~2000 ACK 2001 ❌ 丢失 发送数据 2001~3000 ACK 3001 (确认收到1~3000) 发送数据 3001~4000 ACK 4001 ✅ 到达 (确认收到1~4000)

上图中,ACK(2001)丢失,但主机A随后收到了ACK(4001)。根据累计确认,ACK(4001)代表1~4000全部确认,因此无需重传任何数据。这就是"后一个ACK能涵盖前一个ACK的含义"

乱序ACK的处理⭐

由于网络延迟不同,ACK 确实可能不按顺序到达。例如,主机 A 连续发送了 1~10001001~20002001~3000 三个报文段,接收方 B 对应生成 ACK 1001ACK 2001ACK 3001

ACK 3001 先到达,ACK 2001 后到达,会发生什么?

滑动窗口天然支持乱序 ACK,处理规则极其简单:永远以收到的最大确认序号为准。

  • ACK 3001 是一个累计确认,表示"1~3000 已全部收到"。
  • 主机 A 收到后,立即将发送窗口左边界滑动到 3001,此时即使 2001~3000 刚刚才被确认,也不影响窗口前进,3001~4000 可以马上发送。
  • 随后,迟到的 ACK 2001 到达。由于它要确认的范围 1~2000 早已被 ACK 3001 覆盖,这条 ACK 就是冗余信息,直接忽略。窗口不会倒退,也不会做任何重传。

整个过程如下图所示:
主机B 主机A 主机B 主机A 生成 ACK 1001、2001、3001 窗口右移至 3001 可发送 3001~4000 确认范围已被覆盖,忽略 数据 1~1000 数据 1001~2000 数据 2001~3000 ACK 3001(先到,确认1~3000) ACK 2001(后到,冗余) 数据 3001~4000

核心要点:

  • 累计确认让 TCP 只关心"已连续确认到哪个序号",不依赖 ACK 的到达顺序。
  • 窗口只沿序号增大方向滑动,不后退、不重传已确认数据
  • 迟到的、确认范围更小的 ACK,在更大的 ACK 面前直接变成冗余,安全丢弃。

快速重传:数据丢失问题⭐

当发送方一口气发出整个滑动窗口的数据时,某个中间数据段一旦丢失,接收方会收到大量跳过空洞的后续数据。此时 TCP 的累积确认 规则会让接收方不断发送相同的 ACK (期望缺失的那个起始序号),这就是重复 ACK

快速重传的规则是:

发送方只要连续收到 3 个重复 ACK (即一共 4 个相同的 ACK),就立刻判定对应的数据段已丢失,不等待超时,马上重传该段。

假设每个数据段固定 1000 字节 ,起始序号为 1。发送方一口气发出了 1~7000 的数据(共 7 个段),但第二个段 1001‑2000 在网络中丢失了。

1. 接收方不断催促"我要 1001"

  • 主机 A 发送:1‑1000、1001‑2000(丢失)、2001‑3000、3001‑4000、4001‑5000、5001‑6000、6001‑7000。
  • 主机 B 收到 1‑1000,回复 ACK=1001(期望下一字节是 1001)。
  • 随后 B 陆续收到 2001‑3000、3001‑4000...... 但由于 1001‑2000 缺口存在,这些段无法累积确认,B 只能一次又一次 地回复 ACK=1001
  • 于是主机 A 会接连收到多个完全相同的 ACK。

2. 发送方果断重传

当主机 A 连续收到 3 个重复的 ACK=1001 时,它立即意识到 1001‑2000 已丢失,不等计时器超时,直接重传该段。

3. 缺失补齐,ACK 一次跳跃

主机 B 终于收到重传的 1001‑2000,它检查缓冲区发现:

  • 缺口之前:1‑1000 已确认。
  • 缺口本身:1001‑2000 刚到位。
  • 缺口之后:2001~7000 早已在缓冲区中排队。

现在从 1 到 7000 的所有字节都已连续收到,于是 B 回应一个 ACK=7001,表示"下一个期望的是第 7001 字节"。原本一直在喊"1001",补上缺失的一块后,确认号一下子就跳到了 7001。
接收方(主机B) 发送方(主机A) 接收方(主机B) 发送方(主机A) 滑动窗口批量发送,段1001-2000丢失 累计收到3个重复ACK=1001 触发快速重传 收到缺失段,缓冲区已有1~7000连续数据 继续正常传输7001之后的数据 Seq=1, Len=1000 (1~1000) Seq=1001, Len=1000 (丢失) Seq=2001, Len=1000 Seq=3001, Len=1000 Seq=4001, Len=1000 Seq=5001, Len=1000 Seq=6001, Len=1000 ACK=1001 (期望1001) ACK=1001 (重复,因收到2001-3000) ACK=1001 (重复,因收到3001-4000) ACK=1001 (重复,因收到4001-5000) 重传 Seq=1001, Len=1000 ACK=7001 (累积确认向前跳跃)

为什么重传成功后 ACK 直接变成 7001?

这正是 TCP 累积确认的威力。

  • 在 1001‑2000 补上之前,接收方虽然已经正确收到了 2001~7000,但因为数据流中间有个空洞,协议不允许确认不连续的部分,所以 ACK 只能停留在 1001。
  • 一旦缺失的段被填充,接收方就能把 1 到 7000 的所有数据一次性"拼图成功",期望序号立刻跳到 7001。

这种设计既保证了可靠性,又能通过一次 ACK 确认一大批数据,非常高效。

超时重传和快速重传机制的分工⭐

快速重传并非要取代超时重传,它们有着明确的分工。下面的流程图展示了发送方的完整决策逻辑:

否/超时




发送数据段
收到 ACK?
正常更新窗口
重传计时器超时?
超时重传
收到 3 个重复 ACK?
快速重传丢失段
继续等待 ACK 或更多重复 ACK

再将两者特点浓缩为一表:

机制 核心思想 依赖条件 效率
超时重传 等待定时器到期后重传 任何场景均可兜底 较低,但绝对可靠
快速重传 通过重复 ACK 提前触发重传 需要滑动窗口批量发送,产生 ≥3 个重复 ACK 高,能快速恢复
  • 两者相辅相成:实际通信中,优先由快速重传修复大部分丢包(当前网络几乎总是批量传输),极少数无法产生重复 ACK 的场景(例如窗口末尾丢包)再由超时重传来兜底。
  • 深刻理解这两大重传机制,是掌握 TCP 拥塞控制(慢启动、快重传/快恢复)必不可少的基础。

窗口动态调整

窗口越大,一次批量发送的数据越多,网络利用率越高。但窗口不能无限大,原因有二:

  • 可靠性风险:窗口过大,一旦发生丢包,需要重传的数据量也大,可能加剧网络拥堵。
  • 接收方缓存限制:接收方通告的"接收窗口"是发送窗口的上限,不能超出。

因此,实际发送窗口 = min( 接收方窗口 , 拥塞窗口 )

其中拥塞窗口由发送方根据网络状况动态调整:收到新的 ACK 时逐步增大(慢启动、拥塞避免),检测到丢包时迅速减小(快重传、快恢复)。

滑动窗口无法消除可靠性带来的开销 (重传、排序、连接管理等),因此 TCP 的效率不可能超过无连接的 UDP。UDP 只管发送,不关心是否到达,速度最快;TCP 的滑动窗口只是让可靠传输的折损变得更小。
效率对比
引入滑动窗口后
UDP

无可靠性,效率最高
TCP停-等

可靠性高,效率极低
TCP滑动窗口

可靠性高,效率较高

相关推荐
MrSYJ3 天前
TCP协议理解
后端·tcp/ip
两个人的幸福10 天前
Windows 桌面应用自研 PHP 队列(下):完整代码与六大工程化优化
php
BingoGo12 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
JaguarJack12 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
用户30745969820713 天前
PHP 扩展——从入门到理解
php
鹏仔先生14 天前
拷贝漫画APP下载页PHP程序,后台带免费AI写作
php
云水一下14 天前
从零开始学 PHP 系列(一):PHP 的前世今生与开发环境搭建
开发语言·php
treesforest14 天前
AI安全系统如何识别异常访问?IP风险识别正在成为关键能力
网络·人工智能·tcp/ip·安全·web安全
xingpanvip14 天前
星盘接口开发文档:本命盘接口指南
android·开发语言·css·php·lua
江华森14 天前
TCP/IP 协议栈实战 — 7 个实验详解
网络·tcp/ip·智能路由器