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

简介:

超时与重传

 

    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也是如此处理的。

相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。 &nbsp; &nbsp; 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
目录
相关文章
|
网络协议
为何UDP协议不可靠?DNS为何选择UDP?
总的来说,UDP和TCP各有优势,选择哪种协议取决于应用的具体需求。UDP可能不如TCP可靠,但其简单、快速的特性使其在某些场景下成为更好的选择。而DNS就是这样的一个例子,它利用了UDP的优势,以实现快速、高效的名字解析服务。
686 14
|
网络协议 Linux Go
自己动手编写tcp/ip协议栈1:tcp包解析
学习tuntap中的tun的使用方法,并使用tun接口解析了ip包和tcp包,这是实现tcp/ip协议栈的第一步。
550 15
|
网络协议
TCP报文格式全解析:网络小白变高手的必读指南
本文深入解析TCP报文格式,涵盖源端口、目的端口、序号、确认序号、首部长度、标志字段、窗口大小、检验和、紧急指针及选项字段。每个字段的作用和意义详尽说明,帮助理解TCP协议如何确保可靠的数据传输,是互联网通信的基石。通过学习这些内容,读者可以更好地掌握TCP的工作原理及其在网络中的应用。
|
网络协议
网络通信的基石:TCP/IP协议栈的层次结构解析
在现代网络通信中,TCP/IP协议栈是构建互联网的基础。它定义了数据如何在网络中传输,以及如何确保数据的完整性和可靠性。本文将深入探讨TCP/IP协议栈的层次结构,揭示每一层的功能和重要性。
1241 5
|
网络协议 网络性能优化 数据处理
深入解析:TCP与UDP的核心技术差异
在网络通信的世界里,TCP(传输控制协议)和UDP(用户数据报协议)是两种核心的传输层协议,它们在确保数据传输的可靠性、效率和实时性方面扮演着不同的角色。本文将深入探讨这两种协议的技术差异,并探讨它们在不同应用场景下的适用性。
647 4
|
监控 网络协议 网络性能优化
网络通信的核心选择:TCP与UDP协议深度解析
在网络通信领域,TCP(传输控制协议)和UDP(用户数据报协议)是两种基础且截然不同的传输层协议。它们各自的特点和适用场景对于网络工程师和开发者来说至关重要。本文将深入探讨TCP和UDP的核心区别,并分析它们在实际应用中的选择依据。
887 3
|
网络协议
深入解析:TCP四次挥手断开连接的全过程及必要性
在网络通信中,TCP(传输控制协议)以其可靠性和顺序保证而闻名。然而,TCP连接的建立和终止同样重要,它们确保了网络资源的有效管理和数据传输的完整性。本文将详细描述TCP连接的四次挥手过程,并探讨为何需要四次挥手来正确终止一个TCP连接。
712 2
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
901 140
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
1425 29
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
565 4