一、滑动窗口基本原理
TCP 流量控制是保证可靠传输的方法之一,所谓 “流量控制”,本质是 控制 TCP 发送数据的速率,避免 发送太快 或 发送太慢,让接收方来不及接收,或 没东西可收。具体的控制方法是通过滑动窗口机制( 滑动窗口机制还体现了 TCP 面向字节流的思路 )。
如果一下子发送所有的数据只会加大网络压力,造成丢包重试,轻则传输更慢,重则网络崩溃。因为TCP是顺序发送的,操作系统将这些数据包一批一批的发送给对方,就像一个窗口,不停地往后移动,所以,称之为TCP滑动窗口协议。
TCP 报文里有两个字节的✨窗口✨字段,专门表示滑动窗口的字节容量,也就是说 TCP 的标准窗口最大是 2 的 16 次方-1 =65535 个字节。通过滑动窗口控制流量的方法也被称为 以收定发 的原则。
另外在 TCP 的 ✨选项✨字段中还包含了一个 TCP 窗口扩大因子,option-kind 为 3,option-length 为 3个字节,option-data 取值范围0-14。窗口扩大因子用来扩大TCP窗口,可把原来 16bit 的窗口,扩大为31bit。
TCP是双工的协议,会话的双方都可以同时接收、发送数据。TCP会话的双方都各自维护一个 “发送窗口” 和一个 “接收窗口” 。其中各自的 “接收窗口” 大小取决于应用、系统、硬件的限制(TCP传输速率不能大于应用的数据处理速率)。各自的“发送窗口”则要求取决于对端通告的“接收窗口”。
1.发送窗口
对于TCP会话的发送方,任何时候在其发送缓存内的数据都可以分为4类:
- 已经发送 并 得到对端ACK的
- 👉已经发送 但 还未收到对端ACK的
- 未发送 但对端允许发送的 👈
- 未发送 且对端不允许发送
“已经发送但还未收到对端ACK的” 和 “未发送但对端允许发送的” 这两部分数据称之为发送窗口。
当收到接收方新的 ACK 对于发送窗口中 后续字节的确认是窗口滑动
2.接受窗口
对于TCP的接收方,任何时候在其发送缓存内的数据都可以分为4类。
- 已接收的
- 👉未接收准备接收 👈
- 未接收并未准备接收
(由于ACK直接由TCP协议栈回复,默认无应用延迟,不存在“已接收未回复ACK”)。
其中 “未接收准备接收” 称之为接收窗口。
发送窗口与接收窗口关系
- 最基本的传输可靠性来源于“确认重传”机制。
TCP的滑动窗口的可靠性也是建立在“确认重传”基础上的。
发送窗口 只有收到 对方 对于本段发送窗口内字节的 ACK确认,才会移动发送窗口的左边界。
接收窗口只有在前面所有的段都确认的情况下才会移动左边界。当在前面还有字节未接收但收到后面字节的情况下,窗口不会移动,并不对后续字节确认。以此确保对方会对这些数据重传。
(应用程序在需要(如内存不足)时,通过API通知TCP协议栈缩小TCP的接收窗口。然后TCP协议栈在下个段发送时包含新的窗口大小通知给对端,对方按通知的窗口来改变发送窗口,以此达到减缓发送速率的目的。)
假如对方给我确认应答序号,可接受窗口大小为0,怎么办?
答:
首先对方在确认应答时可以发送窗口大小为0,这点是没问题的。我方在收到对方窗口大小为0时,不是一直等待着,此时我方会启动一个定时器,定时发送一个试探报文给对方,试探报文没有数据的,当对方回复我方窗口大小不为 0 时继续传输数据;如果为 0 ,重新启动计时器。
这个定时器叫 持续计时器。
通常我们衡量一个网络的有效处理负荷的能力为 吞吐量,把网络中发送端输入的负荷称之为 输入负荷,理想情况下网络的输入负荷和吞吐量之间的关系是如下图的关系。
理想情况下网络的处理能力随之输入负荷的增大呈线性关系增大,但实际上 到达网络的最大处理能力时,吞吐量达到最大,此时不会随着输入负荷的增大而增大网络吞吐量。
二、网络拥塞
1.本质和征兆
网络拥塞的本质就是 对网络资源的需求大于可用资源。
从原理上讲,寻找拥塞控制的方案无非就是 减少网络资源的需求 (如 拒绝接受新的建立连接的请求)和 增大可用资源(如 业务繁忙时 增加一些链路,或者使额外的通信量从其他通路分流),使得两者能够平衡。
当前网络正朝着高速化的方向发展,这很容易出现缓存不够大而造成分组的丢失。但分组的丢失是网络发生拥塞的征兆而不是原因。
2.解决方法
目前解决网络拥塞的方法主要有四种,慢启动、拥塞避免、快重传、快恢复。
(1)慢启动
慢启动可不是慢慢或者缓慢启动喔,慢启动指的是在网络建立链接之后不立即发送最大数据段的数据,比如 TCP最大数据段是 1500 字节,慢启动会在建立连接之后第一次发送 100 个字节,收到确认后发送 200 字节,再次收到确认就发送 300 字节,依次增加直到达到最初设定的最大值。
在慢启动方案设计时,之前滑动窗口里发送窗口大小是1000,慢启动增加到 1500 字节了,这到底依谁的?
慢启动会维持一个 拥塞窗口(CWND)。每次发送出去的字节大小会是 发送窗口 和 拥塞窗口 中的较小值。 举个例子,发送窗口可以发送1500字节,但是此时拥塞窗口是1000字节,最终发送出去的字节就是1000字节。
拥塞窗口大小也是变化的。TCP 建立完成后会有一个初始化的拥塞窗口大小。初始化拥塞窗口大小为当前TCP链接使用的最大数据段大小(Maximum Segment Size,MSS)。
当发送一次数据后,在定时器过期之前收到了确认应答消息(ACK),则拥塞窗口大小变为原来的2倍,以此类推,只要在定时器过期之前能收到ACK,都会增大2倍。一直到数据分组发生丢失,这个发生丢分组的点叫做 慢启动阈值(Slow Start Threshold,SSTHRESH),初始化慢启动阈值是64KB。
到达阈值后 拥塞窗口大小 衰减为最初的大小 即 1MSSS大小,慢启动阈值衰减为原来阈值的一半。此后又是一个新的慢启动过程。不过拥塞窗口(CWND)再次到达慢启动阈值(SSTHRESH)之后,会启动拥塞避免机制。
(2)拥塞避免
拥塞避免 是在慢启动的基础上的,当慢启动的拥塞窗口第二次达到阈值的时候启动拥塞避免,这也说明都第二次时网络必然是有些问题存在了。
拥塞避免就是控制拥塞窗口增长速度。之前慢启动是拥塞窗口是以当前窗口大小的二倍速度增长的,现在网络有问题啦,不能再以这种速度增大拥塞窗口了,不然一直这样继续估计最后网络可传输的字节就是,n趋近于无限大,可传输字节无限趋近于0。这就没法玩了,就是死锁了。
于是拥塞避免在第二次慢启动到达阈值后就使拥塞窗口呈线性增长而不是指数增长。弄个图看下具体过程
(3)快重传
快重传就是一个字快,在一个TCP数据段发送出去之后会启动一个定时器,看是否接受ACK会超时。然而快重传就是解决这个只能通过定时器超时才能判断数据是否丢失的机制,达到快速判断的效果,快速判断才能早点发送已经丢失的数据。节省时间,减少网络阻塞。
什么情况下才会触发快重传机制呢?可不是随便一个数据段过来,就直接触发快重传了。
当接收端接收到不是按序到达的数据段时,接收端接到三次重复 ACK 报文即可确认该数据段丢失,此时清零计时器,触发重传数据。
这一整个流程就是所谓的快重传。快,没话说吧。**比超时重传快,根本不用等计时器超时。**整个过程如图所示。
(4)快恢复
快恢复是在快重传的基础上的。当快重传已经发送了丢失的数据后,快恢复机制被触发。
在收到第三个重复 ACK 时,把当前 拥塞窗口 值设为当前 慢启动阈值 的一半,以减轻网络负荷,然后执行前面介绍的 “拥塞避免” 算法,使 拥塞窗口 值慢慢增大,以避免再次出现网络拥塞。