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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
云解析 DNS,旗舰版 1个月
简介:

超时与重传

 

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

    对每个连接,TCP4个不同的定时器:

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

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

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

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

 

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

 

超时时间设置

 

    TCP设置获得确认ACK包的超时时间设置序列可能为1.5S3S6S12S24S48S64S,当超时持续时间多于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

    增量g0.125(1/8)Err为上一个得到的值和新的RTT的差。A为上一个测到的增量后的数据,h0.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

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

 

 

 

[cpp]   view plain copy
  1. bool PseudoTcp::process(Segment& seg) {  
  2. ......  
  3.  // Check if this is a valuable ack  
  4.   if ((seg.ack > m_snd_una) && (seg.ack <= m_snd_nxt)) {  
  5.     // Calculate round-trip time  
  6.     if (seg.tsecr) {  
  7.       long rtt = talk_base::TimeDiff(now, seg.tsecr);//计算RTT  
  8.       if (rtt >= 0) {  
  9.         if (m_rx_srtt == 0) {  
  10.           m_rx_srtt = rtt;  
  11.           m_rx_rttvar = rtt / 2;  
  12.         } else {  
  13.           m_rx_rttvar = (3 * m_rx_rttvar + abs(long(rtt - m_rx_srtt))) / 4;  
  14.           m_rx_srtt = (7 * m_rx_srtt + rtt) / 8;  
  15.         }  
  16.         m_rx_rto = bound(MIN_RTO, m_rx_srtt +  
  17.             talk_base::_max<uint32>(1, 4 * m_rx_rttvar), MAX_RTO);  
  18.       } else {  
  19.         ASSERT(false);  
  20.       }  
  21.     }  
  22. ......  
  23. }  


 

 

    当重传后,仍然超时时,PTCP也采用指数退避算法。

拥塞避免算法

 

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

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

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

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

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

 

快速重传与快速恢复算法

 

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

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

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

 

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

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

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

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

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

 

[cpp]   view plain copy
  1. bool PseudoTcp::process(Segment& seg) {  
  2. ......  
  3.     if ((seg.ack > m_snd_una) && (seg.ack <= m_snd_nxt)) {//当收到合法的ACK时  
  4. ......  
  5.     if (m_dup_acks >= 3) {//如果进行过重传  
  6.       if (m_snd_una >= m_recover) { // 时重传后的数据确认  
  7.         uint32 nInFlight = m_snd_nxt - m_snd_una;//未确认数据  
  8.         m_cwnd = talk_base::_min(m_ssthresh, nInFlight + m_mss); // cwnd设置为ssthreth  
  9.         m_dup_acks = 0;//重复ACK计数器清零  
  10.       } else {  
  11.         if (!transmit(m_slist.begin(), now)) {//慢启动、继续传送  
  12.           closedown(ECONNABORTED);  
  13.           return false;  
  14.         }  
  15.         m_cwnd += m_mss - talk_base::_min(nAcked, m_cwnd);  
  16.       }  
  17.     } else {  
  18.       m_dup_acks = 0;  
  19.       // Slow start, congestion avoidance  
  20.       if (m_cwnd < m_ssthresh) {//慢启动  
  21.         m_cwnd += m_mss;  
  22.       } else {  
  23.         m_cwnd += talk_base::_max<uint32>(1, m_mss * m_mss / m_cwnd);//拥塞避免,增加1/cwnd  
  24.       }  
  25.      }  
  26.     }  
  27.     else if (seg.ack == m_snd_una) {  
  28.       // !?! Note, tcp says don't do this... but otherwise how does a closed window become open?  
  29.       m_snd_wnd = static_cast<uint32>(seg.wnd) << m_swnd_scale;  
  30.       // Check duplicate acks  
  31.       if (seg.len > 0) {  
  32.         // it's a dup ack, but with a data payload, so don't modify m_dup_acks  
  33.       } else if (m_snd_una != m_snd_nxt) {  
  34.         m_dup_acks += 1;  
  35.         if (m_dup_acks == 3) { //当收到3个重复的ACK时进行快速重传  
  36.           if (!transmit(m_slist.begin(), now)) {  
  37.             closedown(ECONNABORTED);  
  38.             return false;  
  39.           }  
  40.           m_recover = m_snd_nxt;  
  41.           uint32 nInFlight = m_snd_nxt - m_snd_una;  
  42.           m_ssthresh = talk_base::_max(nInFlight / 2, 2 * m_mss);//ssthresh设置为2个MSS和cwnd的最小值  
  43.           m_cwnd = m_ssthresh + 3 * m_mss;//cwnd设置为ssthresh加3  
  44.         } else if (m_dup_acks > 3) {  
  45.           m_cwnd += m_mss;//当收到发送重传后的重复的ACK时,只增加一个MSS,即快速恢复算法  
  46.         }  
  47.       } else {  
  48.         m_dup_acks = 0;  
  49.       }  
  50.     }  
  51. ......  
  52. }  


 

 

重新分组

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

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
目录
相关文章
|
19天前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
39 0
|
17天前
|
存储
让星星⭐月亮告诉你,HashMap的put方法源码解析及其中两种会触发扩容的场景(足够详尽,有问题欢迎指正~)
`HashMap`的`put`方法通过调用`putVal`实现,主要涉及两个场景下的扩容操作:1. 初始化时,链表数组的初始容量设为16,阈值设为12;2. 当存储的元素个数超过阈值时,链表数组的容量和阈值均翻倍。`putVal`方法处理键值对的插入,包括链表和红黑树的转换,确保高效的数据存取。
39 5
|
19天前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
|
19天前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
19天前
|
算法 Java 程序员
Map - TreeSet & TreeMap 源码解析
Map - TreeSet & TreeMap 源码解析
29 0
|
19天前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
29 0
|
19天前
|
存储 Java C++
Collection-PriorityQueue源码解析
Collection-PriorityQueue源码解析
33 0
|
19天前
|
安全 Java 程序员
Collection-Stack&Queue源码解析
Collection-Stack&Queue源码解析
45 0
|
2月前
|
存储 缓存 Java
什么是线程池?从底层源码入手,深度解析线程池的工作原理
本文从底层源码入手,深度解析ThreadPoolExecutor底层源码,包括其核心字段、内部类和重要方法,另外对Executors工具类下的四种自带线程池源码进行解释。 阅读本文后,可以对线程池的工作原理、七大参数、生命周期、拒绝策略等内容拥有更深入的认识。
116 29
什么是线程池?从底层源码入手,深度解析线程池的工作原理
|
2月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
335 37

推荐镜像

更多