当前位置: 首页 > article >正文

linux网络 | TCP可靠性策略之连接管理、滑动窗口、拥塞控制

        前言:本节内容是TCP的可靠性策略。 讲解可靠性策略中的连接管理、滑动窗口、拥塞控制。其实TCP的可靠性策略还有很多, 博主上一节已经讲过一部分(检验和、序列号、确认应答、超时重传) ,有兴趣的友友们可以看一下。 linux网络 | TCP报头之六个标记位与部分可靠性策略 -CSDN博客

        ps:友友们在看本节内容之前最好熟悉一下TCP的报文哦。

目录

链接管理机制

为什么握手是三次,而挥手是四次?

为什么要三次握手

TCP状态介绍

全连接 

backlog 

半链接 

四次挥手

流量控制 

滑动窗口 

滑动窗口三个结论 

丢包时如何理解滑动窗口 

如何理解滑动窗口的变化

滑动窗口会越界吗

拥塞控制 

拥塞控制控制策略 

拥塞窗口

慢启动


链接管理机制

        什么是连接管理机制, 其实就是三次握手和四次挥手。 

        TCP在进行通信之前,要进行三次握手。第一次就是客户端向服务单发送请求连接SYN,然后服务端确认应答ACK + 请求连接SYN,最后客户端确认应答ACK。 此时客户端发送ACK后就认为握手成功,服务端当收到最后一个ACK就认为握手成功。

        在这,我们要重新认识一下connect系统调用。 connect,其实只是创建一个SYN报文,推送给服务端,也就是connect只负责发起三次握手。 当connect发起请求后,就可以当成connect此时处于阻塞状态,服务端返回ACK + SYN后, established后,connect再返回。然后是accept,accept不参与连接,它是把底层已经建立好的数据拿上来。

        然后就是数据的正常通信过程, 其实本质就是write将数据写到内核缓冲区, tcp协议
传输控制。read将内核缓冲区读取。 

        最后是断开协议。断开协议就是客户端先请求断开连接,发送一个FIN请求。然后服务端就确认应答ACK。一这是客户端方向上断开链接了。 然后服务端也请求断开连接,客户端就确认应答ACK。——这是服务端方向上断开连接了。这就是四次挥手。

 

为什么握手是三次,而挥手是四次?

         这是因为握手和挥手都应该是四次,只不过握手的三次其实是挥手其中的两次合起来了。握手的第二次和第三次是可以合起来的,但是挥手的第二次和第三次是不可以合起来的。
        因为在建立连接的时候,双方都是想要建立连接的。所以服务器在应答的时候同时发送一个建立连接请求是合理的。而挥手的时候可能客户端想要挥手而服务器不想要挥手。这个时候,就先客户端进行断开连接,服务器要先同意,然后再发送一些自己想要发送的信息,然后再发送断开请求。 这就是四次挥手。 

为什么要三次握手

        其实,三次握手和四次挥手,本质其实就是一来一回的可靠性。

​ 

        客户端第一次请求和服务端的捎带应答。 这里面客户端有着一次发和一次收。服务端有着一次发一次收。意义是什么? 意义就是可靠的验证全双工是否通畅。这是第一个原因。 

​ 

        如果是一次握手,那么只要客户端发送了请求,我们就认为连接建立成功了。 如果此时我们的客户端一直发送建立连接的请求,那么服务端和客户端这里就都要一直消耗内存资源。这个就叫做SYN洪水。
        两次握手呢,如果是两次握手,其实是一样的,只要客户端发送了请求,服务器收到了请求,那么服务器就一定要进行应答。 进行应答后,服务器就要消耗内存资源创建连接结构体。 这个时候其实是和一次握手情况一样。如果客户端不停发送SYN请求,都会造成SYN洪水的情况。这其实就是一个嫁接的原理, 如果两次握手,第一次握手我们不怕,因为这个时候谁都不会建立连接。 但是一旦到了第二次握手,这个时候服务端就要建立连接,消耗资源。然后哪怕建立连接失败,这个时候服务端的资源已经消耗了,这就导致了建立连接失败的成本嫁接到了服务端。

 

         如果是三次握手,我们不怕第一次丢,第二次丢。因为第一次,第二次丢双方连接稍微建立完成。 连接成本不是特别高。 如果第三次丢,那么对于客户端讲,他认为他的链接建立成功了,服务器没有认为建立成功。 此时建立的成本就到了客户端的身上。 这样,失败的成本就转移到了我们的客户端,维护了服务器的稳定性。

TCP状态介绍

全连接 

        只要我们客户端connet上服务端,那么服务端和客户端就会有一条tcp连接。这个tcp链接,状态是ESTABLSH(完成),说明建立成功。也就是说,连接建立成功,和上层有没有accept没有关系。三次握手是双方操作系统自动完成的。

        那么accept和这个链接有什么关系?

        我们刚说客户端和服务端进行三次握手成功后,在服务端这边就会生成一个tcp链接的结构体对象。 客户端有很多个,所以服务端这边生成的结构体链接就有很多。然后这些链接就要被管理起来,管理的方式就是队列。也就是说这些结构体是用队列组织起来的。然后accept的时候,就会从队列里面拿到一个链接,放到文件管理里面。这样,以后进程就可以使用文件fd了。

backlog 

        listen的第二个参数我们以前说过是叫做backlog,这个backog + 就是表示底层建立好的连接队列的最大长度,我们把整个的队列叫做全链接队列。

        如果此时队列已经满了,却还有客户端在发起连接请求,这个时候服务端就不进行回应了,这个时候的服务端和客户端的连接状态就是SYN.RECV,就是下图的这个状态:

​ 

        相反, 如果此时队列没满, 那么就到了这个状态: 

​ 

半链接 

        我们如何理解这个SYN.RECV?就是对于服务端和客户端来说,它们想要从一个状态到达另一个状态,必须要接收到对应的请求。那么对于服务端来说,如果客户端发送了一个连接请求SYN,那么服务端接收到请求后就变成了SYNRECV状态。此时服务端进行挡带应答。客户端接收到服务端的报文后就变成了ESTABLSH状态。但是当客户端再给服务端发送最后一次ACK的时候,服务端因为队列满了,所以就只能把ACK丢掉,不接受。所以就一直处于了SYN RECV状态。

        SYN.RECV状态,被称为半链接。同时SYN.RECV也有自己的维护的队列。这个队列叫做半链接队列。同时,半链接的节点,不会长时间的维护。所以我们的服务端,如果有SYN_RECV状态的tcp连接。可能过一会儿这个连接就不复存在了。 (半链接长度不关心这里) 

        当我们的服务端的SYN.RECV半链接释放掉,客户端那边还有一个ESTABLSH呢。这个时候,服务端这边有,但是客户端那边没有,这就造成了cient和server 链接建立不一致的问题。

        这个半链接和全连接合作,就类似于我们平时在学校报英语四六级。我们报英语四六级的时候,是不是经常遇到点开网页,然后服务器很繁忙的状态。这个状态显然不是服务器挂掉了,而是里面的全队列被打满了,服务器不能给我们响应,没有建立链接导致的。甚至可能半链接都被打满了,所以就造成了根本挤不进去的情况。而有时候我们可能等了一会就进去了,就是我们正好挤进去了半销接,并目从半链接拿到了全连接里面,这个时候就成功链接到服务器了。

        这个listen的第二个参数为什么不能太长,为什么不能没有?
        因为会造成有些链接来不及被上层处理,但事实还在系统中长时间维持的情况。就比如当我们的服务器非常忙的时候,上层没有时间将全队列里面的数据拿到上面去。但是全队列里面还非常满。这个时候就是全队列空占着资源但是不创造价值。所以就没必要将backlog设置太长。

        为什么不能没有,这是因为如果上层空出位置来的时候,如果没有一个维护tcp的队列,那么此时上面就空出来了,资源闲置,就减少效益了。相反呢,如果有这个全队列,那么每当上层有位置的时候,都会拿着全队列里面的链接放到上层处理,这样效益就高。

四次挥手

        主动断开连接的一方,在四次挥手完成之后,要进入time_wait状态,等待若干时常,之后会自动释放。有的时候,我们的服务器断开后,我们再重启服务器的时候,如果刚断开就重启,并且端口号还是一样的,这个时候就会绑定失败。为什么会这样呢?

        为什么会这样呢?主要因为,如果我们主动断开的一方是服务方,那么服务器方就要主动进入time wat状态。这个tme wait状态,就是连接没有彻底断开,ip和port正在被使用。所以,我们如果想要重启服务器,但是此时端口号正在被上一个服务器time wait状态占用呢,就不能使用。

        我们要知道,未来我们如果是一个大型服务器,几乎每秒产生的消费数字都是非常大的。所以这个时候如果一个服务器挂掉了,还不能重启,造成的影响就非常大,所以为了能够立刻就重启,就可以使用 setsockopt这个函数,来设置地址复用,这样就可以立即重启了。

        为什么客户端不需要担心time_wat的影响呢?因为客户端每次重启的端口号都不一样。而服务端的端口号必须一样。 

        TIME_WAIT,为什么要有,并且,为什么要等待这么长时间 (TIME_WAIT要等待两个MSL)?

        首先我们要知道这个MSL(即最大存在时常)的意思是什么? 首先我们要知道,一个报文从发出去到收到。这个时间窗口内,数据包都是在网络里面,数据包在网络里面的这个时间的最长时常,就叫做MSL。这个MSL一般是秒级别的。
        那么,TIME_WAIT,为什么要有?我们在进行断开连接的时候,我们历史上可能发送了很多数据,不管是命令还是数据。当我们断开的时候,因为我们发送过很多数据,那么这些数据就有可能不存在于网络当中,所以等待的TME WAT就是为了让这些数据在双方信道中进行消散。——即,让通信双方历史数据得以消散。         

        还有一个为什么要等待这么长时间呢? 就是客户端双方进行四次挥手的时候, 如果三次回收后,这个时候客户端最后发送一个ACK, 直接就退出了。如果这个时候ACK丢失了,那么服务端就一直处于LAST_ACK的状态了。然后服务端即便向客户端再次请求FIN,客户端也不会响应,因为客户端已经释放了。——即,让我们断开连接,4次挥手,具有较好的容错性。

        但是,这里有一个问题了,MSL为什么是秒级别的? 我们的网络收发一次,有可能是正常的报文,有可能这个报文就阻塞了。如果是正常的报文,那么它在网络中的最大传送时常和最大存在时常是相同的。但是如果是不正常的报文,这个报文阻塞住了,那么这个报文的最大存在市场就应该更长,因为它在一直阻塞着呢。如果是最大传送时常,那么就是毫秒级别的,如果是最大存在时常,那么就多了,并且不稳定,因为路由器有时候卡的话一直阻塞,可能阻塞很长时间。所以,一般建议这个是秒级别的。 

流量控制 

        TCP根据报文里面的十六位端口号来判别接收端的接受能力,来决定发送端的发送速度,这个过程就叫做流量控制。 这个博主在之前在讲解十六位窗口大小的时候已经谈过。        

        这里有几个问题就是:

  •         第一次的时候,怎么保证发送的数据是合理的呢? 这个就是三次握手的工作,三次握手不只是三次握手,三次握手也交换了报文!!!已经协商了双方的接受能力!!! 
  •         2、第三次握手的时候,是可以携带数据的。(本质上就是捎带应答)
  •         3、流量控制,属于可靠性,还是效率。答案是既属于可靠性,又属于效率。

滑动窗口 

滑动窗口三个结论 

        第一个问题,在这些多条报文中,其中有许多没有收到应的报文。这些报文我们之前说过要被tcp暂时保存起来,那么保存到哪里呢?
        第二个问题,在这些报文中,有许多已经发出去,但是没有收到应答的报文,这些报文是不是有非常多个?

        这两个问题能合成一个问题,就是这些报文中,已经发出去,但是暂时没有收到应答的多个报文,会被保存到哪里呢? 

        其实就是保存到发送缓冲区中。发送缓冲区分为三个板块,一个是已发送已确认,一个是已发送末确认,一个是待发送。那些发送出去,未收到应答的报文,就是在已发送未确认报文里面。这个已发送未确认报文,就是滑动窗口,大小是可变的!!!

 

        所以第一个结论,滑动窗口在哪里?——就是我们发送缓冲区的一部分!!! 

        我们的通信双方,正是因为有着滑动窗口的存在,才可以一次性向对方发送大量的报文。所以第二个结论一-滑动窗口的大小,是对方接受窗口。 

        滑动窗口,我们说发送缓冲区是一块连续的控件,这个滑动窗口是怎么做到的呢?
        这个就类似于那个紧急报文,就是利用两个整型变量,指向两个字节位置。 然后分隔成了三个部分。 第一个部分就是已发送已确认,第二个部分就是已发送未确认,第三个部分就是待发送。

        所以第三个结论-如何理解区域划分,就是通过指针/下标来进行区分! 

丢包时如何理解滑动窗口 

        目前认为,至少滑动窗口的大小,不能超过对方的接收缓冲区的剩余空间的大小,即应答报文的窗口大小。

        现在有几个问题:
        问题1、如果丢包了,怎么理解滑动窗口? 

        

         先讨论ACK丢了。

        我们之前说过,我们对于确认序号的定义:确认序号是x,x之前的报文我们全部收到了!也就是允许少量的ACK丢失。所以,就像上图,即便1001 ~4000的报文没有对应的应答,但是有一个应答的确认序号为5001,那么就认为5001之前的报文全部都被收到了! 

        那么滑动窗口向前滑动,就要滑动到5001的位置!而不会卡在1001,即便1001没有应答!!! 

 

        然后讨论数据的丢失: 

        假如我们的1001收到了,2001收到了,4001收到了,5001收到了,但是3001没收到。对于数据,没有收到不能装作收到。所以确认序号最多就到2001。也就是说,即便是4001那里,5001那里,填写的确认序号也是2001。到时候滑动窗口等待左指针,最多就向右滑动到2001。 所以,我们就不用怕滑动窗口直接越过3001的问题。 

        所以,确认序号的定义,就保证了滑动窗口,线性的连续的向后更新不会出现跳跃的情况。

        就是上边的情况,一开始1~1000的数据发送了,然后1001确认应答。然后呢, 1001~2000的报文丢失了。 那么就不能确认应答了。并且之后的报文像2001~ 3000、3001~ 4000等这些报文,确认应答都是1001,不能是3001或者4001。1001~2000的报文丢失了,那么就要等,等到判定为超时的时候,就进行超时重传。 但是这样等待的时间就太长了。 所以就规定,一旦有三次重复的确认应答, 那么就立刻进行超时重传。 然后进行确认应答。这就叫做快重传。
        问题是,已经有了快充穿,为什么还要有超时重传呢?主要是因为快重传是有条件的,只有超过三次重复的确认应答时,才会出现快重传。所以,快充穿的本质是来提高效率的。即便有了快重传也不能摒弃超时重传,超时重传是用来兜底的。

如何理解滑动窗口的变化

        问题2:如何理解滑动窗口的变化?

        滑动窗口会不会向左移动,会不会向右移动,移动的时候大小会变化吗? 怎么变化呢?会变成0吗?
        首先不会向左移动,因为序号和确认序号只会增加。会向右移动。移动的时候,窗口的大小是变化的。动态变化,那么就涉及到三个,变大,变小、不变。

        所以针对性的,我们就要提出来向右移动的三种方式:右不变,左移动、左右都移动,范围变大(主机A一边向主机B发送数据,主机B一边向上取数据)、左不变,右移动。

 

        start和end是标已窗口大小起始和结尾的招针。是int类型,start就是确认序号。 根据确认序号来设置start的值。即,start等于确认序号。end指针就等于确认序号 +窗口大小,这个窗口大小就是对方给我们的ACK中一定也存在窗口大小,到时候end就直接等于stat + ACK中的窗口大小即可。

滑动窗口会越界吗

        TCP采用了类似环状算法。就是那个基于数组的环形队列。所以就不需要担心溢出的问题。 

拥塞控制 

        其实当我们数据通信的时候,如果发送数据,出现问题,不仅仅是对方主机出现问题,也可能是网络出现了问题。 

        如果通信双方出现了大量的数据包问题(少量可能是因为常规性质的问题),tcp会判断网络出问题了(网络拥塞) 。

        这个时候我们发送方,应该怎么办?——我们不能对报文进行超时重发!!-为什么?因为会加重网络的拥塞。所以,应该怎么办呢?比如等一等,比如发送少量的。 

        为什么等一等就可以了呢?因为一个局域网当中的主机非常多。所以,到时候一个主机少一些报文,那么一群主机一起减少。就可以减少大量的报文。

拥塞控制控制策略 

        TCP引入慢启动机制,先发送少量的数据,探探路。摸清楚拥堵的状态,再决定按照多大的速度传输数据。

拥塞窗口

        滑动窗口的大小 = min(窗口大小,拥塞窗口),窗口大小考虑的是对方主机的接受能力。拥塞窗口考虑的是动态的,网络的接受能力。

        那么拥塞窗口是什么呢?就是主机判断网络健康程度的指标,超过拥塞窗口,会引发网络拥塞,否则不会。拥塞窗口本来就是用来评估网络状态的,网络是动态的,所以拥塞窗口肯定是动态的。 

慢启动

        TCP虽然有了滑动窗口,能够提高很大的效率。但是如果一开始就发送大量的报文,还是有很大的问题。就比如网络上很多计算机在不知道现在网络状态的情况下,直接一股脑发送大量的数据,就有可能引起网络的更加拥堵。

        所以TCP引入了慢启动的方式,就是先发送少量的数据,探探路,摸清楚当前的网络拥堵的状态,再决定按照多大的速度传输数据。 

 

        就比如上面的图,这里面就用到了拥塞窗口,因为拥塞窗口可以一开始为1,然后指数上升,当拥塞窗口的大小小于滑动窗口的大小的时候,发送的数据量就按照拥塞窗口来控制。

        就是一开始定义拥塞窗口大小为1。每次收到一个ACK应答,拥塞窗口加1。每次发送数据包,就将拥塞窗口和滑动窗口作比较,取小的那个作为实际的发送的窗口。

        上面这就叫做慢启动。为了不增长那么快,所以不能让拥塞窗口单纯的加倍,此处就引入了一个叫做慢启动的闽值,当拥塞窗口超过这个闻值的时候,不再按照指数方式增长,而是按照线性方式增长。 

        网络出现拥塞,发送少量的报文,如果都ok,就说明网络已经趋于健康了。应该尽快恢复正常通信了。

        那么实际机器发送的数据量,会一直指数级别增长吗?答案是不会, 原因就是因为:当拥塞窗口超过这个闻值的时候,不再按照指数方式增长,而是按照线性方式增长。 

        慢启动的阈值:最近一次发生网络拥塞时,拥塞窗口大小/2;一旦网络发送拥塞,此时拥塞多大,然后下一次闯值就是拥塞窗口的大小/2。
        随着拥塞窗口越来越大,说明拥塞的改率越来越高。 最后一定会导致拥塞,但是实际上在发送数据的时候,不仅仅拥塞窗口限制我们,对方的接受能力也能限制我们。所以发送网络拥塞并不是一定会发生的。 

  ——————以上就是本节全部内容哦, 如果对友友们有帮助的话可以关注博主, 方便学习更多知识哦!!!   


http://www.kler.cn/a/522176.html

相关文章:

  • 【云安全】云原生-K8S-搭建/安装/部署
  • 论文阅读(十三):复杂表型关联的贝叶斯、基于系统的多层次分析:从解释到决策
  • doris:Bitmap
  • ESP32-S3模组上跑通esp32-camera(36)
  • 第3章 基于三电平空间矢量的中点电位平衡策略
  • 星火大模型接入及文本生成HTTP流式、非流式接口(JAVA)
  • CSS Fonts(字体)
  • Yolo11 + OCR 营业执照识别+信息抽取(预期后续改用其他ocr更简单,推理预计使用onnxruntim加速,分c++和python两种方式部署)
  • C#,入门教程(04)——Visual Studio 2022 数据编程实例:随机数与组合
  • Python3 OS模块中的文件/目录方法说明十三
  • 通过Redis命令建立锁机制
  • 字符设备驱动模版-中断
  • 5.1.4 软件工具+开发环境
  • 【Docker】Docker入门了解
  • 本地大模型编程实战(04)给文本自动打标签
  • 【Spring】Spring概述
  • 寒假1.26
  • 【深度学习】常见模型-Transformer模型
  • 基于微信小程序游泳馆管理系统 游泳馆管理系统小程序 (设计与实现)
  • 梯度下降优化算法-RMSProp
  • 【源码+文档+调试讲解】基于Spring Boot的摇滚乐鉴赏网站的设计与实现
  • Git 出现 Please use your personal access token instead of the password 解决方法
  • 发布 VectorTraits v3.1(支持 .NET 9.0,支持 原生AOT)
  • 基于微信小程序的助农扶贫系统设计与实现(LW+源码+讲解)
  • 98.1 AI量化开发:长文本AI金融智能体(Qwen-Long)对金融研报大批量处理与智能分析的实战应用
  • 高阶C语言|深入理解字符串函数和内存函数