TCP高级机制

Posted zkccnb

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TCP高级机制相关的知识,希望对你有一定的参考价值。

TCP高级机制

之前总结过,TCP作为传输层协议有5大特性:

  • **面向连接:**用三次握手和四次挥手机制建立连接后进行通信。
  • **可靠:**确保报文一定可以到达对端,使用 TCP高级机制 来确保这一点。
  • **字节流:**确保报文段之间是有序的,只有当前序报文都准备就绪时才会从内核缓冲区递交到用户区。
  • **无边界:**连续的TCP报文段之间是没有边界的,需要上层协议正确解析。
  • **传输层分组:**为了确保IP层的效率更高,TCP在传输层就对超过MSS字节的报文段进行拆分分组。

其中第二条可靠性是本文要重点探讨的话题,主要介绍TCP为了确保发送端的报文一定可以被对方接收到,协议做的一些努力。

一、重传机制

TCP的重传机制确保当发生某个报文段丢失(或应答丢失)时,可以重新传送报文段,依托TCP应答机制来实现。

图1 TCP应答机制

TCP主要使用三种重传机制:超时重传、快速重传、SACK

1. 超时重传

超时重传的思路非常简单粗暴:超过指定的时间没收到对方某个数据的ACK,就重传该数据的报文段。(这包括两种情况,数据丢失或ACK丢失)

常做算法的同学到这里可能就会嗅到调参的气息了。。。是的,这种机制下的超时时间需要我们来确定。。

原则上说,超时时间显然应该略大于RTT(数据包在网络中往返一次的平均时间,Round Trip Time)。太大了不行,太小了更不行。

这样说来,超时时间这个参数显然不适合设定为一个固定参数然后就走人了。。。控制专业出身的可以察觉到这一点,实际上应该做成自适应的:按一定频率采样RTT,然后取历史RTT的平均。这样就可以保证超时时间参数随RTT实际变化而变化了。

另外一点,如果超时重发的数据再次超时,TCP会将超时时间重置为原来的2倍。因为同一数据连续超时说明网络条件现在极差,不如多等等再传,让网络层的压力缓一缓。。

2. 快速重传

超时重传是被动地感知超时,这样做可能还是会很慢。。而快速重传是一种主动地重传,可以更快地侦测到对端的数据缺失:

图2 快速重传原理

快速重传的思路也很简单,如果发送端收到了连续3个应答号一样的ACK,就说明应答号的前一个报文段没了。。此时需要重传之,顺利的话,接下来就会收到应答号更新的ACK了。

快速重传确实可以更快地感应报文缺失了,可是这样做还有一个问题:

由图2,可以确定的是,Seq2报文段肯定是丢了,但Seq3, Seq4, Seq5呢?丢没丢呢?恐怕不能马上知道,需要看重传Seq2后收到的ACK应答号是多少才能知道。。然后继续等待可能的快速重传触发。。

3. SACK(Selective ACK,选择性确认)

为了优化快速重传不知道具体哪些数据没收到的问题,SACK在每次应答的同时将自己目前接收到了哪些数据连同ACK一起发送回去:

图3 SACK原理

可见,每次ACK时,除了发送应答号外,还会发送本次的SACK字段,标识接收端当前接受到了哪些字节,这样发送端就可以推测出接收方哪些字节没有接收到。

二、滑动窗口与流量控制机制

之前为了方便演示,TCP的发送效果都是收到应答后再发送下一个(如上图),但实际上这么做肯定不行啊。。因为这样意味着每发送一个报文段,需要消耗一个RTT,如果两端离得很远(想象中国到美国),RTT会很大,那通信岂不是卡得一批。。。所以实际工程中多个报文段的发送和应答一定是下面这种做法;不等待应答,连续发送

图4 实际工程中的TCP报文段发送

这种“流水线”模式的连续发送中,应答机制还是和之前讨论的一样,采用累计确认。仍能判断出哪个包缺失了,没什么问题。

但是连续发送有一个问题需要考虑:就是如果连续发送太多报文段,接收方的内核缓冲区爆了怎么办?溢出的数据都要丢失了啊,这可能会造成大量重传,得不偿失。。到这里,就需要引入滑动窗口机制了!

回忆“TCP协议简介”文章中提到TCP协议的报文段格式,头格式中就有一个“窗口大小”,就与滑动窗口机制有关。

“窗口大小”字段是由接收方决定的,数据接收方每次通过ACK应答把当前自己计算的“窗口大小”发送给发送方,以控制其连续发送报文段的字节数。一个原则是:发送方发送的数据不能超过当前“窗口大小”,否则接收端缓冲区就要溢出了。。接下来我们分别看看连续发送数据时,发送方和接收方是怎么操作的:

  • 发送方连续发送数据时的处理:

    图5 发送窗口的运作机理

    图中的数据即为发送方在内核缓冲的数据,缓冲数据的总体流向是从右向左的(假设窗口固定不动)。#1部分的数据为经过确认无误的数据,可以移除缓冲区,#2#3#4的数据仍保留在发送端内核缓冲区中。接收到一个ACK,#2部分便向左移动一个;发送出去一个数据,#3部分便向左移动一个。。。以此类推。

    #2#3两部分构成了发送窗口的大小(字节数),任一时刻发送的数据不能超过此大小,否则就会认为接收方发生了缓冲区溢出。

  • 接收方连续接受数据时的处理:

    图6 接受窗口的运作机理

    接收方就相对相对简单了,只需数据流向从右向左移动即可(固定接受窗口不动)。只不过接受方需要**时刻计算自己剩余的接受缓冲区大小,改变接收窗口的长度。**然后通过ACK报文通知发送方自己最新的窗口大小。

    所以,滑动窗口的大小一定是时刻都在改变的:

    • 滑动窗口大小=接受窗口剩余字节数
    • 发送窗口大小 趋近于 接收窗口大小;因为接收端需要把自己的窗口大小通过ACK报文形式通知发送端,所以发送窗口大小总是会有一个延迟。

通过这样的滑动窗口机制,接受端和发送端就可以最高效地连续发送数据,又避免接收缓冲区溢出了。

而发送端利用滑动窗口感知接收端的最大接受能力,这一机制就是流量控制。当发送窗口满了,发送端会认为接收端此刻已经不能再接受数据了,于是就会停止发送一段时间,等待窗口大小变大或收到更多的ACK后才会继续发送。

  • 发送端的窗口探测报文:

    值得一提的是,如果接收方上层处理速度过慢,导致接受缓冲区全部被沾满,此时明显窗口大小为0。通知发送端后,发送端将不能继续发送数据,不能发送数据也就意味着不能收到接收端的ACK,无法获取后续的窗口变化了!这种情况可能会导致死锁。。即使接收端的缓冲区腾出空位了,发送端也感知不到。这时就需要发送端定时主动发送 窗口探测报文 以避免死锁。
    图7 发送端窗口探测报文的作用

三、拥塞控制

最后一点是拥塞控制。拥塞控制这个名词给人感觉和流量控制有点像。。其实还是很不一样的!

  • 流量控制:

    让发送端可以实时感应接收端的接受能力,当接受能力不够时,停止发送,让接收端缓一缓。。

  • 拥塞控制:

    管的不是接收端的实时情况,而是从发送端到接收端的网络链路中的情况。当感知到底层链路出现延迟时,采取一定手段减缓发送速度。

可见,二者的感知对象并不相同,但其实目的都一样,确保报文可以更快且可靠地到达对端。

如果没有拥塞控制的话,当一个数据包耽误在延迟的网络中,会触发重传机制。但如果网络条件本身就不好,此时仍不停地重传重传。。。势必会增大网络的压力,形成恶性循环。拥塞控制就是对这个问题进行感知并解决的。

为了实现拥塞控制,TCP协议维护一个拥塞窗口(cwnd)。加入拥塞控制后,实际的发送窗口大小为:min(cwnd, rwnd)。即考虑拥塞控制时,发送窗口实际大小为:拥塞窗口和接受窗口的最小值(rwnd是接收端通过ACK报文发来的)。

拥塞窗口可以感知网络发送的变化:总趋势是,网络出现了拥塞,则cwnd减小;网络没有拥塞,cwnd增大。

接下来我们看一下拥塞控制的具体算法:

拥塞控制算法具体分为4个阶段:慢启动、拥塞避免、拥塞发生、快速回复

  • 慢启动

    TCP连接建立完成后,拥塞算法首先进入慢启动阶段。

    慢启动算法的显著特点是:初始cwnd=1(单位),随后每收到1个单位的ACK,cwnd就会加1(单位)

    而每增加一个单位的cwnd,就意味着将会收到更多的ACK,下次cwnd就会增加更多。。。如此推算,cwnd的增长是呈指数型的。


图8 慢启动算法的cwnd增长规律

  • 拥塞避免

    慢启动显然不能一直下去,这会导致cwnd变得过大。当cwnd超过慢启动门限时,进入拥塞避免算法:

    • cwnd < ssthresh:慢启动
    • cwnd >= ssthresh:拥塞避免

    默认慢启动门限为65535字节。

    进入拥塞避免时,算法规则变为:每收到一个ACK,cwnd增加1/cwnd

    同理可以推算出,拥塞避免阶段的cwnd呈近似线性增长,此时cwnd的增长就很慢了:


图9 慢启动与拥塞避免的cwnd增长规律

  • 拥塞发生:

    当发生重传时,就说明网络可能发生一定程度的拥塞了。重传主要有两种:超时重传和快速重传。

    当发生超时重传时,说明网络可能已经严重拥堵了,此时应该想办法马上减缓发送速率。。

    具体的做法是:ssthresh=cwnd/2 ; cwnd=1并重新进入慢启动状态。

    图10 拥塞发送(超时重传的处理)

    当发送快速重传时,说明网络拥堵可能没那么严重,此时突然刹车可能会影响用户的网络使用体验。。

    所以此时的做法是cwnd=cwnd/2; ssthresh=cwnd 并进入快速恢复算法

  • 快速恢复

    上面提到,快速恢复发生在触发快速重传时,此时的网络拥塞状况可能没有那么糟糕,所以不需要踩死刹车,只需要适当减速观察即可。

    图11 快速启动示意

    进入快速启动状态后,先执行cwnd=ssthresh+3,然后重传数据。如果此时又收到了重复的ACK(说明重传的数据还是没收到。。)那么执行cwnd++

    如果此时收到了新数据的ACK(说明重传成功),那么就回到拥塞避免的状态即可。

总得来说,拥塞避免的过程如下图:

图12 拥塞避免状态转换过程中cwnd的变化

相信结合图8-图11加上文字描述,想要理解基础的拥塞控制算法还是比较容易的。

总结

本文简要介绍了三种TCP协议的高级机制——重传机制、滑动窗口与流量控制机制、拥塞控制机制。三种高级机制的目的都是为了使数据尽可能快速且可靠地传输到对端,确保了TCP协议的可靠性。

以上是关于TCP高级机制的主要内容,如果未能解决你的问题,请参考以下文章

Vue3官网-高级指南(十七)响应式计算`computed`和侦听`watchEffect`(onTrackonTriggeronInvalidate副作用的刷新时机`watch` pre)(代码片段

JavaScript笔试题(js高级代码片段)

阶段1 语言基础+高级_1-3-Java语言高级_07-网络编程_第2节 TCP协议_3_TCP通信的客户端代码实现

AIDE,sudo,TCP_Wrappers,PAM认证等系统安全访问机制

你知道的Go切片扩容机制可能是错的

linux epoll机制对TCP 客户端和服务端的监听C代码通用框架实现