上一篇介绍TCP的《TCP三次握手,四次挥手和一些细节》反馈还不错,还是很开心,这次我来说说关于超时和重传部分。我们都知道TCP协议有重传机制,即如果发送方认为发生了丢包,就会重新发送这些数据包。显然,我们需要一种方法来“猜测”是否发生了丢包。最简单的想法是,接收方每收到一个数据包,就向发送方返回一个ACK,表示已经收到数据。反之,如果发送方在一段时间内没有收到ACK,则很可能是数据包丢失,然后重新发送数据包,直到收到ACK。大家可能注意到了,我用的是“猜测”,因为即使超时了,也不一定丢包,只是兜了一大圈,来的很晚。TCP协议毕竟是传输层的协议,不可能确切知道数据链路层和物理层到底发生了什么。但这并不妨碍我们的超时重传机制,因为接收方会自动忽略重复的数据包。超时和重传的概念其实就这么简单,但是内部细节很多。我们首先想到的一个问题是,要多久才算超时?超时是如何确定的?一刀切的做法是我直接把timeout设置成一个固定的值,比如200ms,但是这样肯定有问题。我们的计算机与许多服务器交互。这些服务器位于不同的地方,在国内和国外,延迟是不同的。巨大,打个比方:我的个人博客是在国内搭建的,延迟大概是30ms,也就是说正常情况下,数据包在60ms左右就已经可以收到ACK了,但是按照我们的方法,需要200ms判断丢包(Normal可能是90到120ms),效率有点低。假设你访问一个国外网站,延迟130ms,这就麻烦了。正常的数据包可能会被认为超时,导致大量数据包被重发。可想而知,重发的数据包也很容易被误判为超时。..雪崩效应的感觉,所以设置一个固定值是很不可靠的。我们需要根据网络延迟动态调整超时时间。延迟越大,超时时间越长。这里引入两个概念:RTT(RoundTripTime):往返时延,即数据包发送到收到相应ACK的时间。RTT是连接特定的,每个连接都有自己独立的RTT。RTO(RetransmissionTimeOut):重传超时,也就是上面说的超时时间。比较标准RTT定义:测量发送具有特定序列号的数据八位字节与接收包含该序列号的确认之间经过的时间(发送的数据段不必与接收的数据段匹配)。此测量的经过时间是往返时间(RTT)。经典方法的原始规范“RFC0793”使用如下公式得到平滑的RTT估计(称为SRTT):SRTT<-αSRTT+(1-α)RTTRTT指的是最新的样本值,这种估计方法称为“exponentiallyweightedmovingaverage”,这个名字听起来比较高大上,但是整个公式比较容易理解,就是用已有的SRTT值和最新测得的RTT值进行加权平均。有了SRTT,就该设置相应的RTO值了。《RFC0793》是这样计算的:RTO=min(ubound,max(lbound,(SRTT)·β))其中ubound是RTO的上边界,lbound是RTO的下边界,β称为延迟分散因子,推荐值为1.3至2.0。这个计算公式是用(SRTT)·β的值作为RTO,但同时也限制了RTO的上下限。乍一看,这种计算方法没有问题(至少我是这么觉得的),但在实际应用中,有两个缺陷:RFC-793中规定的RTO计算有两个已知问题。首先,当存在重传时,RTT的准确测量是困难的。其次,计算平滑往返时间的算法不充分[TCP:7],因为它错误地假设RTT值的方差很小且恒定。这些问题分别由卡恩算法和雅各布森算法解决。这段话摘自“RFC1122”,我来解释一下:当有包重传时,RTT的计算会很“麻烦”,我画了一张图来说明这些情况:上面列举了两种情况,方法这两种情况计算RTT是不同的(这就是所谓的重传歧义):Case1:RTT=t2-t0Case2:RTT=t2-t1但是对于client来说,它并不知道什么发生了这样的情况。选择错误情况的结果是RTT过大或过小,影响RTO的计算。(最简单粗暴的解决方案是忽略重传的数据包,只统计那些没有重传的,但是这样会带来其他问题,详见Karn的算法。)还有一个问题是这个算法假设RTT波动比较小,因为这种加权平均算法也叫低通滤波器,对突发的网络波动不敏??感。如果网络延迟突然增加,实际RTT值远大于估计值,就会造成不必要的重传,增加网络负担。(RTT的增加已经表明网络过载,这些不必要的重传会进一步增加网络负载)。标准法说实话这个标准法比较麻烦,直接贴公式:SRTT<-(1-α)SRTT+αRTT//和基本法一样,求SRTT的加权平均值rttvar<-(1-h)·rttvar+h·(|RTT-SRTT|)//计算SRTT与真实值的差距(称为绝对误差|Err|),同样使用加权平均RTO=SRTT+4·rttvar//估计新的RTO,rttvar的系数4调出参数。这个算法的总体思路是结合平均值(也就是基本方法)和平均偏差来估计,一波形而上学的调参就能得到很好的效果。如果你想了解更多关于这个算法的信息,请参考“RFC6298”。重传——TCP的一个重要事件。在基于定时器的重传机制下,每个数据包都有一个对应的定时器。一旦超过RTO且未收到ACK,则重新发送数据包。没有收到ACK的数据包会存放在重传缓存中,收到ACK后会从缓存中删除。首先明确一点,对于TCP来说,超时重传是一个非常重要的事件(RTO往往大于RTT的两倍,超时往往意味着拥塞)。一旦出现这种情况,TCP不仅会重传相应的数据段,还会降低当前的数据发送速率,因为TCP会认为当前网络拥塞。简单的超时重传机制往往效率低下,如以下情况:假设数据包5丢失,数据包6、7、8、9都已经到达接收端。这时候客户端只能等待服务器发送ACK。注意对于6、7、8、9号包,服务器不能发送ACK,这是滑动窗口机制决定的。因此,对于客户端而言,他不知道丢失了多少数据包。可能悲观的是,经过5个数据包也全部丢失,所以重传这5个数据包,比较浪费。快速重传快速重传机制“RFC5681”根据接收端的反馈信息触发重传,而不是重传定时器超时。刚才提到,基于定时器的重传往往需要等待很长时间,而快速重传使用了一个非常巧妙的方法来解决这个问题:如果服务端收到乱序的数据包,它也会回复ACK给客户端,只不过是一个重复的ACK而已。举刚才的例子,当收到乱序包6、7、8、9时,服务器都发送ACK=5,这样客户端就知道少了5。一般来说,如果客户端连续3次收到重复的ACK,它会重传相应的数据包,而不会等待定时器超时。但是快速重传还是没有解决第二个问题:应该重传多少包?具有选择性确认的改进重传方法是SACK(选择性确认)。简单的说就是在快速重传的基础上返回最近收到的报文段的序号范围,让客户端知道哪些数据包到达了服务器端。下面举几个简单的例子:case1:第一个包丢了,剩下的7个包都收到了。当接收到这7个数据包中的任何一个时,接收方都会返回一个带有SACK选项的ACK来通知发送方它收到了哪些乱序的数据包。注意:LeftEdge和RightEdge是这些乱序数据包的左右边界。触发ACK左边缘右边段5000(丢失)550055005500600060006000500055006500650065005000500055007000700070007000500055007500750075005500550055008000800080005000550055008500850085005500550055009000Case2:数据包丢失。收到第一个包时,没有乱序的情况,正常回复ACK。当收到第3、5、7个包时,由于包乱序,用SACK回复ACK。因为本例中有很多分片段,所以对应的Block段也有很多组。当然,由于option字段的大小限制,Block也有上限。触发ACK第一个块第二个块第三个块段LeftRightLeftRightLeftRightEdgeEdgeEdgeEdgeEdge500055005500(丢失)60005500600065006500(丢失)7000550070007500600065007500(丢失)8000550005007500600065008500(lost)然而,SACK规范“RFC2018”有点坑爹。接收方在提供SACK告诉发送方信息后可能会“食言”。也就是说,接收方可能会将这些(乱序的)数据包删除,然后通知发送方。以下内容摘自“RFC2018”:请注意,数据接收方可以丢弃其队列中尚未向数据发送方确认的数据,即使数据已在SACK选项中报告。不鼓励丢弃SACKed数据包,但如果接收器用完缓冲区空间,则可以使用。最后一句说这个措施可以在接收者的缓冲区快用完的时候采取。当然,不推荐这种行为。..由于这种操作,发送方在收到SACK后,不能直接清除重传缓冲区中的数据,直到接收方发送一个大于其最大序列号的正常ACK号。此外,重传定时器也会受到影响。重传定时器应该忽略SACK的影响。毕竟,接收方删除了数据并丢失了数据包。DSACK扩展了DSACK,即重复SACK。该机制在SACK的基础上,携带附加信息,告知发送方哪些数据包被自己重复接收。DSACK的目的是帮助发送方确定是否发生了数据包乱序、ACK丢失、数据包重复或错误重传。让TCP做更好的网络流量控制。关于DSACK,《RFC2883》中有很多例子。有兴趣的读者可以阅读。这里我就不细说了。超时和重传的内容大概就这么多,希望对大家有所帮助。如果这篇文章对你有帮助,欢迎关注我对公众号tobe的狂欢,带你深入电脑的世界~公众号后台回复关键词【电脑】有惊喜哦~
