TCP Timeout and Retransmission

到目前为止,我们主要关注在TCP是如何保证准确性。TCP基于不可靠的IP协议之上提供了可靠的数据传输,必须解决报文的丢失、重复、乱序的问题。面对已经丢失的数据,TCP重传这些数据。TCP有两种机制来完成重传,一种是基于时间的,一种是基于acknowledgements的结构的,第二种效率更高。

TCP在发送数据的时候会设置一个定时器,如果ACK没有准时的收到,就会触发重传。定时器所设定的时间就是叫作RTO,原文有点拗口.

The timeout occurs after an interval called the retransmission timeout (RTO)

另外一种重传叫作快速重传(fast retransmission),如果发现返回的多个ACK并没有递增,或者说返回的ACK中包含着SACK表明这乱序数据的接收,都将触发快速重传。在本章会关注到:超时时间如何设置,基于超时的重传如何工作的,快速重传是如何工作的。此外还将看到如何使用SACK来帮助发送方来知晓哪些数据被接收方乱序地接收了。

Simple Timeout and Retransmission Example

首先先来看下最简单的继续时间的重传。这里我没有自己做实验,因为似乎自己抓包的很难观察到超时重传,如果我直接断开服务器,会导致abort而发送RST报文。所以使用的是书上的例子,如下:

超时重传的例子

上面图片中,wireshark标记了TCP retransmission的报文就是超时重传的报文。这几个重传之间的间隔分别为:0.206s,0.420s,0.841s,1.680s,3.360s。这个过程叫作指数回退(exponential backoff),和TCP建立连接的重传(初始的SYN报文)的行为是相类似的。在多次发送之后,客户端将会收到Connection closed by foreign host的错误提示(telnet程序)。

一般来说,TCP有两个阈值(threshold)来决定要坚持多久的重传。第一个Threshold R1表明了重传的次数,而第二个Threshold R2表明了坚持重传多少秒。这些阈值建议分别为3次和100s。对于SYN报文来说,这些值可能稍微不同,R2在SYN报文中建议至少3min

在Linux中,R1由net.ipv4.tcp_retries1设置,R2由net.ipv4.tcp_retries2。不过对于它们都是以重传次数(the number of retransmission)为单位的,而不是以时间

在Linux中,这些参数的默认情况为:

net.ipv4.tcp_retries1:3

net.ipv4.tcp_retries2:15,大约13-30分钟

而对于SYN报文来说(下列数据大约都是180秒):

net.ipv4.tcp_syn_retries:5,这是active open那端重传的次数

net.ipv4.tcp_synack_retries:5,这是passive open那端的次数

上面这些数据在Windows也有对应的注册表内容。

Setting the Retransmission Timeout (RTO)

设置定时器的关键是基于RTT来设置RTO。如果一个TCP在小于RTT之前就重传,可能就会在网络引入很多重复的数据,如果TCP在远大于RTT之后再重传数据(即定时器所设置的时间过长),丢失的数据无法重传从而会导致接收方一直无法发送ACK,使得发送方的窗口一直无法前进,所以网络的利用率十分低下。此外,知晓RTT也是一件较为复杂的事情,因为它会随着网络情况的而逐渐改变。

因为TCP会对所发送的数据都会返回一个确认(acknowledgement),所以先发送一个字节的数据来知道RTT是可能的,这个叫作RTT sample。问题是RTT会随着时间的改变而改变,另外一个问题是,如何基于RTT来设置合适的RTO。

下面来讨论几种算法来设置RTT。

The Classic Method

最初是的TCP标准[RFC793]算法如下所示,称为smoothed RTT estimator
$$
SRTT = \alpha (SRTT)+(1-\alpha)RTT_{s}
$$
SRTT会根据它原先的值和新得到的RTT sample而改变。常量$\alpha$推荐设置为0.9或者0.8。在这种方法中(假设$\alpha$ =0.8),它(SRTT)的80%来自它先前的数据,20%来自RTT sample。这种均值也被称为exponentially weighted moving average (EWMA) or low-pass filter。

随着RTT的改变,SRTT也会改变,RFC 793建议RTO设置为如下:
$$
RTO=min(ubound,max(lbound,(SRTT)\beta))
$$
其中的$\beta$叫作偏移方差因子(delay variance factor),取值为1.3-2.0之间。ubound表示的是upper bound,建议设置为1min,lbound是lower bound,建议设置为1s。这个是经典方法,它通常会将TRO设置为1s或者两倍的SRTT。

It generally results in the RTO being set either to 1s, or to about twice SRTT.

The Standard Method

下面所阐述的是标准方法,我没有明白标准方法具体在数学表达上哪里更加优越。原文中提到,在RTT波动较大的情况中,会导致不必要的重传而增加网络负载。

that the timer specified by [RFC0793] cannot keep up with wide fluctuations in the RTT (and in particular, it causes unnecessary retransmissions when the real RTT is much larger than expected).

在标准方法中,RTO的计算方法如下:

jjiji'sji'saji'san集散

M:就是sample RTT

g:为1/8,即$2^{-3}$

h:为1/4, 即 $2^{-2}$

PS:其中srtt和rttvar的初始值并在接下来会介绍。

g和h选择为2的负次方,是因为在计算的时候更加简单,因为乘1/4相当于右移2位。

Clock Granularity and RTO Bounds

TCP内部也有一个时钟,它并不是系统的时钟,它只是一个变量,随着系统时钟的增加而增加。不过并不是一对一的同步更新。

Rather, the TCP clock is usually the value of a variable that is updated as the system clock advances, not necessarily one-for-one

TCP时钟”滴答“一次的时长叫作粒度(granularity),通常的话,这个值都会比较大,超过500ms,但是在最近的Linux中,这个值通常为1ms。粒度进一步的能够影响RTO的设置

In [RFC6298], the granularity is used to refine how updates to the RTO are made.

通过如下公式来设置RTO,下面公式中的srtt和rttvar都是来自standard method中:
$$
RTO=max(srtt+max(G,4(rttvar)),1000)
$$
其中G表示的是粒度。

Initial Values

在SYN报文发送之前,我们不知道如何去设置RTO(除非有一些先前的缓存),因为我们此时还没有任何的交互来获得RTT sample。根据RFC6298,初始的RTO应该设置为1s,在SYN报文中,RTO设置为3s。当第一个RTT samlpe收到的时候(就是下面的M),将srtt和rttvar设置为:
$$
srtt=M \\
rttvar=M/2
$$
所以,srtt和rttvar的初始取决于RTT samlpe,但是有一些例外,接下来就是讲述这些问题。

Retransmission Ambiguity and Karn's Algorithm

在测量RTT sample的时候,如果因为超时发生了重传,我们无法判断此时的重传是因为数据丢失还是ack的丢失而引发的,重传后在收到的ACK,我们无法判断是第一次所返回的ACK迟到了,还是重传的ACK。这就是重传的模糊性问题(retransmission ambiguity problem),除非使用了时间戳的选项来避免这个问题。

It happens because unless the Timestamps option is being used, an ACK provides only the ACK number with no indication of which copy (e.g., first or second) of a sequence number is being ACKed

Karn算法就是来避免这个问题的,Karn算法有两部分。

Karn算法的第一部分:如果发生了重传,返回来的RTT都不能更新RTT estimators(就是前面的,srtt和rttvar)。

Karn算法的第二部分:如果我们直接忽略了重传回来的ACK对于RTO的影响,那么此时网络情况所反应的信息也被直接忽略。比如说,如果发生了重传,此时网络内部可能是比较拥塞的。对于发送方来说,应该适当的较低它的发送速度,这也就是前面的指数回退(expoential backoff)的理由。TCP将backoff factor应用到了RTO中,它将之前的超时时间乘2,直到一个正确的ACK被接收,此时的backoff factor会被设置为1,这就是karn算法的第二部分。注意,这里正确的意思是:返回的ACK并非是重传数据的ACK,而是发送了只有一次的数据的ACK。

此外我们仍需直到,如果发生了超时,拥塞控制过程也将被启用。

Note that when TCP times out, it also invokes congestion control procedures that alter its sending rate.

这一段也说的很不错,简洁明了。

When an acknowledgement arrives for a packet that has been sent more than once (i.e., is retransmitted at least once), ignore any round-trip measurement based on this packet, thus avoiding the retransmission ambiguity problem. In addition, the backed-off RTO for this packet is kept for the next packet. Only when it (or a succeeding packet) is acknowledged without an intervening retransmission will the RTO be recalculated from SRTT -------quoted directly from the 1987 paper [KP87]

Karn算法从rfc1122以来的一段时间以内一直都是必须所实现的部分。但是也有一个例外,如果TCP timestamp选项使用的话,那么就可以避免Karn算法的第一个部分。

RTT measurement(RTTM) with Timestam Option

在之前已经讲述了如何使用RTT sample来对srtt和rttvar初始化。接下来这节要讲述的是如何来计算获得RTT sample。TCP的timestamp(TSOPT)选项,它是PWAS算法的基础,此时可以用来计算TCP的RTT(RFC1323)。一直没有对RTT进行一个明确的解释:

Round Trip Time (RTT) is the length time it takes for a data packet to be sent to a destination plus the time it takes for an acknowledgment of that packet to be received back at the origin. ------MDN--RTT

RTT是报文发送在加上ACK返回的时间。

timestamp value(TSV)填充在TSOPT的第一个部分,然后伴随着SYN报文发送出去,最后在TSER中跟着SYN+ACK报文返回来,这些timestamp可以用于设置srtt,rttvar,和RTO。这样的方法虽然很直观,但是接收方并不会为每一个报文都返回ACK

This seems straightforward enough but is made more complex because TCP does not always provide an ACK for each segment it receives.

在大数据传输的时候,TCP通常每隔一个报文发送一个ACK:

For example, TCP often provides one ACK for every other segment (see Chapter 15) when large volumes of data are transferred

此外因为累计ACK的机制,并不需要使得ACK和发送报文之间有有严格的一一对应关系。

In addition, when data is lost, reordered, or successfully retransmitted, the cumulative ACK mechanism of TCP means that there is not necessarily any fixed correspondence between a segment and its ACK.

因为上述条件的存在,测量一个准确的RTT变得更加复杂。大多数的TCP实现都会使用时间戳选项(包括windows和Linux),采用如下的方法来计算RTT:

  1. 发送的报文包含着32bit的timestamp TSV,这个字段包含着此时的TCP时钟值(注意该时钟并不是系统的时钟,看Clock Granularity and TRO bounds那节)。
  2. 接收方的TCP会保存着它刚接收到的报文的TSV并且会在ACK中返回(保存在TsRecent),而且还会保存刚才发出去的ACK号(保存在LastACK),注意ACK中包含的是接收方下想要收到的下一个报文的的序列号
  3. 如果新到达的报文的序列号和LastACK相匹配,那么就将附带过来的TSV放在TsRecent中。
  4. 当接收方返回ACK的时候,在TSER中放着刚才收到的TsRecent。
  5. 当发送方接收到ACK的时候,滑动它的窗口,将它此时的TCP时钟和返回来的TSER相减来得到RTT,并且以此来更新RTT estimators。

PS:我最开始在思考,为什么要这么做,十分麻烦。难道在本地保存一个临时变量,然后在接收到ACK的时候在获得此时的时间,减一下不就好了?

我认为,因为窗口是不定长的,维持不定个数的临时变量是不可能的。

在FreeBSD,Linux,windows中timestamp选项都是默认使用的,在Linxu中通过net.ipv4. tcp_timestamps来设置,0(不使用),1(使用)。在Windows中也有与之相类似的注册表项。

上面的几个section,从Clock granulrity and TRO Bounds开始到RTT的测量,称为标准方法(standard method),Linux中使用的有些不同,原文中比较繁琐,暂略。

The Linux Method

上面的方法称为标准方法,Linux中的实现和找个稍有些不同。Linux使用的TCP时钟粒度(granularity)为1ms,比很多的TCP实现都要小,这使得它的时间戳选项也更加的细。这使得它对于RTT的估计更加准确,而且使得rttvar更小。

The combination of frequent measurements of the RTT and the fine-grain clock contributes to a more accurate esti- mate of the RTT but also tends to minimize the value of rttvar over time

我没有理解标准方法的缺点具体在哪里,下面只是过一下Linux 中的方法。

Linux除了使用之前已经介绍过的两个变量:srtt和rttvar之外,还引入了两个变量mdev和mdev_max。

mdev:使用标准方法中的计算rttvar的算法来计算得到。

mdev_max:是从上一次RTT测量之后所遇到的mdev的最大值,且不能小于50ms、通常,rttvar都会设置为至少和mdev_max一样大。因为前面公式指定了RTO = srtt+4(rttvar),而rttvar至少和medv_max一样大,所以RTO不会小于200ms。

rttvar会更新为mdev_max的值,且总是将RTO设置为srtt和4(rttvar)的和而且确保RTO不能超过TCP_RTO_MAX,它的默认值是120s。

Linux updates rttvar to the value of mdev_max whenever the maximum increases. It always sets the RTO to be the sum of srtt and 4(rttvar) and ensures that the RTO never exceeds TCP _RTO_MAX, which defaults to 120s

下面是原文中给出的一个例子,如下图:

第一次发送SYN报文获得了RTT sample,获得了M = 16,回想标准方法中的,srtt=M,所以的到: srtt=16ms。

medv和标准方法中计算rttvar的方法一样,为M/2,所以medv=8ms

medv_max是medv和TCP_RTO_MAX两者之间的最大值,medv_max= max(medv,TCP_RTO_MAX) = 50ms

rttvar通常会更新为mdev_max的值,rttvar=50ms。

RTO=srtt+4(rttvar)=216ms

这初始的情况,接下来的情况,先不写了,难懂。

Timer-Based Retransmission

一旦将基于RTT的测量将RTO设置好之后,发送数据之后,就要将定时器设置为合适的值。如果一个ack到了,就取消定时器,下次重新发送数据的时候,再一个设置定时器。所以,这里说明了,定时器是只有一个的,而不是每个包一个定时器。

超时以后,如果还没有收到ACK,它就执行超时重传。TCP对超时重传的处理十分谨慎,一旦超时重传发生以后,它马上就会降低发送数据的速率。第一个方法是它的窗口大小(这里目前还没涉及到),第二个方法是之前说过的backoff来使得每一次重传的时间间隔翻倍。这是Karn算法的第二部分。具体看Karn算法部分。

The other way is to keep increasing a multiplicative backoff factor applied to the RTO each time a retransmitted segment is again retransmitted. This is implemented in the “second part” of Karn’s algorithm mentioned previously

Fast Retransmit

快速重传能够根据接收方的反馈来引发重传,而不是等待定时器的超时。通常来说,快速重传比超时重传更快也更有效。

As a result, packet loss can often be more quickly and efficiently repaired using fast retransmit than with timer-based retransmission.

一般的TCP实现都实现了超时重传和快速重传。当我们接收到失序的报文的时候,应该马上返回一个ACK(即 重复的ACK),当重复ACK发生的时候,说明在TCP中有一个hole,因此发送方的工作是赶紧将这个hole填充好。

一个重复的ACK到达暗示着之前有数据已经丢失了,此外,如果接收到的了一个乱序的数据也会返回一个重复ACK。面对重复ACK,发送方必须设置一个阈值来决定多少个重复ACK后就开始重传。这个阈值叫作duplicate ACK threshold ,通常来说,这个值都是3。

TCP发送方观察到dupthresh个重复ACK,就开始重传一个或者多个分组,而不会等待定时器的超时,此外冗余ACK的出现,也将会被认为是网络拥塞的一种表现。如果没有SACK,那么每次只能发送一个缺失的分组,如果有选择确认机制(SACK),那么在一个RTT之内可以发送多个和分组。

Without SACK, no more than one segment is typically retransmitted until an acceptable ACK is received.With SACK, ACKs contain additional information allowing the sender to fill more than one hole in the receiver per RTT

Example

下面是书上一个快速重传的例子,如下图:

快速重传的例子

红框内的是冗余ACK,3个冗余ACK会出发快速重传,所以在第四个ACK触发了快速重传,TCP Retransmission表明重传开始了。注意,第一个报文(图中的window update),虽然ACK也是一样的,但是Window Update不会被认为重复ACK。

Thus, it is not counted toward the three-duplicate-ACK threshold required to initiate a fast retransmit.

图中的第二个重传有些不一样。当第一个重传开始的时候,在重传开始前最后一个发送的报文的序列号叫作复原点(recovery point),当接收到的ACK号等于或者超过复原点的时候,TCP就认为此时已经从重传中恢复过来了。在这个例子中,43401+1400=44801(红框中的倒数第三个报文),这就是本例中的复原点。

TCP is considered to be recovering from loss after a retransmission until it receives an ACK that matches or exceeds the sequence number of the recovery point

数据重传

在第二个重传中,就是上图中的seq=26601报文,所返回的ACK虽然大于之前的ACK=23801(看前一张图,23801在红框中),不过还没有到达44801这个复原点。这些ACK叫作partial ACK。

当partial ACK到达的时候,TCP马上就发送所需要的数据,并不会等待ACK的个数达到dupthresh,直到到达复原点。

When partial ACKs arrive, the sending TCP immediately sends the segment that appears to be missing (26601 in this case)

如果有拥塞控制的允许,此时还可以发送之前没有发送过的数据,不仅仅是需要重传的数据。

If permitted by congestion control procedures (see Chapter 16), it may also send new data it has not yet sent

例子中的TCP没有使用SACK,所以每个RTT只能恢复一个接收方的hole。

Because no SACKs are being used, the sender can learn of at most one receiver hole per round-trip time

恢复的行为各个版本的TCP都不同,取决于是如何配置的。所示的例子发送方使用的是newReno的算法,这是一个相当常见的TCP版本。

newReno中使用partial ACK来使得发送方继续处于恢复的状态,一些老的版本,任何一个可以接收的ACK都可以使得发送方离开恢复状态,所以会有一些性能问题。NewReno可以和SACK组合在一起,这样的方法称为:advanced loss recovery

Retransmission with Selective Acknowledgement

标准的SACK[rfc2018]标准使得TCP的receiver能够描述那些序列号超过ACK的数据。这句话的意思就是,支持SACK的TCP接收方可以在报文中返回和SACK的信息。正如前面所言,在接收方窗口中,ACK号和窗口内部缓存之间的gap叫hole,即接收方缓存中失序的数据。在大多数情况下,支持SACK的sender能够填充这些空洞更快,因为它以此可以恢复多个holes,而不是如同non-sack sender那样,一次只能恢复一个。

In many circumstances, the properly operating SACK sender is able to fill these holes more quickly and with fewer unnecessary retransmissions than a comparable non-SACK sender because it does not have to wait an entire RTT to learn about additional holes

SACK被启用的时候,SACK选项中至多包含着3或4个SACK block(这取决于是否使用了timestamp选项,看上一章中对于option那节可以理解)。每一个SACK block包含着两个32bit的序列号,分别表示着失序的数据块的左边界的序列号和右边界的序列号+1。如下图所示:

各个方块之间的就是缺失的数据

那么SACK block中 所包含的信息:170-200,220-250(这两个块是失序的,中间存在hole,目前的ack是150,所以我们就只都了150-170是缺失的数据)。一个TCP分组做多可以持有3个SACK block(取决于timestamp是否启用),所以在一个在接下来的RTT之内就可以发送多个分组,效率更高。

结合当前接收方发送过来的ACK和SACK相结合就可以知道要补充哪里的数据。原文说到:

Here we see that the ACK for 23801 contains a SACK block of [25201,26601], indicating a hole at the receiver. The receiver is missing the sequence number range [23801,25200], which corresponds to the single 1400-byte packet starting with sequence number 23801

ACK=23801,第一个SACK block=[25201,26601]。所以发送方就知道[23801,25200]这里的数据缺少了,需要重新发送。

If not limited by congestion control (see Chapter 16), all three could be filled within one round-trip time using a SACK-capable sender

一个ACK分组中包含着SACK blocks有时候就简称为SACK。

SACK Receiver Behavior

这里说的接收方不是指SACK的接收方,而是在数据接收方中,它是如何实现SACK的。通常来说,一旦发现数据中有任何失序的数据,就应该立刻返回SACK。失序的数据,可能是因为数据的丢失,或者是在传输中到达的先后顺序出了问题。目前,先讨论由于数据丢失的失序问题。

接收方将在第一个SACK block存放最近所收到的失序数据的序列号范围。比如说上图中的220-250这里就是最近收到的。这是因为SACK blocks的数量有限,我们尽量让最近接收到的信息返回给发送方。

Because the space in a SACK option is limited, it is best to ensure that the most recent information is always provided to the sending TCP , if possible

剩下的SACK blocks以他们在先前报文中出现的顺序保存。所以就会使得SACK block的重复。

Other SACK blocks are listed in the order in which they appeared as first blocks in previous SACK options.

因为SACK数据是保存在返回的ACK确认报文中的,ACK报文会丢失,所以我们让SACK blocks在不同的ACK确认报文中重复来提供更好的可靠性。

SACK Sender Behavior

一个支持SACK的发送方必须只重传在接收方缓存中缺少的数据。这个过程叫做选择重传(selective repeat)。因为前面说,接收方返回的SACK blocks可能是重复的,所以发送方不能重送那些已经被接收方正确就接收的数据。

下面还涉及到了所谓SACK 食言(renege)的问题,但是没明白,略。

Suprious Timesouts and Retransmission

在某些情况下,TCP会开始重传数据,即使没有数据丢失,这种情况叫作伪重传,通常因为定时器所设置的时间太短了,以及其他原因分组的重排,分组的重复,或者ACK丢失。伪重传通常发生在RTT大幅度上升的时候,这种情况在无线环境中比较多。

下面是一个伪重传的举例:

伪重传的例子

数据1-8都已经发送,但是因为A5-A8的ACK来的比较晚(此时因为RTT已经变得比较长了),从而引发了已经被正确接收的数据的重传。且出现了一种go back to N的形式,使得一大堆已经被接收的重复数据的发送,浪费系统网络资源。

PS:伪重传这名字看起来有些诡异,因为它更应该被描述为RTT较大使得ACK丢失引起的不必要的重传问题。

主要来说两大类方法来解决伪重传,一种叫做:伪重传的探测算法它的目的在于发现网络中的伪重传,另外一种响应算法。

Duplicate SACK Extension

目前所讨论的SACK并不具备面对重复分组的方法。DSACK 或者称D-SACK,是duplicate SACK的简称[RFC2883]。它应用接收方中,它返回的第一个SACK blocks中包含着重复分组的序列号。

that causes the first SACK block to indicate the sequence numbers of a duplicate segment that has arrived at the receive

D-SACK的实现非常简单,不需要在原先的SACK基础上做太多的改进。但是需要双方对第一个SACK blocks的理解做一些改变。如果一个non-DSACK TCP接受到了一个来自DSACK TCP的SACK block,那么它将会误解。

If a non-DSACK TCP shares a connection with a DSACK TCP , they will interoperate, but without any of the benefits of DSACK.

此外,因为SACK block的原本意图是,其中所包含的序列号是乱序数据之间的gap,肯定大于ACK确认号,引入DSACK之后,使得第一个SACK block的序列号小于ACK,这不符合原来的SACK block的意图,但是问题不大。

还有一个问题就是,DSACK block不会在不同的ACK报文中重复,这就使得D-SACK失去了一些可靠性。

DSACK information is not repeated across multiple SACKs as conventional SACK information is. As a consequence, DSACKs are less robust to ACK loss than regular SACKs.

The Eifel Detection Algorithm

Eifel算法的思路是:在发生重传之后,如果下一个返回的ACK是可接受的ACK,但是这个引发这个ACK是原始所发送的分组,那么就可以断定伪重传发生了。

比如说上图中,A5重传之后,接着收到的ACK是之前(原始的A5)的ACK。所以我们要有某种方法来知道接收到的ACK是之前的ACK,这就用到了timestamp选项。

Eifel算法的很简单,它需要TCP使用timestamp选项。当重传开始后,记录下此时的TSV,当可接受的ACK返回的时候,就去检查TESR。因为,如果是重传5所返回的ACK,那么TESR肯定和TSV相等。如果不是,那么先前ACK的TSER肯定小于此时的TSV,所以我们就知道伪重传发生了

PS:这里的可接受的意思应该是,比如说,当前序列号1400,发送了1400字节的数据。那么返回的ACK应该是2801。对于其他ACK就不能称作可接受的ACK。

Eifel算法和DSACK相比的好处在于:在重传之后,需要等待对方发送DSACK给发送方。而Eifel算法,表现的更加主动一些,它只要等待下一个ACK来临即可。

Forward-RTO Recovery (F-RTO)

F-RTO是伪重传检测的标准方法,它不需要时间戳选项,因此在一些老的TCP中也挺好用。不过它只能检测那些由定时器超时而导致的伪重传,不能解决其他造成伪重传的原因

It attempts to detect only spurious retransmissions caused by expiration of the retransmission timer; it does not deal with the other causes for spurious retransmissions or duplications mentioned befor

通常超时以后,所重传的数据都是目前没有收到ACK number的最小值,也就是所谓的Go back to N。

下面的描述有问题!!

F-RTO在重传且接收到了重传后的第一个ACK,在发送一个之前未被发送的数据过去,在等待一个ACK返回。如果这两个ACK是不同的,那么说明新发送的数据使得接收方窗口移动了,说明之前的数据都被正确的接收了,此时是伪重传。如果返回的ACK是相同的,说明窗口不能移动,也就是说确实数据存在空洞,所以此次重传有效的。

If such data is only causing duplicate ACKs, there must be one or more holes at the receiver.

The Eifel Response Algorithm

前面是检测伪重传的算法,接下来要介绍的是在发现伪重传之后的处理算法。Eifel响应算法是在伪重传发生之后才会执行的标准操作。Eifel虽然和之前的Eifel检测算法名字相同,但是不一定要和它配套使用。它可以和前面的任何检测算法搭配使用。虽然最开始该算法可以用于both timer-based and fast retransmit spurious retransmissions,但是现在基本用于timer-based retransmissions

Because the response algorithm is logically decoupled from the Eifel Detection Algorithm, it can be used with any of the detection algorithms we just discussed

检测算法中也分为两类,一类是能够早一些发现伪重传的算法,如Eifel和F-RTO,另外一个是晚一点发现伪重传的,依赖于对方返回重传数据的ACK的,如DSACK。第一类称为伪超时(suprious timeout),后一类叫作晚伪超时(later suprious timeouts)

Eifel响应算法只能用于第一类检测算法,下面介绍Eifel相应算法。

当定时器超时以后,首先(这应该是TCP的行为)先记录下此时的,srtt和rttvar (RTO estimator),然后按照下面公式,生成两个新变量:
$$
srttPrev = srrt + 2(G) \\
$$

$$
rttvarPrev=rttvar
$$
这两个变量只有在超时的才会使用,但是只会在伪重传的时候才会被使用。它们的目的是用来更新RTO的,上面公式中的G代表的是TCP时钟的粒度(granularity)。

发生超时后,也完成了对上面两个变量的初始化以后,就调用检测算法(上面介绍的几种检测算法之一)。检测算法在结束以后,都会设置一个特殊的变量--伪恢复(SupriousRecovery),取决于这个变量的是什么样的取值来做不同的伪重传响应。

如果:

伪重传是伪超时(suprious timeout),SpuriousRecovery == SPUR_TO

伪重传是晚伪超时(late suprious timeout),SpuriousRecovery ==LATE_SPUR_TO

都不是,就执行最普通的超时重传算法。

接下来再讲述具体是如何处理这些伪重传的。对于SPUR_TO(伪超时),那么就将下一个要发送的序列号放到目前未发送的序列号。如下图(说明SND.NXT和SND.MAX原先是指在哪里):

即,将SND.NXT=SND.MAX,那么下次发送的数据就是全新的,避免了之前的go back to N的行为。如果是晚超时的行为,因为此时已经接受到了重传后的ACK,那么此时就不会SND.NXT做任何改变了。在拥塞算法中,这里还会涉及到一些其他内容,暂且略过。

然后,重新设置RTO,回想最开始所说的,伪重传的一个原因之一就是:RTO远小于此时的RTT。所以我们在响应算法中肯定要更新RTO,公式如下:

计算公式


m是超时后首个收到的ACK的RTT sample。

Packet Reordering and Duplicate

到目前为止,基本上讨论的都是,TCP如何处理报文丢失的问题。比如说长时间接收不到ACK,那么可能就是ACK丢失或者报文丢失,此时我们就引入了定时器。然而定时器不是那么的高效,因为TCP累计确认的机制,我们又基于此来引入可快速重传,不必等待定时器超时。此外,给定时器设置一个合适时间也是一个值得讨论的问题,我们使用了RTT sample来测量一个RTT,并且基于此使用了介绍了Standard method和Linux Method。还介绍了如何执行更有效的重传,使用了SACK技术。在RTT波动较大的环境中,会出现伪重传(suprious retransmission),这里又引入了很多算法(D-SACK,Eifel等)。

本节将要介绍的是,TCP是如何处理包的乱序问题和重排问题。乱序和重排并不是一个全新的话题,因为数据的丢失就隐含着数据的乱序丢失,只不过接下来是对这些话题的更加全面的介绍。

Reordering

TCP是基于IP协议之上的传输层协议,IP协议并不能保证数据的有效传输。此外,对于IP协议来说,不保证IP数据报的有序性是有好处的。因为IP数据报可以选择不同的链路来达到目标主机,所以会导致新插入到网络中的数据先于之前插入的数据。导致了在接收方中,数据的接受顺序和发送顺序的不一致。

cause traffic freshly injected into the network to pass ahead of older traffic, resulting in the order of packet arrivals at the receiver not matching the order of transmission at the sender

报文的乱序在数据报文的发送以及ACK报文的接受都可能会发送,对于ACK的乱序(即发生在发送方的报文乱序),即大的ACK先于小的ACK到达接收方,累计确认的机制使得接收方能够大幅度滑动它的窗口。发送速度过快会引入一些拥塞控制算法的干预(降低当前窗口),使得网络带宽的利用率变得低下。

This can lead to an unwanted burstiness (instantaneous high-speed sending) behavior in the sending pattern of TCP and also trouble in taking advantage of available network bandwidth, because of the behavior of TCP’s congestion control

对于发送方的所接收到的乱序报文来说,即先接收到序列号大的报文。那么在此时它无法判断到底是报文丢失了还是说序列好小的报文迟到了。如果一收到冗余ACK就开始快速重传,那么就有大量不需要的报文发送到网络中。这就是为什么要为冗余ACK设置一个阈值的原因,dupthresh == 3

下面说明了设置一个阈值的好处:

对于报文失序不是那么严重的情况(左边),那么冗余ACK并不会做任何处理。对于右边的情况,报文失序很严重,于是触发了快速重传,即使到最后报文还是到达了。

话说回来,区分报文的失序还是报文丢失并不是一个简单的,问题。好在报文失序在网络中并不是那么的常见,所以将阈值设置为一个较小的值就挺好的。不过也有一些别的方法来处理严重的失序问题,比如说TCP中使用了动态的阈值。

Fortunately , severe reordering on the Internet is not common [J03], so setting dupthresh to a relatively small number (such as the default of 3) handles most circumstance

Duplication

链路层协议有时候也会传送一个分组多次,虽然这并不常见。但是TCP会因为这个情况而产生疑惑。如下图:

报文的重复发送

在分组4到达之前,多次返回了A3,使得接收方误认为误认为数据丢失,而进入到了快速重传。不过,这个使用D-SACK就可以很好的解决。

Repacketization

如果TCP的定时器超时了,并不需要传送每一个报文。如果丢失的都是很小的报文,TCP就可以执行repacktization,将连个小的合并为一个大的报文,有更好的性能。但是通常不能够超过receiver指定的MSS和链路的MTU。下面是引用书上的例子:

发送hello there之后拔掉网线,所以line number2丢失了,接着重新连接网线,发送了and 3。注意,这里拔掉了网线并不代表着TCP连接的断开,只是会表现为所发送数据的丢失,重连之后数据还是可以发送的,不需要重新建立TCP连接

报文如下,line number 2丢失了。如下图:

重连后返回了and 3,并且包含了一个sack。所以在接下来重新传递之前丢失的数据,如下:

本次将and 3也重新传送了,发生了repacktization,将line number2 和and 3放到了一块一起发送出去了。虽然and 3已经被正确接收了。

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇