【JavaSE-网络部分05】TCP 可靠性 + 高性能的三大核心机制:滑动窗口・流量控制・拥塞控制

好的老铁们,也是好久不见了,我们TCP总共有10个核心机制,上一期我们说了维持TCP可靠性的三大机制:确认应答・超时重传・连接管理。

本期我们介绍TCP的4,5,6机制:维护TCP的可靠性 + 高性能兼顾的机制:滑动窗口・流量控制・拥塞控制

可靠性 + 高性能兼顾的机制

TCP核心机制4,滑动窗口

首先我们先解释一下什么叫做窗口,他的英文单词叫做Windows,那窗口这个词,我们在这边呢就是我们把它传输的数据想象成一个窗子一样。

好比如图所示的一个阳光,阳光只有通过窗口,才能过去。而窗口外部的是过不去的。类似于就是一个区间,区间之内的数据它是能够传过去的。而区间之外的数据它是传不过去的。


如图所示我们没有引入滑动窗口时的图形

每次发一条数据,我们都会有一个ack,那么我们收到ack之后呢再发送下一条。那么在这样的一个情况下,他涉及到最核心的一个问题就是传输效率。你每次传输都有一个等ack的时间,那么你整体等ack的时间加起来它的效率是会很低的。


因此呢应对上述的问题,我们想提高我们的传输效率,我们怎么做呢?我们把我们的数据呢进行:批量进行数据发送,统一等待多份ack

之前我们是发一条等一个ack。而现在呢我们是同时发多条,再同时等多个ack。

之前我们等四个ack是四份时间,而现在呢我们等四个ack用一份时间。这样它的效率就提高了。

那我们这里边采取这种批量发送数据的方案,同时注意啊,我们是不能无限大的批量发送的。虽然批量发送的数据越多,效率确实就越高,但是批量发送的数据太多了的话,对于可靠性也是有影响的,虽然说你发的快【你批量传输一次传输那么多,我们不叫他发的多,而是叫他发的快,因为啊好比就是你的接收方他收到的数据,好像会变多,就代表你传的较快这个意思】,但你的接收方不一定能处理过来,你的通信链路也不一定能够承载得了。


因此对于上述情况此处引入这样的一个概念,对批量发送的进行限制,也就是定量的衡量,这样的概念我们称之为窗口。能够不阻塞的发送数据的最大的量,我们称之为窗口大小。


在明确了上述窗口概念之后,我们再来看"滑动窗口"------什么是"滑动"呢?

假设我批量发送四组数据,窗口大小为 4000。第一组是 1~1000,第二组是 1001~2000,第三组是 2001~3000,第四组是 3001~4000。

此时收到一个 ACK 1001,那么该如何处理?是继续等待,直到收到 ACK 4001 后再批量发送接下来的四组数据,还是收到 ACK 1001 后就立即发送一组新数据?

换句话说,是每收到一个 ACK 就补发一组(即发送四组数据,每回来一个 ACK 就发一组),还是等这四组数据的 ACK 全部返回后,再统一发送下一批四组?
这里我们选择的是:收到 ACK 1001 后立即发送一组新数据

举个例子,就像喝酒。假设我的酒量是四瓶啤酒,那么我可以连续不断地喝下四瓶,这个过程不需要等待。

这里有两种情况:

第一种,我喝完四瓶后吐了,把四瓶全都吐出去,然后再回到酒桌上继续干四瓶。

第二种,我吐的时候只吐出一瓶的量,吐完就回到酒桌上接着喝一瓶。

我肚子里的上限始终都是四瓶。第二种方式更平缓,不需要突然等很久,整个过程很丝滑。


第二种情况我们的图对应如下:

最上边序号1那个图呢,它代表窗口大小是四份,当前发送了1001~ 2000,2001~ 3000,3001 ~ 4000,4001 ~ 5000。

然后下边我们发送方收到了2001这个ack。此时呢我们这个数据收到ack就可以视为灰色了,与此同时呢,我们就发送下一组数据,5001 ~ 6000,变为白色。
当我们把前面的1001视为灰色,把后面的5001视为白色,那么此时我们直观上看起来就好像窗口往后移动了一个格子。随着时间的推移,我们会不断的收到ACK,也不断的发送新的数据。此时呢这个窗口就在往后移动。这个就是滑动。


因此呢,经过上述的描述,这就是我们所谓的滑动窗口。本质上他就是在谈一个批量传送。


滑动窗口是TCP中用来提高传输效率的机制,我们说tcp是可靠传输的,但是可靠传输需要付出效率代价,而我们的滑动窗口就是减小这样的一个代价。


那我们说过我们丢包是一种客观存在,其实在我们滑动窗口这边丢包情况的话也是有两种情况的,但是对于我们发送方来说,他无法区分这两种情况,他的做法就是都是重传。

1)ack丢了

上述如图所示,我们的六组数据丢了三组,这已经是非常高的丢包率了,那我们怎么做呢?此处我们不需要做任何处理。为什么不做任何处理呢?因为这样的原因来自于我们前面【上一篇博客提到的】对于确认序号的巧妙设计:我们说确认序号填写的是收到数据最后一个字节的序号加一,那么就是说1001之前的数据已经收到了。比如1001这个ack丢了,那么我们发送端收到了2001这个ack,这个ack的含义是2001之前的数据已经收到了,代表1~ 1000和1001~2000这两条信息的数据已经收到了。

那么类似的如果接收端返回的3001ack丢了,4001ack也丢了,我们也不用怕,发送端收到了接收端返回的ack5001,所以呢它的含义就是说5001之前的数据都已经收到了,也就是说我们包含了1~ 1000,1001~ 2000,然后等等...4001~5000,这些数据我们都收到了。

好比我们举这么一个例子,你在街上看到一个美女呢,于是你就去搭讪他,哎,说美女你有对象吗?但是那个美女回复说我已经有娃了,嘿,好,那么她说她已经有娃了,那其实呢言外之意也就包含了她已经有对象了【我们不考虑单亲哦】。因此啊,这样的栗子就是上述那种确认应答他的一个编号设计的巧妙之处。


2)数据丢了

如果我们的数据丢了,那肯定是得重传的。那你什么时候知道数据丢了呢?或者说我们如何让发送方知道数据丢了呢?

假设当我们的1001~ 2000这个数据丢了之后,接下来2001~3000这个数据到达对方,对方返回一个ack不是1001,而是3001,但是我们不是说你返回3001就代表之前的数据全收到了吗?所以这的设计方案不行。

因此我们用这样的方案:我们接收端的只要没收到1001那么每一次返回的ack就是告诉你,我要让你重传1001。

后面我们1001~ 2000重传之后呢,此时返回的ack是7001,代表我2001~7000这个范围的数据都已经传输过了,你是不需要去补上的。你只需要把前面未传的1001 ~2000的数据补上,后面没丢的数据就不必重传。这样的一个机制我们叫做快速重传

举个例子:

一直在重复一件事儿。


在这里边我们知道滑动窗口他有快速重传。而确认应答有一个超时重传。

哎,那快速重传和超时重传,他们不是冲突了吗?其实这两组机制并没有冲突这一说,因为他们两个是共同配合生于不同场景的。如果你传输的频次低(两次传输数据的间隔比较大)那么你就用确认应答中的超时重传。

那么如果你传输数据的频次高,连续不断的去写数据。那么呢我们就用滑动窗口中的快速重传。


TCP核心机制5,流量控制

我们的流量控制是顺着滑动窗口下来的。

就是因为我们前面说过,滑动窗口越大,效率越高,但是不可太大,太大了会影响效率,但这是为什么呢?

因为对方可能会接受不了。[注意,你的窗口越大,你带过去的数据越多,然后我们就代表它发的就越快因为你以前一条一条的发,那现在你一次性发那么多条,我们就代表他快啊,并不是多。]


如图所示,接收方维护了一个接收缓冲区,用于暂存已收到的数据。这些数据由谁消费呢?由应用程序通过 read 等方法进行消费。这可以理解为一个生产者-消费者模型:如果生产速度大于消费速度,缓冲区中的数据就会不断积压,直到队列满,再也无法容纳更多数据。

这里的生产速度,取决于发送方使用多大的窗口进行发送;而消费速度,则取决于应用程序中代码的具体实现。

因此,发送方需要限制自身的发送速度,不能超过接收方的处理能力,否则接收缓冲区就会被填满。一旦缓冲区满了,后续发送的数据就会被丢弃。数据一丢,发送方就不得不重传;而重传又会进一步加剧拥塞,导致更多丢包,因为缓冲区始终处于满载状态。这样一来,大量的网络带宽被消耗,但实际有效的传输却几乎没有进展。

那么,如何限制发送方的发送速度呢?接收方会根据自身的处理能力,反过来对发送方的发送速度进行约束,这种机制就叫做流量控制。

流量控制是我们基于滑动窗口的情况下去对发送方发送的数据进行一个速度的控制。

如何控制呢?我们就是根据接收方的处理效率去影响发送方的发送速度,也就是去影响发送方的滑动窗口的大小。

具体怎么做?==》定量衡量

比如:

如图所示啊,我们的接收缓冲区好比就是一个水池。我们往水池里边去蓄水,然后呢它会有一个水位线。然后呢我们就会有一个剩余空间是水位线距离水池高度的距离,那这部分我们叫做接收缓冲区剩余空间的大小,然后它会有一个情况啊,这个剩余空间大小是定量衡量的,也就是说这个东西它是会变动的,如果接收方放水速度比较快的话,水位线始终处于比较低的位置剩余空间就大,如果我们的接收方放水的速度很慢,水位线在处于比较高的位置,那么此时剩余空间就会比较少。

因此我们接收方每次收到数据之后都会计算出这样的一个剩余缓冲区的大小,然后把这个值通过ack返回给发送方。然后我们发送方就参考这个数字,设置下一轮次滑动窗口的大小。

那这样的一个数值我们填在哪呢?我们是填在tcp报头中的16位窗口大小里面。而且这样的一个值仅仅是在ack数据报中才会有效。

那么细心的老铁就会发现一个问题,这里指定的数字是16比特,那是否意味着最大就只能是64kb呢?

答:其实不是这样的,因为我们tcp报头中还有一个选项,这个属性选项中有一个窗口扩展因子,通过它呢,我们实际反馈的数这个滑动窗口数值大小=16位窗口大小<<窗口扩展因子,因此我们理论上他可以是一个非常大的数字。

那么接下来发送方收到这样的一个数字之后,发送方就会以收到的16位窗口大小这样里面的一个数值作为参考,作为下一轮次滑动窗口的窗口大小,如图所示

我发送 1~1000 的数据过去后,接收方返回 ACK 为 1001,同时通告窗口大小为 3000。这意味着发送方接下来可以发送三组数据(每组 1000)。

随后,由于接收方的应用程序处理较慢,接收缓冲区逐渐积压,因此接收方在后续 ACK 中通告的窗口逐渐减小:先降为 2000,再降为 1000,最后变为 0。当窗口大小为 0 时,就表示接收方已满,发送方需要暂停发送业务数据。

这里出现了一个关键问题:如果发送方不再发送数据,接收方也就不会返回 ACK。而一旦没有 ACK,即使后续缓冲区有了可用空间,发送方也无法得知。

为了解决这一问题,引入了窗口探测机制 。当发送方发现接收方通告的窗口为 0 时,虽然停止发送业务数据,但仍会周期性地发送一个不携带业务数据的窗口探测包。这个探测包的目的在于触发接收方返回 ACK,从而通过 ACK 获知接收缓冲区是否已释放出空间。换句话说,发送方会主动询问接收方:"现在还有没有可用空间?"如果仍然为 0,就等一段时间再问;如果有了剩余空间,接收方会在 ACK 中告知剩余窗口大小,发送方即可恢复发送数据。如图:

总结

1.使用接收缓冲区剩余空间的大小,定量的衡量接收方的处理能力。

2.通过ACK中所带的的窗口大小数值通知发送方,窗口大小是多少?

3.发送方根据收到的ack的窗口大小调整滑动窗口的大小

4.如果窗口大小为0了,此时发送方暂停发送业务数据,然后仍然会周期性的发送窗口探测报文。

这四个都是tcp内部实现的,由操作系统内核代码实现的,我们干预不了。


TCP核心机制6,拥塞控制

刚刚叫做流量控制,现在这个叫拥塞控制,拥塞控制也是跟滑动窗口是相关联的。我们说了流量控制它是依据接收方处理能力进行控制发送方的速度[就是看你水池的剩余高度]。

而这里我们的拥塞控制呢是依据传输中间的通信链路的转发能力来进行控制的:

在这样的一个情况下,我们数据的发送能力,它既会受到接收方的处理能力制约,也会受到我们中间通信链路处理能力的制约。对于我们接收方处理能力的制约,我们已经在流量控制中已经解决了,然后呢我们中间通信链路处理能力的制约,我们就在这里去解决,就是通过我们的拥塞控制。

只不过你的流量控制我们很容易根据定量来衡量的,也就是说你那个数值很容易就算出来,但是我们的拥塞控制它是针对中间链路的,它是不好衡量的,比较复杂,因为我们中间的链路它有很多个节点,每次传输的时候呢,走的路线是有可能不同的,不一样的。

那么此时我们怎么办呢?我们就做实验:

我们的核心思路是这样的,首先呢按照比较慢的速度去发送数据。

如果没有丢包,就代表我们的链路通畅,此时我们就把窗口给放大,从而用来代表加快速度。

如果出现了丢包,就代表我们的链路拥塞,此时我们就减小窗口大小,代表放慢速度。

通过这样的一个方式去尝试出合适的窗口大小,我不管你中间走的哪条路,我把所有中间这些一大坨看成一个整体,然后我再去试出窗口的合适大小。


那么根据上述的思路,我们这个活动窗口的大小它是如何变化的呢?哎,它具体是怎么变化的?我们就看一下这个经典的图:传输轮次拥塞窗口详图

首先我们看这个横坐标啊,它的是一个叫做传输轮次,它是个近似的概念。好比说你的滑动窗口所发的第一组数据,第二组数据,第三组数据等等等等。

那么此处我们为了表述和理解简单,把滑动窗口分成轮次,其实真实的情况下呢,它是连续的过程。

来那纵轴这个拥塞窗口呢就代表我们接下来呢我们滑动窗口要按照多大的一个大小去传输,拥塞窗口直接决定了发送发送的窗口大小,那纵轴的单位是多少份?一份可以代表若干个字节。

开始,那我们第0个轮次的时候,初始情况下我们是按照比较小的窗口来传输数据,它的传输速度也就是比较慢的称之为慢启动【窗口大传输速度快,窗口小传输速度慢,我们用数据的多少来衡量速度快慢】,这样做的原因是由于我们不知道当前网络环境是否畅通,为了谨慎一点呢,我们就用小的窗口来传输。

进一步,如果我们没有出现丢包,那么窗口的大小就会进一步的变大,而且它会按照指数的方式增长(每次乘2),这样做的原因是由于我们初始情况下窗口大小非常小,所以呢需要按照更快的方式尽量的把窗口大小变大,如果我们增长速度较慢,那么接下来长时间都会按照这样低的速度来传输的话,这样不太好的。

然后呢我们不是无限的去加快速度的,我们给窗口大小引入一个阈值,当窗口的大小超过阈值的时候,就不再指数增长,而是变成线性增长。这样做的原因是为了避免在某一个轮次下面窗口突然变得非常大的时候,导致出现严重的丢包。

最后 我们虽然说是线性增长它比较缓慢,但最终是增长的,而我们始终增长的话,他的速度就会很快,此时呢必然会有一个点出现丢包,如图所示,24这个点。此时到达24这个点,此时我们就得减小黄的窗口的大小,以降低速度,我们就会有两个方案:无论哪个版本,核心目的都是一样的,都是把窗口的大小给降下来。

1.旧版本:直接把它降为零,从零开始呢,重新搞一次把。指数增长的阈值降为原来的1/2,24÷2=12,然后再慢慢线性增长。就是直接回到最初的慢开始状态。从非常小的窗口进行指数增长,在线性增长。

2.新版本:我们重新回到新的阈值位置,就是丢包的位置的窗口大小24除以二就是24÷2=12,从这个位置呢继续进行线性增长,我们的省去了那个从慢开始然后指数增长的过程。


那么我们就通过熊二和团子谈恋爱的栗子来看一下新旧版本的理解

1)旧版本


2)新版本


最后:我们流量控制和拥塞控制都能够影响到发送方的发送速率(也就是窗口的大小)。

那么最终这两个衡量窗口的方式我们以谁为标准呢?我们到底听谁的呢?

我们的标准是谁的窗口小谁说了算,即我们最终发送的实际成本大小取决于流量控制和拥塞控制得到的窗口较小值的。因为啊也就是说你把你的短板给拿出来,我们较小的那个值,就是说他速度最慢的那一次我都能处理,那么我另一个速度快的,我更加能处理这个速度慢的了,而反之则是不行的,你让一个速度慢的去处理速度快的是不行的。


好啦,本期咱们就把TCP的第四、第五、第六个核心机制------滑动窗口、流量控制、拥塞控制给盘明白了!这三个机制联手,既保可靠性,又提效率,让数据传输又快又稳~下一期咱们继续聊TCP的第7、第8个核心机制:延迟应答・捎带应答,看看TCP还能怎么在细节里"薅"性能!老铁们别忘了点赞👍、关注、加收藏,咱们下期见👋~

相关推荐
OPHKVPS2 小时前
网络安全新威胁:开源AI平台CyberStrikeAI显著降低攻击门槛
大数据·网络·人工智能·安全
IT WorryFree2 小时前
Zabbix 监控多线路出口IP并通过飞书告警完整教程
tcp/ip·飞书·zabbix
Highcharts.js2 小时前
数据更新方案对比|HTTP轮询 vs WebSocket,如何为你的图表选择最佳方案
websocket·网络协议·http·数据更新·highcharts·http轮询·图表数据更新
李庆政3702 小时前
modbus协议四 rtu Over tcp & mbslave & CRC校验码计算方法
网络协议·tcp/ip·modbus·rtu over tcp
头疼的程序员2 小时前
计算机网络:自顶向下方法(第七版)第七章 学习分享(二)
网络·学习·计算机网络
会员果汁2 小时前
网络工程-常用计算
网络
云边有个稻草人2 小时前
数据库性能调优实战:从瓶颈诊断到落地优化
网络·数据库·oracle·金仓·kes
拾贰_C2 小时前
【Ubuntu | 自动联网 | 网络问题】Ubuntu无法自动联网问题
linux·网络·ubuntu
思茂信息2 小时前
基于 CST 的方向图可重构天线仿真分析
网络·人工智能·单片机·算法·重构·cst·电磁仿真