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

目录

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 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态;

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

高速CAN 收发器AMIS30660CANH2RG 用于各种数据传输协议的调制解调器和收发器

AMIS30660CANH2RG CAN 收发器是控制器区域网络 (CAN) 协议控制器和物理总线之间的接口,可在 12 V 和 24 V 系统中使用。该收发器为总线提供差分发射功能,向 CAN 控制器提供差分接收功能。由于接收器输入较宽的共模电压范围和其他设计功能, 能…

Python爬取豆瓣电影Top 250,豆瓣电影评分可视化,豆瓣电影评分预测系统

博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇…

C++ 模板入门详解

目录 0. 模板引入 1.函数模板 1. 函数重载的缺点 2. 函数模板的概念和格式 2. 函数模板的实例化 2.1 隐式实例化&#xff1a;让编译器根据实参推演模板参数的实际类型 2.2 显式实例化&#xff1a;在函数名后的<>中指定模板参数的实际类型 2.3 函数模板参数的匹…

[HFCTF 2021 Final]easyflask

[HFCTF 2021 Final]easyflask [[python反序列化]] 首先题目给了提示&#xff0c;有文件读取漏洞&#xff0c;读取源码 #!/usr/bin/python3.6 import os import picklefrom base64 import b64decode from flask import Flask, request, render_template, sessionapp Flask(_…

【Leetcode-54.螺旋矩阵】

题目&#xff1a; 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,6,9,8,7,4,5]示例 2&#xff1a; 输入&#xff1…

MySQL分组查询与子查询 + MySQL表的联结操作

目录 1 MySQL分组查询与子查询 1.1 数据分组查询 1.2 过滤分组 1.3 分组结果排序 1.4 select语句中子句的执行顺序 1.5 子查询 2 MySQL表的联结操作 2.1 关系表 2.2 表联结 2.3 笛卡尔积 2.4 内部联结 2.5 外联结 2.6 自联结 2.7 组合查询 1 MySQL分组查询与子查询…

如何把1G多的视频压缩到500兆以内?轻松节省内存空间~

微信已经成为了我们上班交流沟通时必不可少的通讯工具之一&#xff0c;在使用微信时&#xff0c;常常会遇到系统提示发送的word、ppt、pdf文件、视频、压缩包等文件超过1G&#xff0c;无法发送。有没有什么办法可以缩小文件的体积呢&#xff1f;今天给大家介绍几款可以用于视频…

基于python+vue研究生志愿填报辅助系统flask-django-php-nodejs

二十一世纪我们的社会进入了信息时代&#xff0c;信息管理系统的建立&#xff0c;大大提高了人们信息化水平。传统的管理方式对时间、地点的限制太多&#xff0c;而在线管理系统刚好能满足这些需求&#xff0c;在线管理系统突破了传统管理方式的局限性。于是本文针对这一需求设…

10BASE-T1S协议基本介绍

10BASE10Base-T1S 是 IEEE 802.3cg 标准的一部分&#xff0c;该标准支持单根双绞线高达 10 Mbps 的数据速率&#xff0c;适用于长达 25 米半双工网络&#xff0c;旨在实现多点网络上的无碰撞、确定性传输。 1、网络拓扑图和ECU连接方式 网络架构支持多播总线式架构&#xff0c…

Windows10 专业版 系统激活

Windows10 专业版 系统激活 参考&#xff1a; Windows10系统激活技巧 第一步&#xff1a;在电脑桌面&#xff0c;新建一个文本文档 第二步&#xff1a;打开文本文档&#xff0c;输入以下代码后&#xff0c;直接保存关闭文档 slmgr/skms kms.03k.org slmgr/ato 第三步&#xff1…

spring多个过滤器和controller接口的代码执行顺序

多个过滤器和controller接口的代码执行顺序 研究此问题的起因 在使用开源框架芋道时, 启用了api访问日志功能, 但是发现未能生效,看源码发现是通过过滤器实现的, 并使用断点测试发现在过滤器中的日志记录代码写在了 filterChain.doFilter(request, response); 后面日志记录代…

png转换jpg怎么操作?这一种方法很方便

很多平台、软件在上传使用图片的时候会对图片格式有限制。而jpg格式的图片相较于其他格式的图片兼容性更高&#xff0c;那么怎么将png格式的图片转换成jpg格式呢&#xff1f;使用在线图片格式转换器。支持上传jpg、webp、gif、png、bmp等格式的图片一键转换。具体操作步骤如下&…

基于JAVA卓越导师双选系统设计与实现

摘 要 如今的信息时代&#xff0c;对信息的共享性&#xff0c;信息的流通性有着较高要求&#xff0c;因此传统管理方式就不适合。为了让导师选择信息的管理模式进行升级&#xff0c;也为了更好的维护导师选择信息&#xff0c;卓越导师双选系统的开发运用就显得很有必要。并且通…

不同的Git仓库单独设置用户名和邮件地址

最近使用公司电脑将自己的一个私人项目推送到远程仓库&#xff0c;仓库显示的公司邮箱地址。因为设置了全局的username和usermail&#xff0c;这样就比较尴尬了。但是又不能频繁来回改用户信息&#xff0c;那么请看下面如何单独设置仓库的用户信息&#xff0c;让不同的仓库展示…

QT增加线程函数步骤流程

在使用线程的时候&#xff0c;不仅要关注线程开启的时机&#xff0c;同时还要关注线程安全退出&#xff0c;这样才能保证程序的健壮性&#xff0c;如果线程开启的较多&#xff0c;且开启关闭比较频繁&#xff0c;建议使用线程池来处理。开启线程有三种方式&#xff1a;第一种C的…

thinkphp 使用phpmailer发送邮件以及使用消息队列异步解耦发送邮件

邮箱注册配置&#xff1a; 注册163或qq邮箱&#xff0c;开启smtp服务 25端口 ssl则465端口 下载phpmailer composer 安装phpmailer composer require phpmailer/phpmailer设置配置文件 配置文件 书写代码 代码 <?php namespace app\job; use think\facade\Log; us…

白话transformer(四):整体架构介绍

transformer现在是最主流的深度学习框架&#xff0c;尤其是大模型的流程让transformer的作用更加凸显&#xff0c;他可以对话、分类、生成文本等功能&#xff0c;那么他到底是如何工作的呢。 B站视频 1、背景知识铺垫 1.1、生成式模型 相信大家在使用手机聊天的输入法时&am…

闪电网络协议设计思想剖析

1. 引言 闪电网络可能是比特币之上部署的最受期待的技术创新。闪电网络&#xff0c;为由 Joseph Poon 和 Tadge Dryja 于2015年首次提出的支付层&#xff0c;承诺支持&#xff1a; 用户之间几乎无限数量的链下交易&#xff0c;几乎免费&#xff0c;同时利用比特币提供的安全性…

基于python+vue云上水果超市的设计与实现flask-django-php-nodejs

本论文的主要内容包括&#xff1a; 第一&#xff0c;研究分析当下主流的web技术&#xff0c;结合超市日常管理方式&#xff0c;进行云上水果超市的数据库设计&#xff0c;设计云上水果超市功能&#xff0c;并对每个模块进行说明。 第二&#xff0c;陈列说明该系统实现所采用的架…

Redis数据类型 Hash Set Zset Bitmap HyperLogLog GEO

Hash 说起Hash大家其实很容易想到java中的集合类HashMap,这里其实就是一个套娃,键值对套了一层键值对他的指令也很简单 首先是设置键值对 这里就是设置两个键值对 我们可以进行获取 使用hget获取值 或者我们使用hgetall来查询所有值 hmset/hmget是批量查找查询,和上面的操作类似…