TCP协议学习总结(下)
在前两边TCP学习总结中,也大概地学习了TCP的整个流程,但许多细节中的细节并没有详细学习,例如超时重传问题,每次瓶颈回归慢启动效率问题以及最大窗口限制问题等。本学习篇章最要针对这些细节中的细节进行学习。TCP的复杂很多时候就是细节太多了,需要考虑许多的场景并利用许多复杂的算法和启动异步线程定时处理这些问题,对于每一个连接,TCP管理4个不同的定时器,分别是:
1)、重传定时器使用于当希望收到另一端的确认;
2)、坚持定(persist)时器使窗口大小信息保持不断流动,即使另一端关闭了其接收窗口;
3)、保活(keepalive)定时器可检测到一个空闲连接的另一端何时崩溃或重启;
4)、2MSL定时器测量一个连接处于TIME_WAIT状态的时间。
TCP的超时与重传
RTO(Retransmission TimeOut)算法
TCP计算超时有一个叫做“指数退避”的算法,例如如果发送方在发送数据的时候,服务端突然关闭服务时,客户端是会根据初始超时时间开始一步步指数的增长超时重传时间,如下图所示:
以上简单例子的第一次超时重传时间是1.5秒,第二次是3秒,第三次是6秒以此指数增长类推,直到最大超时时间64秒后就不再增长,直到9分钟(TCP实现中不可变)后客户端直接发送一个RST报文段断开连接。由于路由器和网络流量均会变化,因此TCP会跟踪这些变化并相应地改变其超时时间,所以超时的初始化时间不是固定的。它会根据报文段的往返时间(RTT)去评估,具体算法如下:
最初算法:R=αR+(1-α)M ;RTO=Rβ;
R:RTT估计器;
M:当前测量值;
α:是一个推荐值为0.9的平滑因子;
β:是一个推荐值为2的时延离散因子。
从以上算法可以看出,每个新估计的90%来自于前一个估计,而10%则取自新的测量。[Jacobson 1988]详细分析了在RTT变化范围很大时,使用这个方法无法跟上这种变化,从而引起不必要的重传。所以Jacobson建议除了被平滑的RTT估计器(R),所需要做的还有跟踪RTT的方差。在往返时间变化起伏很大时,基于均值和方差来计算RTO,将比作为均值的常数倍来计算RTO能提供更好的响应。正如Jacobson所描述的,均值偏差是对标准差的一种好的逼近,但却更容易进行计算(计算标准方差需要一个平方根)。这就引出了下面用于每个RTT测量M的公式:
Err=M-A
A←A+gErr
D←D+h(|Err|-D)
RTO=A+4D
这里的A是被平滑的RTT(均值的估计器),而D则是被平滑的均值偏差。Err是刚得到的测量结果与当前的RTT估计器之差。A和D均被用于计算下一个重传时间(RTO)。增量g起平均作用,取为1/8(0.125)。偏差的增益是h,取值为0.25.当RTT变化时,较大的偏差增益将使RTO快速上升。其实重传测量结果还会存在一个问题,就是发生重传时,然后收到一个确认,那么这个ACK是针对第一个分组还是针对第二个分组呢?这就是所谓的重传多义性问题。
Karn算法
这里又涉及到一个新的算法,叫“Karn算法”。他规定,当一个超时和重传发生时,在重传数据的确认最后到达时,不能更新RTT估计器,因为我们并不知道ACK是针对哪一次传输。并且,由于数据被重传,RTO已经得到了一个指数退避,我们在下一次传输时使用这个退避后的RTO,对于一个没有被重传的报文段而言,除非收到了一个确认,否则不要计算新的RTO。
拥塞避免算法
该算法假定由于分组受到损坏引起丢失是非常少的(远小于1%),因此分组丢失就意味着在源主机和目的主机之间的某处网络上发生了拥塞。有两种分组丢失的指示:发生超时和接收到重复的确认(接收端强调我只接受到这个报文而已)。在上一学习总结中学到了一个叫“慢启动”的限流算法,也抛出了一个老是慢启动也不是办法的问题。拥塞避免算法和慢启动算法是两个目的不同、独立的算法。但是当拥塞发生时,我们希望降低分组进入网络的传输速率,于是可以调用慢启动来做到这一点。所以很多时候,在实际中这两个算法通常在一起实现。
拥塞避免算法和慢启动算法需要对每个连接维持两个变量:一个拥塞窗口cwnd和一个慢启动门限ssthresh。这样得到的算法的工作过程如下:
1)、对于一个给定的连接,初始化cwnd为1个报文段,ssthresh为65535个字节(注意:这是最大窗口大小);
2)、TCP输出例程的输出不能超过cwnd和接收方通告窗口的大小。拥塞避免是发送方使用的流量控制,而通告窗口则是接收方进行的流量控制。前者是发送方感受到的网络拥塞的估计,而后者则与接收方在该连接上的可用缓存大小有关。
3)、当拥塞发生时(超时或收到重复确认),ssthresh被设置为当前窗口大小的一半(cwnd和接收方通告窗口大小的最小值,但最少为2个报文段)。此外,如果是超时引起了拥塞,则cwnd被设置为1个报文段(这就是慢启动);
4)、当新的数据被对方确认时,就增加cwnd,但增加的方法依赖于我们是否正在进行慢启动或拥塞避免。如果cwnd小于或等于ssthresh,则正在进行慢启动,否则正在进程拥塞避免。慢启动一直持续到我们回到当拥塞发生时所处位置一半的时候才停止(因为我们记录了在步骤2中给我们制造麻烦的窗口大小的一半),然后专为执行拥塞避免。
在该例子当中,假定cwnd为32个报文段时就发生拥塞,于是设置shthresh为16个报文段,而cwnd为1个报文段。在时刻0发送了一个报文段,并假定在时刻1接收到它的ACK,此时cwnd为2。接着发送了2个报文段,并假定在时刻2接收到它的ACK,于是cwnd增加为4(对于每个ACK增加1次)。这种指数增加算法一直进行到时刻3和4之间收到8个ACK后cwnd等于ssthresh时才停止,从该时刻开始,cwnd以线性方式增加,在每个往返时间内最多增加1个报文段。从例子可以看出,慢启动与拥塞避免算法的合并使用效果。那么它们的合作是如何避免每次都要重慢启动开始呢?
快速重传与快速回复算法
算法过程如下:
1)、当收到3个重复的ACK时(1~2个ACK无法确认是丢失),将ssthresh设置为当前拥塞窗口cwnd的一半。重传丢失的报文段,设置cwnd为ssthresh加上3倍的报文段大小(因为收到3个重复ACK)。
2)、每次收到另一个重复ACK时,cwnd增加1个报文段大小并发送1个分组(如果新的cwnd允许发送)。
3)、当下一个确认新数据的ACK到达时,设置cwnd为ssthresh(在第1步中设置的值)。这个ACK应该是在进行重传后的一个往返时间内对步骤1中重传的确认。另外,这个ACK也应该是对丢失的分组和收到的第1个重复ACK之间的所有中间段报文的确认。这一步采用的是拥塞避免,因为当分组丢失时我们将当前的速率减半。
说白了就是通过3次重复ACK确认报文的丢失而不是等待超时定时器溢出的确认,这样就避免了慢启动算法的激活,而从最低初始化拥塞窗口(1个报文段)起发送。
TCP的坚持定时器
上一篇学习总结中提到过通告窗口为0时发送方将停止发送数据的问题。为了避免发送方和接收方的死循环等待,发送方使用一个“坚持定时器(presist timer)”来周期性地向接收方查询,以便发现窗口是否已经增大。这些从发送方发出的报文段称为窗口查探(window probe)。
从上图可以看出,坚持定时器使用了普通的TCP指数退避。对于一个典型的局域网连接,首次超时时间算出来是1.5秒,第2次的超时时值增加一倍,为3秒,再下次乘以4为6秒,之后再乘以8为12秒等,但是坚持定时器总是在5~60秒之间。窗口探查包含一个字节的数据(以上例子序号为4098)。TCP总是允许发送已关闭窗口之后一个字节的数据。坚持状态与重传超时之间一个不同的特点就是TCP从不放弃发送窗口探查(重传超时会持续9分钟),这些探查每隔60秒发送一次,这个过程将持续到或者窗口被打开或应用进程使用的连接被终止。
在上一篇总结当中同样提到了一个叫“糊涂窗口综合征”的问题。也就是说,接收方每出现哪怕1个字节的空闲窗口也会通告发送方,那么发送放就会立刻发送数据,但就会让发送方与接收方陷入“小分组”发送的死循环,从而会造成网络拥塞问题。该现象可以发生在两端的任何一端,接收方可以通告一个小的窗口(而不是一直等到有大的窗口时才通知),而发送方也可以发送少量数据(而不是等待其他的数据以便发送一个大的报文段)。可以在任何一端采取措施避免出现“糊涂窗口综合征”的现象:
1)、接收方不通告窗口大小。通常的算法是接收方不通告一个比当前窗口大的窗口(可以为0)除非窗口可以增加一个报文段大小(也就是将要接受的MSS)或者可以增加接收方缓存空间的一半,不论实际有多少;
2)、发送方避免出现“糊涂窗口综合征”的措施只有以下条件之一满足时才发送数据:(a)可以发送一个满长度的报文段;(b)可以发送至少是接收方通告窗口大小一半的报文段;(c)能够发送手头的所有数据并且不希望接收ACK(也就是说,我们没有还未确认的数据)或该连接禁止了Nagle算法。
TCP的保活定时器
保活定时器主要是为服务器应用程序提供的,因为许多时候一个服务器希望知道客户主机是否崩溃并关机或者崩溃又重新启动。但有一点需要注意:
保活定时器并不是TCP规范中的一部分。Host Requirements RFC提供了3个不使用保活定时器的理由:(1)在出现短暂差错的情况下,这可能会使一个非常好的连接释放掉;(2)它们耗费不必要的带宽;(3)在按分组计费的情况下会在互联网上花掉更多的钱。然而,许多实现提供了保活定时器。
如果一个给定的连接在两个小时之内没有任何动作,则服务器就向客户发送一个探查报文段,客户主机必须处于以下4个状态之一:
1)、客户主机依然正常运行,并从服务器可达。客户的TCP响应正常,而服务器也知道对方是正常工作的。服务器在两个小时以后将保活定时器复位。如果在两个小时定时器到时间之前有应用程序的通信量通过此连接,则定时器在交换数据后的未来2小时再复位。
2)、客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的TCP都没有响应。服务器将不能够收到对探查的响应。并在75秒后超时。服务器总共发送10个这样的探查,每个间隔75秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭或终止连接。
3)、客户主机已经崩溃并已经重新启动。这是服务器将收到一个对其保活探查的响应,但是这个响应是一个复位,使得服务器终止这个连接。
4)、客户主机正常运行,但是从服务器不可达。这与状态2相同,因为TCP不能够区分状态4与状态2之间的区别,它所能发现的就是没有收到探查的响应。
学习总结
本篇章主要针对TCP管理的4个定时器的介绍,包括超时重传定时器、坚持定时器、保活定时器,以及第一篇章介绍的2MSL的TIME_WAIT状态定时器。这些都是确保TCP可靠性的重要手段。