TCP在公网上传输数据,往往可能去丢失数据,而TCP为了保证传输的稳定性,就需要一些机制来维持可靠性,也就是大量的重传的策略,其中包含了大量的算法

TCP在实际发送中,每发出一个包,服务器端都会有一个对应的回复,如果一定时间内没有收到回复,就会重新发送这个包,直到有回复的存在

那么整个来回的过程如何呢?是上一个收到了应答,再发送下一个?这种模式是不是像两个人发微信,一人发一句?是不是有点慢?能不能这样,双方都有一个记录,各自记录自己发的,和自己接受的,然后可以一边做事情的同时,以便交代事情

每当服务器端忙完一件事,就在本子上划掉一件事,然后统一一个时间去回复做完的事情,如果客户端发现一段时间都没有回复,就去询问一下,咋样了,既然可以多个事情去处理,那么就需要给每个事情一个编号

那么我们这个具体的协议实现在TCP中就是

我们为了保证顺序性,每个包都有一个ID,在建立连接的时候,会商定起始的ID,然后按照每一个ID一个个的发送,为了保证不丢包,对于每个包都需要应答,而且应答并非一个个的发送,而是会应答之前的一个ID,表示之前的收到了,这种模式称为累计确认或者累计应答

为了记录所有发送的包和接受的包,TCP也需要发送端和接受端分别都有人记录ID,发送端的缓存都按照包的ID一个个排列,根据处理的情况分为四个部分

1.发送了并且已经确认的,这就是交代下属的,并且做完了的,应该划掉的

2.发送了并且没有确认,这部分是你交代服务端的,还没有做完,做完了才能划掉

3,没有发送,但是等待发送的,是马上要发送的

4.没有发送,而且暂时不会发送的

为什么要区分3和4两种的,这就是我们之前说的,流量控制,把握分寸,作为一个上层,要知道下层的处理能力,部署多了任务可能导致人家做不完

那么如何知道一个下端处理的能力呢?在TCP中,接收端会给发送端上报一个窗口的大小,叫做Advertised window,这窗口的大小应该等于上面的第二部分和第三部分的这整合,这就是没做完的加上马上要交代的,超过这个窗口的就是第四部分了,于是发送端要维护下面的数据结构

图片

LastByteAcked:第一部分和第二部分的分界线

LastByteSent:第二部分和第三部分的分界线

LastByteAck+AdvertisedWindow 第三个部分和第四部分的分界线

对于接收端,他缓存的内容要简单一些

图片

第一部分:接受并且确认过的,也就是我领导交代给我的,并且我做完的

第二部分:还没接受,但是马上就能接受的,也就是能够接受的最大工作量的

第三部分:还没接受,也没法接受的,超过工作量的部分

MaxRcvBuffer:最大缓存的数量

LastByteRead:已经接受了的,但是没有被应用去读取的

NextByteExpected:第一部分和第二部分的分界线

第二部分的窗口有多大的

NextByteExpected和LastByteRead的差就是被应用层读取的占用的MaxRcvBuffer的量,定义为A

那么窗口的整体大小就是MaxRecBuffer减去A

就是 AdvertisedWindow = MaxRcvBuffer – ((NextByteExpected -1 ) – LastButeRead)

第二部分和第三部分的分界线在哪里,NextByteExpected 和 AdvertisedWindow就是两个部分和第三部分的分界线,其实就是LastByteRead加上MaxRcvBuffer

顺序问题和丢包问题

假如我们看 123 已经确认发送了 456789都是发送了还没确认的, 10 11 12都是还没发送的, 13 14 15 都是接受方没有空间的,不准备发得

接收端来看 12345已经完成了ACK了,但是没有去读的,67就是等待接受的, 89 已经接受了,但是还没发ACK的

根据这个例子,我们知道了,丢包可以在任何地方发生

那么我们假设4 的确认到了,但是5的ACK丢了 6 7 的数据包丢了,怎么办呢?

一种方法就是超时的重试,对于已经发送了,但是没有ACK的包,都会设置一个定时器,超过了一定的时间,都会重新尝试,如何判定重试的时间的,太短的话,重试的时间短于了RTT,不然会引起不必要的重传,也不宜很长,不让访问就慢了

这样往返的时间,需要TCP通过采用RTT的时间,去加权平均,算出一个值,这个值不断的变化,由于重传的时间是不断变化的,称为自适应的重传算法

就是加入,5 6 7 都超时了,就会重新的发送,接收方发现5已经发送的,于是丢弃了6, 6 已经,发送了ACK,而到了7了呢,7在此超时了,再次需要重传了,TCP 的策略的超时间隔加倍,说明每次遇到一次超时,超时时间间隔都会翻倍

但是如果有特定的问题,不要每次都翻倍呢?

有一个快速的重传的协议,在接收方接受一个序号大于了下一个期望的报文段的时候,就会检测数据流的一个间隔,于是发送了冗余的ACK,仍然ACK的期望接受的接收端,当客户端接受到了三个冗余的ACK,就会在定时器过期之前,重传丢失的报文段

就是好比6收到了 8也收到了,发现7没来,肯定是丢了,于是发送6的ACK,表示需要7的包,而且一口气发送三个,让客户端直接去重发

或者使用SACK方法,就是在TCP中加入一个SACK的东西,这种方式就是在TCP头加一个SACK的东西,将缓存的地图发给发送方,例如发送SACK6,SACK8,SACK9

流量控制问题

对于包的确认,同时携带一个窗口的大小

我们首先假设窗口不变的情况,窗口始终未9 4的确认来了,就会往右移动一个,这就是第13个包可以发送了

图片

假设发送端发送的过猛,将 10 11 12 13全部发送了,之后就停止发送了,导致未发送可发送为0

图片

这样,在五个包到达确认了之后,才能多一格去让未发送填上

图片

但是如果接受的太慢了,导致缓存中没有空间了,只能修改窗口的大小,让其终止发送

我们假设一个极端情况,接收端的应用一直不读取缓存中的数据,在数据包6确认后,窗口大小就会缩小1,变为8

图片

图片

然后如果一直确认了,但是没有处理数据,随着确认的包数量增加,导致窗口越来越小,直到0

图片

这样的话,发送方会定义的发送窗口探测的数据包,来调整窗口的大小,当接收方比较慢的时候,为了防止低能窗口综合症,我们就告诉发送方,窗口别发了,等到一定的大小,或者缓冲区为一半为空了,才更新窗口

拥塞控制问题

拥塞控制,就是通过窗口的大小来控制的,前面的滑动窗口rwnd是怕发送方吧接收方缓存的塞满

而阻塞窗口cwnd,就是怕把网络塞满

我们有一个共识 LastByteSent-LastByteAcked <= min(cwnd,rwnd) 发送速度由阻塞窗口和滑动窗口共同控制

发送方如何判断网络是不是慢的呢?其实就是一个挺难的,就好比往一个水管里灌水,灌到了一定程度,才能探测出水管大小

水管的大小可以比喻为 水管粗细*水管长度, 就是带宽*往返延迟

图片

加入传递一个包的时间是8S,去4S,回4S,每次发送一个包,每个包1024byte,过去了8秒,8个包都发出去了,前面4个包都已经到了接收到了,但是ACK还没有返回,不能算发送成功,5-8包在路上,还没接收到,这样管道塞满了,但是每秒发送1个包,返回时候是8S

我们基础上调大窗口,使得单位时间更多的包可以发送了,如果传递的设备上加上缓存,可以传递更多的包,但是处理不来的都在队列上排着,这样包就不会丢失,但是会增加延迟,而且超过了时延时间,就会超时重传了

而且如果发多了包,而且没有缓存,设备会直接将包丢掉

TCP为了避免包的丢失和超时重传,进行了控制

就是慢启动

一个TCP刚开始的时候,cwnd设置一个报文段,一次只能发送一个,收到这一个确认的时候,cwnd,一次能够发送两个,两个确认就能发四个,这时候是指数型的增长

涨到什么时候是个头呢?就是ssthresh为65535个字节,当超过了,就需要小心一点了,这就需要慢一点了

每当收到一个确认的时候,cwnd就会增加1/cwnd,加入我们一次发送8个,当8个确认到达的时候,每个确认增加1/8,八个确认增加1,于是一次发送九个

但是,线性增长还是增长,越来越多的时候,直到超过了,出现了阻塞,这时候就会降低倒水的速度

我们将sshreash设置了cwnd/2,然后将cwnd设置为1,重新开始来过

当然,也有快速重传算法,接收端发现丢了一个中间包的时候,连续发送三次一个包的ACK,让其快速重发,而且,TCP认为这种情况不严重,所以只会讲cwnd减为cwnd/2,将cwnd设置为1

然后sshthresh = cwnd,当三个包返回的时候,cwnd= sshthresh +3

图片

但是,这两个问题并不是单纯的两个情况

第一个问题,丢包并不代表着通道慢的,可能是公网上带宽本来不满也会丢包

第二个问题,就是TCP就是中间设备都填满了,在填,才发生了丢包,如果能够在填满就不填了,就好了

那么优化这个问题,后来又了TCP BBR堵塞算法,企图找到一个平衡点,通过不断的加快发送速度,将管道填满,但是不填满中间设备的缓存,时延增加,但是能够找到更好的平衡点

图片

课后总结:

顺序问题和丢包问题,流量控制都是通过滑动窗口解决的,而且有对应的序号,工作完了别忘了返回

拥塞控制是通过拥塞窗口来解决的,相当于往管子倒水,快了容易溢出,慢了容易浪费带宽

课后思考

1.TCP的BBR很牛逼,但是如何做到的呢?

2.学会了UDP和TCP,如何基于这两种协议写程序呢?

设备的缓存是会造成传输延迟的

对的,如果经过设备的包不需要进入缓存,那么速度是最快的,进入了缓存并且需要等待,等待的事件就是额外的缓存,BBR就是为了避免这些问题

充分利用带宽,降低Buffer占用率

为何在降低了发送packet的速度之后,反而提速了呢?

基本的TCP拥塞算法遇到丢包的数据时候,快速下降了发送的速度,因为基本算法认为,如果出现丢包,是传输过程中设备已经满了,快速下降后进行慢启动来进行的,整个过程对于带宽是浪费的,而BBR不会认为有缓存的存在,BBR认为丢包,就是已经超过了带宽传送上限,正确的处理了数据丢包,对于网络上有一定认识,导致更加的具有智慧

BBR对于延迟的解决方案

S1:慢启动开始了,以前期的延迟时间为最小值Tmin,然后监控延迟值是否达到了Tmin的n被,达到这个阈值,判断带宽已经耗尽了并且使用了一定的缓存,进入了排空的阶段

S2阶段:指数降低发送速率,知道延迟不再降低

S3:协议进入了稳定运行状态,交替的探测带宽和延迟

发表评论

邮箱地址不会被公开。 必填项已用*标注