首先我们自己思考如何设计一个超时时间的计算公式,超时时间一般肯定是和数据的传输时间有关系的,他必然要大于数据的往返时间(数据在发送端接收端往返一趟所用的时间)。好,那么我们就从往返时间下手,可是又有一个问题就是往返时间并不是固定的我们有如何确定这个值呢?自然我们会想到我们可以取一小段时间的往返时间的平均值来代表这一时间点的往返时间,也就是微积分的思想!
好了我们找到了往返时间(RTT),接下来的超时时间应该就是往返时间再加上一个数就能得到超时时间了。这个数也应该是动态的,我们就选定为往返时间的波动差值,也就是相邻两个往返时间的差。
下面给出我们所预估的超时时间(TimeOut)公式:
TimeOut = AvgRTT2 + | AvgRTT2 - AvgRTT1 |很好,看到这里其实你已经差不多理解了超时时间的计算方式了,只不过我们这个公式不够完善,但是思路是对的。我们这时候来看看 TCP 的实现者们采用的方式。
RTT_New = (1-a)RTT_Current + a*Avg_RTT (计算平均 RTT,a 通常取0.125) DevRTT = (1-b)DevRTT + b|RTT_New - Avg_RTT| (计算差值,b 通常取0.25) TimeOut = RTT_New + 4*DevRTT (计算超时时间)好的,这就是 TCP 实现的超时时间的方式,但是在实际的应用中并不是一直采用的这种方式。假如说我们现在网络状态非常的差,一直在丢包我们根本没必要这样计算,而是采用直接把原来的超时时间加倍作为新的超时时间。
总结:好的现在我们知道了在两种情况下的超时时间的计算方式,正常的情况下我们采用的上面的比较复杂的计算公式,也就是 RTT+波动值 否则直接加倍
上面我们看到在发送方等待一个超时重传时间后会开始重传,但是我们计算的超时重传时间也不定就很准,也就是说我们经常干的一件事就会是等待,而且一般等的时间还挺长。那么可不可以优化一下呢?
当然,在 TCP 实现中是做了优化的,也就是这里说到的快速重传机制。他的原理就是在发送方收到三个冗余的 ACK 的时候,就开始重传那个报文段。那么为什么是三个冗余的 ACK 呢?注意三个冗余的 ACK 其实是四个 ACK 。我们先了解一下发送 ACK 策略,这个是 RFC 5681 文档 规定的。
第一种情况收到一个期望的有序的数据时,最多延时 500ms 发送一个 ACK 表示该数据及以前的数据都收到了。
第二种情况是收到一个期望的有序的数据时,前面的有序数据等待发送 ACK 的时候立即发送一个 ACK 捎带确认前面那个数据,也就是第一个数据还在延时的时候又来一个那么久两个一起确认。
第三种情况,收到比期望序号大的数据的时候立即发送冗余 ACK ,ACK 确认的值就是中间缺少的第一个序号的值。
收到能部分填充或者完全填充中间缺少的数据的,如果这个报文是起始于缺少的数据的低端就立即发送一个 ACK。
好的,那么现在我们可以看到如果出现了三个冗余的 ACK 他只可能是发生了两次情况三,也就是发送了两个比期望值大的数据。但是注意出现情况三有两种可能,一个是丢包,另外一个是乱序到达。
比如说我们现在是数据乱序到达的,我们来看一下。
第一种乱序情况
另外一种乱序
丢包情况
结论: 很显然我们可以看到,如果发生了乱序有可能会出现三次冗余 ACK,但是如果发现了丢包必然会有三次冗余 ACK 发生,只是 ACK 数量可能更多但是不会比三次少
在我们发现丢包以后我们需要重传,但是我们重传的方式也有两种方式可以选择分别是 GBN 和 SR 翻译过来就是 拉回重传 和 选择重传 。好其实我们已经能从名字上面看出来他们的作用方式了,拉回重传就是哪个地方没收到那么就从那个地方及以后的数据都重新传输,这个实现起来确实很简单,就是把发送窗口和接受窗口移回去,但是同样的我们发现这个方式不实用干了很多重复的事,效率低。
那么选择重传就是你想到的谁丢了,就传谁。不存在做无用功的情况。
结论: TCP 实际上使用的是两者的结合,称为选择确认,也就是允许 TCP 接收方有选择的确认失序的报文段,而不是累计确认最后一个正确接受的有序报文段。也就是跳过重传那些已经正确接受的乱序报文段。
数据校验,其实这个比较简单就是头部的一个校验,然后进行数据校验的时候计算一遍 checkSum 比对一下。
3. 数据合理分片和排序