为了提高在数据在链路中的传输数据,滑动窗口的模型,也就是说可以一次发送到多个数据包。因此会带来在链路当中的拥塞问题,即发送方发送数据过快导致链路较为拥挤导致数据通信的时延过大,从而会会引发数据丢包或者超时。
术语解释
丢包:发送方的数据不能成功的到达接收方,也就是在链路中数据包就丢失了,在TCP中如果收到三个冗余ACK,则表示数据发生了丢包。
超时:发送方在发送数据后,经过一段指定时间后还没有收到ACK,则视为超时。
问题引出
TCP为了能够在不可靠的IP协议之上实现可靠数据传输,而发送方与接收方两者之间的速率无法很好匹配,因此实现拥塞控制来达到对发送方的速率控制是十分重要的。因此,对于如何实现拥塞控制我们需要关注一下三个问题:
- TCP的发送方如何限制自身的发送速率?
- TCP的发送方如何感知到超时或者丢包时间的发生?
- 采用何种算法来控制发送速率?
下面来对上述问题一一解答
发送方如何来限制自身的发送速率?
TCP是全双工协议,在发送方与接收方各自都有一个接收缓存,一个发送缓存和一些控制变量:LastByteRead(这是接收方的变量,上层协议读取的最后一个字节序号),LastByteSend(最后一个发送字节的序号),LastByteAcked(最后一个正确接收的字节序号)等等。但是在发送方有一个cwnd(congestion window,拥塞窗口)。因此通过以下不等式对拥塞窗口的大小进行控制:
$$
LastByteSend-LastByteAcked \leq min(cwnd,rwnd)
$$
也就是说最后最后一个正确发送的字节序号-最后一个确认的字节序号小于拥塞窗口的大小,rwnd是用于流量控制的变量,这里暂且可以不管。从而得到发送速率:
$$ v_{send} = \frac{cwnd} {RTT} $$
发送方如何感知超时或者丢包事件的发生?
- 设置定时器(感知超时事件)
在TCP当中,发送方在发送自身的数据后会启用定时器,在超过定时器所设置的时间后如果没有收到在TCP报文中的确认号的ACK报文或者比确认号大的ACK报文,则视为超时,TCP采用累计确认的机制(Accumulative ackonwledge)的机制,即接收方总是确认最后一个被正确确认的字节序号,另外TCP使用的是单一定时器,即使一次发送的数据包有很多个,如果收到一个ACK,那么就会重启定时器。 - 冗余ACK(Duplicate ACKs)
如果连续接收到4个同样确认序号的ACK报文(大于四个也算),那么认为数据发生了丢包,这四个ACK,第一个ACK是理解为正确接收的,那么后面的3个ACK就是冗余ACK了,TCP一旦收到三个冗余ACK,那么就会快速重传丢失的数据。
TCP通过何种算法来实现控制发送速率?
TCP通过 TCP拥塞控制算法来控制传输速率,(TCP congestion control algorithm),该算法包括:慢启动,拥塞避免,快速恢复这个三个部分。
- 慢启动
当一个TCP链接开始的时候,拥塞窗口
的大小通常为一个MSS的大小。在接下来的过程当中,每收到一个ACK,会让cwnd增加一个MSS的大小。也就是说,第一次发送一个报文,增加一个MSS,cwnd变大为两倍。第二次发送两个报文,收到2个ACK,增加两个MSS,cnwn变为初始的4倍。因此cnwd= cwnd*2^n,n为当前迭代的次数。cwnd
接踵而至的一个问题就是,拥塞窗口不能无限制的增长,那么什么时候结束这种指数级的增长?
首先对于这个问题,不同的Tcp版本有不同的应对措施,例如TCP taheo 和 Tcp Reno都不同。
- 超时事件
发生超时事件的时候,结束指数增长模式,并且将“慢启动阈值ssthresh(slow start threshold)”设置为当前cwnd的一般,然后再将cwnd设置为一个MSS的大小,即cwnd = 1 ,一般来说初始的ssthresh可以是人为设定的。 - 超过ssthresh
超过阈值的时候,就转入拥塞避免模式,在拥塞避免模式,在每个RTT只增加一个MSS,不像在之前的阶段当中,每收到一个ACK就增加一个MSS,此时cwnd是线性增长模式。当发生超时事件的时候,又会将cwnd = 1,ssthresh = cwnd/2,然后继续执行指数增长。将增长模式由指数级增长转换到线性增长称为拥塞避让,在拥塞避让的情况下,每一个RTT增长一个MSS,而不是在指数增长的阶段的每一个ACK增长一个MSS。 - 发生冗余ACK(这里只是讨论Reno版本的情况)
在发生冗余ACK的时候,TCP执行的快速重传(快速重传:当接收到三个重复的ACK的时候,那么不等待发送方的定时器是否超时,直接发送发生丢包的数据),接着进入快速恢复阶段。在快速恢复阶段,执行以下事件:- ssthresh = 当前窗口/2
- cwnd = ssthresh + 3在快速恢复阶段,每收到一个冗余ACK,cwnd = cwnd +1,因为这里已经三个冗余ACK,所以直接+3
- 如果持续收到冗余ACK,cwnd = cwnd+1
- 一旦丢失报文被确认,结束快速恢复。然后执行线性增长模式
下面摘录一段RFC2001对于快速恢复的解释:
When the third duplicate ACK in a row is received, set ssthresh to one-half the current congestion window, cwnd, but no less than two segments. Retransmit the missing segment. Set cwnd to ssthresh plus 3 times the segment size. This inflates the congestion window by the number of segments that have left the network and which the other end has cached (3).
Each time another duplicate ACK arrives, increment cwnd by the segment size. This inflates the congestion window for the additional segment that has left the network. Transmit a packet, if allowed by the new value of cwnd.
When the next ACK arrives that acknowledges new data, set cwndto ssthresh (the value set in step 1). This ACK should be the acknowledgment of the retransmission from step 1, one round-trip time after the retransmission. Additionally, this ACK should acknowledge all the intermediate segments sent between the lost packet and the receipt of the first duplicate ACK. This step is congestion avoidance, since TCP is down to one-half the rate it was at when the packet was lost.
点这里查看RFC201
当收到冗余ACK的时候,发送方知道发生了丢包事件,这时候因为累计确认的机制,TCP执行快速重传并且进入快速恢复阶段。在快速恢复阶段,每收到一个冗余ACK,cwnd就会增大一个MSS的大小,同时在这个快速快速恢复阶段,如果发生超时或者超过阈值,就会按照之前的方式去处理。快速恢复是TCP推荐的而非必须的构建,也就是说有些版本有,有些版本没有。
在Taheo当中,当发生冗余ACK事件,就将当前ssthreshold置为当前ssthresh的一半,并且将cwnd = 1,接下来继续从慢启动开始,这与它在超时事件当中执行的事情一样。在Reno当中,每当发生冗余ACK事件,就将当前cwnd置为当前拥塞窗口的一半,同时也将ssthreshold设置为新的拥塞窗口大小。Taheo和Reno在超时的时候所执行的事件是一样的,只是在处理冗余ACK的时候有所不同。