TCP可靠传输机制

概述

TCP实现可靠传输主要采用如下4种机制:

  1. 字节编号机制

    TCP对数据部分一一编号,确保每个字节的数据都是有序的。

  2. 数据段确认机制

    每接收到一个数据段都要返回一个确认数据段,其中确认号表明已经正确接收的数据段。

  3. 超时重传机制

    通过重传定时器(RTT, Retransmission Timer)来发现丢失的段,发送一个数据段就会启动定时器,如果定时器过期仍然没接收到确认段的话则会重新传输该段。

  4. 选择性确认机制

    在SACK的支持下,可以只确认重传部分数据,而不重传已正确接收的数据。

在以上机制中,字节编号最容易理解,只是单纯的按字节编号,接收端根据这个编号就可以发现接收的数据是否有丢失错漏等。其他在后面一一详解。


数据段确认机制

简而言之,即是接收方返回确认号来确认已正确接收的内容。但仍然有如下特性要理解透彻:

  1. TCP可一次连续发送多个数据段

    TCP不需要等待对方的确认,就可以连续发送多个数据段,这样可以大大提高数据发送的效率。但一次性最多发送多少是受对方返回的窗口大小字段和当前可用发送窗口的双重限制的。窗口实际上就是缓存未被确认的数据段的(因为不可能再向应用程序要这些数据),因此未被确认的数据段总和不得超出窗口大小。

  2. 仅对连续接收的数据段进行确认

    返回的确认数据段里的确认号,确认的是已正确接收的连续数据段,而不是已接收的最高序号,这是不同的概念,因为中间的有可能并未正确接收。

  3. 不连续的序号会被缓存

    当主机接收到的序号不连续时,会先将其缓存在接收窗口中,等接收到中间的数据段后变为连续再按序组合一起提交。这也是为什么前面提到最多发送多少要取决于窗口大小的原因。


超时重传机制

这是保证数据可靠的另一个重要机制,前面的确认机制通过返回确认数据段来确认差错,但如果整个数据段丢失,对方自然就不会返回任何确认段。这时候就需要超时机制。

其原理是利用超时重传计时器(RTT, Retransmission Timer)来发现对方一定时间内都没有返回确认的数据段。

要注意的是,计时器超时后并不是立即发送的,因为找出对应的数据段重新安排发送是要消耗时间的,因此,重发时间间隔(RTO, Retransmission Time Out)总是大于RTT。

RTT的计算

确认RTT和RTO值是没有看起来容易的,因为实际通信中,途经的网络并不一定一致,而且拥塞情况也不会一成不变。这就造成不同数据段的RTT值应该设计为不一致的,甚至波动非常大,所以TCP需要动态跟踪这些变化并相应的做出改变。

因为RTT值不固定,因此出现了平滑RTT(SRTT, Smooth RTT)的概念。

平滑RTT(SRTT)

在最初的RFC793中,其计算公式如下:

SRTT=oldSRTT+(1a)RTT

其初始值就是第一个RTT值,这里的a就是一个平滑因子,它决定了旧SRTT值所占的权重a的取值范围介乎于0~1之间,如果a越接近0,则表示新旧SRTT值接近,变化小,反之则变化大。这种方式计算出的SRTT值会更加的平滑,更加接近网络环境。

RFC2988推荐a的典型值应为0.125。

RTO的计算

这个取决于RTT超时后多久才重传,这是必须要考虑的事情。

一般情况下,TCP使用 b*SRTT(b>1) 作为 RTO,b的初值一般为2。也就是说,两倍SRTT时间后仍然没收到确认数据段才重传。

RTTD

但经验表明,常数的b不够灵活,于是1988年Jacobson提出了用平均偏差作为标准差的新估计算法,要求维护另一个平滑的偏差RTTD。

当一个确认数据段到达时,就可以得出SRRT和新的RTT样本值之间的偏差:

RTTD=SRTTRTT

这样就可以取得初值,随后的测量中按以下公式计算:
RTTD=oldRTTD+(1a)(SRTTRTT)

这里的a通常取值为0.25。

虽然RTTD不能完全等同于标准偏差,但已经足够的反映SRTT值的动态变化,现在大多数TCP实现都是用该算法,RTO会设置为

RTO=SRTT+4RTTD

另外,仍然有一个问题,就是对于重传的数据段如何计算RTT值,因为发送方不可知对方的确认数据段到底是因为收到重传后还是重传前的而发送的,这样就会使其计算值有较大的偏差。所以现在一般的解决方法是,只要是重传的,就不将其作为计算SRTT和RTO的样本。

注意,当超时后,TCP会重传对应序号后面的所有数据,除非使用了选择性确认机制。


选择性确认机制(SACK)

上述提到的方法中,重传所有的段在很多时候都是昂贵的,因为很有可能只是中间的一部分出现错误。

为了避免这种现象,RFC3517中就提出了选择性重传机制,就是在TCP数据段头部的可选字段中,添加一个支持SACK的选项。

为了使用SACK,就必须在建立TCP传输连接时的SYN数据段中包含SACK-Permit字段。然后在其他数据段中包含SACK字段,在这个字段里,就包含了已接收到的不连续的数据段。

可以预见的是,SACK字段也仅在ACK标志为1时才有效

SACK-Permit字段

该字段如下表所示:

大小(比特) 8 8
字段 类型 长度

在连接建立时,该字段为固定值:

  1. 类型(Kind):固定为4,表示允许使用SACK扩展选项,占8位。
  2. 长度(Length):固定为2,表示SYN数据段中SACK允许扩展选项占2字节,占8位。

SACK字段

而其他数据段中,其类型值固定为5,表示这是非连接建立时的SACK选项,长度则按照实际可变。如下图所示:

TCP可靠传输机制

上图来自《深入理解计算机网络》

在长度后面的就是n个用于表示不连续的数据段起始和结束号的部分,每个序号占32位。

由于TCP头部可选项字段最长为40字节,而SACK头占用了2个字节,表示一个不连续的数据段需要8个字节,因此,通过计算可知,在实际应用中,SACK最多标识出4个不连续的数据段,其长度字段最高为34。