如前面所述,针对丢包情况,TCP采用的首要机制是重传,包括超时重传和快速重传。针对保证接收端的顺序问题和接收速率比较慢的问题,TCP采用了滑动窗口的机制。这两个可以往回看前两篇。今天说的情况是,当网络处于拥塞奔溃状态时,共用一条网络传输路径的多个TCP连接却需要重传更多的数据包。这就好比火上浇油,可想而知,结果只会更糟,所以我们今天讨论的是如何避免这个问题。
6.1 TCP拥塞控制典型的TCP只有在断定拥塞发生的情况下,才会采取相应的行动。推断是否出现拥塞,通常看是否有丢包情况发生。在TCP中。丢包也被用作判断拥塞发生与否的指标。
6.1.1 减缓TCP发送
假如我们已经测试出当前网络比较拥塞了,那怎么去减缓TCP发送呢?在TCP头部设置了通知窗口大小字段,该数组是可以控制TCP发送端的速率的。如果控制的,可以看上一节,滑动窗口。
基于网络传输能力的估计,可以在发送端引入一个窗口控制变量,确保发送窗口大小不超过接收端接收能力和网络传输能力,所以TCP发送端发送速率等于接收速率和传输速率两者的最小值。(在前面的机制上又添加了网络传输速率)
反应网络传输能力的变量称为拥塞窗口,记作cwnd。因此,发送端实际窗口W就是接收端通知窗口awnd和拥塞窗口cwnd的较小着:
W = min(cwnd, awnd)
应该到这里大家都明白了,怎么减缓TCP发送吧,还是用到老知识。但是拥塞窗口怎么确定值呢?有待下面可以探讨。
6.1.2 慢启动
我们怎么去测试当前网络传输速率呢?目前最有效的方法,就是以越来越快的速率不断发送数据,直到出现数据包丢失(或网络拥塞)为止。如果以全速启动的话,会影响其他连接的传输性能,所以通常会有特定的算法来避免过快启动,直到稳定传输后才会运行相应的其他算法。
上面所说的以越来越快的速率不断发送数据,这种方法叫做慢启动。在什么情况下,会启用慢启动算法呢?
- 一个新的TCP连接建立
- 检测到由重传超时(RTO)导致的丢包
- 发送端长时间处于空闲状态
先来看图,图比较直观
看完图是不是恍然大悟。但是初始窗口的带下呢?我们一般讨论的都是初始化窗口为1SMSS。SMSS为接收方的MSS和路径MTU两者中较小值。
上面的图是假设没有拥塞的情况下,每个ACK都有回复,所以发送窗口以指数的形式增长。
如果大量数据包发送导致网络拥塞,cwnd将大幅度减小(减少至原来的一半)。
6.1.3 拥塞避免
慢启动的过程中就是为了确立一个慢启动阈值。一旦达到阈值,就意味着可能有更多可用的传输资源。如果立即全部占用这些资源,可能又会导致网络拥塞。
为了得到更多的传输资源而不致影响其他连接传输,TCP实现了拥塞避免算法。
由图可以看出,拥塞避免阶段额增长是比较缓慢的,因为我们应该确实了慢启动阈值了,所以如果再想发包的话,就只能这种缓慢的增长了。
6.1.4 慢启动和拥塞避免如何选择
前面我们已经说过慢启动阈值,这个值跟cwnd的关系是决定采用慢启动还是拥塞避免的界限。
当cwnd<ssthresh(慢启动阈值),使用慢启动算法
当cwnd > ssthresh,需要执行拥塞避免
慢启动阈值不是固定的,而是随着时间改变的。它的只要目的是,在没有丢包发生的情况下,记住上一次最好的操作窗口值。当有重传情况发生,不管是超时重传,还是快速重传,都会更新ssthresh
ssthresh = max(在外数据值/2), 2*SMSS) (16-1)
我们已经知道,如果出现了重传情况,TCP会认为操作窗口超出了网络传输能力范围。这是会将慢启动阈值减少到至当前窗口大小一半(在外数据值/2就是当前窗口的一半)但不小于2*SMSS。从而减少最优窗口估计值。
6.1.5 标准TCP
是不是看了上面说的选择还是不太懂,其实我也是,所以还是需要一个实例来屡屡思路。标准TCP就是一个很好的例子。
在TCP连接建立之初首先是慢启动阶段(cwnd=1SMSS),ssthresh通常取一个较大值(至少为awand)。当接收到一个好的ACK之后,cwnd会相应更新:(一般刚开始都是慢启动,等到接收端返回了很多ack之后,cwnd增长,一直增长到慢启动阈值之后,才会减缓增长,直到没有接收到ack)
cwnd += SMSS (若cwnd < ssthresh)慢启动
cwnd+= SMSS* SMSS/cwnd(若cwnd > ssthresh)拥塞避免
当收到三次重复ACk(或其他表面需要快速重传的信号)时,会执行一下行为:
- ssthresh更新为大于等于(16-1)中的值
- 启用快速重传算法,将cwnd设为(ssthresh+3*SMSS)
- 每接收一个重复ACK,cwnd值暂时增加1SMSS
- 当接收到一个好的ACK,讲cwnd重设为ssthresh
步骤2和步骤3构成了快速恢复。步骤2设置cwnd的大小,cwnd会见到之前值的一半+3SNSS,然后,考虑到每接收一个重复ACK,就意味着相应的数据包已成功传输,这时候新的数据可以发送了,所以cwnd的值会增加,直到接收到发送一个好的ACK之后,才会把cwnd重设为ssthresh,重新开始发送。
图就先别画了,我先用文字总结一下,有空再补补图
简单流程应该是这样的 :Tcp建立连接,开始慢启动,cwin会以1smss的速度增长或者以拥塞避免的方式增长; 直到遇到没有收到的ack包,cwin直接减为原来的的一半再加3SMSS;
如果这时候还是没接受到ack包,就还会往下减 ;
如果这时候能接受到ack包,cwin会暂时增加1SMSS;
如果网络变好就会一直往上加,出现了丢包就往下减, 以此循环。
还有TCP/IP协议有一些算法优化也还没看,以后有时间补补。