TCP的RTO值估算

2023-12-19 09:48
文章标签 tcp rto 估算

本文主要是介绍TCP的RTO值估算,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

大致介绍一下Linux内核实现的RTO值计算方式,以及与RFC6298的不同之处。

RTT估算

static void tcp_rtt_estimator(struct sock *sk, long mrtt_us)
{struct tcp_sock *tp = tcp_sk(sk);long m = mrtt_us; /* RTT */u32 srtt = tp->srtt_us;/*  The following amusing code comes from Jacobson's article in SIGCOMM '88.*  Note that rtt and mdev are scaled versions of rtt and mean deviation.*  This is designed to be as fast as possible  m stands for "measurement".**  On a 1990 paper the rto value is changed to:*  RTO = rtt + 4 * mdev** Funny. This algorithm seems to be very broken.* These formulae increase RTO, when it should be decreased, increase too slowly,* when it should be increased quickly, decrease too quickly etc. * I guess in BSD RTO takes ONE value, so that it is absolutely does not matter * how to _calculate_ it. Seems, it was trap that VJ failed to avoid. 8)*/

如果SRTT之前已经有值,新的SRTT的值等于7/8倍的原有SRTT值与1/8倍的新测量的RTT值之和,即新测量值在SRTT中比重为八分之一。 由于m为测量值,为原值,而srtt为SRTT值的8倍,以下代码计算新的srtt值, 其等于: srtt + m - srrt1/8,即srtt7/8 + m,最终结果为8倍的SRTT,即新的srtt的值。RFC6298定义的SRTT计算公式如下,其中alpha为1/8,R’为RTT测量值。

	SRTT <- (1 - alpha) * SRTT + alpha * R’

之后,计算MDEV(Mean DEViation)的值,其表示RTT的变化情况,注意RFC6298中并没有定义MDEV值,而是使用RTTVAR值。RTTVAR的计算公式如下,其中beta取值为1/4。其中SRTT为上次计算的值,而不是更新之后的值。

    RTTVAR <- (1 - beta) * RTTVAR + beta * |SRTT - R’|

内核中MDEV值的计算类似于RFC6298中的RTTVAR(Round-Trip Time VARiation)计算公式,不同的是,如果mdev测量值快速减小,幅度超过记录的MDEV(变量mdev_us表示4倍的MDEV)值时,内核将减低mdev测量值在MDEV值中的比重(再减低1/8),防止因RTT急剧减小导致RTO值增加(参见RTO值的计算),此情况下mdev测量值在MDEV新值中的比重为:(alphabeta = 1/81/4 = 1/32)。

如下计算公式,其中mdev_m表示本次的mdev测量值,mdev_us为最终的MDEV值。

    mdev_us + (mdev_m - mdev_us/4)/8= mdev_us + mdev_m/8 - mdev_us/32 = mdev_us *31/32 + mdev_m/8等同于以下,注意mdev_us中保存的为4倍的MDEV值。MDEV * 31/32 + mdev_m/32

否则,如果本次测量的mdev值没有急剧下降,或者其值大于等于零,使用RFC中定义的计算公式获取MDEV值(得到的mdev_us为4倍MDEV): mdev_us + (mdev_m - mdev_us/4) = mdev_us *3 /4 + mdev_m。

    if (srtt != 0) {m -= (srtt >> 3);   /* m is now error in rtt est */srtt += m;      /* rtt = 7/8 rtt + 1/8 new */if (m < 0) {m = -m;     /* m is now abs(error) */m -= (tp->mdev_us >> 2);   /* similar update on mdev *//* This is similar to one of Eifel findings.* Eifel blocks mdev updates when rtt decreases.* This solution is a bit different: we use finer gain* for mdev in this case (alpha*beta).* Like Eifel it also prevents growth of rto,* but also it limits too fast rto decreases,* happening in pure Eifel.*/if (m > 0)m >>= 3;} else {m -= (tp->mdev_us >> 2);   /* similar update on mdev */}tp->mdev_us += m;       /* mdev = 3/4 mdev + 1/4 new */

不同于RFC6298,以下使用MDEV值更新rttvar的值,只要得到的新MDEV值大于记录的最大值,更新MDEV最大值,并且在大于之前RTTVAR时,同步更新RTTVAR值,即RTTVAR值为MDEV最大值,每次测量都由可能更新RTTVAR值。这样可以避免内核在大的窗口下对过多的报文进行测量,而RTT变化很小,导致的RTO值减少的问题。

如果当前未确认的数据序号SND.UNA位于记录的rtt_seq序号之后,说明进入了下一个发送窗口期(新的RTT),更新rtt_seq为新的SND.NXT值,并且,如果mdev_max_us小于rttvar_us的值,将rttvar_us的值更新为3/4倍的rttvar_us值加上1/4倍的mdev_max_us的值。

rttvar - (rttvar - mdev_max)/4
= rttvar - rttvar *1 /4 + mdev_max/4
= rttvar * 3/4 + mdev_max/4

以上可见,内核仅允许RTTVAR值在一个RTT周期内减少一次,这样可以避免在窗口过大的情况下,进行的测量次数过多,而RTT变化很小的情况下,RTO减小的风险,造成不必要的重传。

        if (tp->mdev_us > tp->mdev_max_us) {tp->mdev_max_us = tp->mdev_us;if (tp->mdev_max_us > tp->rttvar_us)tp->rttvar_us = tp->mdev_max_us;}if (after(tp->snd_una, tp->rtt_seq)) {if (tp->mdev_max_us < tp->rttvar_us)tp->rttvar_us -= (tp->rttvar_us - tp->mdev_max_us) >> 2;tp->rtt_seq = tp->snd_nxt;tp->mdev_max_us = tcp_rto_min_us(sk);}

如果SRTT没有值,表明这是第一次测量,RFC6298规定SRTT和RTTVAR的初始值如下,R为首次RTT测量值。

    SRTT <- RRTTVAR <- R/2

内核中与RFC基本相同,将srtt值设置为本次测量的RTT时间值,初始偏差值mdev_us(Mean DEViation)等于SRRT值的一倍;而rttvar_us值等于MDEV与TCP_RTO_MIN(tcp_rto_min_us默认200ms)两者之间的较大值。最后记录下RTT计算时的SND.NXT值。与RFC6298不同,Linux内核使用的RTO最小值为200ms,而RFC中定义为1秒。

    } else {/* no previous measure. */srtt = m << 3;      /* take the measured time to be rtt */tp->mdev_us = m << 1;   /* make sure rto = 3*rtt */tp->rttvar_us = max(tp->mdev_us, tcp_rto_min_us(sk));tp->mdev_max_us = tp->rttvar_us;tp->rtt_seq = tp->snd_nxt;}tp->srtt_us = max(1U, srtt);
}

RTO值计算

以下RTO计算函数,其等于SRTT加上4倍的RTTVAR值。RFC6298中的计算公式为:RTO <- SRTT + max (G, K*RTTVAR),其中G为时钟精度,K等于4。

static inline u32 __tcp_set_rto(const struct tcp_sock *tp)
{return usecs_to_jiffies((tp->srtt_us >> 3) + tp->rttvar_us);
}

在没有退避的情况下,内核中使用函数tcp_set_rto设置RTO值。RTTVAR不太可能小于50毫秒,其中solaris和freebsd不稳定的ACK回复方式使其不可能发生,但是这与延时ACK无关,因为在拥塞窗口大于2时,不会等到延时ACK的定时器超时的时候发送ACK,3个报文完全可以触发接收端的ACK回复。实际上,Linux-2.4在一些情况下也会产生不稳定的ACK。

所以RTO计算中的4倍RTTVAR至少为200毫秒,RTO值不会小于TCP_RTO_MIN(HZ/5)。函数tcp_bound_rto设置RTO上限值。

static void tcp_set_rto(struct sock *sk)
{   const struct tcp_sock *tp = tcp_sk(sk);/* Old crap is replaced with new one. 8)** More seriously:* 1. If rtt variance happened to be less 50msec, it is hallucination.*    It cannot be less due to utterly erratic ACK generation made*    at least by solaris and freebsd. "Erratic ACKs" has _nothing_*    to do with delayed acks, because at cwnd>2 true delack timeout*    is invisible. Actually, Linux-2.4 also generates erratic*    ACKs in some circumstances.*/inet_csk(sk)->icsk_rto = __tcp_set_rto(tp);/* 2. Fixups made earlier cannot be right.*    If we do not estimate RTO correctly without them,*    all the algo is pure shit and should be replaced*    with correct one. It is exactly, which we pretend to do.*//* NOTE: clamping at TCP_RTO_MIN is not required, current algo* guarantees that rto is higher.*/tcp_bound_rto(sk);

如下函数tcp_bound_rto将RTO值的上限设置在120秒。

static inline void tcp_bound_rto(const struct sock *sk)
{   if (inet_csk(sk)->icsk_rto > TCP_RTO_MAX)inet_csk(sk)->icsk_rto = TCP_RTO_MAX;
}
#define TCP_RTO_MAX ((unsigned)(120*HZ))

内核版本 5.0

这篇关于TCP的RTO值估算的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/511742

相关文章

TCP 可靠传输的工作原理

转载地址:https://my.oschina.net/xinxingegeya/blog/485233 感谢原作者 TCP 可靠传输的工作原理 ARQ(Automatic Repeat-reQuest)(自动重传请求) 停止等待ARQ协议 连续ARQ协议   停止等待ARQ协议 全双工通信的双发既是发送方也是接收方。下面为了讨论问题的方便,我们仅考虑A发送数据而B接受数据

ESP32使用MQTT_TCP连接阿里云

ESP32-IDF中MQTT函数的介绍 esp_mqtt_client_handle_t esp_mqtt_client_init(const esp_mqtt_client_config_t *config) 函数功能:mqtt 客户端初始化 函数形参: mqtt 客户端的配置 函数返回值:正确则返回根据配置创建的 mqtt 客户端句柄;异常则返回 NULL。 其中函数形参 esp_mqtt

【LinuxC语言】第一个简单的tcp/ip客户端

文章目录 前言一、客户端连接服务器的示意图二、客户端涉及的相关函数socket函数connect函数struct sockaddr结构体相关的转换函数connect主体函数 发送数据函数接收数据函数关闭socket客户端示例代码 总结 前言 在计算机网络中,TCP/IP 是最常见的网络协议。它为我们提供了一种可靠的方式来发送和接收数据。在这篇文章中,我们将使用 Linux C

TCP与UDP_三次握手_四次挥手

TCP vs UDP TCP数据 具体可以通过Cisco Packet Tracer工具查看: UDP数据 三次握手、四次挥手 为什么是3/4次?这牵扯到单工、双工通信的问题 TCP建立连接:表白 TCP释放连接:分手 TCP—建立连接—三次握手 解释: 首先,启动服务器,让服务器进入监听状态(监听客户端的连接请求)。客户端向服务器发送同

【linuxC语言】第一个简单的TCP/IP服务器

文章目录 前言一、服务器开发结构图二、深度解析服务器三、服务器开发相关函数htonl函数bind函数listen函数accept函数INADDR_ANY 宏struct sockaddr_in的不同场景区别tcp/ip服务器示例代码 总结 前言 在计算机网络中,TCP/IP 是一种非常重要的通信协议。它是互联网的基础,也是许多网络应用的核心。在 Linux 环境下,我们可以使

TCP、HTTP详解

TCP和HTTP   TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,采用三次握手建立连接,四次挥手终止连接。   TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去,服务器和客户端均可主动发起断开连接的请求。   HTTP(Hypertext Transfer Pr

Netty解决TCP粘包/拆包导致的半包读写问题

一.TCP粘包/拆包问题说明   TCP是个“流”协议,就是没有界限的一串数据。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包拆分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。 二.利用LinedBasedFrameDecoder解决TCP粘包问题    为了

iOS 网络相关面试题(TCP、三次握手、四次挥手、代码实现)

一、TCP的特点和报文结构 1、面向连接、可靠传输、面向字节流、全双工服务 2、TCP的报文结构 TCP报文段由首部字段和一个数据字段组成。 数据字段包含一块应用数据。最大报文长度MSS(Maximum Segment Size)限制了报文段数据字段的最大长度。MSS选项用于在TCP连接建立时,收发双方协商通信时每一个报文段所能承载的最大数据长度。 所以当TCP发送一个大文件(比如一张高清图

用户态协议栈06-TCP三次握手

最近由于准备软件工程师职称考试,然后考完之后不小心生病了,都没写过DPDK的博客了。今天开始在上次架构优化的基础上增加TCP的协议栈流程。 什么是TCP 百度百科:TCP即传输控制协议(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通讯协议。 这里最需要关注的就是基于字节流,在我们使用Linux的Posix API创建TCP的Sock

面向连接的TCP和无连接的UDP

用户数据报协议UDP和传输控制协议TCP: 1.TCP传输的是TCP报文段,UDP传输的是UDP用户数据报。 2.UDP在传送数据之前不需要先建立连接。远地主机的运输层在收到UDP报文后,不需要给出任何确认。虽然UDP不提供可靠交付,但在某些情况下UDP却是一种最有效的工作方式。TCP则提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后释放连接。TCP不提供广播或多播服务。由于T