TCP超时与重传
Posted vector6_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TCP超时与重传相关的知识,希望对你有一定的参考价值。
TCP超时与重传
本文主要分析TCP传输的效率与性能。由于IP层不保证传输的可靠性,可能出现丢失、重复或失序丢包等情况,TCP协议提供可靠数据传输,为保证数据传输的正确性,TCP重传其认为已丢失的包。
TCP拥有两套独立机制来完成重传,一是基于时间,二是基于确认信息的构成。第二种方法通常比第一种更高效。
对于基于时间的重传:TCP在发送数据时会设置一个计时器,若至计时器超时仍未收到数据确认信息,则会引发相应的超时(基于计时器的)重传操作,计时器超时被称为重传超时(RTO)。
另一种方式的重传称为快速重传,通常发生在没有延时的情况下。若TCP累积确认无法返回新的ACK,或者当ACK包含的选择确认信息(SACK)表明出现失序报文段时,快速重传会推断出现丢包。
通常当发送端任务接收端可能出现数据丢失时,需要决定是发送(未发送过的)新数据还是重传。
超时重传
TCP重传超时值
TCP基于计时器的重传策略为重传计时器启动重传的时间间隔每次为上一次的2倍,这种每次重传间隔时间加倍称为二进制指数退避。
TCP拥有两个阈值R1、R2来决定如何重传同一个报文段。
RFC1122描述了这两个阈值,其中,R1表示TCP在向IP层传递“消极建议”(如重新评估当前的IP路径)前,愿意尝试重传的次数(或等待时间),一般应至少设为三次重传。
R2(大于R1)指示TCP应放弃当前连接的时机,一般至少应设为100秒。对连接的建立过程(发送SYN报文段)R2应至少设为3分钟。
在linux中,对一般数据段来说,R1和R2的值可以通过应用程序,或使用系统配置变量net.ipv4.tcp_retries1(默认值为3) 和 net.ipv4.tcp_retries2(默认值为15) 设置。变量值为重传次数,而不是以时间为单位。对于SYN报文段与数据段传输不同,使用net.ipv4.tcp_syn_retries 和 net.ipv4.tcp_synack_retries 限定重传次数,默认值为5.
cfs@cfs:/proc/sys/net/ipv4$ cat tcp_retries1
3
cfs@cfs:/proc/sys/net/ipv4$ cat tcp_retries2
15
由上我们可知,TCP需要为重传计时器设置超时值,指示发送数据后等待ACK的时间。假设TCP只工作在静态环境,那么为超时设置一个合适的值很容易;但TCP需要适应不同环境进行操作,可能随着实际不断变化,因此需要基本不同时段的当前环境设置超时值。即TCP需要动态设置RTO。
设置重传超时
TCP超时重传基础且重点要考虑的是怎样根据给定连接的RTT设置RTO。若TCP先于RTT开始重传,可能会在网络中引入不必要的数据。反之若延迟至远大于RTT的间隔发送重传数据,整体网络利用率(以及单个连接的吞吐量)会随之下降。
TCP在收到数据后会返回确认信息,因此可在该信息中携带一个字节的数据(采用一个特殊序列号)来测量传输该确认信息所需的时间。每个此类的测量结果称为RTT样本。TCP要根据样本值估计RTT,再基于RTT设置RTO。如何恰当设置RTO是保证TCP性能的关键因素之一。
经典方法
最初的TCP规范计算平滑的RTT估计值方法为:
S
R
T
T
=
α
(
S
R
T
T
)
+
(
1
−
α
)
R
T
T
SRTT=α(SRTT)+(1-α)RTT
SRTT=α(SRTT)+(1−α)RTT
式中:SRTT——基于现存值和新的样本值RTT得到的更新结果。
α——平滑因子,一般取0.8~0.9
这种估算方法被称为指数加权移动平均(EWMA)或低通过滤器。
而RTO的设置方法为:
R
T
O
=
m
i
n
(
u
b
o
u
n
d
,
m
a
x
(
l
b
o
u
n
d
,
(
S
R
T
T
)
β
)
)
RTO=min(ubound,max(lbound,(SRTT)β))
RTO=min(ubound,max(lbound,(SRTT)β))
式中:β——时延离散因子,推荐值为1.3~2.0
ubound——RTO上边界,推荐值1min
lbound——RTO下边界,推荐值1s
一般称该放为经典方法,它使得RTO的值为1s,或约两倍的SRTT。对于相对稳定的RTT这种方法能取得不错的性能。但是在RTT变化较大的网络中,难以取得理想的效果。
标准方法
上述经典方法的缺点就是难以适应RTT大规模变动,为了解决该问题,一些改进的方法通过记录RTT测量值的变化情况以及均值来得到较为准确的估计值。基本思想是同时测量均值和方差来更好地估计将来值。
更具体地,我们使用平均偏差进行估计,因为平均偏差是对标准差的逼近,计算起来更容易(计算标准差需要平方根运算,对于快速TCP实现来说代价较大)。
结合平均值和平均偏+差进行估算的方法为:
E
r
r
=
M
−
s
r
t
t
Err=M-srtt
Err=M−srtt
s r t t = s r t t + g ( E r r ) srtt=srtt+g(Err) srtt=srtt+g(Err)
r t t v a r = r t t v a r + h ( ∣ E r r ∣ − r t t v a r ) rttvar=rttvar+h(|Err|-rttvar) rttvar=rttvar+h(∣Err∣−rttvar)
R T O = s r t t + 4 ( r t t v a r ) RTO=srtt+4(rttvar) RTO=srtt+4(rttvar)
式中:M——RTT测量值
Err——测量值M与当前RTT估计值srtt之间的偏差
srtt——均值的EWMA
rttval——绝对误差|Err|的EWMA
g——新RTT样本M占srtt估计值的权重,一般取1/8
h——新平均偏差样本占偏差估计值rttvar的权重,一般取1/4
此方法由于昨晚RFC6298的基础,因此被称为标准方法。
时钟粒度对RTO的影响
时钟粒度(TCP时钟一个“滴答”的时间长度)会影响RTT的测量以及RTO的设置。在RFC6298中,粒度用于优化RTO的更新情况,并给RTO设置了一个下界:
R
T
O
=
m
a
x
(
s
r
t
t
+
m
a
x
(
G
,
4
(
r
t
t
v
a
r
)
)
,
1000
)
RTO=max(srtt+max(G,4(rttvar)),1000)
RTO=max(srtt+max(G,4(rttvar)),1000)
式中:G——计时器的粒度
1000ms —— RTO的下界值
初始值
除了上述的更新方法,RTO还需要一个初始值,但在首个SYN交换前,TCP无法设置RTO初始值。除非某些系统提供。
根据RFC6298,RTO的初始值为1s,而初始SYN的RTO为3s。
带时间戳选项的RTT测量
TCP时间戳选择(TSOPT)可以作为RTT策略的方法。TSOPT允许发送者在返回对应确认信息中携带一个32比特的数。
时间戳值(TSV)携带于初始的SYN的TSOPT中,并在SYN+ACK的TSOPT的TSER部分返回,以此设定srtt、rttvar与RTO的初始值。
时间戳值(TSV)携带于初始的SYN的TSOPT中,并在SYN+ACK的TSOPT的TSER部分返回,以此设定srtt、rttvar与RTO的初始值。其他报文段中也包含TSOPT,因此可结合其他样本值估算该连接的RTT。该过程由于DACK、SACK的影响,当数据出现丢失、失序或重传成功时,TCP的累计确认机制表明报文段与其ACK之间并非严格的一一对应关系。为解决这些问题,使用时间戳选项的TCP采用如下算法来测量RTT样本值:
1.TCP发送端在其发送的每个报文段的TSOPT的TSV部分携带一个32比特的时间戳值。该值包含数据发送时刻的TCP时钟值。
2.接收端记录接收到的TSV值(名为TsRecent的变量)并在对应的ACK中返回,并且记录其上一个发送的ACK号(名为LastACK的变量)。
3.当一个新的报文段到达时,如果其序列号与LastACK的值吻合,则将其TSV值存入TsRecent。
4.接收端发送的任何一个ACK都包含TSOPT,TsRecent变量包含的时间戳被写入其TSER部分。
5.发送端接收到ACK后,将当前TCP时钟减去TSER值,得到的差即为新的RTT样本估计值。FreeBSD、Linux以及近期的Windows版本都默认启用时间戳选项。在Linux中,系统配置变量net.ipv4.tcp_timestamps控制是否使用该选项(0代表禁用,1代表使用)。
Linux采用的RTT测量方法
Linux的RTT测量过程与标准方法有所差别。Linux采用的时钟粒度为1ms,与其他实现方法相比,其粒度更细,TSOPT也是如此。更频繁的RTT测量使得其RTT测量也更为精准。但也易于导致rttvar值随时间减为最小。(当累积了大量的平均偏差样本时,这些样本之间易产生相互抵消的效果)。
Linux除了记录变量srtt和rttvar,同时还记录着mdev、mdev_max这两个新变量。deve为采用标准方法的瞬时平均偏差估计值,即rttvar,mdev_max则记录在测量RTT样本过程中的最大mdev,其最小值不小于50ms,另外rttvar需要定期更新以保证其不小于mdev_max。因此RTO不会小于200ms。
RTTM对丢包和和失序的鲁棒性
在没有丢包的情况下,不论接收端是否延迟发送ACK,TSOPT都可以很好地工作。那在其他特殊情况下呢?
-
失序报文段:当接收端收到失序报文段时,一般是之前出现了丢包。应当立即返回ACK以启动快速重传。该ACKA的TSER部分包含的TSV值为接收端收到最近的有序报文段的时刻,即最新的使窗口前进的报文段,通常不会是失序报文段。
这会使得发送端RTT样本增大,从而使得RTO增大。这在一定程度上是有利的,此时发送端不会急于重传,减少了重传,可以在包失序时,发送端有更多的时间去发现出现的失序问题而非丢包问题。
-
成功重传:当收到接收端缓存中缺失的报文段时(如成功接收重传报文段),窗口通常会前移。此时对应ACK中的TSV值来自最新到达的报文段,这时比较有利的。若采用原来报文段中的TSV,可能对应的是前一个RTO。
基于计时器的重传
TCP得到RTT测量值并设置了RTO后,发送报文段时应确保重传计时器设置合理。在设定计时器前,需记录被计时的报文段序列号,若及时收取到了该报文段的ACK,那么计时器被取消;每一个TCP连接的发送端需要不断地设定和取消一个重传计时器,如果没有数据丢失,则不会触发计时器超时。
若在连接设定的RTO内,TCP没有收到被计时报文段的ACK,将会触发超时重传。TCP对于超时重传的情况,还会降低当前数据发送率来对此进行快速响应。主要方法有:
-
基于拥塞控制机制减小发送窗口大小
-
每当一个重传报文段被再次重传时,增大RTO的退避因子。特别是当同一报文段出现多次重传时,RTO值会乘上γ来作为新的超时退避值。
R T O = γ R T O RTO=γRTO RTO=γRTO
在通常环境下,γ值为1,但会随着多次重传加倍增长为2、4、8… 通常γ不能超过最大退避因子(Linux 系统中为 TCP_RTO_MAX,其默认值为120s)。一旦接收到相应的ACK,γ会重置为1。
快速重传
快速重传,作为另一种重传机制是基于接收端的反馈信息而非重传计时器的超时,该方法能更加及时有效地修复丢包情况。
当接收到失序报文段时,TCP需要立即生成确认信息,即重复ACK,并且失序情况表明在后续数据到达前出现了丢段,即接收端缓存出现了空缺。当失序数据到达时,重复ACK应立即返回而不能延时发送。
重复ACK不仅可能表明先前发送的某个分组已经丢失,也可能在网络中出现失序分组时出现。对于接收端收到当前期盼序列号的后续分组时,当前期待的包可能丢失,也可能仅为延迟到达。此时一般我们无法得知是哪种情况,所以TCP设定等待一定数目的重复ACK,即重复ACK阈值或dupthresh,来决定数据是否丢失并触发快速重传。通常dupthresh为3,但也有一些非标准的实现可基于当前的失序程度动态调整该值。
如上图所示,TCP发送端在观测到至少dupthresh个重复ACK后,即重传可能丢失的数据分组,而不必等到重传计时器超时。
根据重复ACK推断的丢包通常与网络拥塞有关,因此伴随快速重传应触发拥塞控制机制。不采用SACK时,在接收到有效ACK前至多只能重传一个报文段。采用SACK,ACK可包含额外信息,使得发送端在每个RTT时间内可以填补多个空缺。
带选择确认的重传
选择确认选项
由于采用累积ACK确认,TCP不能正确地确认之前已经接收的数据。由于接收的数据是无序的,所以接收到的数据的序列号也是不连续的。这种情况下就有可能出现空缺现象(一般将ACK号与接收端缓存中的其他数据之间的间隔称为空缺)。
如果TCP发送方能够了解接收方当前的空缺,它就能在报文段丢失或接收方遗漏时更好的进行重传工作。TCP选择确认(SACK)选项提供了该功能。TCP接收方能够提供该选择确认信息。
通过允许选择确认选项,TCP接收方当接收到乱序的数据时,它就能提供一个SACK选项来描述这些乱序的数据,从而帮助对方有效地进行重传。SACK信息保存于SACK选项中,包含了接收方已经成功接收的数据块的序列号范围。每一个范围被称作一个SACK块 ,由一对32位的序列号表示。因此,一个SACK选项包含了n个SACK块,长度为(8n+2)字节。额外的两个字节用于保存SACK选项的种类与长度。
当然,由于TCP头部选项的空间有限,一个报文段中最大SACK块数目为3个。3个块表明可向发送端报告3个空缺。
使用SACK的重传 – SACK接收端行为
TCP接收端提供了SACK功能,接收端在TCP连接建立期间收到SACK许可选项即可生成SACK。一般当缓存中存在失序数据时,接收端就可以生成SACK。
第一个SACK块内包含的是最近接收到的报文段的序列号范围。有SACK选项的空间有限,应尽可能确保向TCP发送端提供最新信息。其余的SACK块包含的内容也按照接收的先后顺序排列。最新一个块中包含的内容除了包含最近接收的序列号信息,还需重复之前的SACK块。重复这些SACK信息的原因是SACK可能丢失,要为防止SACK丢失提供一些备份。
SACK发送端行为
发送端提供的SACK功能,记录接收到的累积ACK信息,还需记录接收到的SACK信息,并利用该信息来避免重传正确接收的数据。一种简单且常用的方法是推断需要重传的空缺数据,首先填补接收端的空缺,然后再继续发送新数据。
伪超时与重传
很多情况即时没有出现数据丢失也可能引发重传,这种不必要的重传即为伪重传。造成该现象的主要原因是伪超时(过早判定超时)或其他如失序、包重复、ACK丢失等。在实际RTT显著增长,超过当前RTO时,可能出现伪超时。
处理伪超时问题的方法主要有检测算法与响应算法两类。检测算法用于判断某个超时或基于计时器的重传是否真实,若认定出现伪超时则执行响应算法,用于撤销或减轻该超时带来的影响。
重复SACK(DSACK)
由上文可知,在非SACK的TCP中,ACK只能向发送端告知最大的有序报文段。而采用SACK则可以告知其他的失序报文段。
而结合重复SACK扩展,在SACK接收端采用DSACK,并结合通常的SACK发送端,可在第一个SACK块中告知接收端收到的的重复报文段序列号。可以使用DSACK来检测判断何时的重传是不必要的,因此发送端至少可以推断是否发生了包失序、ACK丢失、包重复或伪重传。
Eifel响应算法
一旦通过相关的检测算法判定出现了伪重传,则会引发一套标准操作,即Eifel响应算法。原则上超时重传和快速重传都可以使用Eifel响应算法,但目前只对超时重传做了相关规定。
响应算法针对通过检查ACK或原始传输从而检测伪超时的重传事件,在重传计时器超时后,它会查看srtt 和 rttvar的值,并按如下方式记录新的变量srtt_prev和rttvar_prev:
s
r
t
t
_
p
r
e
v
=
s
r
t
t
+
2
(
G
)
srtt\\_prev = srtt+2(G)
srtt_prev=srtt+2(G)
r t t v a r _ p r e v = r t t v a r rttvar\\_prev=rttvar rttvar_prev=rttvar
式中:G代表TCP时钟粒度。
srtt_prev设为srtt加上两倍的时钟粒度是由于:srtt的值过小,可能会出现伪超时。如果srtt稍大,可能就不会发生超时。得到srtt_prev,之后都用该值设置RTO。
完成srtt_prev和 rttvar_prev的存储后,触发某种检测算法。运行检测算法后可得到一个伪恢复值。
伪恢复值的更新策略:如果检测到一次伪超时,则将伪恢复置为SPUR_TO。如果检测到迟伪超时,则将其值置为LATE_SPUR_TO。否则,该次超时为正常超时。
若伪恢复为SPUR_TO,TCP可在恢复阶段完成之前进行操作。通过将下一个要发送报文段(称为SND.NXT)的序列号修改为最新的未发送过的报文段(称为SND.MAX)。这样就可以在首次重传后避免不必要的回退N的行为。若果检测到一次迟伪超时,此时已经生成对首此重传的ACK,则SND.NXT不改变。并且一旦接受到重传计时器超时后发送的报文段的ACK,就要按如下方式更新srtt、rttvar、RTO值:
s
r
t
t
=
m
a
x
(
s
r
t
t
_
p
r
e
v
,
m
)
srtt=max(srtt\\_prev,m)
srtt=max(srtt_prev,m)
r t t v a r = m a x ( r t t v a r _ p r e v , m / 2 ) rttvar=max(rttvar\\_prev,m/2) rttvar=max(rttvar_prev,m/2)
R T O = s r t t + m a x ( G , 4 ( r t t v a r ) ) RTO=srtt+max(G,4(rttvar)) RTO=srtt+max(G,4(rttvar))
式中,m是一个样本值,它是基于超时后首个发送数据收到的ACK而计算得到的。
当然进行这些变量更新的原因在于,实际的RTT可能发生了变化,RTT的估计值可能不适用与RTO了。
以上是关于TCP超时与重传的主要内容,如果未能解决你的问题,请参考以下文章