当前位置: 首页 > news >正文

TCP中的流量控制

作用

核心目标:防止发送方发送数据过快、过多,导致接收方的缓冲区溢出

可以把它想象成一个对话:

  • 发送方:一个语速很快的演讲者。

  • 接收方:一个记笔记的人,但他的手边只有一个大小有限的笔记本(接收缓冲区)。

  • 流量控制就是接收方不断地告诉演讲者:“慢一点,我的笔记本快写不下了!”或者“好了,现在可以继续讲了。”

如果没有流量控制,接收方的缓冲区一旦被填满,后续到达的数据包就会被丢弃,导致大量的重传,浪费网络资源。

关键点:流量控制解决的是点对点(发送方和接收方之间)的速度不匹配问题,它与解决网络路径拥堵的拥塞控制是两个不同的概念,但通常会协同工作。

实现流量控制的核心机制:滑动窗口

TCP 的流量控制是通过一个称为 “滑动窗口” 的机制来实现的。这个窗口的大小决定了发送方在未收到确认的情况下,最多能发送多少字节的数据。

核心思想:一种“管道管理”技术

想象一下,发送方要向接收方通过一根管道送水(数据)。如果每送一桶就停下来等一个回执(ACK),效率太低了。滑动窗口允许发送方连续发送多桶水,形成一个“在途的列车”,从而填满管道,提高效率。

但关键是:这个“列车”不能太长,否则会溢出接收方的“蓄水池”(接收缓冲区)。
当然理解这个机制的话,我们先需要先解几个关键字段和概念:

1.TCP 段首部相关字段:

  • 序列号:发送方为每个字节数据分配的唯一编号。

  • 确认号:接收方期望收到的下一个字节的序列号。意味着确认号之前的所有数据都已被正确接收。

  • 窗口大小:接收方在 TCP 段首部中通告的一个字段,告诉发送方自己当前还有多少可用的接收缓冲区空间。这就是流量控制的直接指挥棒

我们把发送方要发送的数据想象成一个长长的字节流。滑动窗口就是这个字节流上的一个“窗口”,它规定了哪些数据是允许被发送的。这个窗口由三个指针界定,并分为四个部分。

我们用一个具体的例子来图解。假设:

  • 字节流序列号从 1 开始。

  • 当前发送方最后未确认的字节是 SND.UNA = 1。

  • 接收方通告的窗口大小 RWND = 10。

  • 下一个要发送的字节是 SND.NXT = 4。

下图展示了此刻发送方的视角:

    ... 已发送并已确认 ... | 已发送但未确认 | 允许发送但尚未发送 | 禁止发送 ...
发送字节流: [ ... 1, 2, 3 | 4, 5, 6 | 7, 8, 9, 10 | 11, 12, 13, 14, 15, ... ]^         ^               ^                 ^SND.UNA   SND.NXT      SND.UNA + RWND      (后续数据)|         |                 ||         |                 |窗口左边界  下一个发送位置     窗口右边界<------------------------->发送窗口 (Size = RWND = 10)<-----> | <--------------->已发送待ACK     可用窗口(Size=3)     (Size=7)

我们来详细解释图中的每一个部分和指针:

  1. 三个关键指针
  • SND.UNA(Send Unacknowledged):窗口左边界。指向序列号最小的、已发送但还未收到确认的字节。在这个例子中是 1。所有在它左边的数据(序列号更小)都已经被对方确认接收了。

  • SND.NXT(Send Next):下一个要发送的字节。指向即将要发送的第一个字节。在这个例子中是 4。

  • SND.UNA + RWND:窗口右边界。由 SND.UNA 加上接收方通告的窗口大小 RWND 计算得出。在这个例子中是 1 + 10 = 11。这意味着序列号为 11 及以后的数据目前禁止发送

  1. 窗口内的四个区域
    基于以上三个指针,窗口被划分为三个区域,窗口外还有一个区域:
  • 1 已发送并已确认(Sent and Acknowledged)

    • SND.UNA 左边的所有数据。例如序列号 … 1, 2, 3 之前的数据。发送方可以安心地将这些数据从缓存中清除。
  • 2 已发送但未确认(Sent But Not Yet Acknowledged)

    • 位于 [SND.UNA, SND.NXT) 之间。在这个例子中是 [4, 5, 6]。发送方已经发出这些数据,但还在等待接收方的ACK。这些数据必须被保留在缓存中,因为可能需要重传。
  • 3 允许发送但尚未发送(Available to Send)

    • 位于 [SND.NXT, SND.UNA + RWND) 之间。在这个例子中是 [7, 8, 9, 10]。这是发送方立即就可以发送的数据,不需要等待任何ACK。这部分也称为 可用窗口
  • 4 禁止发送(Not Available to Send)

    • SND.UNA + RWND 右边的所有数据。在这个例子中是 11 及以后。发送方必须等待窗口滑动后,才能发送这些数据。

“滑动”是如何发生的?

“滑动”的本质是窗口边界的移动,它由两个事件触发:

  • 收到新的ACK(推动左边界)

  • 收到新的窗口通告(更新右边界)

让我们延续上面的例子,看看接下来会发生什么。

步骤 1:收到ACK,左边界滑动

假设发送方收到了一个ACK包,其中:

  • 确认号 = 7

  • 新通告窗口 RWND = 8

这意味着接收方成功收到了序列号 1 到 6 的所有数据,并期望下一个收到序列号为 7 的数据。同时,接收方通知发送方,它的可用缓冲区现在只有 8 个字节了。

发送方如何处理?

  • 移动左边界 SND.UNA:从 1 滑动到 7。

  • 更新右边界:新的右边界 = 新的 SND.UNA + 新的 RWND = 7 + 8 = 15。

  • 更新 SND.NXT:它原本指向 4,但现在数据 4,5,6 已被确认,所以它仍然指向下一个要发送的字节(可能是 10 之后,取决于期间是否发送了数据)。

滑动后的状态如下图所示:

    ... 已发送并已确认 ...          |已发送但未确认| 允许发送但尚未发送 | 禁止发送 ...
发送字节流: [ ... 1, 2, 3, 4, 5, 6 | 7, 8, 9 | 10, 11, 12, 13, 14 | 15, 16, ... ]^         ^                    ^SND.UNA   SND.NXT          新窗口右边界|         |                    ||         |                    |新窗口左边界  下一个发送位置        (SND.UNA+RWND=15)<------------------------>新发送窗口 (Size = RWND = 8)<-----> | <------------->已发送待ACK   可用窗口(Size=3)    (Size=5)

你看到了什么?

  • 窗口整体向右“滑动”了

  • 原本在“禁止发送”区域的数据 [11, 12, 13, 14] 现在进入了“可用窗口”,可以被发送了。

  • 同时,因为接收方通告的新窗口变小了(从10变为8),窗口的宽度(大小)也收缩了。

步骤 2:发送数据,移动 SND.NXT

发送方现在可以利用新的可用窗口 [10, 11, 12, 13, 14] 来发送数据。比如,它连续发送了数据 10, 11, 12。

此时,SND.NXT 指针会从 10 移动到 13。

    ... 已发送并已确认 ...         | 已发送但未确认         | 允许发送但尚未发送 | 禁止发送 ...
发送字节流: [ ... 1, 2, 3, 4, 5, 6 | 7, 8, 9, 10, 11, 12 | 13, 14          | 15, 16, ... ]^                    ^                 ^SND.UNA            SND.NXT            窗口右边界<------------------------>发送窗口 (Size = 8)<------------------> | <->已发送待ACK        可用窗口(Size=6)        (Size=2)

此时,可用窗口只剩下 2 个字节(13, 14)了。

小结

  • 窗口的本质:一个在发送方字节流上动态变化的许可区域,规定了“能发什么”。

  • “滑动”的含义

    • 左边界滑动:由ACK驱动,表示数据被成功接收,发送方可以清理缓存并向前推进。

    • 右边界滑动:由接收方的窗口通告驱动,表示接收方处理能力的变化,从而控制发送方的上限。

    • 这个左右边界独立或协同移动的过程,就是“滑动”。

  • 流量控制的实现:接收方通过减小通告窗口 RWND,来迫使发送方收缩其窗口的右边界,从而降低发送速率。当接收方缓冲区快满时,它甚至可以将 RWND 设为 0,此时发送方的可用窗口为 0,完全停止发送。

三、流量控制的工作流程

整个过程是一个动态的、持续反馈的循环:

1.连接建立:

  • 在 TCP 三次握手时,双方会互相通告自己的初始接收窗口大小

2.数据传输:

  • 发送方:根据当前的 SND.WND(来自接收方的通告窗口)来发送数据。它不能发送超过 SND.UNA + SND.WND 的数据。

  • 接收方:接收到数据后,将其放入接收缓冲区。然后,应用程序会从缓冲区中读取数据,释放出空间。

  • 接收方发送 ACK

    • 接收方处理完数据后,会向发送方发送一个确认段(ACK)。

    • 这个 ACK 包中包含两个关键信息:

      • 确认号:RCV.NXT,告诉发送方我已经成功收到哪些数据。

      • 窗口大小:更新后的 RCV.WND,告诉发送方我现在还有多少空闲缓冲区。

3.发送方处理 ACK 并更新窗口:

  • 收到 ACK 后,发送方知道 ACK号 之前的数据都已被成功接收,于是将发送窗口的左边界 SND.UNA 向右滑动到 ACK号 的位置。

  • 同时,发送方用 ACK 段中携带的新窗口大小来更新自己的 SND.WND,从而确定窗口的右边界

  • 这个“左边界向右滑动,右边界根据新窗口扩展”的过程,就是**“滑动窗口”**名字的由来。窗口像是一个在字节流上向右滑动的框,框的大小还可以动态变化。

举例说明:

假设:

  • 初始序列号为 0。

  • 接收方初始通告窗口 RWND = 4000 字节。

  • 发送方每次发送 1000 字节的数据。

  • 接收方缓冲区总大小为 5000 字节。

步骤发送方动作接收方状态 (缓冲区已用/剩余)接收方通告窗口 (RWND)发送方窗口变化
1发送 seq=0-9991000/40004000窗口 [0, 4000)
2发送 seq=1000-19992000/30003000窗口 [0, 3000) 注意右边界左移
3收到 ACK=1000应用程序读取了500字节 (1500/3500)3500窗口左边界滑到1000,窗口变为 [1000, 4500)
4发送 seq=2000-29992500/25002500窗口 [1000, 3500)
5收到 ACK=2000应用程序又读取了1500字节 (1000/4000)4000窗口左边界滑到2000,窗口变为 [2000, 6000)

关键观察

  • 在步骤2,发送方虽然还没收到ACK,但收到了一个更小的窗口通告(3000),它立即收缩了自己的发送窗口右边界,这就是流量控制的作用。

  • 在步骤3,发送方收到ACK,左边界滑动,同时根据新的窗口大小(3500)更新了右边界,窗口得以“滑动”并“扩大”。

特殊情况和解决方案:零窗口与死锁

如果接收方的缓冲区满了,应用程序没有及时读取数据,会发生什么?

  • 零窗口:接收方会向发送方通告一个 窗口大小 = 0

  • 发送方动作:当发送方收到零窗口通告后,它会停止发送数据

  • 潜在死锁:如果之后接收方缓冲区有空闲了(应用程序读取了数据),它会通告一个新的非零窗口。但如果这个非零窗口的 ACK 包在网络中丢失了,发送方会一直等待,接收方也在等待数据,这就形成了死锁。

解决方案:持续计时器

为了解决零窗口死锁,TCP 设计了持续计时器。

  • 当发送方因为零窗口而停止发送时,它会启动一个持续计时器。

  • 计时器超时后,发送方会发送一个很小的探测段(通常只有1字节数据)。

  • 这个探测段有两个作用:

    • 提醒接收方重新通告窗口大小。

    • 本身可能被 ACK,而 ACK 中会携带当前的窗口大小。

  • 如果窗口仍然为0,发送方会重置持续计时器,重复此过程;如果窗口打开了,数据传输就可以恢复。

流量控制与拥塞控制的区别与协作

这是一个非常重要的概念区分:
在这里插入图片描述
协作关系:
发送方实际能发送的数据量,受制于以下两者的最小值
(实际发送窗口 = min(接收方通告窗口, 发送方拥塞窗口))

  • 即使接收方说“你还可以发 10000 字节”,如果网络很拥堵(拥塞窗口只有 1000 字节),发送方也最多只能发 1000 字节。

  • 反之,即使网络很通畅(拥塞窗口很大),如果接收方说“我只能收 1000 字节”,发送方也不能发更多。

小结

TCP 流量控制是一个精巧的端到端的反馈机制,它通过:

  • 接收方在 ACK 中通告其接收窗口来指导发送方的行为。

  • 发送方根据这个窗口维护一个滑动窗口,确保发送的数据不会超出接收方的处理能力。

  • 通过 持续计时器 等机制处理零窗口死锁的特殊情况。

  • 拥塞控制协同工作,共同保证了 TCP 连接的可靠性和对网络资源的公平性。

正是这种细致入微的设计,使得 TCP 能够在复杂多变的网络环境中成为一个稳定可靠的传输协议。

http://www.dtcms.com/a/465109.html

相关文章:

  • 专业建站推广网络公司网站建设和维护实训
  • AMD发布专为工业计算与自动化平台打造的锐龙嵌入式9000系列处理器
  • 短视频矩阵系统哪个好用?2025最新评测与推荐|小麦矩阵系统
  • 代理IP+账号矩阵:Cliproxy与TGX Account如何赋能品牌全球化表达?
  • 张量、向量与矩阵:多维世界的数据密码
  • 前端框架深度解析:Angular 从架构到实战,掌握企业级开发标准
  • 廊坊做网站教程泉州网站建设技术支持
  • 安全月报 | 傲盾DDoS攻击防御2025年9月简报
  • 有哪些做品牌特卖的网站做网页专题 应该关注哪些网站
  • 探索MySQL8.0隐藏特性窗口函数如何提升数据分析效率
  • 对于生物样本库的温湿度监控是如何实现对数据进行历史数据分析的呢?
  • 深入解析 Amazon Athena:云上高效数据分析的关键引擎
  • [SQL]如何使用窗口函数提升数据分析效率实战案例解析
  • Centos 7 | 定时运行 gzip 进程导致 CPU 过高,但无法确定系统自动运行 gzip 的原因 排查思路
  • Python爬虫实战:获取证监会外国投资机构信息及数据分析
  • seo网站推广费用装饰公司看的设计网站
  • 全栈开发杂谈————JAVA微服务全套技术栈详解
  • 微服务——SpringBoot使用归纳——Spring Boot中使用拦截器——拦截器的快速使用
  • 仿小红书短视频APP源码:Java微服务版支持小程序编译的技术解析
  • 免费行情网站app斗印wordpress增加内存分配给php
  • mysql高可用架构之MHA部署(一)(保姆级)
  • MySQL索引优化实战从慢查询到高性能的解决方案
  • 力扣每日一题(二)任务安排问题 + 区间变换问题 + 排列组合数学推式子
  • LeetCode-33.搜索旋转排序数组-二分查找
  • R语言基础入门详细教程
  • 用wordpress建立学校网站吗人工智能教育培训机构排名
  • 网站及其建设的心得体会wordpress能做大站吗
  • Java SpringMVC(二) --- 响应,综合性练习
  • 【保姆级教程】VMware Workstation Pro 17安装及基础使用
  • 网站开发源代码mvc电子商务网站建设与管理实训报告