滑动窗口和拥塞窗口
(1)窗口分为滑动窗口和拥塞窗口
- 滑动窗口是接受数据端使用的窗口大小,用来告知发送端接收端的缓存大小,以此可以控制发送端发送数据的大小,从而达到流量控制的目的。
- 拥塞窗口是数据的发送端,拥塞窗口不代表缓存,拥塞窗口指某一源端数据流在一个RTT内可以最多发送数据包
(2)实现一个靠谱的协议:TCP 协议使用的也是同样的模式。为了保证顺序性,每一个包都有一个 ID。在建立连接的时候,会商定起始的 ID 是什么,然后按照 ID 一个个发送。为了保证不丢包,对于发送的包都要进行应答,但是这个应答也不是一个一个来的,而是会应答某个之前的 ID,表示都收到了,这种模式称为累计确认或者累计应答(cumulative acknowledgment),目的是节省时间,提高效率!
ACK:通常被理解为收到数据后给出的一个确认ACK
- 一是期望接收到的下一字节的序号n,该n代表接收方已经接收到了前n-1字节数据
- 二是当前的窗口大小m,假定当前发送方已发送到第x字节,则可以发送的字节数就是y=m-(x-n).这就是滑动窗口控制流量的基本原理.
**解决的问题:**发送端的缓存里是按照包的ID一个个排列的。 因为TCP协议是全双工通信协议。 所以客户端既是发送端,也是接收端。 服务器端既是接收端,也是发送端。 所以客户端既有发送端缓存,也有接收端缓存。 而服务器端既有接收端缓存,也有发送端缓存。
滑动窗口包含:已发送未反馈+准备发送但未发送的数据。
发送端:
发送端缓存会被分为4部分:
- 第一部分,已经发送并且已经确认的包
- 第二部分,已经发送但是尚未确认的包
- 第三部分,尚未发送但是马上准备发送的包
- 第四部分,尚未发送但是暂时不准备发送的包。
其中:
- LastByteAcked:第一部分和第二部分的分界线
- LastByteSent:第二部分和第三部分的分界线
ex:滑动窗口大小:12-4+1=9; 已经发送到:9 希望接受的下一序号:10
则可以发送的数据=12-10+1=3
我们使用三个术语来描述窗口左右边沿的运动:
- 窗口左边沿向右边沿靠近为窗口合拢。
- 当窗口右边沿向右移动时将允许发送更多的数据,我们称之为窗口张开。
- 这种现象发生在另一端的接收进程读取已经确认的数据并释放了TCP的接收缓存时。
当接收端的缓冲区满了,发送端接收到接收端的窗口大小为0,这个时候停止发送数据,这个时候发送端会过了超时重发的时间,发送一个窗口探测的包,此数据端仅含一个字节以获取最新的窗口大小信息
接收端:
- 第一部分:接受并且确认过的
- 第二部分:还没接收,但是马上就能接收的
- 第三部分:还没接收,也没法接收的。
其中:
- MaxRcvBuffer:最大缓存的量;
- LastByteRead: 之后是已经接收了,但是还没被应用层读取的;
- NextByteExpected :是第一部分和第二部分的分界线。
联系发送端和接收端看:
(1)1、2、3 没有问题,双方达成了一致。
(2)4、5 接收方说 ACK 了,但是发送方还没收到,有可能丢了,有可能在路上。
(3)6、7、8、9 肯定都发了,但是 8、9 已经到了,但是 6、7 没到,出现了乱序,缓存着但是没办法 ACK。
根据以上状态引入几个知识点:
- 确认与重发的机制
- 发送包丢失或ACK包其中一方丢失,都会导致重传。实际场景是:假设 4 的确认到了,不幸的是,5 的 ACK 丢了,6、7 的数据包丢了。
- 超时重试:每一个发送了,但是没有 ACK 的包,都有设一个定时器,超过了一定的时间,就重新尝试。
- 重试时间:数据包重传需要一个算法去计算超时的时间,时间太短的话包还没发送到就丢了,需要重新传,时间太长的话访问会变慢,时间必须大于往返时间 RTT
- 在重传时间也就是往返的RTT的估计中引入自适应重传算法(Adaptive Retransmission Algorithm):估计往返时间,需要 TCP 通过采样 RTT 的时间,然后进行加权平均,算出一个值,而且这个值还是要不断变化的,因为网络状况不断地变化。除了采样 RTT,还要采样 RTT 的波动范围,计算出一个估计的超时时间。
- TCP对于超时的策略是:超时间隔加倍,每当遇到一次超时重传的时候,都会将下一次超时时间间隔设置为先前值的两倍,两次超时,说明网络环境差,不宜频繁反复的发送
- 快速重传的机制:当接收方收到一个序号大于下一个所期望的报文段时,就会检测到数据流中的一个间隔,于是它就会发送冗余的 ACK,仍然 ACK 的是期望接收的报文段。而当客户端收到三个冗余的 ACK 后,就会在定时器过期之前,重传丢失的报文段。
ex:
超时重传及时间:如果过一段时间,5、6、7 都超时了,就会重新发送。接收方发现 5 原来接收过,于是丢弃 5;6 收到了,发送 ACK,要求下一个是 7,7 不幸又丢了。当 7 再次超时的时候,有需要重传的时候,TCP 的策略是超时间隔加倍。每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。两次超时,就说明网络环境差,不宜频繁反复发送。
快速重传:接收方发现 6 收到了,8 也收到了,但是 7 还没来,那肯定是丢了,于是发送 6 的 ACK,要求下一个是 7。接下来,收到后续的包,仍然发送 6 的 ACK,要求下一个是 7。当客户端收到 3 个重复 ACK,就会发现 7 的确丢了,不等超时,马上重发。
区别:
- 快速重传:由接收端控制的,接收端会反复告诉发送端,我需要前面的包,中间丢了一个。
- 超时重传:发送端在超过定时器的时间间隔后没有接到已发包的ack确认,超时重发。
流量控制问题
发送端发送的每一个数据包,服务端都要给一个确认包(ACK).确认它收到了。 服务端给发送端发送的确认包(ACK包)中,同时会携带一个窗口的大小。
窗口大小 = 接收端最大缓存量 - 接收已确认但还未被应用层读取的部分
发送窗口大小和接受窗口应用层读取的速度有关
- 读取速度>窗口 窗口会不断变大
- 读取速度<窗口 窗口会不断变小,极端情况下为0
- 读取速度=窗口 窗口不变
拥塞控制问题
发送未确认<={cwnd, rwnd}
Advertised window:接收端给发送端报的窗口大小,滑动窗口包含:已发送未反馈,准备发送但未发送的数据。
rwnd:receiver window,接收方滑动窗口,用于防止接收方缓存占满
cwnd:congestion window,拥塞窗口,用于控制将带宽占完也就是怕把网络塞满
TCP 的拥塞控制主要来避免两种现象
ex:如图所示,假设往返时间为 8s,去 4s,回 4s,每秒发送一个包,每个包 1024byte。已经过去了 8s,则 8 个包都发出去了,其中前 4 个包已经到达接收端,但是 ACK 还没有返回,不能算发送成功。5-8 后四个包还在路上,还没被接收。这个时候,整个管道正好撑满,在发送端,已发送未确认的为 8 个包,正好等于带宽,也即每秒发送 1 个包,乘以来回时间 8s。
如果我们在这个基础上再调大窗口,使得单位时间内更多的包可以发送,原来发送一个包,从一端到达另一端,假设一共经过四个设备,每个设备处理一个包时间耗费 1s,所以到达另一端需要耗费 4s,如果发送的更加快速,则单位时间内,会有更多的包到达这些中间设备,这些设备还是只能每秒处理一个包的话,多出来的包就会丢失,这是我们不想看到的。这个时候,我们可以想其他的办法,例如这个四个设备本来每秒处理一个包,但是我们在这些设备上加缓存,处理不过来的在队列里面排着,这样包就不会丢失,但是缺点是会增加时延,这个缓存的包,4s 肯定到达不了接收端了,如果时延达到一定程度,就会超时重传,也是我们不想看到的。
拥塞窗口大小控制原则:
出现拥塞之后重传方案:
- 拥塞的一种表现形式是丢包,需要超时重传,这个时候,将 sshresh 设为 cwnd/2,将 cwnd 设为 1,重新开始慢启动。这真是一旦超时重传,马上回到解放前。但是这种方式太激进了,将一个高速的传输速度一下子停了下来,会造成网络卡顿。
- 前面我们讲过快速重传算法。当接收端发现丢了一个中间包的时候,发送三次前一个包的 ACK,于是发送端就会快速地重传,不必等待超时再重传。TCP 认为这种情况不严重,因为大部分没丢,只丢了一小部分,cwnd 减半为 cwnd/2,然后 sshthresh = cwnd,当三个包返回的时候,cwnd = sshthresh + 3,也就是没有一夜回到解放前,而是还在比较高的值,呈线性增长。
但是以上拥塞控制存在两个问题:
- 丢包并不代表着通道满,例如公网上带宽不满也会丢包,这个时候就认为拥塞了,退缩了,其实是不对的。
- TCP 的拥塞控制要等到将中间设备都填充满了,才发生丢包,从而降低速度,这时候已经晚了。其实 TCP 只要填满管道就可以了,不应该接着填,直到连缓存也填满。
为了解决以上问题引入了TCP BBR 拥塞算法。它企图找到一个平衡点,就是通过不断地加快发送速度,将管道填满,但是不要填满中间设备的缓存,因为这样时延会增加,在这个平衡点可以很好的达到高带宽和低时延的平衡。
总结
|