5.3.8 超时估计

在 TCP 的发送空间里,有一部分数据称为“已发送未确认”,如图5-117所示:

图5-117 已发送未确认数据

应该说,“已发送未确认”的数据是必然存在的,只是存在的时间长短的问题。一个报文中的数据,在发送出去那一刹那,其状态肯定是“已发送未确认”。在一切都正常的情况下,TCP 发送方会在较短的时间内收到对方的 ACK 报文:确认这些数据已经被收到。于是这些“已发送未确认”的数据的状态会变成“已发送已确认”——往事随风去,TCP 发送方再也不会关心这些数据。

但是,如果由于某些原因(比如网络丢包、时延,TCP 接收方处理缓慢),TCP 发送方可能在一段时间内都收不到对方的 ACK 报文。此时,TCP 发送方该怎么办?

死等?显然不是办法!如果对方的那个 ACK报文被网络丢包了,那死等的结果就是把这个 TCP 连接给“等死”了。

TCP 的对策是“超时重传”:在一定的时间内,如果还没有收到对方相应的 ACK 报文,那么 TCP 发送方会将这部分数据重新发送给对方。

超时重传的机制没有问题,问题是“超时”的事件确定,也就是说,TCP 发送方等多少时间认为是超时了?如果超时时间过长,则发送效率有问题(时间都花在不必要的等待上了),如果超时时间过短,则会产生不必要的重复发送,如图5-118所示:

图5-118 不必要的重复发送

图5-118中,T1 时刻,A 给 B 发送了一批数据,T2 时刻,B 给 A 回以1个 ACK 报文,T3 时刻,A 还没有收到 B 的 ACK 报文,它以为超时了,于是就“迫不及待”地又将该报文再发给 B。这就产生了不必要的重复发送。

重复发送本身不不太可能避免,除非 A 将超时时间设置得足够长(比如1年),否则总会有像图5-118所描述的事情发生。但是如果超时时间设置的过短,就会使不必要的重复发送太多。要知道未能及时收到对方的 ACK 报文,很大的原因是因为网络拥塞,而这种迫不及待所产生的大量的不必要的重复发送,会更加加剧网络拥塞,从而造成恶性循环。

超时时间,长也不行,短也不行,那么到底该等于多少是合适的呢?

山一程,水一程,身向榆关那畔行,夜深千帐灯。

风一更,雪一更,聒碎乡心梦不成,故园无此声。

在 TCP 超时估计者的眼里,网络可真是“山一程,水一程,风一更,雪一更”,总之是变幻莫测,而超时估计也得紧跟这种变幻,随时更新超时时间的长短。

为了能做到随时更新,RFC 793、RFC 6298、RFC 1323 都分别提出了各自的 TCP 超时估计算法。在介绍这些算法之前,我们先看看 TCP 超时估算的本质。

5.3.8.1 TCP 超时估算的本质

为了介绍 TCP 超时估算的本质,我们先介绍一个名词:RTT,Round Trip Time。正如这个词组本身的含义所表明的,RTT 的意思就是一对报文的往返时间,如图5-119所示:

图5-119 RTT 示意

图5-119中,T1.1 时刻,A 给 B 发送了1个报文(SEG1),T1.2 时刻,A 收到了 B 对应的 ACK 报文,这样的一来一往,称为一对报文,而 T1.2 - T1.1 这个时间差,也就称为 RTT。

第2个要介绍的名词是 RTO(Retransmission Timeout,重传超时),也就是 TCP 在发送1个报文以后,所需要等待的时间。如果在这个时间段内一直没有收到对方的相应的 ACK 报文,那么就需要对该报文进行重传。

事实上,由于网络是变化的,TCP 一直不知道 RTO 应该等于多少,但是 TCP 还必须对 RTO 做一个估算,因为它发送1个报文以后,必须要设置1个重传超时的时间——前文已经说过,它总不能在那死等。

而估算 RTO(Retransmission Timeout,重传超时)的算法,就是基于以前的 RTT,对下一次 RTT 做一个预测,如图5-120所示:

图5-120 对下一次 RTT 的估算

图5-120中,TCP 已经发送和 n -1 个报文,也收到了对应的 n - 1 个 ACK 报文,所以 TCP 就知道了 RTT1(等于 T1.2 - T1.1)、RTT2(等于 T2.1 - T2.1) ...... RTTn-1(等于 Tn-1.2 - Tn-1.1)。现在的问题是:在 Tn.1 时刻,TCP 发送了报文(运载数据 Data_n),那么 Tn.2 应该等于多少?也就是说,在什么时间,A 会收到 B 的对应的 ACK 报文。

假设上帝告诉了 A ,Tn.2 的值应该等于多少,那么 RTOn 就等于 RTTn = Tn.2 - Tn.1。如此一来,对于 TCP 就简单了:A 在 Tn.1 时刻发送了报文 SEGn,只要等待 RTOn 时间还没有收到 B 的对应的 ACK 报文,A 就认为是超时了,就可以重传报文 SEGn——绝对不会错误重传,因为是上帝告诉了 A 的超时时间。

但是,现实中,上帝是不存在的,最起码对于 TCP 来说,没有上帝告诉它发送1个报文之后,它的超时时间(RTO)应该是多少。关于 RTO 的估算,只能靠人类算法的估算。

最简单的估算算法就是认为 RTO 等于1个固定值,比如就是等于1分钟。这个算法好记易懂,如果让我考试的话,我真希望 TCP 就是采用这个算法,^_^。

但是这个算法过于简单,无法适应网络的变化。可是网络的变化在哪里呢?正如图5-120所示,TCP 知道了过去的一段时间的网络的变化(因为它知道了 RTT1、RTT2 ... RTTn-1),那么 TCP 就需要基于这些过去的变化,对未来(当前这一次发送的报文)的超时时间做一个预测。

所以说,TCP 超时估算的本质,就是认为未来网络的变化,与网络过去的变化,有一定的相关性。事实上,如果相隔时间不长(图5-120中的 Tn.1 与 Tn-1.1 的时间间隔),这种相关性存在的概率是比较大的。

5.3.8.2 RFC 793 的超时估算算法

RFC 793 的超时估算算法分为两部分:(1)对于 RTT 的估算;(2)基于估算的 RTT,计算 RTO。

RFC 把对于 RTT 的估算,称为 SRTT(Smoothed Round Trip Time,平滑往返时间),为什么要用“平滑”这个单词呢,我们先看看它的估算公式:

SRTTi+1 = ɑ * SRTTi + (1 - ɑ) * RTTi, i = 1, 2, 3 ...

其中:初始值 SRTT1 是一个经验值,比如等于 60(秒);ɑ 是一个常数,也是一个经验值,介于 0.8~0.9 之间。

单独看这个公式,可能看不出“平滑”的含义,我们将这个公式展开:

SRTT2 = ɑ * SRTT1 + (1 - ɑ) * RTT1

SRTT3 = ɑ * SRTT2 + (1 - ɑ) * RTT2

= ɑ * (ɑ * SRTT1 + (1 - ɑ) * RTT1) + (1 - ɑ) * RTT2

= ɑ2 * SRTT1 + ɑ * (1 -ɑ) * RTT1 + (1 - ɑ) * RTT2

SRTT4 = ɑ * SRTT3 + (1 - ɑ) * RTT3

= ɑ * (ɑ2 * SRTT1 + ɑ * (1 -ɑ) * RTT1 + (1 - ɑ) * RTT2) + (1 - ɑ) * RTT3

= ɑ3 * SRTT1 + ɑ2 * (1 - ɑ) * RTT1 + ɑ * (1 - ɑ) * RTT2 + (1 - ɑ) * RTT3

SRTTn = ɑn-1 * SRTT1 + ɑn-2 * (1 - ɑ)RTT1 + ɑn-3 * (1 - ɑ) * RTT2

+ ɑn-1-i * (1 - ɑ) * RTTi + ɑ0 * (1 - ɑ) * RTTn-1

= ɑn-1 * SRTT1 + (1 - ɑ) * Σɑn-1-i * RTTi, i ∈ [1, n-1]

根据以上所推导的  SRTTn 的公式,可以看出:

(1)这个公式,其实用大白话说就是将历史上的所有 RTT 加权平均,这也就是所谓的“平滑”的含义吧,ɑ 也被称为平滑因子(smoothing factor)。

(2)因为 ɑ 介于 0.8~0.9 之间,所以越“古老”的 RTT,其权重越低——俗话说的好,远亲不如近邻,^_^

RFC 估算出了 SRTT 以后,它还要经过一道转换,才得出 RTO:

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

其中 UBOUND、LBOUND、β 都是经验数值,分别等于:60秒、1秒、1.3~2.0之间,当然,你也可以有自己的经验数值。

这个公式没有什么高深的,它就是一个经验公式,是对 RTT 的一个修正,它的意思是:

(1)为了避免错误的重传,需要保守一点,将 RTO 的值设置的大一点,就是将估算出的 SRTT 增大到 β * SRTT 后再作为 RTO

(2)如果增大到 β * SRTT 后还是小于 LBOUND,那就再保守一点,将 RTO 设为 LBOUND(比如1秒)

(3)当然 RTO 也不能太保守,一味地设为“大”值,这样的话,会影响发送效率,所以将其最大值设为 UBOUND(比如60秒)

5.3.8.3 RFC 6298 的超时估算算法

在 RFC 793 中,RTO 的计算公式为:

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

如果 β * SRTT 的最小值大于1秒,最大值小于60秒,那么 RTO 的计算公式可以简化为:(假设 β = 1.3)

RTO = β * SRTT = 1.3 * SRTT = SRTT + 0.3 * SRTT

这个公式的本质是什么?为什么 RTO 不是直接等于 SRTT?为什么还要加上 0.3 * SRTT?

RFC 793 称 β 为方差因子(and BETA is a delay variance factor (e.g., 1.3 to 2.0))。为什么要引入方差因子呢?我们举一个简单的例子,如图5-121所示:

图5-121 平均值与偏差

图5-121中,P1 = P3 = 10,P2 = P4 = 0。我们简化处理,也不计算加权平均,就只计算算术平均:M = (P1 + P2 + P3 + P4) / 4 = 5。假设我们拿这个平均值来预测 P5 的值,这其实就是简化的 SRTT 的估算公式(SRTT 是加权平均,这里是算术平均),这样我们就得到 P5 的预测值 E5 = M = 5。

而实际上,我们假设 P5 = 10,但是我们的预测值却是5,这中间的误差很大。为什么会出现这样的误差?是因为我们没有考虑到方差。P1~P4,4个值的平均值是5,但是这4个值与平均值之间的偏差都是很大的。在如此大的偏差情况下,用平均值(无论是加权平均还是算术平均)来预测下一个数据,其大概率会有很大的预测偏差。

也正是鉴于此,RFC 793 才会将 RTO 做一个偏差修正:(假设 β = 1.3)

RTO = β * SRTT = 1.3 * SRTT = SRTT + 0.3 * SRTT

但是,这个偏差修正系数虽然称为方差因子,其实却与方差没有任何关系,仅仅是一个经验公式罢了,而且它还不能动态反应网络的变化。

RFC 6298 的算法,正是对 RFC 793 的算法的一个改进:它真正将方差这一因素考虑了进去,而不是仅仅用一个经验系数进行修正。RFC 6298 算法又称 Jacobson/Karels 算法。

RFC 6298 算法如下:

(1)当 RTT 还没有被测量出来时,令 RTO = 1(秒)。以前的 RFC(RFC 2988)定义 RTO = 3(秒),而且有的 TCP 的具体的实现,仍然可能会采用3(秒)。不过我们不纠结于细节,这里就认为 RTO = 1(秒)

(2)当第1个 RTT 被测量出来以后,令:

SRTT1 = RTT1

RTTVAR1 = RTT1 / 2

RTO2 = SRTT1 + K * RTTVAR1  ----(X)

其中:K 是一个经验数据,等于4;RTTVAR 是 round-trip time variation(往返时间变化)的缩写,它的本意是要借用方差(variance)这一概念(下文会介绍 RTTVAR 与方差之间的关系)。也正是由于 RTTVAR 借用了方差这一概念,RTO 的预测(RTO2)也就有了方差的味道。

实际上 RTO 的预测不仅有了方差的味道,而且还跟方差很相近。

(3)当后续的 RTT 继续被测量出来时,计算公式如下:

RTTVARi+1 = (1 - β) * RTTVARi + β * |SRTTi - RTTi|  ----(A)

SRTTi+1 = (1 - ɑ) * SRTTi + alpha * RTTi  ----(B)

RTOi+1 = SRTTi+1 + K * RTTVARi+1  ----(C)

其中,ɑ、β、K 都是经验数据,它们分别等于:1/8、1/4、4。这3个经验数据很有意思,它们虽然是一个经验值,但是非常“准确”,而且在计算机计算时,还可以通过移位来实现乘法,从而提高计算效率。

这里面有3个公式,其中公式(B)就是 RFC 793 所介绍的 SRTT 的估算公式,它是一个迭代公式,同时本质上也是一个加权平均公式。

公式(A)也是一个迭代公式,同时本质上也是一个加权平均公式。从含义上来讲,公式(A)是为了估算 SRTT 的估算偏差。我们看到 |SRTTi - RTTi| 其实是对标准差(方差的平方根)的一种近似。也就是说,这个公式采用了方差(标准差)的思想,但是实际计算时采用了近似算法。因为这种近似算法比真正的标准差的计算效率要高(标准差又要计算平方,又要计算平方根),这对于 TCP 转发效率来说非常重要。

知道了公式(A)、(B),再来看公式(C)就比较好理解了:

RTOi+1 = SRTTi+1 + K * RTTVARi+1

也就是说,RTO 的估算考虑了 RTT 的平滑估算(也就是 SRTT),也考虑了 RTT 方差的平滑估算(也就是 RTTVAR)。

如此看来,RFC 6298 比起 RFC 793 简单地采用 β * SRTT 来对 SRTT 进行修正要更加准确一些,也更能体现网络的动态变化。

我们总结一下 RFC 793 和 RFC 6298 的公式:

RT0 = β * SRTT  ----RFC 793(简化表述)

RT0 = SRTT + K * RTTVAR  ----RFC 6298

(1)RFC 793,对 RTT 做了平滑处理得到了 SRTT,SRTT 反应了网络的动态变化。但是 RFC 793 仅仅是对 SRTT 的偏差做了一个简单的修正(β * SRTT),这个简单修正没有反应出网络的动态变化。

(2)RFC 6298 除了对 RTT 做了平滑处理(SRTT),反应了网络的动态变化(这一点与 RFC 793相同),而且还对 SRTT 的偏差(均方差的近似公式)做了平滑处理,并且以这个平滑的偏差(RTTVAR)对 SRTT 进行修正,这个修正也反应了网络的动态变化。

所以说,RFC 6298 比 RFC 793 更加准确、更加能反应网络的动态变化。

关于 RFC 6298 我们还要补充两点:

第1点,与 RFC 793 一样,RFC 6298也规定了 RTO 的最小值(比如1秒)和最大值(比如60秒)。

第2点,RFC 6298还考虑了测量 RTT 的时钟精度。我们一般的手表的时钟精度是1秒,RFC 6298没有特别规定测量 RTT 的时钟精度,其精度可以是毫秒级别,也可以是秒级。当然这个精度也不能太粗糙,比如精度是小时级,那测量本身已经失去意义,从而对 RTO 的估算也就没有意义。

我们假设测量 RTT 的测量精度是 G(秒),那么上述的公式(X)和公式(C)要分别修正为:

RTO2 = SRTT1 + max(G, K * RTTVAR1)  ----公式(X)的修正

RTOi+1 = SRTTi+1 + max(G, K * RTTVARi+1)  ----公式(C)的修正

这两个修正也比较好理解,就是要抵消时钟的精度误差。

5.3.8.4 Karn 算法

Karn 算法也被称为 Karn-Partridge 算法,最早是由 Phil Karn 和 Craig Partridge 发表于1987年的“Improving Round-Trip Time Estimates in Reliable Transport Protocols ”——ACM SIGCOMM. pp. 2–7。

Karn 算法并不是一个完整的估算 TCP 超时的算法,它是对前文所描述的算法的补充。无论是 RFC 793 还是 RFC 6298,在测量 RTT 时,都没有考虑“超时-重传-ACK”的情形,如图5-122所示:

图5-122 超时-重传-ACK

图5-112中:T1 时刻,A 给 B 发送了1个报文 SEG1;T2 时刻,A 还没有收到 B 的 ACK 报文,认为超时了,于是又重新发送了 SEG1;T3 时刻,A 收到了 B 针对 SEG1的 ACK 报文。

现在问题来了:T3 时刻的 ACK 报文,是针对 T1 时刻的 SEG1 的回应,还是针对 T2 时刻的 SEG1 的回应?

傻傻分不清楚。不是 TCP傻,确实是无法分清。笔者以为,既然分不清楚,那就不去区分,直接就认为 T3 时刻的 ACK报文是针对 T1 时刻的 SEG1 所做的回应,也就是说,图5-122所描述的情形,其 RTT = T3 - T1。

当然,笔者的以为没有用,有一句话说的好:你以为你以为的就是你以为的?

业界公认的算法是 Karn 算法。Karn 算法也没有纠结于那个 ACK报文到底是针对谁的回应(因为确实是搞不清楚),它是直接将 RTO 的估值采用指数回退的方法翻倍。所谓指数回退,就是发现1次超时,就将 RTO 乘以2,再发现1次超时,再将 RTO 乘以2。不过更准确的说法,Karn 算法应该是将 SRTT 进行指数回退,因为参与迭代的是 SRTT 而不是 RTO。

5.3.8.5 RTTM

RTTM,Round-Trip Time Measurement,往返时间测量。在前文的描述中,关于 TCP 往返时间的测量,我们只是一笔带过。不过话又说回来,往返时间测量确实没有什么好讲述的,除非遇到了超时重传,如图5-123所示:

图5-123 往返时间测量

图5-123中,对于正常场景,只需要记录下 T1 和 T2 两个时刻,那么关于报文 SEG1 的往返时间 RTT 就能测量出来:RTT = T2 - T1。

图5-123中,对于超时重传场景,如前文所述,实在是没法测量报文 SEG1 的往返时间。针对这种场景,Karn 算法的思路是:既然无法测量 RTT 那就不测量,直接将 RTO(准确地说是 SRTT)进行指数回退。

应该说,Karn 算法是一种解决思路,但是有点过于简单粗暴,毕竟对于超时重传场景,不是绝对的无法测量,只是 TCP 机制上有点缺陷致使 ACK(SEG1) 与两个 SEG1 报文无法对应而已。

为此,TCP 提出了对应的解决方案:Timestamps Option(时间戳选项)。基于时间戳选项的往返时间测量机制,TCP 称为 RTTM 机制(RTTM mechanism)。

1)时间戳选项的基本概念

与其他 TCP 选项一样,时间戳选项的数据结构(报文结构)也符合 Kind-Length-Value格式,如图5-124所示:

图5-124 时间戳选项数据结构

时间戳选项一共有10个字节,包括:Kind 字段,占用1个字节,其值为8;Length 字段,占用1个字节,其值为10;TSval(Timestamp Value)字段,占用4个字节;TSecr(Timestamp Echo Reply)字段,占用4个字节。

TSval 和 TSecr 两个字段的含义,我们先通过1个例子来讲述,如图5-125所示:

图5-125 TSval 和 TSecr 的示意

图5-125中有 TSecr = ***、TSval = ###,这两个字段我们先忽略,然后再看图5-125所表达的含义。首先 A 给 B 发送了报文 SEG1,其中 TSval = 100,这个“100”代表什么我们先不纠结(下文会讲述),然后 B 给 A 回以1个 ACK 报文(ACK(SEG1)),这个报文中的 TSecr = 100,就是 B 所收到的报文 SEG1 中的 TSval。正如 TSecr(TS Echo Reply)名字所暗示的那样,TSecr 就是在 ACK 报文中,将它所对应的报文中的 TSval 给“弹”回去。

由于 TCP 是一个对称的协议,也就是说,B 会将 A 的 TSval 通过 TSecr 给弹回去,A 也可以通过 TSecr 将 B 的 TSval 给弹回去。所以,图5-125中有 TSecr = ***、TSval = ###,虽然没有写具体的值,它们的含义并没有特殊,笔者只是为了讲述方便,而将它们有意“隐藏”。下面给出1个完整的例子,通过这个例子我们可以更加直观地感受到 TSecr 是将 TSval 给弹回去,如图5-126所示:

图5-126 TSval 与 TSecr 的示意(2)

与其说 TSecr 将 TSval 给弹回去,不如说是“ecr(Echo Reply)”将“val(Value)”给弹回去,与 TS(Timestamp)无关,因为到目前为止,笔者仅仅将 TSecr、TSval 当作两个普通整数而已,与时间戳无关。

事实上,笔者以为所谓“时间戳”选项,其更本质的含义是“ID”,一个辅助 TCP Sequence Number 进行唯一标识报文的整数字段而已。我们再仔细看 TCP 如何确认1个报文的往返,如图5-127所示:

图5-127 正常场景下确认1个报文的往返

图5-127中,TCP 之所以能够确认 ACK 报文是对 SEG1 的确认,全凭对 ACK 报文中 AKN(Acknowledgement Number)的分析:因为 SEG1 的 SEQ = 1000、DataLen = 100,而 ACK 报文的 AKN = 1101 = SEG1.SEQ + SEG1.DataLen + 1,所以这个 ACK 报文就是对 SEG1 的确认,从而可以计算出 RTT(SEG1) = T2 - T1。

我们再来看看超时重传场景下 TCP 的困惑,如图5-128所示:

图5-128 超时重传场景下确认1个报文的往返

图5-128中,T1 时刻 A 向 B 发送了 SEG1 报文,T2 时刻又重传了1次,T3 时刻,A 收到了 B 的 ACK 报文。显然,如果仅仅根据 AKN,TCP 无法确认这个 ACK 报文是对 T1.SEG1 的确认还是对 T2.SEG1 的确认。

但是,如果再叠加上 TSval 和 TSecr,TCP 的困惑就迎刃而解:因为 ACK.TSecr = T1.SEG1.TSval = 100,所以这个 ACK 报文是对 T1.SEG1 的确认。

所以说,到目前为止,TSval/TSecr 的作用仅仅是为了和 TCP Sequence Number 一起唯一标识1个报文,也就是说,TSval(TSecr) + SEQ,唯一标识1个报文。这是 TSval/TSecr 的本质作用。

但是既然名字里由 TS(Timestamp)开头,TSval/TSecr 总得与时间戳有点关系,如图5-129所示:

图5-129 时间戳示意

图5-129中,为了简化描述,我们只举了1个正常的场景(没有举超时重传)的例子。图5-129还画了1个时钟示意,这是 TCP 的虚拟时钟,这个时钟与真实时钟的“准度”是基本一致的,比如1个真实的时钟走了1秒钟,这个虚拟的时钟也走了1秒钟,这样的话才能用这个虚拟时钟计算 RTT。

图5-129中,所谓 T1 时刻,指的就是虚拟时钟的数值 100,所以此时 SEG1 报文的 TSval 也等于 100。这个100的单位,取决于虚拟始终的精度,比如虚拟时钟的精度是毫秒,那么100就代表100毫秒的意思。

图5-129中,所谓 T2 时刻,也是当时虚拟时钟的数值,具体来说是200。而计算 RTT,TCP不是用公式“RTT = T2 - T1”进行计算,而是采用“RTT = T2 - ACK.TSecr”这个公式计算,因为 ACK.TSecr = SEG1.TSval = T1。

至此,我想已经很明显了:把 TSval/TSecr 与虚拟时钟挂钩,这样的话,TSval/TSecr 的作用除了是与 SEQ 联合标识1个报文以外,还能标识当时虚拟时钟的时间。

这样做的好处是:TCP 没有必要再保存 T1 时刻的时间,因为 ACK 报文中 TSecr 已经记录了该时间。笔者以为,这样做的好处是有限的,但是将 TSval/TSecr 与虚拟时钟挂钩,也就是以时间戳的面貌出现,其坏处应该是不少,最起码是增加了人们理解的复杂度。

当然,还是那句话,我们不纠结标准文案或者 RFC 的是是非非,只要不是大是大非的问题,他们说之,我们听之。

既然是虚拟时钟,我们还有几个问题:

(1)TCP 的双方共用1个虚拟时钟吗?

答:是2个,TCP 的双方,每方1个时钟。

(2)这两个时钟需要同步吗?

答:不需要,这两个时钟是解耦的。

(3)这两个时钟的精度需要一样吗?

答:不需要,这两个时钟是解耦的。

总之,TCP 双方各有1个时钟,这两个时钟是解耦的,它们既不需要同步,也不需要有相同的精度。

图5-129中,我们假设A 的虚拟时钟精度是毫秒,所以可以计算出报文 SEG1 的往返时间 RTT(SEG1) = 100毫秒。我们再举1个例子,就能直观地看出 TCP 双方的虚拟时钟是解耦的这一特征,如图5-130所示:

图5-130 B 的虚拟时钟

由于 TCP 是全双工的,所以 A 可以给 B 发送数据,B 同时也可以给 A 发送数据。图5-130中的 T3 时刻,与图5-129中的 T1 时刻,在真实时钟里,是同一时刻,比如都是2019年2月5日21点零8分800毫秒,但是由于两者都是虚拟时钟,而且是解耦的,所以我们在图5-129中看到 T1 的虚拟时钟是100,图5-130中的 T3 的虚拟时钟是500。这都没有关系,因为虚拟时钟既是相对的,也是解耦的。

另外,图5-130中的虚拟时钟的精度与图5-129中的时钟精度也不一样,它的精度是“10毫秒”,所以图5-130中计算出的 RTT(SEG2) = T2 - ACK.TSecr = 505 - 500 = 5 = 50毫秒。

2)时间戳选项的协商

Timestamps Option 既然是 TCP 的一个选项,那就需要协商。时间戳选项的协商,发生在 TCP 连接的创建过程。

A、B 之间创建连接,假设 A 是连接创建的主动发起方,也就是说 A 首先发起 SYN 报文。此时,如果 A 期望能有时间戳选项,那么 A 就可以在 SYN 报文中附带上时间戳选项。如果 B 也支持时间戳选项,那么 B 就可以在<SYN、ACK>报文中也附带上时间戳选项。如此一来,A、B 之间的时间戳选项协商成功。

当然,也可以由 B 发起协商。也就是说,A 发送的 SYN 报文并没有包含时间戳选项,但是 B 发送的<SYN、ACK>报文中附带上时间戳选项,然后 A 在随后的 ACK 报文中再附带上时间戳选项,如此一来,A、B 之间的时间戳选项也是协商成功。

发起时间戳选项协商的第1个报文,它的 TSecr 应该填写为0,因为它是第1个报文,它也不知道应该 Echo Reply 什么,那就填个0吧。

如果协商成功,那么在接下来的报文中,除了 RST 报文,TCP 双方都必须带上时间戳选项。RST 报文不强制要求,但是也可以带上时间戳选项。

如果协商成功,后续的非 RST(non-<RST>)报文中没有带有时间戳选项,TCP 接收方应该丢弃该报文(但是不能 abort 该连接)。

如果未能在三次握手中协商成功时间戳选项,那么在接下来的报文中,如果携带了时间戳选项,TCP 接收方必须忽略该选项字段。

啰里啰嗦这么多,我们用“三字经”来总结时间戳选项的协商吧:

时间戳,须协商。败与未,不能用。协商成,必须用。

3)RTTM 的规则

前文说过,RTTM(Round-Trip Time Measurement,往返时间测量)这个词组特指利用时间戳选项所进行的 RTT 测量。为了能更加有效、精确地测量 RTT,TCP 不仅仅是简单采用时间戳选项(与 SEQ 一起)进行报文标识,它还得遵循一定的规则。

我们先看1个例子,如图5-131所示:

图5-131 发送时间间隔

图5-131比较繁琐,笔者建议您仔细阅读该图。

图5-131中,我们假设接收方收到报文和发送回应的 ACK 报文之间的时间间隔为0,比如图中的 T2时刻,既是 B收到报文 SEG1 的时刻,也是发送报文 ACK1 的时刻。

图5-131中,还有两点特别重要:(1)虽然报文 SEG1、SEG2、SEG3 没有标识 ACK,但是它们的 ACK 同样等于1,也就是说 SEG1~SEG3,同样也是 ACK 报文;(2)虽然报文 ACK1 标识了 ACK,但是它同样也是发送了数据,只是为了画图清晰,才仅仅标识了 ACK 而已,图中 T3 时刻,A 收到 ACK1 以后马上就发送了 SEG2,这里面的潜台词就是——ACK1 包含了数据,SEG2 也是对 ACK1 所包含数据的确认(ACK)。

图5-131中,T5 和 T6 之间,有一个时间间隔 Δt,这是因为:(1)ACK2 报文中没有包含数据,不然的话,A 必须在 T5 时刻发上回以1个对应的 ACK 报文;(2)在 T5 时刻,A 没有数据需要发送,待等了 Δt,也就是在 T6 时刻,A 才有数据需要发送,也就是发送了报文 SEG3。

以上的说明也比较繁琐,笔者同样建议您仔细阅读,然后我们再开始正式讲述。

图5-131中,对于 A 来说它一共发送了3个报文 SEG1~SEG3,同样也收到了对应的 ACK 报文 ACK1~ACK3。按照前文的描述,可以计算出相应的 RTT:

RTT_A1 = T3 - ACK1.TSecr(T1)

RTT_A2 = T5 - ACK2.TSecr(T3)

RTT_A1 = T8 - ACK3.TSecr(T6)

图5-131中,对于 B 来说,它一共发送了3个报文,其中对于 ACK1、ACK2 报文来说,其对应的报文是 SEG2、SEG3,所以也可以计算出相应的 RTT:

RTT_B1 = T4 - SEG2.TSecr(T2)

RTT_B2 = T7 - SEG3.TSecr(T4)

以上的计算,RTT_A1、RTT_A2、RTT_A3、RTT_B1 都没有问题,但是 RTT_B2 的计算却有问题,因为从 T4 到 T7,实际上是经历了:T4~T5、T5~T6、T6~T7等3个时间段,而 T5~T6 时间段,与网络传输无关,纯属“等待”时间——这段时间内,A 没有数据需要发送,所以才等了一段时间。

如果把“等待发送”时间也算作 RTT 的一部分,这显然是错误的——说的夸张点,如果等待了1年,总不能认为 RTT 就是1年。

回忆一下这个等待时间为什么会发生?也就是说 T5~T6 之间为什么会有时间间隔?这固然是因为这段时间内 A 没有数据需要发送,但是更重要的是因为报文 ACK2 里面没有包含数据——如果 ACK2 里面包含数据,A 必须马上应答(暂不考虑延迟应答),也就不存在 T5~T6 之间有一个时间间隔。

正是如此,TCP 提出了第1个 RTTM 规则:要计算1个报文的“往返”时间,这个报文必须包含数据,否则就忽略该报文的 RTT(该报文的 RTT 不参与 RTO 的迭代计算)。

RFC 7323 关于这个规则的描述,是另一种表达方法,也许它的表达更准确更严谨吧,但是把那个表述仔细分析一下,它的含义其实就是笔者所说的这种大白话。RFC 7323 的表述是:

A TSecr value received in a segment MAY be used to update the averaged RTT measurement only if the segment advances the left edge of the send window, i.e., SND.UNA is increased.

我们在讲述 RTTM 的第1个规则时,特别说明“暂不考虑延迟应答”,而 RTTM 的第2个规则,恰恰就是关于延迟应答的,如图5-132所示:

图5-132 延迟应答中的 TSecr

图5-132中,B 收到了 A 发送的报文 SEG1 后,并没有马上应答,而是等待再收到 SEG2、SEG3 后又等待了一小段时间才发送了应答报文 ACK。这个应答报文的 AKN = 130,也就是说1个 ACK 报文应答了 SEG1、SEG2、SEG3等3个报文。但是这个应答报文的 TSecr 应该等于多少呢?

我们直接说答案:在延迟应答场景中,1个 ACK 报文应答了多个的报文,这个 ACK 报文的 TSecr 等于这些被应答的报文中的第1个报文的TSval。

也就说说,图5-132中,A 一共发送了3个报文,但是它只会也只能计算第1个报文(SEG1)的往返时间:RTT_SEG1 = T4 - ACK.TSecr(T1)。

这就是 RTTM 的第2个规则。想一想,这也是有其道理的:毕竟延迟应答中的延时,也是广义的网络传输的一部分。

还有一个问题,延迟应答会使得有效的 RTT 变少(图5-132中,A 发送了3个报文,却只计算出1个 RTT),会不会影响 RTO 的估算精度呢?RFC 7323 认为无伤大雅:

When [RFC1323] was originally written, it was perceived that taking RTT measurements for each segment, and also during retransmissions, would contribute to reduce spurious RTOs, while maintaining the timeliness of necessary RTOs.  At the time, RTO was also the only mechanism to make use of the measured RTT.  It has been shown that taking more RTT samples has only a very limited effect to optimize RTOs.

在前面的讲述中,我们一直都是假设 TCP 的报文是按顺序到达的,但是如果是乱序呢?RTTM 的第3个规则讲述的就是面对乱序时,TSecr 应该等于多少。不过这个规则与 TCP 如何处理乱序强相关,我们放到以后再说。

传输层协议(11):超时重传相关推荐

  1. Linux_网络_传输层协议 TCP通信滑动窗口(快重传),流量控制,拥塞控制(慢启动),延迟应答,捎带应答,TCP常见问题(字节流,粘包),Listen半连接队列

    紧跟Linux_网络_传输层协议 TCP/UDP继续补充 文章目录 1. TCP通信时滑动窗口(效率) 2. 流量控制(可靠性) 3. 拥塞控制(慢启动) 4. 延迟应答 5. 捎带应答(提高通信效率 ...

  2. 8月11日 网工学习 APR协议 传输层协议 TCP UDP 数据封装转发全过程

    目录 APR协议 传输层协议 TCP UDP 数据封装转发全过程 APR协议 作用:将IP地址解析为MAC地址 ARP的主要内容 在ARP高速缓存表中查找目的IP地址对应的MAC地址 广播发送ARP请 ...

  3. 搬砖:新一代基于UDP的低延时网络传输层协议——QUIC详解

    技术扫盲:新一代基于UDP的低延时网络传输层协议--QUIC详解 本文来自腾讯资深研发工程师罗成的技术分享,主要介绍 QUIC 协议产生的背景和核心特性等. 1.写在前面 如果你的 App,在不需要任 ...

  4. 简述tcp协议三报文握手过程_华为原理 | 传输层协议amp;交换转发原理

    Interface GigabitEthernet0/0/0 ip address 12.1.1.2 255.255.255.0 arp-proxy enable \\华为接口下默认没有开启代理ARP ...

  5. 4-1:TCP协议之传输层的作用及传输层协议TCP和UDP

    文章目录 一:传输层的定义 二:通信处理 三:传输层协议 四:TCP协议的可靠和性能 一:传输层的定义 前面说过,IP首部有一个协议字段用于标识网络层(IP)的上一层采用哪一种传输层协议.根据这个字段 ...

  6. 前端工程师如何理解 TCP/IP 传输层协议?| 技术头条

    作者 | 浪里行舟 责编 | 郭芮 网络协议是每个前端工程师都必须要掌握的知识,TCP/IP 中有两个具有代表性的传输层协议,分别是 TCP 和 UDP,本文将介绍下这两者以及它们之间的区别. TCP ...

  7. 弱网络环境下最优调度和优化传输层协议方案

    一.背景 与有线网络通信相比,无线网络通信受环境影响比较大(例如高层建筑.用户移动.环境噪音.相对封闭环境等等),网络的服务质量相对来说不是非常稳定,导致用户经常会在弱信号的网络环境下通信.而当用户在 ...

  8. 传输层协议 ——— TCP协议

    文章目录 TCP协议 谈谈可靠性 TCP协议格式 序号与确认序号 窗口大小 六个标志位 确认应答机制(ACK) 超时重传机制 连接管理机制 三次握手 四次挥手 流量控制 滑动窗口 拥塞控制 延迟应答 ...

  9. Linux网络编程(传输层协议 )—tcp三次握手/四次挥手

    传输层协议:负责应用程序之间数据传输-TCP/UDP UDP协议: 16位源端-对端端口:用于描述识别通信两端进程 16位数据报长度:能够存储最大数字 65535,(udp报文总大小不超过64k) 1 ...

  10. TCP-面向连接的传输层协议

    TCP 主要特点 工作方式 建立连接---三次握手 为什么 TCP 建立连接需要三次握手,而不是两次? 连接终止---四次挥手 为什么要四次挥手 为什么要等待2MSL TCP流量控制 TCP拥塞控制 ...

最新文章

  1. 配置.NET程序使用代理进行HTTP请求
  2. 如何用c语言ics文件,大一下学ics,书里在linux上用C编程,刚安系统老师就留了几个作业...
  3. 关于vs2010编译程序一闪就没的解决办法
  4. 全新ARM base PocketPC 2003 Emulator Beta 已登場。
  5. AutoMapper在MVC中的运用小结
  6. CODE[VS]-求和-整数处理-天梯青铜
  7. python中配置opencv_在Windows中安装OpenCV-Python|四
  8. 关于iOS7里的JavaScriptCore framework
  9. 整合Flex和Java(上)
  10. JS 操作 HTML 和 AJAX 请求后台数据
  11. Angular CLI: 全局脚本
  12. 同济大学 线性代数 第六版 pdf_线性代数同济第六版第五章课后习题答案!
  13. 基金会总线协议分析(FF协议)
  14. 免费从麦田影视下载英文字幕电影方法图解#
  15. 复旦计算机对口,2019年长宁区公办初中划片电脑派位对口入学方式
  16. 带你重新认识一下应用层协议
  17. Bibexcel 与 Pajek 基本分析
  18. Linux系统开机显示BusyBox v1.22.1 built-in shell(ash) 解决方法
  19. 棋牌游戏进入游戏房间流程
  20. android studio黄油刀依赖,【Android】AndroidStudio 依赖 ButterKnife 出现的空指针异常

热门文章

  1. DATASNAP数据序列之FIREDAC的TFDJSONDataSets
  2. JAVA之day3对象
  3. hdu---2087---剪花布条
  4. 64bit win2003 + 64bit sql2005使用不上oledb驱动
  5. [书目20080630]人一生要养成的50个习惯
  6. 四、 vSphere 6.7 U1(四):部署VCSA
  7. 第十一课 Solidity语言编辑器REMIX指导大全
  8. asp教程一:创建 Active Server Page 页
  9. 安防的未来五年 如何把握机遇深耕市场?
  10. android内部培训视频_第三节(3)_常用控件(ViewPager、日期时间相关、ListView)