Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >TCP之滑动窗口原理

TCP之滑动窗口原理

作者头像
高性能架构探索
发布于 2021-08-06 06:52:57
发布于 2021-08-06 06:52:57
5.6K00
代码可运行
举报
文章被收录于专栏:技术随笔心得技术随笔心得
运行总次数:0
代码可运行

在我们当初学习网络编程的时候,都接触过TCP,在TCP中,对于数据传输有各种策略,比如滑动窗口、拥塞窗口机制,又比如慢启动、快速恢复、拥塞避免等。通过本文,我们将了解滑动窗口在TCP中是如何使用的。

滑动窗口实现了TCP流控制。首先明确滑动窗口的范畴:

  • TCP是双工的协议,会话的双方都可以同时接收和发送数据。
  • 会话的双方都各自维护一个发送窗口和一个接收窗口。各自的接收窗口大小取决于应用、系统、硬件的限制(TCP传输速率不能大于应用的数据处理速率)。各自的发送窗口则要求取决于对端通告的接收窗口,要求相同。

滑动窗口解决的是流量控制的的问题,就是如果接收端和发送端对数据包的处理速度不同,如何让双方达成一致。接收端的缓存传输数据给应用层,但这个过程不一定是即时的,如果发送速度太快,会出现接收端数据overflow,流量控制解决的是这个问题。

发送端窗口

上图是发送端滑动窗口的简图。我们可以将数据分为4个部分:

  • 发送和已确认的字节(蓝色部分)
  • 已发送但尚未确认的字节(黄色部分)
  • 未发送的字节和接收方准备接收的字节,即在缓冲区buffer中(绿色部分)
  • 未发送且接收方未准备接收的字节,即已经在缓冲区,但是该部分数据还未被处理(灰色部分)

其中第三部分,也就是绿色部分,也称为可用窗口,因为这是发送方可以使用的窗口。

发送窗口由黄色和绿色部分组成。这些字节要么已经发送,要么可以发送。

当发送方发送21-25字节并使用可用窗口中的所有字节时,可用窗口可能为空,发送窗口保持不变(如下图)。

当发送方收到第16-19字节的 ACK 时,发送窗口向右滑动 4 个字节。更新的可用窗口可用于队列中的以下字节(如下图)。

为了便于理解,我们后续将窗口名使用简称,即:

  • SND.WND,代表发送窗口
  • SND.UNA, 代表Send Unacknowledged指针,指向发送窗口的第一个字节
  • SND.NXT, 代表Send Next指针,指向可用窗口的第一个字节

使用简写后,如下图所示:

基于这些定义,我们可以用公式表示可用的窗口大小。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
可用窗口(可用窗口)大小 = SND.UNA + SND.WND - SND.NXT

接收端窗口

接收窗口有三种:

  • 1、接收并且已经向发送端发送确认ACK
  • 2、尚未接收但允发送端发送数据,即可用接收窗口
  • 3、尚未接收且不允许发送端发送数据,即已经在缓冲区中,但还未被处理

第二种称为接收窗口,也称为RCV.WND。类似于发送窗口,指针RCV.NXT,代表Receive Next指针,指向接收窗口的第一个字节。

接收窗口不是静态的。如果服务端性能高,读取数据快,接收窗口可能会扩大。否则,它可能会缩小。

接收方通过在TCP段报头中的窗口字段中指示大小来传达其接收窗口。当发送方收到它时,这个窗口大小就成为可用窗口。

发送和接收数据需要时间。因此,接收窗口不等于特定时刻的可用窗口。

下面,为了更好的理解滑动窗口在TCP中的使用,我们将使用一个简单的例子进行模拟说明。

示例(大小不变)

我们模拟一个请求和响应,以更好地理解滑动窗口的工作原理。为了模拟起来简单,我们尽可能的简化里面的过程,比如:

  • 我们忽略最大段大小 (MSS)。MSS 因选择的网络路由而不同。
  • 使接收窗口等于可用窗口,并且在此过程中两者保持不变。

上图示例中,有10个步骤。客户端请求资源,服务器分三段响应:

  • 1、一个 50 字节的包头
  • 2、一个 80 字节的数据1
  • 3、一个 100 字节的数据2

每一方都可以同时是发送方和接收方。

我们假设客户端的发送窗口 (SND.WND) 是 300 字节,接收窗口 (RCV.WND) 是 150 字节。因此,服务器的 SND.WND 为 150 字节,RCV.WND 为 300 字节。

上图客户端的起始状态。

我们假设它之前已经从服务器接收了300个字节,所以RCV.NXT指向301。由于它还没有发送任何东西,SND.UNA和SND.NXT都指向1。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
可用窗口(可用窗口)大小 = SND.UNA + SND.WND - SND.NXT

根据这个公式,客户端的可用窗口大小为 1 + 300 - 1 = 300。

这是服务端的起始状态,镜像另一端即客户端的状态。

因为它已经发送了300个字节,所以SND.UNA和SND.NXT都指向301。

RCV.NXT指向1,因为客户端尚未发送任何请求。服务器的可用窗口是301 + 150 - 301 = 150。

现在,我们从步骤1开始:

客户端发送它的第一个100字节请求。

此刻,窗户发生了变化。

  • 这 100 个字节已发送,但尚未收到 ACK。因此,SND.NXT 向右滑动 100 个字节。
  • 其他指针保持不变。

可用窗口更改为 1 + 300 - 101 = 200。

在第 2 步,我们的焦点转移到服务器上,从服务端的角度来分析。

  • 当服务器收到请求时,RCV.NXT 向右滑动 100 个字节。
  • 然后它发送一个对于50字节的ACK回复。这 50 个字节的确认ACK已发送至发送端,即客户端,但尚未收到回复ACK,因此 SND.NXT 向右移动 50 个字节。
  • SND.UNA不动。

可用窗口大小变为301 + 150 - 351 = 100。

让我们现在继续转向客户端。

  • 当收到50字节的回复时,RCV.NXT向右移动50字节。
  • SND.UNA 在收到前一个发送的 100 个字节的 ACK 时向右滑动。
  • SND.NXT保持不变,因为客户端不发送任何数据。

可用窗口更改为101 + 300 - 101 = 300。

再次移动到服务器端。

可用窗口为 100 字节。服务器可以发送 80 字节的段。

  • SND.NXT 向右滑动 80 个字节。
  • SND.UNA 保持不变,因为上一次的50 字节尚未得到确认。
  • RCV.NXT 保持不变,因为服务器没有收到任何数据。

可用窗口更改为 301 + 150 - 431 = 20。

客户端收到数据的第一部分并立即发送ACK。

  • 当客户端接收到 80 字节的数据时,RCV.NXT 向右移动。
  • 其他部分不变。

可用窗口大小仍为300。

此时,服务器在发送 50 字节的回复时收到了第 2 步的 ACK。

  • SND.UNA 向右移动 50 个字节。
  • 其他部分保持不变。

可用窗口大小变为351 + 150 - 431 = 70。

当服务器发送数据1即80字节部分时,再次收到第4步的另一个ACK。

  • SND.UNA 向右移动 80 个字节。
  • 其他部分保持不变。

可用窗口大小变为431 + 150 - 431 = 150。

在第 8 步,服务器数据2,大小为100字节。

  • SND.NXT向右移动 100 个字节。
  • 其他部分保持不变。

可用窗口大小变为431 + 150 - 531 = 50。

继续转到客户端。

  • 当客户端收到 100 字节时,RCV.NXT 向右移动 100 字节。
  • 其他部分保持不变。

可用窗口大小保持不变。

最后,服务器收到前一个响应的 ACK。

  • SND.UNA向右移动100个字节。
  • 其他部分保持不变。

可用窗口大小变为531 + 150 - 531 = 150。

至此,对于滑动窗口不变的示例,讲解完毕,那么对于滑动窗口大小变化的呢?在TCP中又是如果实现的呢?

示例(大小变化的窗口)

在前面的示例中,我们假设发送窗口和接收窗口保持不变。这个假设本身在实际中就是不成立的,因为不存在。

两个窗口中的字节都存在于操作系统缓冲区中,可以对其进行调整。例如,当我们的应用程序没有足够快地从中读取字节时,缓冲区中的可用空间就会缩小。

我们来介绍一下这种情况下的窗口变化,看看它是如何影响可用窗口的。

我们简化了这种情况以将可用窗口集中在客户端上。在这个例子中,客户端始终是发送方,而服务器是接收方。

当服务器发送 ACK 时,它也会在其中包含更新后的窗口大小。

一开始,客户端发送第一个150字节的请求。

  • 这 150 个字节已发送,但尚未发送 ACK。
  • 可用窗口缩小到 150 字节。

发送窗口保持在300字节。

当服务器收到请求时,应用程序读取前 50 个字节,还有 100 个字节仍在缓冲区中,从接收窗口中占用 100 个字节的可用空间。因此,接收窗口缩小到 200 字节。

接下来,服务器发送带有更新的 200 字节接收窗口的 ACK。

客户端收到 ACK 并将其发送窗口大小更新为 200。

此时,可用窗口与发送窗口相同,因为所有 150 个字节都被确认。

客户端再次发送另一个 200 字节的请求,使用可用窗口中的所有可用空间。

服务器接收到 200 字节后,应用程序仍然运行缓慢,总共只读取了 70 字节,并在缓冲区中留下了 280 字节。

这会导致接收窗口再次缩小。现在,我们只剩下 20 个字节了。

在 ACK 消息中,服务器与客户端共享更新的窗口大小。

同样,客户端在收到 ACK 后将其发送窗口更新为 20 字节。可用窗口也变为 20 字节。

在这种情况下,客户端停止发送任何大于 20 字节的请求,直到它收到以下消息中的另一个窗口更新。

如果没有更多来自服务器的消息,我们会被困在 20 字节的可用窗口吗?

我们不会。为了避免这种情况,客户端的 TCP 会定期检测窗口大小。一旦释放更多空间,可用窗口就会扩大,并且可以发送更多数据。

结语

可用窗口的计算是理解TCP滑动窗口的关键。

要学习可用窗口的计算,我们需要了解 3 个指针——SND.UNA、SND.NXT 和 RCV.NXT。

假设一个永不改变的窗口大小可以帮助我们了解进度。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-08-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 高性能架构探索 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Git 系列教程(10)- 仓库别名
如果不想每次都输入完整的 Git 命令,可以通过 git config 文件来轻松地为每一个命令设置一个别名
小菠萝测试笔记
2021/05/18
3690
2.7 Git 基础 - Git 别名
在我们结束本章 Git 基础之前,正好有一个小技巧可以使你的 Git 体验更简单、容易、熟悉:别名。 我们不会在之后的章节中引用到或假定你使用过它们,但是你大概应该知道如何使用它们。
shaonbean
2019/05/26
4020
掌握这个技巧,Git命令效率提升99%!
作为娇贵的程序员,我可不想每次都得输入完整的 Git 命令,可以通过 git config 文件来轻松地为每一个命令设置一个别名。 这里有一些例子你可以试试:
JavaEdge
2020/05/27
3920
掌握这个技巧,Git命令效率提升99%!
Git 入门
Git 是一个分布式版本控制系统,用于跟踪计算机文件的变化,并协调多人合作的项目。无论是初学者还是有经验的开发者,都能从中受益匪浅。本教程将带你从基础开始,逐步了解Git的各种功能,直至掌握一些高级技巧。
井九
2024/10/12
1610
《Pro Git》 读书笔记1
git config 1.config file 有以下几种 Config file location --global use global config file --system use system config file --local use repository config file -f, --file <file> use given config file -
elson
2020/01/02
5760
git进阶 | 01-git基础操作进阶
上次写git入门教程还是2019年(Git & Github学习总结),三年期间使用最多的命令不过三条:
Mculover666
2022/05/23
5080
git进阶 | 01-git基础操作进阶
[Git] 配置 Git
在掌握了本地和远程的基本操作之后,我们来学习一些 Git 的 配置技巧,让使用更加顺畅和个性化。
DevKevin
2025/06/12
1460
[Git] 配置 Git
Git基础知识(四)
经常可以看到一些软件的版本是V1.0,V2.0这样的,Git也支持给历史的某个提交打上这样一个东西,也就是标签。
zx钟
2019/07/19
3130
Git之配置别名
配置别名 有没有经常敲错命令?比如git status?status这个单词真心不好记。 如果敲git st就表示git status那就简单多了,当然这种偷懒的办法我们是极力赞成的。 我们只需要敲一行命令,告诉Git,以后st就表示status: $ git config --global alias.st status 好了,现在敲git st看看效果。 当然还有别的命令可以简写,很多人都用co表示checkout,ci表示commit,br表示branch: $ git
兮动人
2021/06/11
9590
Git之配置别名
git常用操作,都在这里了(一)
配置git 配置Name和Email 命令格式: git config --global user.name "your name" git config --global user.email "your email address" 为了提高命令输出的可读性 输入 git config --global color.ui true 可以通过起别名缩短命令 git config --global alias.co checkout # 别名 git config --global alias.ci
阿章-python小学生
2018/05/18
1.1K0
Git 配置别名 —— 让命令变得更简单
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details/79254607
程序员徐公
2018/09/17
1.2K0
Git 配置别名 —— 让命令变得更简单
GIT的环境搭建
$ apt-get install libcurl4-gnutls-dev libexpat1-dev gettext libz-dev libssl-dev
用户2726576
2020/12/24
9590
Git Pro深入浅出(一)
什么是“版本控制”?版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。 版本控制系统(VCS)应用而生。有了它你就可以将某个文件回溯到之前的状态,甚至将整个项目都回退到过去某个时间点的状态,你可以比较文件的变化细节,查出最后是谁修改了哪个地方,从而找出导致怪异问题出现的原因,又是谁在何时报告了某个功能缺陷等等。
奋飛
2019/08/15
1.1K0
my-dot-file
oh-my-zsh sh -c "$(wget https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)" 修改SHELL为zsh sudo usermod -s /bin/zsh $(whoami) 安装fzf git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf ~/.fzf/install git alias设置 g
王云峰
2019/12/25
5610
Git最全系列教程(二)
读完本章你就能上手使用 Git 了。本章将介绍几个最基本的,也是最常用的 Git 命令,以后绝大多数时间里用到的也就是这几个命令。读完本章,你就能初始化一个新的代码仓库,做一些适当配置;开始或停止跟踪某些文件;暂存或提交某些更新。我们还会展示如何让 Git 忽略某些文件,或是名称符合特定模式的文件;如何既快且容易地撤消犯下的小错误;如何浏览项目的更新历史,查看某两次更新之间的差异;以及如何从远程仓库拉数据下来或者推数据上去。
兔云小新LM
2019/07/24
9040
Git最全系列教程(二)
一些实用装X的Git命令
IMWeb前端团队
2018/01/08
7790
Git常用命令总结
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
拓荒者
2019/09/25
5910
repo 和git的用法
repo init -u git@192.168.1.11:i700t_60501010/platform/manifest.git-b froyo_almond -m M76XXTSNCJNLYA60501010.xml
awwewwbbb
2022/04/26
9290
Git使用总结
Git(读音为/gɪt/)是一个开源的分布式版本控制系统,可以有效、高速地处理从很小到非常大的项目版本管理。
网络安全自修室
2020/07/22
7140
Git使用总结
Git 最全教程
其它大部分系统以文件变更列表的方式存储信息,这类系统(CVS、Subversion等)将它们存储的信息看作是一组基本文件和每个文件随时间逐步累积的差异 (基于差异的版本控制)。
Jasonangel
2022/10/25
1.5K0
Git 最全教程
相关推荐
Git 系列教程(10)- 仓库别名
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验