TCP协议中的传输控制机制图文详解「重传机制」「流量控制」「拥塞控制」

news2024/11/14 14:30:45

目录

TCP重传机制

超时重传

快速重传

SACK 方法

Duplicate SACK

TCP 流量控制

滑动窗口

累积确认

窗口大小由哪一方决定?

接收窗口和发送窗口的大小是相等的吗?

流量控制

窗口关闭的后果

糊涂窗口综合症

 TCP拥塞处理

为什么要有拥塞控制呀,不是有流量控制了吗?

什么是拥塞窗口?和发送窗口有什么关系呢?

怎么知道当前网络是否出现了拥塞呢?

拥塞控制算法

慢开始算法:

拥塞避免算法

拥塞发生算法

快速恢复算法


TCP重传机制

        在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回一个确认应答消息,表示已收到消息。TCP 针对数据包丢失的情况,会用重传机制解决。

常见的重传机制:

  • 超时重传

  • 快速重传

  • SACK

  • D-SACK

超时重传

在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的 ACK 确认应答报文,就会重发该数据,也就是我们常说的超时重传

这种情况要么就是自己发过去的数据包丢失,要么就是对方的应答丢失

超时时间应该设置为多少呢?

首先来了解下什么是RTT:

RTT 指的是数据发送时刻到接收到确认的时刻的差值,也就是包的往返时间。

上图中有两种超时时间不同的情况:

  • 当超时时间 RTO 较大时,重发就慢,丢了老半天才重发,没有效率,性能差;

  • 当超时时间 RTO 较小时,会导致可能并没有丢就重发,于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发。

        所以,超时重传时间 RTO 的值应该略大于报文往返 RTT 的值,实际上「报文往返 RTT 的值」是经常变化的,因为我们的网络也是时常变化的。也就因为「报文往返 RTT 的值」 是经常波动变化的,所以「超时重传时间 RTO 的值」应该是一个动态变化的值

  • 超时触发重传存在的问题是,超时周期可能相对较长。那是不是可以有更快的方式呢?

于是就可以用「快速重传」机制来解决超时重发的时间等待。

快速重传

快速重传机制,它不以时间为驱动,而是以数据驱动重传

在上图,发送方发出了 1,2,3,4,5 份数据:

  • 第一份 Seq1 先送到了,于是就 Ack 回 2;

  • 结果 Seq2 因为某些原因没收到,Seq3 到达了,于是还是 Ack 回 2;

  • 后面的 Seq4 和 Seq5 都到了,但还是 Ack 回 2,因为 Seq2 还是没有收到;

  • 发送端收到了三个 Ack = 2 的确认,知道了 Seq2 还没有收到,就会在定时器过期之前,重传丢失的 Seq2。

  • 最后,收到了 Seq2,此时因为 Seq3,Seq4,Seq5 都收到了,于是 Ack 回 6 。

所以,快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。

        但快速重传机制只解决了一个问题,就是超时时间的问题,但是它依然面临着另外一个问题。就是重传的时候,是重传一个,还是重传所有的问题。

        举个例子,假设发送方发了 6 个数据,编号的顺序是 Seq1 ~ Seq6 ,但是 Seq2、Seq3 都丢失了,那么接收方在收到 Seq4、Seq5、Seq6 时,都是回复 ACK2 给发送方,但是发送方并不清楚这连续的 ACK2 是接收方收到哪个报文而回复的, 那是选择重传 Seq2 一个报文,还是重传 Seq2 之后已发送的所有报文呢(Seq2、Seq3、 Seq4、Seq5、 Seq6) 呢?

  • 如果只选择重传 Seq2 一个报文,那么重传的效率很低。因为对于丢失的 Seq3 报文,还得在后续收到三个重复的 ACK3 才能触发重传。

  • 如果选择重传 Seq2 之后已发送的所有报文,虽然能同时重传已丢失的 Seq2 和 Seq3 报文,但是 Seq4、Seq5、Seq6 的报文是已经被接收过了,对于重传 Seq4 ~Seq6 折部分数据相当于做了一次无用功,浪费资源。

可以看到,不管是重传一个报文,还是重传已发送的报文,都存在问题。

SACK 方法

   SACK选择性确认。这种方式需要在 TCP 头部「选项」字段里加一个 SACK 的东西,它可以将已收到的数据的信息发送给「发送方」,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据

        如下图,发送方收到了三次同样的 ACK 确认报文,于是就会触发快速重发机制,通过 SACK 信息发现只有 200~299 这段数据丢失,则重发时,就只选择了这个 TCP 段进行重复。

Duplicate SACK

D-SACK 有这么几个好处:

  1. 可以让「发送方」知道,是发出去的包丢了,还是接收方回应的 ACK 包丢了;

  2. 可以知道是不是「发送方」的数据包被网络延迟了;

  3. 可以知道网络中是不是把「发送方」的数据包给复制了;


TCP 流量控制

滑动窗口

        我们都知道 TCP 是每发送一个数据,都要进行一次确认应答。当上一个数据包收到了应答了, 再发送下一个。但这种方式的缺点是效率比较低的

        为解决这个问题,TCP 引入了窗口这个概念。即使在往返时间较长的情况下,它也不会降低网络通信的效率。

        窗口的实现实际上是操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除

累积确认

        假设窗口大小为 3 个 TCP 段,那么发送方就可以「连续发送」 3 个 TCP 段,并且中途若有 ACK 丢失,可以通过「下一个确认应答进行确认」。如下图:

        图中的 ACK 600 确认应答报文丢失,也没关系,因为可以通过下一个确认应答进行确认,只要发送方收到了 ACK 700 确认应答,就意味着 700 之前的所有数据「接收方」都收到了。这个模式就叫累计确认或者累计应答

窗口大小由哪一方决定?

TCP 头里有一个字段叫 `Window`,也就是窗口大小。

        这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。

        所以,通常窗口的大小是由接收方的窗口大小来决定的。发送方发送的数据大小不能超过接收方的窗口大小,否则接收方就无法正常接收到数据。

接收窗口和发送窗口的大小是相等的吗?

并不是完全相等,接收窗口的大小是约等于发送窗口的大小的。

        因为滑动窗口并不是一成不变的。比如,当接收方的应用进程读取数据的速度非常快的话,这样的话接收窗口可以很快的就空缺出来。那么新的接收窗口大小,是通过 TCP 报文中的 Windows 字段来告诉发送方。那么这个传输过程是存在时延的,所以接收窗口和发送窗口是约等于的关系。


流量控制

        对于发送端和接收端而言,TCP 需要把发送的数据放到发送缓存区,将接收的数据放到接收缓存区。 而流量控制要做的事情,就是在通过调整接收缓存区的大小,控制发送端的发送。如果对方的接收缓存区满了,就不能再继续发送了

        为了控制发送端的速率,接收端会告知客户端自己接收窗口大小(接收缓冲区中空闲的部分)。在请求数据的ACK确认(和TCP建立连接的ACK无关)中会带有当前接收端的窗口大小

        发送方根据ACK确认响应中的客户端此时的接收窗口大小来调节自身发送滑动窗口的大小,将滑动窗口的数据都发送过去后,要等待ACK再次确认报告接收窗口大小

  • 接收窗口大小为0,发送端阻塞

  • 接收窗口大小不为零,发送端滑动窗口拓展到指定大小后进行后移操作

窗口关闭的后果

        当服务端系统资源非常紧张的时候,操作系统可能会直接减少了接收缓冲区大小,这时应用程序又无法及时读取缓存数据,会出现数据包丢失的现象。为了防止这种情况发生,TCP 规定是不允许同时减少缓存又收缩窗口的,而是采用先收缩窗口,过段时间再减少缓存,这样就可以避免了丢包情况。

        而如果假设服务端系统资源不是很紧张,接收缓冲区不需要变动,那么如果服务端业务读取慢,接收端口容易变为了0,就会阻止发送方给接收方传递数据,直到窗口变为非 0 为止,这就是窗口关闭。

窗口关闭的风险:

         当发生窗口关闭时,接收方处理完数据后,会向发送方通告一个窗口非 0 的 ACK 报文,如果这个通告窗口的 ACK 报文在网络中丢失了,这会导致发送方一直等待接收方的非 0 窗口通知,接收方也一直等待发送方的数据,如不采取措施,这种相互等待的过程,会造成了死锁的现象。

为了解决这个问题,TCP 为每个连接设有一个持续定时器,只要 TCP 连接一方收到对方的零窗口通知,就启动持续计时器。

如果持续计时器超时,就会发送窗口探测报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。

  • 如果接收窗口仍然为 0,那么收到这个报文的一方就会重新启动持续计时器;

  • 如果接收窗口不是 0,那么死锁的局面就可以被打破了。

        窗口探测的次数一般为 3 次,每次大约 30-60 秒(不同的实现可能会不一样)。如果 3 次过后接收窗口还是 0 的话,有的 TCP 实现就会发 RST 报文来中断连接。

糊涂窗口综合症

如果接收方太忙了,来不及取走接收窗口里的数据,那么就会导致发送方的发送窗口越来越小。

到最后,如果接收方腾出几个字节并告诉发送方现在有几个字节的窗口,而发送方会义无反顾地发送这几个字节,这就是糊涂窗口综合症

我们的 TCP + IP 头有 40 个字节,为了传输那几个字节的数据,要搭上这么大的开销,这太不经济了。

于是,要解决糊涂窗口综合症,可以采用如下方案:

  • 让接收方当「窗口大小」小于 min( MSS,缓存空间/2 ) ,也就是小于 MSS 与 1/2 缓存大小中的最小值时,就会向发送方通告窗口为 0,也就阻止了发送方再发数据过来。反之就把真实窗口大小发过去

  • 让发送方使用 Nagle 算法,要等到窗口大小 >= MSS 并且 数据大小 >= MSS或者收到之前发送数据的 ack 回包;只要上面两个条件都不满足,发送方一直在囤积数据,直到满足上面的发送条件。

所以,接收方得满足「不通告小窗口给发送方」+ 发送方开启 Nagle 算法,才能避免糊涂窗口综合症


 TCP拥塞处理

为什么要有拥塞控制呀,不是有流量控制了吗?

        前面的流量控制是避免「发送方」的数据填满「接收方」的缓存,但是并不知道网络的中发生了什么。一般来说,计算机网络都处在一个共享的环境。因此也有可能会因为其他主机之间的通信使得网络拥堵。

        在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是一重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大....

        所以,TCP 不能忽略网络上发生的事,当网络发送拥塞时,TCP 会自我牺牲,降低发送的数据量。于是,就有了拥塞控制,控制的目的就是避免「发送方」的数据填满整个网络。

为了在「发送方」调节所要发送数据的量,定义了一个叫做「拥塞窗口」的概念。

什么是拥塞窗口?和发送窗口有什么关系呢?

我们在前面提到过发送窗口 swnd 和接收窗口 rwnd 是约等于的关系,那么由于加入了拥塞窗口的概念后,此时

发送窗口的值是swnd = min(cwnd, rwnd),也就是拥塞窗口和接收窗口中的最小值。

拥塞窗口 cwnd 变化的规则:

  • 只要网络中没有出现拥塞,cwnd 就会增大;

  • 但网络中出现了拥塞,cwnd 就减少;

怎么知道当前网络是否出现了拥塞呢?

其实只要「发送方」没有在规定时间内接收到 ACK 应答报文,也就是发生了超时重传,就会认为网络出现了拥塞。

拥塞控制算法

拥塞控制主要是四个算法:

  • 慢启动

  • 拥塞避免

  • 拥塞发生

  • 快速恢复

慢开始算法:

从1开始,由小到大逐渐增加拥塞窗口大小来控制发送速率,每经过一个往返时间RTT就更新(窗口大小 * 2)

为什么要使用慢启动:最初的TCP在连接建立成功后会向网络中发送大量的数据包,这样很容易导致网络中路由器缓存空间耗尽,从而发生拥塞。因此新建立的连接不能够一开始就大量发送数据包,而只能根据网络情况逐步增加每次发送的数据量,以避免上述现象的发生。

        慢开始算法以指数次幂增长过快,为了防止引起网络拥塞,还需设置一个慢开始门限ssthresh状态变量。当你通过慢开始算法增长窗口,当窗口大小大于慢开始门限后就会采用拥塞避免算法

拥塞避免算法

每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍

        无论是在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(TCP认为网络拥塞的一种依据是它重传了一个报文段。上面提到过,TCP对每一个报文段都有一个定时器,称为重传定时器(RTO),当RTO超时且还没有得到数据确认,那么TCP就会对该报文段进行重传),就把慢开始门限设置为出现拥塞时的发送窗口大小的一半。然后把拥塞窗口设置为1,执行慢开始算法

拥塞发生算法

当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种:

  • 超时重传

    • ssthresh 设为 cwnd/2

    • cwnd 重置为 1 (是恢复为 cwnd 初始化值,我这里假定 cwnd 初始化值 1)

    但是这种方式太激进,反应也很强烈,会造成网络卡顿。还有更好的方式,前面我们讲过「快速重传算法」。当接收方发现丢了一个中间包的时候,发送三次前一个包的 ACK,于是发送端就会快速地重传,不必等待超时再重传。
    
  • 快速重传

    • cwnd = cwnd/2 ,也就是设置为原来的一半;

    • ssthresh = cwnd;

    • 进入快速恢复算法

快速恢复算法

快速重传和快速恢复算法一般同时使用,快速恢复算法是认为,你还能收到 3 个重复 ACK 说明网络也不那么糟糕,所以没有必要像 RTO 超时那么强烈。

正如前面所说,进入快速恢复之前,cwndssthresh 已被更新了:

  • cwnd = cwnd/2 ,也就是设置为原来的一半;

  • ssthresh = cwnd;

然后,进入快速恢复算法如下:

  • 拥塞窗口 cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了);

  • 重传丢失的数据包;

  • 如果再收到重复的 ACK,那么 cwnd 增加 1;

  • 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1546237.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LinkedIn账号为什么被封?被封后如何解决?

近期会有一些小伙伴说自己遇到了帐号无法登录的情况,其实出现领英帐号被封号(被限制登录)主要会有两类情况,今天就给大家分享一下如果被封该如何解决,强烈建议收藏。 在电脑领英官网或者手机领英APP上,输入领英帐号密码点击登录后…

力扣● 84.柱状图中最大的矩形

84.柱状图中最大的矩形 需要找到元素i的上一个更小的元素leftmin和下一个更小的元素rightmin,这样leftmin和rightmin之间的元素都比当前元素i更大,那么矩形的宽就是中间的这些元素:可以从leftmin1延伸到rightmin-1,长即为height[i…

rancher2.6部署

rancher2.6部署 1、准备环境镜像 2、部署3、密码获取密码设置新密码 4、设置语言5、导入已有集群 1、准备 环境 docker-ce-20.10.23-3.el8.x86_64.rpm以及依赖rpm kubernetes:v1.23.17 镜像 (rancher和k8s有个版本对应关系,rancher2.5就不…

走进redisson

这里作者将大家走进redisson,读完这篇相信加深你对redisson的获取锁,重入,超时,看门狗,发布订阅等原理和功能的理解。 本文将深入原理代码,给出每行代码的意义以及最后的效果,过程有些枯燥&…

Python 指南-最短路径(Dijkstra 算法):

Dijkstra 算法可在 Python 库 OSMNX 中实现,可用于查找两个位置之间按距离或时间加权的最短路径。该算法使用 OpenStreetMap (OSM) 网络来驾驶、步行或骑自行车,并在后台使用 Python 库 NETWORKX 查找路线。 编码练习 正如我提到的,我将做一…

MySQL数据库 @@transaction_isolation参数的查询及修改

在应用开发过程中,可能会检查mysql数据库初始化参数符合要求。 遇到这种情况就要进行相应的调整。 1、查询参数信息 select transaction_isolation; 2、找到配置文件 ,以window系统为例。 修改前先关闭MySQL数据库服务 对应需要修改的参数&#xff…

C语言看完我这篇最详细文件操作,你不会也得会!!!

1.使用文件 我们写的程序的数据是存储在电脑内存中,如果程序退出,内存就会被回收,数据就丢失,内存更具有一些实时性,等再次运行程序的数据的,数据就消失了,如果想要持久化的保存,可以…

LangChain-Chatchat

文章目录 关于 LangChain-Chatchat特性说明实现原理文档处理流程技术路线图(截止0.2.10) 使用 关于 LangChain-Chatchat Langchain-Chatchat(原Langchain-ChatGLM)基于 Langchain 与 ChatGLM 等语言模型的本地知识库问答。 gith…

[自研开源] 数据集成之分批传输 v0.7

开源地址:gitee | github 详细介绍:MyData 基于 Web API 的数据集成平台 部署文档:用 Docker 部署 MyData 使用手册:MyData 使用手册 试用体验:https://demo.mydata.work 交流Q群:430089673 介绍 本篇基于…

STM32F4x7标准库移植LWIP

项目背景 使用GD芯片的我们,都会去参考ST的代码。可是呢,有一个很大的问题就是,ST早就提供HAL库了,而目前GD还只有标准库。在移植LWIP的时候,会有很多不便。 好在天无绝人之路,找到了一份ST的官方例程&am…

java常用IO流功能——字符流和缓冲流概述

前言: 整理下学习笔记,打好基础,daydayup! 之前说了下了IO流的概念,并整理了字节流,有需要的可以看这篇 java常用应用程序编程接口(API)——IO流概述及字节流的使用 字符流 FileReader(文件字…

基于注意力机制和损坏特征掩蔽的遮挡人脸识别

Occluded Face Recognition Based on Attention Mechanism and Damaged Feature Masking 摘要 本文提出了一种基于注意力机制(BAM)和掩模生成器的新型遮挡人脸识别方法。在主干网络中嵌入BAM以提取更多可区分的特征,同时设计掩模生成器来清理…

媒介盒子揭秘0基础写好软文的秘诀

在流量见顶的传播环境下,品牌获取用户注意力的主要方式就在软文,然而有许多品牌在写软文时往往摸不着头脑,不知道怎么写,如何写才能使品牌有效曝光,今天媒介盒子就来和大家聊聊:0基础也能写好软文的秘诀&am…

SAP BAS中Fiori开发的高阶功能(storyboard, navigation, guided development, variant)

1. 前言 在之前的几篇文章中,我介绍了SAP BAS的一些基本功能,包括账户申请,创建工作区,git的使用以及如何step-by-step去创建出你的第一个Fiori项目等等。在本篇中,我将进一步介绍一些在开发Fiori应用程序时会用到的高…

【数仓】DataX软件安装及配置,从mysql同步到hdfs

相关文章 【数仓】基本概念、知识普及、核心技术【数仓】数据分层概念以及相关逻辑【数仓】Hadoop软件安装及使用(集群配置)【数仓】Hadoop集群配置常用参数说明【数仓】zookeeper软件安装及集群配置【数仓】kafka软件安装及集群配置【数仓】flume软件安…

C语言例4-7:格式字符f的使用例子

%f&#xff0c;实型&#xff0c;小数部分为6位 代码如下&#xff1a; //格式字符f的使用例子 #include<stdio.h> int main(void) {float f 123.456;double d1, d2;d11111111111111.111111111;d22222222222222.222222222;printf("%f,%12f,%12.2f,%-12.2f,%.2f\n&qu…

关于在forEach循环中使用异步,造成forEach里面的函数还未执行完毕,外层的同步已经被执行的问题

使用 原生的 for循环替代forEach循环即可解决问题 1.实例代码&#xff1a; select_Father_comment_sql_res.forEach( (item) > {const Select_FId_children_sql util.format("Select *, \IFNULL(User.UserName,) as CommentUserName, \IFNULL(User.UserName,) as AtU…

2015年认证杯SPSSPRO杯数学建模B题(第一阶段)替换式密码全过程文档及程序

2015年认证杯SPSSPRO杯数学建模 B题 替换式密码 原题再现&#xff1a; 历史上有许多密码的编制方法。较为简单的是替换式密码&#xff0c;也就是将文中出现的字符一对一地替换成其它的符号。对拼音文字而言&#xff0c;最简单的形式是单字母替换加密&#xff0c;也就是以每个…

如何学习VBA_3.2.19:利用Shell函数运行可执行程序

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的劳动效率&#xff0c;而且可以提高数据处理的准确度。我推出的VBA系列教程共九套和一部VBA汉英手册&#xff0c;现在已经全部完成&#xff0c;希望大家利用、学习。 如果…