Switch-Router

TCP的重传退避与公平

Published at 2019-08-28 | Last Update 2019-08-28

TCP在发送报文后,如果没有收到对端应答,那么在重传定时器超时后会触发重传,超时时间遵循二进制退避原则,也就是{1,2,4,8,16}这样成倍地扩大超时时间。退避是因为TCP认为丢包意味着网络有拥塞,为了不加重网络的拥塞,TCP选择等待更长的时间再进行重传。这和CSMA/CD中的二进制退避算法如出一辙。

TCP拥塞基础曾经提到过网络拥塞的来历,网络中的网络设备(路由器、交换机)在收到了超过队列限制的报文后,后续的报文会被丢弃。从TCP采用的二进制退避算来看,TCP绝对算得上是网络里的谦谦君子了,它信守的规则是:既然已经堵了,我就等一会儿再发,如果还堵,我就再多等翻倍的时间

对整个网络来说,这的确是减轻负担的好办法。要知道,在发送窗口已满的情况下,指数退避一次,意味着单位时间内发送的报文变成了原来的1/2,再退避一次,就只有原来的1/4!就像是汽车限号出行,单双号限行不好用,我就规定一辆车只能在4天中开1天…

But, TCP真的需要如此克制自己吗?,换个说法,为什么TCP一定要x2退避?难道重传定时器超时时间不能线性增大(每次增加X秒),或者乘以一个更小的系数(比如x1.5) ? 我们可以从CSMA/CD找到灵感。

CSMA/CD使用x2的原因很好理解,共享介质中的每个节点并不知道其他还有多少节点,使用x2退避就是利用二分法快速找到让整个系统稳定运行的时隙分配方案!

举个例子,假设系统中有4个节点发包速率相同,那么最终的稳定分配方案自然就是将一段时间分为4份,每个节点占用1个时隙。如果此时又加入4个相同的节点。那么显然,所有的8个节点都会发包冲突。如何才能不冲突呢?当然是分配给每个节点的时间再减小一半!当然这里举的例子节点都是2的整数幂。如果不是呢?此时2进制退避依然能很快地达到稳定,也许这个时候的时隙分配方案不是最合理的,但是正如前面说的,每个节点并不知道其他还有多少节点,对单个节点来说,二进制退避是最快速找到让每个节点都正常工作的方案!

二进制退避方案中隐含了对公平性的考虑,它是站在整个网络的角度,而不是其中某一台主机!

但对一台特定的主机,不遵守这个退避规则显然好处更多…比如使用固定的重传定时器时间。在这种网络中,没有拥塞时大家相安无事,一旦出现了拥塞,那么不退避的主机理论上就能发出更多的报文!

当然,这似乎牺牲了其他遵守规则主机的利益,它们的重传次数会增加。那么,如果大家都不遵守呢?结果就是大家的重传次数都增加了,拥塞甚至比大家都遵守还要糟糕,因为网络上的设备丢的包更多了!

这就有点囚徒困境的味道了,如果别人退避你不退避,你能获利,如果别人比退避而你退避,那么你就吃亏了,如果大家都不遵守,那么比都遵守还要糟糕……

当然,现实中的网络并不同于CSMA/CD中的共享介质,一个简单的区别就是,在CSMA/CD中,如果一个节点监听到信道繁忙,它是不是发送数据的,它需要等待;而在网络中,并不存在这样的共享介质,并且路由器是又缓冲队列的,因此两个主机还是可以都发送报文。

话虽如此,很多游戏为了保证实时性不会选择TCP !毕竟,TCP太无私了,它是为了整个网络考虑的。而实时游戏需要的是什么!快速!那么如果它们有需要可靠传输怎么办? 很简单,使用UDP,然后在用户态自己做ARQ就好了,想怎么折腾怎么折腾,比如KCP就是这么个东西。