三十天学不会TCP,UDP/IP网络编程 -- RTT的计算

欢迎去gitbook(https://legacy.gitbook.com/@rogerzhu/)看到完整版。linux

 

若是对和程序员有关的计算机网络知识,和对计算机网络方面的编程有兴趣,虽说如今这种“看不见”的东西真正能在实用中遇到的机会很少,可是我始终以为不管计算机的语言,热点方向怎么变化,做为一个程序员,不少基本的知识都应该有所了解。而当时在网上搜索资料的时候,这方面的资料真的是少的可怜,因此,我有幸前两年接触了这方面的知识,我以为我应该把我知道的记录下来,虽然写的不必定很好,可是但愿能给须要帮助的人多个参考。个人计划是用半年时间来写完这一系列文章,这个标题也是我对太多速成文章的一种态度,好了,废话再也不多扯了,下面是其中的一节内容,更多内容能够去gitbook上找到。git

 

 

TCP中,超时重传机制是和应答确认机制同样组成TCP可靠传输的关键设计。而超时重传机制中最最重要的就是超时计时器的时间选择的了,很明显,在工程上,在数据发送的过程当中,若是用一个固定的值一直做为超时计时器的时长是很是不经济也很是不许确的方法,因此这一篇就来讲说TCP中的超时计时器的设计哲学。程序员

过短不行,太长也不行

超时超时,首先你得定义什么是正常的时间,才能知道有没有超过正常的时间。先假设一个很是理想的环境,这个环境理想到和之前不少物理题同样,不考虑摩擦力。咱们假设网络很通畅且速率稳定,并且处理包的速度忽略不计,这样一个包发送到对端的时间永远都是同样的,将这个时间记为t。那么很明显,若是超过两倍的t尚未收到对端的回复,咱们就能够确定超时了。因此在这种状况下超时计时器只要设置的比两倍t长就好了。只要过了这个时间,发送端就会从新发送这个包。算法

那么这个时间是否是越长越好呢?答案很明显不是,由于太长会人为的减小通讯的速率,对于通讯这种有时候一点点速率的提升都让人欣喜若狂了,若是你还人为的浪费时间那真是暴殄天物了。编程

那么若是这个时间设置的过短会怎样呢?在这个理想状况下就是小于2倍的t,这会致使太多没必要要的重传。也许ACK正在路上,你却错误的认为是丢失了,那么网络中就会增长不少原本就没必要要的包。网络

并且,要知道,现实的网络环境是十分复杂多变的,有时候可能忽然的抽风,有时候可能忽然的又很顺畅,因此说若是只用一个一直不变的时间做为重传计时器的时长是彻底不现实,不可用的。因此不少计算重传时间的算法就被设计出来。spa

调的一手好参数

TCP把一个包从发送端发送出去到接收到这个包的回复这段时间称之为RTT,学名round-trip time。若是在一个包发送出去之后,超过了RTT尚未接受到回复确认,那么很明显,这个包超时了。若是你还记得前面的关于的PING那一篇,里面就有一个time指示了来回时间,可是这个是ICMP的来回时间,和TCP的这个RTT是彻底不同的概念。经过wireshark你能够看到每一次的RTT的值。计算机网络

 

 

这个RTT的计算很简单,只要把收到确认包的时间减去发送包的时间就获得了这个答案。设计

如今开始对于重传计时的第一次思考,上面说了这样一个来回就说明包是成功的接收了而且没有发生任何异常,那么可不能够简单的用这个值做为标准来做为判断超时的依据呢?也就是若是超过了0.285s没有收到ack就开始重传,很明显,不能。缘由是这个RTT是过去完成时了,是上一次成功的时候的时间,和下一次网络会不会忽然抽风,仍是会忽然变得更通畅没有太大的必然关系,最愚蠢的一种思惟就是简单的用过去表明将来。可是数学给咱们提供了一种用已知大概去逼近未知的方法,那就是用几率统计的思惟。因此最简单的一个办法是用过去的几回平均值来做为这一次重传计时器的时长,毕竟这是初中学过的理论。不过这个方法明显太过于幼稚,缺少灵活的控制,因此说,第一次设计的尝试就出现了。3d

为了可以用更加灵活的方法来估算出重传时间,一个叫SRTT的概念被引进,SRTT学名是Smoothed RTT。估算重传时间(之后称之为RTO,Retransmission Timeout)的算法以下:

SRTT = (α * SRTT) + ( (1 -α) *RTT)

其中这个奇妙莫测的阿尔法取值在0.8到0.9之间,为何这样取,我也不知道,我至今也没有找到缘由。对于这第一个公式,具体实际中的作法是这样的,首先采样几回RTT的值,而后在第一次迭代的时候SRTT的初始值为RTT,后面就是根据每次计算出来的SRTT来计算就好了。这个公式有个你应该比较熟悉的中文名字,叫作加权移动平均。

在计算出SRTT以后,就使用这个值来计算咱们须要的RTO,其方法以下:

RTO = min[UBOUND, max[LBOUND,(β * SRTT)]]

这其中UBOUND是一个上限时间,好比1分钟,LBOUND是一个下限时间,好比1秒钟,β,哈哈,又是一个神奇的参数,取值在1.3到2.0之间,叫作延迟方差因子,到底取啥,为何取这个值,我,仍是不知道。

这个方法有什么问题呢?问题就在这个RTT的计算上,前面说过RTT的计算是接收到ACK的时间和包发送出去的时间的差值,在正常状况下还好,若是是在采样的过程当中发生了重传,那么到减去的时间是第一次发送的时间仍是重传发送的时间呢?

若是是减去第一次发送的时间,那么很明显,这个RTT计算大了。那你可能会说了,从直观上说,用第二次发送的时间计算才是合理的。可是有一种状况,假设原本应该到达的ACK不是丢失了,只是延迟到达了,也就是说你刚重传,这个迷路的ACK就到了,那么你用这个时间减去第二次发送的时间,明显就小了。

这个时候两个叫Phil Karn和Craig Partridge的人就针对这个问题提出了一个算法,其解决方案十分简单,既然重传状况这么复杂,那么在采用RTT的时候直接忽略重传不就好了。你先收起你的吐槽说尼玛这样我要早出生几年也能想出这个办法啊,人家论文里还写了不少其余的东西,这个只是其中之一,并且这个算法也有很大的问题,Karn针对这个问题还提出了一个可行的解决方案,至少在工程上有了个可行的路子。

这个问题是什么呢?假设在某个时间,网络极度的抽风,忽然由快变得很慢,致使全部的包都要重传。这下好了,由于前面一直很通畅,因此必然RTO很小,那么你又说重传的包不参与RTT的采样,这下完了,RTO永远不会更新,只会不断的重传,状况会愈来愈糟。而Karn针对这个提出了一个解决方案,只要重传,那么RTO就翻倍,这样就保证了在极端状况下不会致使愈来愈糟。

Karn的算法解决了初代算法的问题而且有了个可行的方案,可是RTO粗暴翻倍的作法感受仍是比较浪费。因此,在一年以后又有两我的Jacobson 和 Karels 针对这种加权移动平均的算法对RTT波动handle能力不强的弊端作了修正。其原理是用最新采样的RTT和平滑过的SRTT的差距来做为另外一个影响因子。

SRTT = SRTT + α * (RTT - SRTT)

DevRTT = (1-β) * DevRTT + β *(|RTT - SRTT|)

RTO = μ * SRTT + δ * DevRTT

这三个公式就是如今TCP协议中真正运用的算法,关于这些参数,α是取0.125,β是0.25,μ 是1,δ是4,这就是linux中的取值,至于为何,没有人知道,可是在实际效果中,果然就颇有效,在编程过程当中,咱们称这种玄学叫作调的一手好参数。

相关文章
相关标签/搜索