说说 TCP 的拥塞控制?

什么是拥塞控制?不是有了流量控制吗?

之前提到的流量控制是为了防止发送方的数据填满接收方的缓存,但它并不能感知整个网络的状态。

在计算机网络中,通常是共享的环境,因此可能会出现网络拥塞的情况,这是由于其他主机之间的通信导致的。

当网络出现拥塞时,继续发送大量数据包可能会导致延迟和丢包等问题。TCP协议会重传数据,但每次重传都会增加网络的负担,进而导致更大的延迟和丢包,形成恶性循环。

因此,TCP不能忽略整个网络中发生的情况,它被设计为一种无私的协议,当网络出现拥塞时,TCP会自我牺牲,降低发送的数据量。

这就是拥塞控制的概念,其目的是避免发送方的数据充满整个网络。

可以将其类比为水管,不能让过多的水(数据流)流入水管,如果超过水管的承受能力,水管就会爆裂(丢包)。

发送方会维护一个拥塞窗口(congestion window)的变量,用于调整要发送的数据量。

什么是拥塞窗口?它和发送窗口有什么关系?

拥塞窗口(cwnd)是发送方维护的一个状态变量,它会根据网络的拥塞程度进行动态调整。

发送窗口(swnd)和接收窗口(rwnd)大致相等,而引入拥塞窗口的概念后,发送窗口的大小将变为swnd = min(cwnd, rwnd),即拥塞窗口和接收窗口中的最小值。

拥塞窗口(cwnd)的变化规则如下:

  • 如果网络中没有出现拥塞,cwnd会增大;
  • 一旦网络出现拥塞,cwnd会减小;

拥塞控制有哪些常用算法?

拥塞控制有几种常用的算法:

  • 慢启动(Slow Start)
  • 拥塞避免(Congestion Avoidance)
  • 拥塞发生(Congestion Occurrence)
  • 快速恢复(Fast Recovery)
慢启动算法

慢启动算法的思想是逐渐增加拥塞窗口的大小,以探测网络的拥塞程度。在TCP建立连接后,发送方开始时不会发送大量数据,而是先进行探测。对于每个收到的ACK,拥塞窗口cwnd的大小会增加1(以MSS为单位)。每个轮次中,发送窗口的大小会加倍,呈指数增长。如果没有出现丢包,发送窗口会持续增大;如果出现丢包,拥塞窗口会减半,然后进入拥塞避免阶段。

举个例子:

  • 在建立连接后,初始化拥塞窗口cwnd为1,表示可以传输一个MSS大小的数据。
  • 每收到一个ACK确认应答,cwnd增加1,因此一次可以发送2个MSS大小的数据。
  • 当收到两个ACK确认应答时,cwnd增加2,因此可以比之前多发送2个,一次可以发送4个MSS大小的数据。
  • 当这4个ACK确认到来时,每个ACK确认增加1,总共增加4,因此可以比之前多发送4个,一次可以发送8个MSS大小的数据。

可以看到,发送的数据包数量以指数形式增长。

为了防止cwnd增长过大导致网络拥塞,还引入了一个慢启动阈值(ssthresh)状态变量。当cwnd达到该阈值时,会减缓增长速度,进入拥塞避免算法。

拥塞避免算法

一般情况下,慢启动阈值(ssthresh)设为65535字节。当cwnd达到慢启动阈值后:

  • 每收到一个ACK时,cwnd = cwnd + 1/cwnd
  • 每过一个往返时间(RTT),cwnd = cwnd + 1

可以看出,这是一种线性增长的算法,避免了过快增长导致网络拥塞的问题。

继续上面的慢启动例子,假设ssthresh为8:

  • 当收到8个ACK确认应答时,每个确认增加1/8,总共增加1,因此可以发送9个MSS大小的数据,进入线性增长阶段。
拥塞发生

当网络发生拥塞导致丢包时,会出现两种情况:

  • 超时重传(RTO Timeout)
  • 快速重传(Fast Retransmit)

对于超时重传,会采用拥塞发生算法:

  • 慢启动阈值ssthresh = cwnd/2
  • cwnd重置为1
  • 进入新的慢启动过程

这种方式就像是高速驶入紧急刹车,然后急速倒车,有点不太优雅...

其实有更好的处理方式,即快速重传(Fast Retransmit)。当发送方收到3个连续的重复ACK时,会快速重传,而不必等待RTO超时再重传。

在快速重传发生后的拥塞发生算法中:

  • 拥塞窗口大小cwnd = cwnd/2
  • 慢启动阈值ssthresh = cwnd
  • 进入快速恢复算法
快速恢复

快速重传和快速恢复算法通常同时使用。快速恢复算法认为,收到3个重复的ACK说明网络并不是很糟糕,所以不必像RTO超时那样强烈地回退。

在快速重传后,cwnd和ssthresh已经被更新为:

  • cwnd = cwnd/2
  • ssthresh = cwnd

然后进入快速恢复算法:

  • cwnd = ssthresh + 3
  • 重传丢失的那几个数据包(对应重复的ACK)
  • 如果再收到重复的ACK,cwnd = cwnd + 1
  • 如果收到新数据的ACK,cwnd = ssthresh。因为收到新数据的ACK表示恢复过程已经结束,可以重新进入拥塞避免算法。

标签: java, Java面试题, Java问题合集, Java编程, Java问题精选, Java常见问题, 计算机网络, 计算机网络面试题