TCP的流量控制
RTT算法
在TCP超时重传机制中,Timeout的设置对于重传非常重要:
- 设长了,重发就慢,丢了老半天才重发,没有效率,性能差。
- 设短了,会导致可能并没有丢就重发。于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发。
而且,这个超时时间在不同的网络环境下不同,必须动态设置。为此,TCP引入了RTT(Round Trip Time,环回时间):一个数据包从发出去到回来的时间。这样,发送端就大约知道正常传输需要多少时间,据此计算RTO(Retransmission TimeOut,超时重传时间)。 听起来似乎很简单:在发送方发包时记下t0,收到接收方的Ack时记一个t1,于是RTT = t1 – t0。然而,这只是一个采样,不能代表网络环境的普遍情况。以下是一些RTT算法 ,具体原理就不介绍了。
RTT算法 : 经典算法,Karn / Partridge 算法,Jacobson / Karels 算法。
TCP滑动窗口
TCP使用滑动窗口(Sliding Window)做流量控制与乱序重排。乱序重排在TCP的重传机制中已经介绍,下面介绍流量控制。
TCP头里有一个字段叫Window(或Advertised Window),用于接收方通知发送方自己还有多少缓冲区可以接收数据。发送方根据接收方的处理能力来发送数据,不会导致接收方处理不过来,是谓流量控制。
TCP协议的发送缓冲区和接收缓冲区:
假设位置序号从左向右增长(常见的读、写缓冲区设计),解释一下:
- 发送方:LastByteAcked指向收到的连续最大Ack的位置;LastByteSent指向已发送的最后一个字节的位置;LastByteWritten指向上层应用已写完的最后一个字节的位置。
- 接收方:LastByteRead指向上层应用已读完的最后一个字节的位置;NextByteExpected指向收到的连续最大Seq的位置;LastByteRcvd指向已收到的最后一个字节的位置。可以看到NextByteExpected与LastByteRcvd中间有些Seq还没有到达,对应空白区。
据此在接收方计算AdvertisedWindow,在发送方计算EffectiveWindow:
- 接收方在Ack中记录自己的
AdvertisedWindow = MaxRcvBuffer – (LastByteRcvd - LastByteRead)
,随Ack回复到发送方。 - 发送方根据Ack中的AdvertisedWindow值,需保证
LastByteSent - LastByteAcked ≤ AdvertisedWindow
,则窗口内剩余可发送的数据大小EffectiveWindow = AdvertisedWindow - (LastByteSent - LastByteAcked)
,以保证接收方可以处理。
TCP的拥塞控制
通信中的拥塞指:到达通信子网中某一部分的分组数量过多,使得该部分网络来不及处理,以致引起这部分乃至整个网络性能下降的现象,严重时甚至会导致网络通信业务陷入停顿即出现死锁。
为什么要进行拥塞控制?假设网络已经出现拥塞,如果不处理拥塞,那么延时增加,出现更多丢包,触发发送方重传数据,加剧拥塞情况,继续恶性循环直至网络瘫痪。可知,拥塞控制与流量控制的适应场景和目的均不同。
TCP拥塞检测的方法
- 典型的TCP只有在断定拥塞发生的情况下,才会采取相应的行动。 推断是否出现拥塞,通常看是否有丢包情况发生。
- 其他拥塞探测方法,包括时延测量和显式拥塞通知(ECN),使得TCP能在丢包发生前检测拥塞。
rwnd与cwnd
- rwnd是用于流量控制的窗口大小,即上述流量控制中的AdvertisedWindow,主要取决于接收方的处理速度,由接收方通知发送方被动调整。
- cwnd是用于拥塞处理的窗口大小,取决于网络状况,由发送方探慢启动算法查网络主动调整。
介绍流量控制时,我们没有考虑cwnd,认为发送方的滑动窗口最大即为rwnd。实际上,需要同时考虑流量控制与拥塞处理,则发送方窗口的大小不超过min{rwnd, cwnd}。下述4种拥塞控制算法只涉及对cwnd的调整,同介绍流量控制时一样,暂且不考虑rwnd,假定滑动窗口最大为cwnd;但读者应明确rwnd、cwnd与发送方窗口大小的关系。
4种拥塞控制算法
拥塞发生前,可避免流量过快增长拖垮网络;拥塞发生时,唯一的选择就是降低流量。主要使用4种算法完成拥塞控制:
- 慢启动
- 拥塞避免
- 拥塞发生
- 快速恢复
算法1、2适用于拥塞发生前,算法3适用于拥塞发生时,算法4适用于拥塞解决后。
慢启动算法
慢启动算法(Slow Start)作用在拥塞产生之前:对于刚刚加入网络的连接,要一点一点的提速,不要妄图一步到位。如下:
- 连接刚建好,初始化cwnd = 1(当然,通常不会初始化为1,太小),表明可以传一个MSS大小的数据。
- 每收到一个ACK,cwnd++,线性增长。
- 每经过一个RTT,cwnd = cwnd * 2,指数增长(主要增长来源)。
- 还有一个ssthresh(slow start threshold),当cwnd >= ssthresh时,就会进入拥塞避免算法。
因此,如果网速很快的话,Ack返回快,RTT短,那么,这个慢启动就一点也不慢。下图说明了这个过程:
拥塞避免算法
前面说过,当cwnd >= ssthresh(通常ssthresh = 65535)时,就会进入拥塞避免算法(Congestion Avoidance):缓慢增长,小心翼翼的找到最优值。如下:
- 每收到一个Ack,cwnd = cwnd + 1/cwnd,显然,cwnd > 1时无增长。
- 每经过一个RTT,cwnd++,线性增长(主要增长来源)。
慢启动算法主要呈指数增长,粗犷型,速度快(“慢”是相对于一步到位而言的);而拥塞避免算法主要呈线性增长,精细型,速度慢,但更容易在不导致拥塞的情况下,找到网络环境的cwnd最优值。
拥塞发生时的算法
慢启动与拥塞避免算法作用在拥塞发生前,采取不同的策略增大cwnd;如果已经发生拥塞,则需要采取策略减小cwnd。那么,TCP如何判断当前网络拥塞了呢?很简单,如果发送方发现有Seq发送失败(表现为“丢包”),就认为网络拥塞了。
丢包后,有两种重传方式,对应不同的网络情况,也就对应着两种拥塞发生时的控制算法:
-
超时重传。TCP认为这种情况太糟糕,调整力度比较大:
ssthresh = cwnd /2 cwnd = 1,重新进入慢启动快速恢复算法(网络糟糕,要慢慢调整)
-
快速重传。
ssthresh = cwnd /2 cwnd = cwnd /2,进入快速恢复算法(网络没那么糟,可以快速调整,见下)
快速恢复算法
如果触发了快速重传,即发送方收到至少3次相同的Ack,那么TCP认为网络情况不那么糟,也就没必要提心吊胆的,可以适当大胆的恢复。为此设计快速恢复算法(Fast Recovery),下面介绍TCP Reno中的实现。
- cwnd = ssthresh + 3 * MSS (尝试一步到位)
- 重传重复Ack对应的Seq
- 如果再收到该重复Ack,则cwnd++,线性增长(缓慢调整)
- 如果收到了新Ack,则cwnd = ssthresh ,然后就进入了拥塞避免的算法了