zl程序教程

您现在的位置是:首页 >  工具

当前栏目

libjingle源码解析(6)-【PseudoTcp】建立UDP之上的TCP(4):超时与重传

源码TCP 解析 建立 超时 UDP 之上 重传
2023-09-27 14:27:58 时间

 

    TCP是面向连接的可靠的运输层。当数据丢失时,TCP需要重传包。TCP通过设置定时器解决这种问题。

    对每个连接,TCP有4个不同的定时器:

        1)重传定时器:用于当希望收到另一端的确认,而没有收到时。

        2)坚持定时器:使窗口大小信息保持不断流动。

        3)保活定时器:可检测空闲连接另一端何时崩溃或重启。

        4)2MSL定时器:测量TIME_WAIT状态的时间。

 

    PTCP本身是没有提供定时器的,而通过方法GetNextClock让调用者获取下一个定时器触发的时机,当定时器触发下一个超时时,需要调用方法NotifyClock。

 

超时时间设置

 

    TCP设置获得确认ACK包的超时时间设置序列可能为1.5S,3S,6S,12S,24S,48S,64S,当超时持续时间多于9分钟时,TCP会被复位(RST),即“指数退避”。

    那么这个超时值是怎么计算呢?

    如果能很好的估计RTT话,如果确认包在一个RTT之内没有收到回报,那么可以认为丢包发生。

    TCP最初的RTT估算方法为

        R = aR+(1-a)M

    其中平滑因子a取为90%,M表示这次测量的RTT,即这个包发送到获取ACK的时间间隔。

    这个算法通过平滑因子来避免R的值受新的M的浮动过大的影响。然而这恰恰在RTT浮动比较大的连接中无法及时的反应连接情况。并且网络处于饱和状态时,频繁重传会导致火上烧油。Jacobson对此设计了新的算法:

        Err = M - A

        A = A+g*Err

        D = D + h(|Err| -D)

        RTO = A + 4D

    增量g为0.125(1/8),Err为上一个得到的值和新的RTT的差。A为上一个测到的增量后的数据,h为0.25。

当RTT变化大时,Err也会变大,导致D也会变大,导致RTO快速上升。某一次连接的估值和真正的RTT关系估下:

 

 

     PTCP实现如下:

     PTCP设置最大超时时间为60S。当收到ACK时,计算RTT是通过PTCP头部的TimeStamp差值计算,所以Karn算法在此不管用。RTO的算法和上面所述一致:

         1)Err = rtt - m_rx_srtt

         2)D=D+0.25*(abs(Err-D))

         3)m_rx_srtt = m_rx_srtt + err/8

         4)RTO = m_rx_srtt+D

     下面的代码实现,有一定的不同,但仔细分析和上面算法是一致的。

 

 

 


 // Check if this is a valuable ack     if ((seg.ack   m_snd_una)   (seg.ack  = m_snd_nxt)) {       // Calculate round-trip time       if (seg.tsecr) {         long rtt = talk_base::TimeDiff(now, seg.tsecr);//计算RTT         if (rtt  = 0) {           if (m_rx_srtt == 0) {             m_rx_srtt = rtt;             m_rx_rttvar = rtt / 2;           } else {             m_rx_rttvar = (3 * m_rx_rttvar + abs(long(rtt - m_rx_srtt))) / 4;             m_rx_srtt = (7 * m_rx_srtt + rtt) / 8;           }           m_rx_rto = bound(MIN_RTO, m_rx_srtt +               talk_base::_max uint32 (1, 4 * m_rx_rttvar), MAX_RTO);         } else {           ASSERT(false);         }       }   ......  

 

    拥塞避免算法通常和慢启动算法一起使用,主要是限制发送方的流量。慢启动的目的是,不要过快的发送数据导致中间的路由器填满缓冲,而拥塞避免算法是当发现到网络被拥塞时限制发送方处理丢失分组的一种方法。

    拥塞避免算法和慢启动算法同时在一个连接上维护两个变量cwnd和ssthresh。

        1)对一个给定连接cwnd初始化为1。

        2)当拥塞发生时(超时或者受到重复的第三个ack)时ssthreth取当前窗口的一半,如果超时引起的拥塞,则cwnd取为1。

        3)当新的数据包受到确认时,如果cwnd ssthreth则进行慢启动算法,否则cwnd在每个确认增加1/cwnd。

 

快速重传与快速恢复算法

 

     为什么上面判断拥塞时,获得三个以上重复的ACK时,认为产生拥塞了呢?

     因为,当接收方收到失序的报文段时,立即发送需要收到的下一个报文段,然而发送方发送两个以上报文时,因报文的路由不一样,会产生短暂的失序,为了避免因此而产生的重传,把拥塞判断设置为3个以上。

     当收到三个以上重复报文段时,发送方认为此包被丢失,于是立即重传丢失报文段,不会等到超时定时器溢出。这就是快速重传算法。

 

     当发送方重传后,会持续发送后面没有发送的数据,而不启动慢启动,等待ACK,是因为发送方收到了连续的3个以上ACK说明,接收方收到了3个以上数据报文,并缓存起来了。这就是快速恢复算法,实现如下:

        1)当收到3个重复ACK时ssthreth设置为当前窗口的一半,并cwnd设置为ssthresh+3。

        2)每次收到另一个重复的ACK时,cwnd增加一个报文段并重传。

        3)当下一个ACK到达时cwdn设置为ssthreth,即采用拥塞避免,速率减半。

    对于重传PTCP有一点不同,就是上述第一步,当收到重复3个ACK时,ssthresh设置为还未确认的字节数的一半。

 


    if (m_dup_acks  = 3) {//如果进行过重传         if (m_snd_una  = m_recover) { // 时重传后的数据确认           uint32 nInFlight = m_snd_nxt - m_snd_una;//未确认数据           m_cwnd = talk_base::_min(m_ssthresh, nInFlight + m_mss); // cwnd设置为ssthreth           m_dup_acks = 0;//重复ACK计数器清零         } else {           if (!transmit(m_slist.begin(), now)) {//慢启动、继续传送             closedown(ECONNABORTED);             return false;           }           m_cwnd += m_mss - talk_base::_min(nAcked, m_cwnd);         }       } else {         m_dup_acks = 0;         // Slow start, congestion avoidance         if (m_cwnd   m_ssthresh) {//慢启动           m_cwnd += m_mss;         } else {           m_cwnd += talk_base::_max uint32 (1, m_mss * m_mss / m_cwnd);//拥塞避免,增加1/cwnd         }        }       }       else if (seg.ack == m_snd_una) {         // !?! Note, tcp says dont do this... but otherwise how does a closed window become open?         m_snd_wnd = static_cast uint32 (seg.wnd)   m_swnd_scale;         // Check duplicate acks         if (seg.len   0) {           // its a dup ack, but with a data payload, so dont modify m_dup_acks         } else if (m_snd_una != m_snd_nxt) {           m_dup_acks += 1;           if (m_dup_acks == 3) { //当收到3个重复的ACK时进行快速重传             if (!transmit(m_slist.begin(), now)) {               closedown(ECONNABORTED);               return false;             }             m_recover = m_snd_nxt;             uint32 nInFlight = m_snd_nxt - m_snd_una;             m_ssthresh = talk_base::_max(nInFlight / 2, 2 * m_mss);//ssthresh设置为2个MSS和cwnd的最小值             m_cwnd = m_ssthresh + 3 * m_mss;//cwnd设置为ssthresh加3           } else if (m_dup_acks   3) {             m_cwnd += m_mss;//当收到发送重传后的重复的ACK时,只增加一个MSS,即快速恢复算法           }         } else {           m_dup_acks = 0;         }       }   ......  

重新分组

    当TCP超时重传时,可以允许以更大的且不大于MSS的报文发送,即合并后续的数据一起发送,PTCP也是如此处理的。


性能提升 57% ,SMC-R 透明加速 TCP 实战解析 TCP 协议作为当前使用最为广泛的网络协议,场景遍布移动通信、数据中心等。对于数据中心场景,通过弹性 RDMA 实现高性能网络协议 SMC-R,透明替换应用 TCP 协议,实现应用网络透明加速。
深入解析TCP协议 1. OSI七层模型 TCP(Transmission Control Protocol 传输控制协议)是一种面向连接(连接导向)的、可靠的、 基于IP的传输层协议。 TCP在IP报文的协议号是6。
一、TCP报文格式   TCP/IP协议的详细信息参看《TCP/IP协议详解》三卷本。下面是TCP报文格式图:
  上图中有几个字段需要重点介绍下:   (1)序号:Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
TCP/IP详解 卷1 第二十一章 TCP的超时与重传 21.1 引言 可靠性的保证之一就是超时重传 前面两个超时重传的例子 1)  ICMP端口不能到达时,TFTP客户使用UDP实现了一个简单的超时和重传机制,假定5s是一个适当是时间间隔,并每隔5s进行重传 2)  在向一个不存在的主机发送ARP的 例子中,可看到当TCP试图建立连接的时候,在每个重传之间使用一个较长的时延来重传SYN 对于每个连接,TCP管理4个不同的定时器: 1)  重传定时器使用于当希望收到另一端的确认 2)  坚持(persist)定时器使窗口大小信息保持不断流动,即使另一端关闭了其接收窗口。