本篇中先介绍一下慢启动和拥塞避免的大概过程,下一篇中将会给出多个linux下reno拥塞控制算法的wireshark示例,并详细解释慢启动和拥塞避免的过程。
一、慢启动(slow start)
一个TCP连接启动的时候并不知道cwnd应该取多大的值适合当前的网络状况,因此TCP发送方会从一个较小的初始值指数抬升cwnd到某一个值,这个cwnd抬升的过程就叫做慢启动。除了初始建立tcp连接(SYN包交换后)后的数据发送使用慢启动外,在TCP超时重传、TCP空闲一段时间后重新开始数据发送这些场景下也会触发慢启动过程。
在TCP连接的慢启动过程中,cwnd的初始值为IW(initial window)。IW的原始值为1个SMSS的大小,但是RCF5681协议允许IW按照如下设置(RFC3390中也有详细的设置原则描述和讨论)
SMSS>2190bytes时,IW=2*SMSS 且最大不能超过2个报文段
2190bytes>=SMSS>1095bytes时,IW=3*SMSS 且最大不能超过3个报文段
其他场景下,IW=4*SMSS 且最大不能超过4个报文段
为了接下来描述方便,我们取cwnd=IW=1*SMSS。其中SMSS一般为min(接收方的MSS,扣除header的path MTU)。假设没有丢包发生并且接收方每接收一个包就反馈一个ACK,则每当发送方接收到一个Good ACK的时候,发送方就会调整cwnd=cwnd+min(N,SMSS),其中N表示这个Good ACK反馈确认的接收方新收到的数据量,单位为byte。比如之前收到的最大ACK Number为1000,新收到的Good ACK中ACK Number为2000,则这个Good ACK指示接收方新收到了1000byte的数据,这种场景下N=1000bytes。当发送端以SMSS大小发送数据报文,接收方对每个报文都立即回复ACK的时候,简洁的整理以下算法流程如下
1)连接建好的开始先初始化cwnd = 1,表明可以传一个SMSS大小的数据。
2)每当收到一个ACK,cwnd++; 呈线性上升
3)每当过了一个RTT,cwnd = cwnd*2; 呈指数让升
4)还有一个慢启动门限ssthresh(slow start threshold),是一个上限,当cwnd >= ssthresh时,就会进入拥塞避免。
5)当遇到RTO超时重传时会触发cwnd和ssthresh的调整,ssthresh=max(cwnd/2, 2),cwnd=1,然后重新开始慢启动过程。
连接初始建立场景下可以设置ssthresh为一个很大的值,然后开始慢启动流程后,直到发送速率一直增大,最终触发RTO超时或者快速重传,然后根据cwnd设置ssthresh从而得到一个有效的ssthresh值。这里我们暂时只考虑RTO超时场景,后面会介绍快速重传、ECN等场景下的处理。
慢启动流程示意图如下
从上图可以看到每过一个RTT,cwnd就会增加一倍,因此TCP的慢启动是指数增长的(慢启动的"慢"是指TCP的发送端不能直接以接收方通告的awnd为窗口进行发送,而要有一个启动过程,因此叫做慢启动)。当发送端使用delay ACK时,慢启动的cwnd仍然是指数增长的,但是增长速度会稍微慢一些。因此有一些TCP实现会等TCP连接完成慢启动过程后在使用delay ACK,例如linux中在连接初始建立时候的quick ACK模式。
二、拥塞避免(congestion avoidance)
从慢启动可以看到,cwnd可以很快的增长上来,从而最大程度利用网络带宽资源,但是cwnd不能一直这样无限增长下去,一定需要某个限制。TCP使 用了一个叫慢启动门限(ssthresh)的变量,当cwnd超过该值后,慢启动过程结束,进入拥塞避免阶段。慢启动门限 ssthresh 的用法如下:
当 cwnd < ssthresh 时,使用慢开始算法。
当 cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。
当 cwnd = ssthresh 时,既可使用慢开始算法,也可使用拥塞避免算法。
拥塞避免算法的思路是让拥塞窗口 cwnd 缓慢地增大,假设cwnd=k*SMSS,当发送端收到一个包的ACK反馈的时候,按照cwnd=cwnd+(1/k)*SMSS。这样每经过一个RTT发送k个数据包的时候,cwnd增长了大约一个SMSS,通常上我们即描述为每经过一个往返时间 RTT 就把发送方的拥塞窗口 cwnd 加 1,使拥塞窗口 cwnd 按接近线性规律缓慢增长。这种增长方式叫做“加法增大”(Additive Increase)。另外当接收方使用delay ACK时,cwnd的增长仍然是接近线性的,只不过增长速度相对慢一些。总结如下
1)收到一个ACK时,cwnd = cwnd + 1/cwnd
2)当每过一个RTT时,cwnd = cwnd + 1
简单图示如下
无论在慢开始阶段还是在拥塞避免阶段,在Tahoe版本中,只要发送方判断网络出现拥塞(其根据就是触发了快速重传或者RTO重传),就要更新慢开始门限 ssthresh =max(flight size/2,2*SMSS),因为一般情况下flight size接近或者等于cwnd,所以也可以认为更新ssthresh =max(cwnd/2,2*SMSS),这个过程叫做“乘法减小”(Multiplicative Decrease)。加法增大与乘法减小常常合称为AIMD算法。另外,Windows中的实现据传为ssthresh =max(min(cwnd, awnd)/2, 2*SMSS) 。总结如下
- sshthresh = max(cwnd/2,2*SMSS) (一般来说当发生丢包的时候 cwnd一般都是大于等于4的,所以也可以认为ssthresh =max(cwnd/2,2*SMSS)=cwnd/2)
- cwnd 重置为 1
- 进入慢启动过程
三、拥塞控制示例
上面所讲解的慢启动和拥塞避免就是Tahoe版本的TCP实现,这个也是带有拥塞控制功能的第一个TCP版本,随着4.2 BSD UNIX一起发布。同时Tahoe版本是支持快速重传的,但是Tahoe版本对于快速重传和RTO重传的处理是一致(Reno版本的TCP对于快速重传和RTO重传的处理略有不同,接下来的拥塞控制系列会讲解)。
下面我们看一个图解示例以加深理解,下图中横坐标以RTT为单位,纵坐标cwnd用报文段为单位(可以看成SMSS大小为单位),我们假设接收端通告的窗口足够大,只考虑cwnd的变化。
分别对图中标示的箭头做如下说明
1、在标号为1的箭头处,TCP初始连接进行数据交换,开始慢启动,初始cwnd=IW=1,ssthresh=16,在传输轮次0-4阶段进行慢启动过程,cwnd按照1-2-4-8-16的顺序进行指数增长
2、在标号为2的箭头处,cwnd=16=ssthresh,此时触发拥塞避免过程,开始线性增长,在传输轮次4-12阶段,cwnd按照16-17-18-19-20-21-22-23-24进行线性增长。
3、在标号为3的箭头处,TCP发生了RTO重传,认为网络发生拥塞,于是设置ssthresh=cwnd/2=12,cwnd=1重新进行慢启动过程
4、在标号为4的箭头处,TCP从cwnd=1开始重新开始慢启动过程
5、在标号为5的箭头处,当 cwnd = 12 时改为执行拥塞避免算法,拥塞窗口按按线性规律增长,每经过一个往返时延就增加一个 MSS 的大小。