TCP-超时与重传

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010853261/article/details/82109971

主要内容:

  1. 超时与重传简单实例(见TCP/IP详解)
  2. 设置超时重传方法
  3. 基于计时器重传
  4. 快速重传
  5. 带选择确认的重传
  6. 伪超时与重传
  7. 包失序与包重复
  8. 目的度量
  9. 重新组包
  10. 与TCP重传相关的攻击

TCP重传机制

由于下层IP网络层或则路由器可能出现丢失、重复、失序包的情况、TCP要提供可靠数据传输服务必须cover住这些丢包重包异常情况并能够做出正常的处理。

注意,接收端给发送端的Ack确认只会确认最后一个连续的包,比如,发送端发了1,2,3,4,5一共五份数据,接收端收到了1,2,于是回ack 3,然后收到了4(注意此时3没收到),此时的TCP会怎么办?我们要知道,因为正如前面所说的,SeqNum和Ack是以字节数为单位,所以ack的时候,不能跳着确认,只能确认最大的连续收到的包,不然,发送端就以为之前的都收到了。

TCP的重传指:重传尚未确认的数据,TCP有两套独立的机制来完成重传:一是基于时间的,二是基于确认信息的构成。第二种比第一种更加高效。

(1)基于时间重传:TCP在发送数据的时候回设置一个计时器,若计时器超时任未收到数据的确认信息,则会引发相应的超时或则计时器的重传操作,计时器超时称为重传超时RTO(Retransmission Timeout)

(2)基于确认信息的构成重传称为快速重传:在没有发生延时的情况下,若TCP积累确认无法返回新的ACK或则当ACK包含的选择确认信息SACK表明出现失序报文段时,快速重传会推断出现丢失包。

1.设置超时重传RTO

TCP超时和重传的基础是:根据给定连接的数据传输往返延时RTT(Round-Trip Time)来设置RTO。

设置一个合理的RTO对于网络整体利用率至关重要。TCP会在传输过程中采样一些数据传输该确认信息所需要的时间的一些样本。每个此类的测量结果称为RTT样本。TCP首先根据一段时间内的样本值建立好的估计值,第二步是基于估计值设置RTO。RTO设置得当是保证TCP性能的关键。

下面主要介绍一些计算RTO的方法,只做概述,具体算法可查看TCP/IP详解。

经典方法

1)通过下面的公式计算得到平滑的RTT估计值(SRTT):

SRTT <- α(SRTT) + (1-α)RTTs

最新的SRTT是根据现存的SRTT和新的样本值RTTs计算出来的。 常量α为平滑因子,推荐值是0.8~0.9。这种计算方法叫做指数加权移动平均法或则低通过滤器。

根据前面求的SRTT计算出RTO:

RTO = min(ubound, max(lbound, (SRTT)β))

β是时延离散因子,推荐值1.3~2.0。 ubound是RTO上边界,lbound是下边界。这种计算方法就是经典方法。

这种方法缺点就是没法适应大规模的变动(网络不稳定情况)

标准方法:

具体公式就不给出了,主要说说大致方法论:基于RTT测量值的变化方差、平均值来得到较为精确的估计值RTO。这样更加能适应RTT变化幅度大的情况。

2. 基于计时器的重传

TCP发送端得到了基于时间变化的RTT测量值,就能据此设置RTO,发送报文段时应确保重传计时器设置合理。设置计时器前要记录报文序列号,若及时收到了报文的ACK,就取消计时器。TCP的连接发送端不断的设定和取消重传计时器,如果没有数据丢失就不会出现计时器超时。

TCP将超时重传视为相当重要的事件,当发生这种情况时候,通过降低当前数据发送率来对此进行快速响应。实现有两种方法:一是基于拥塞控制机制减小发送窗口,二是当一个报文段被再次重传时候,增大RTO的退避因子。 退避因子正常情况下是1,随着多次重传会加倍成2,4,8等等,直到上限。

大多数情况下计时器超时并触发重传是不必要的,因为RTO的设置一般大于RTT的2倍,因此基于计时器的重传会导致网络利用率下降。幸运的是,TCP有另外一种方法检测和修复丢包,因为该方法不需要触发计时器超时,所以称为快速重传。

3. 快速重传-Fast Retransmit

快速重传机制:基于接收端的反馈信息(ACK)来引发重传,而非重传计时器的超时。不以时间驱动,而以数据驱动重传。也就是说,如果,包没有连续到达,就ack最后那个可能被丢了的包,如果发送方连续收到3次相同的ack,就重传。Fast Retransmit的好处是不用等timeout了再重传。

比如:如果发送方发出了1,2,3,4,5份数据,第一份先到送了,于是就ack回2,结果2因为某些原因没收到,3到达了,于是还是ack回2,后面的4和5都到了,但是还是ack回2,因为2还是没有收到,于是发送端收到了三个ack=2的确认,知道了2还没有到,于是就马上重转2。然后,接收端收到了2,此时因为3,4,5都收到了,于是ack回6。示意图如下:
这里写图片描述

Fast Retransmit只解决了一个问题,就是timeout的问题,它依然面临一个艰难的选择,就是,是重传之前的一个还是重传所有的问题。对于上面的示例来说,是重传#2呢还是重传#2,#3,#4,#5呢?因为发送端并不清楚这连续的3个ack(2)是谁传回来的?也许发送端发了20份数据,是#6,#10,#20传来的呢。这样,发送端很有可能要重传从2到20的这堆数据(这就是某些TCP的实际的实现)。可见,这是一把双刃剑。

SACK 方法

另外一种更好的方式叫:Selective Acknowledgment (SACK)(参看RFC 2018),这种方式需要在TCP头里加一个SACK的东西,ACK还是Fast Retransmit的ACK,SACK则是汇报收到的数据碎版。参看下图:

这里写图片描述

这样,在发送端就可以根据回传的SACK来知道哪些数据到了,哪些没有收到。于是就优化了Fast Retransmit的算法。当然,这个协议需要两边都支持。在 Linux下,可以通过tcp_sack参数打开这个功能(Linux 2.4后默认打开)。

这里还需要注意一个问题——接收方Reneging,所谓Reneging的意思就是接收方有权把已经报给发送端SACK里的数据给丢了。这样干是不被鼓励的,因为这个事会把问题复杂化了,但是,接收方这么做可能会有些极端情况,比如要把内存给别的更重要的东西。所以,发送方也不能完全依赖SACK,还是要依赖ACK,并维护Time-Out,如果后续的ACK没有增长,那么还是要把SACK的东西重传,另外,接收端这边永远不能把SACK的包标记为Ack。

注意:SACK会消费发送方的资源,试想,如果一个攻击者给数据发送方发一堆SACK的选项,这会导致发送方开始要重传甚至遍历已经发出的数据,这会消耗很多发送端的资源。详细的东西请参看《TCP SACK的性能权衡》

Duplicate SACK – 重复收到数据的问题

Duplicate SACK又称D-SACK,其主要使用了SACK来告诉发送方有哪些数据被重复接收了。RFC-2883 里有详细描述和示例。下面举几个例子(来源于RFC-2883)

D-SACK使用了SACK的第一个段来做标志,

  • 如果SACK的第一个段的范围被ACK所覆盖,那么就是D-SACK
  • 如果SACK的第一个段的范围被SACK的第二个段覆盖,那么就是D-SACK

示例一:ACK丢包
下面的示例中,丢了两个ACK,所以,发送端重传了第一个数据包(3000-3499),于是接收端发现重复收到,于是回了一个SACK=3000-3500,因为ACK都到了4000意味着收到了4000之前的所有数据,所以这个SACK就是D-SACK——旨在告诉发送端我收到了重复的数据,而且我们的发送端还知道,数据包没有丢,丢的是ACK包。

Transmitted  Received    ACK Sent
Segment      Segment     (Including SACK Blocks)

3000-3499    3000-3499   3500 (ACK dropped)
3500-3999    3500-3999   4000 (ACK dropped)
3000-3499    3000-3499   4000, SACK=3000-3500

示例二,网络延误

下面的示例中,网络包(1000-1499)被网络给延误了,导致发送方没有收到ACK,而后面到达的三个包触发了“Fast Retransmit算法”,所以重传,但重传时,被延误的包又到了,所以,回了一个SACK=1000-1500,因为ACK已到了3000,所以,这个SACK是D-SACK——标识收到了重复的包。

这个案例下,发送端知道之前因为“Fast Retransmit算法”触发的重传不是因为发出去的包丢了,也不是因为回应的ACK包丢了,而是因为网络延时了。

Transmitted    Received    ACK Sent
Segment        Segment     (Including SACK Blocks)

500-999        500-999     1000
1000-1499      (delayed)
1500-1999      1500-1999   1000, SACK=1500-2000
2000-2499      2000-2499   1000, SACK=1500-2500
2500-2999      2500-2999   1000, SACK=1500-3000
1000-1499      1000-1499   3000
               1000-1499   3000, SACK=1000-1500

可见,引入了D-SACK,有这么几个好处:

1)可以让发送方知道,是发出去的包丢了,还是回来的ACK包丢了。

2)是不是自己的timeout太小了,导致重传。

3)网络上出现了先发的包后到的情况(又称reordering)

4)网络上是不是把我的数据包给复制了。

知道这些东西可以很好得帮助TCP了解网络情况,从而可以更好的做网络上的流控。

Linux下的tcp_dsack参数用于开启这个功能(Linux 2.4后默认打开)

猜你喜欢

转载自blog.csdn.net/u010853261/article/details/82109971