【Linux | 网络】传输层(UDP和TCP)

在这里插入图片描述

目录

  • 一、再谈端口号
    • 1.1 端口号
    • 1.2 端口号的范围划分
    • 1.3 常见知名端口号
    • 1.4 netstat 命令
    • 1.5 进程与端口号的关系
    • 1.6 pidof 命令
  • 二、UDP协议
    • 2.1 UDP协议段格式
    • 2.2 如何理解UDP报头和UDP报文
      • 2.2.1 UDP报头
      • 2.2.2 UDP报文和UDP报文的管理
      • 2.2.3 UDP封装过程
    • 2.3 UDP的特点
    • 2.4 UDP的缓冲区
    • 2.5 UDP使用注意事项
    • 2.6 基于UDP的应用层协议
  • 三、TCP协议
    • 3.1 TCP协议段格式
      • 3.1.1 16位紧急指针(标记位URG)
      • 3.1.2 标记位PSH
      • 3.1.3 标记位RST
    • 3.2 流量控制(16位窗口大小)
    • 3.3 确认应答机制(32位序号和32确认序号)
    • 3.4 捎带应答(32位序号和32确认序号)
    • 3.5 超时重传机制
    • 3.6 三次握手和四次挥手
      • 3.6.1 三次握手中服务器和客户端状态变化
      • 3.6.2 三次握手(建立连接)
        • 3.6.2.1 为什么要进行三次握手?
      • 3.6.3 四次挥手(断开连接)
        • 3.6.3.1 为什么要进行四次挥手
        • 3.6.3.2 CLOSE_WAIT 和 TIME_WAIT
    • 3.7 滑动窗口
      • 3.7.1 滑动窗口在哪里?
      • 3.7.2 如何理解滑动窗口?
      • 3.7.3 滑动窗口的大小变化吗?
      • 3.7.4 报文/响应报文丢失了怎么办?(快重传)
    • 3.8 拥塞控制
    • 3.9 面相字节流
    • 3.10 粘包问题
    • 3.11 TCP异常情况
    • 3.12 半连接队列和全连接队列(listen 的第二个参数)
    • 3.13 文件、Socket、系统和网络之间的关系
  • 四、UDP与TCP的对比
    • 4.1 UDP与TCP的特点对比
    • 4.2 用UDP实现可靠传输
  • 结尾

一、再谈端口号

1.1 端口号

端口号(Port)标识了一个主机上进行通信的不同的应用程序
在这里插入图片描述
在TCP/IP协议中,用 “源IP”,“源端口号”,“目的IP”,“目的端口号”,“协议号” 这样一个五元组来标识一个通信。

在这里插入图片描述


1.2 端口号的范围划分

  • 0 - 1023:知名端口号,HTTP、FTP、SSH等这些广为使用的应用层协议,它们的端口号都是固定的
  • 1024 - 65535:操作系统动态分配的端口号。客户端程序的端口号,就是由操作系统从这个范围分配的

1.3 常见知名端口号

  • ssh服务器,使用22端口
  • ftp服务器,使用21端口
  • telnet服务器,使用23端口
  • http服务器,使用80端口
  • https服务器,使用443端口

执行下面的命令,可以看到知名端口号

cat /etc/services

所以在我们自己程序中需要使用端口号时,需要避开这些知名端口号


1.4 netstat 命令

语法netstat [选项]

功能:netstat 是网络管理中常用的命令行工具

常见选项

  • -n:以数字的形式显示IP地址和端口号
    在这里插入图片描述

  • -a:显示所有端口
    在这里插入图片描述

  • -l:仅仅显示监听状态的端口
    在这里插入图片描述

  • -t:仅仅显示TPC连接
    在这里插入图片描述

  • -u:仅仅显示UDP连接
    在这里插入图片描述

  • -p:显示与每个连接关联的进程 ID(需要root权限)
    在这里插入图片描述


1.5 进程与端口号的关系

  1. 一个进程是否可以bind多个端口号?不可以,端口号只能标识唯一的进程。
  2. 一个端口号是否可以被多个进程bind?可以

1.6 pidof 命令

语法pidof [进程名]

功能:通过进程名,查看进程id,在查看服务器的进程id时非常方便。

在这里插入图片描述


二、UDP协议

2.1 UDP协议段格式

在这里插入图片描述

  • 16位源端口号:标识发送方端口号
  • 16位目的端口号:标识接收方端口号
  • 16位UDP长度:标识整个UDP报文的长度
  • 16位UDP校验和:用于检测UDP数据报在传输过程中是否发生错误。

UDP协议由UDP报头和有效载荷(数据)构成,谈到协议就有下面两个问题需要解决:

  1. 如何解决报头与有效载荷分离的问题
    UDP报头采用的是8字节固定报头长度,剩下的就是有效载荷
  2. 如何解决有效载荷向上交付的问题
    UDP的报头中有一个字段叫做目的端口号,UDP就是通过目的端口号将有效载荷交付给上层进程的

对于UDP协议还有下面一个问题:UDP是面向数据报的,发送方发什么,接收方就收到什么,并且接收方不需要解决拆分报文,总需要有谁来解决这个问题,那它是如何知道报文是否被收齐的呢?

这个工作实际上是由接收方的操作系统来完成的,UDP报文中有一个字段叫做UDP长度,它记录的是整个UDP报文的长度,包括报头和有效载荷,去除报头部分的长度就是有效载荷的长度,若实际收到有效载荷的长度比它小,则说明没有收齐,就需要将该报文丢弃。


2.2 如何理解UDP报头和UDP报文

2.2.1 UDP报头

UDP报头实际上就是一个结构体:

struct udphdr
{uint32_t srcport:16;uint32_t dstport:16;uint32_t length:16;uint32_t checksum:16;
}

Linux操作系统内核就是这样表示的。

在这里插入图片描述


2.2.2 UDP报文和UDP报文的管理

在客户端和服务器中,一定存在同时收到很多UDP报文的情况,所以客户端和服务器需要对这些UDP报文进行管理,管理就需要提到先描述再组织了,先使用一个结构体描述UDP报文,再使用链表将所有的结构体管理起来。

对于UDP报文来说,它也是一个结构化字段(结构体):

struct sk_buff
{char* data;  // 指向报文开头char* tail;  // 指向报文结尾struct sk_buff* next;  // 指向下一个结构体
}

Linux操作系统内核中对UDP报文的描述更加详细。
在这里插入图片描述

当发生方的应用层向传输层中的UDP交付数据后,此时UDP就需要对该数据进行封装,操作系统会为其创建一个sk_buff结构体和缓冲区,将数据和UDP报头依次保存好后,再将结构体连接到链表中,再将结构体向下交付给数据链路层继续进行封装。

当接收方在接收到报文后,会向上进行解包和分用,到传输层时,由于发送方和接收方使用的都是UDP协议,也就是同样的结构化字段,就可以根据UDP报文对应的结构体和UDP报头所对应的结构体,对UDP报文做出解释。

在这里插入图片描述


2.2.3 UDP封装过程

首先缓冲区中没有任何数据,所以data和tail最开始指向的同一个位置。

然后根据数据的大小,data指针向前移动数据大小的位置,将数据存放在data与tail指向的区间。

最后再添加报头,data指针向前移动报头大小的位置,将UDP报头存存放在data与tail指向的区间。
在这里插入图片描述


2.3 UDP的特点

UDP传输的过程类似于寄信。

  • 无连接:知道对端的IP和端口号就直接进行传输,不需要建立连接
  • 不可靠:没有确认机制,没有重传机制;如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息
  • 面向数据报:不能够灵活的控制读写数据的次数和数量,将每一个独立的报文作为一个整体发送,保留报文边界

面向数据报:应用层交给UDP多长的报文,UDP原样发送,既不会拆分,也不会合并

用UDP传输100个字节的数据:如果发送端调用一次sendto,发送100个字节,那么接收端也必须调用对应的一次recvfrom,接收100个字节;而不能循环调用10次recvfrom,每次接收10个字节。


2.4 UDP的缓冲区

  • UDP没有真正意义上 显示的发送缓冲区。调用sendto会直接交给内核,由内核将数据传给网络层协议进行后续的传输动作。

  • UDP具有真正意义上显示的接收缓冲区。但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致,如果缓冲区满了,再到达的UDP数据就会被丢弃。

UDP的socket既能读,也能写,这个概念叫做 全双工


2.5 UDP使用注意事项

我们注意到,UDP协议首部中有一个16位的最大长度。也就是说一个UDP能传输的数据最大长度是64K(包含UDP首部)。

然而64K在当今的互联网环境下,是一个非常小的数字。所以当用户需要传输的数据超过64K时,用户需要在应用层手动进行分包,多次发送,并在接收端需要将这些数据进行手动拼装。


2.6 基于UDP的应用层协议

  • NFS协议:网络文件系统
  • TFTP协议:简单文件传输协议
  • DHCP协议:动态主机配置协议
  • BOOTP协议:启动协议(用于无盘设备启动)
  • DNS协议:域名解析协议

三、TCP协议

3.1 TCP协议段格式

在这里插入图片描述

  • 16位源端口号:标识发送方端口号
  • 16位目的端口号:标识接收方端口号
  • 32位序号:数据段第一个字节的序列号(用于排序和去重)
  • 32位确认序号:用于检测UDP数据报在传输过程中是否发生错误
  • 4位首部长度:TCP头部的长度(单位4字节)
  • 6位标志位:控制TCP行为(URG、ACK等)
    • URG:紧急指针是否有效
    • ACK:确认号是否有效
    • PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走
    • RST:对方要求重新建立连接,我们把携带RST标识的称为复位报文段
    • SYN:请求建立连接,我们把携带SYN标识的称为同步报文段
    • FIN:通知对方,本端要断开连接
  • 16位窗口大小:接收方当前可接收的字节数(用于流量控制)
  • 16位校验和:头部和数据的校验和
  • 16位紧急指针:紧急数据的偏移量(仅在URG标志位为1时有效)。

TCP协议由TCP报头和有效载荷(数据)构成,谈到协议就有下面两个问题需要解决:

  1. 如何解决报头与有效载荷分离的问题
    TCP报头中,有20字节固定长度,其中包含一个字段4位首部长度,它的单位为4字节,4位比特位能表示[0,15],也就是首部长度最大为60字节,减去20字节的固定长度,剩下的就是选项,这样就完整的找到了报头,剩下的就是有效载荷了
  2. 如何解决有效载荷向上交付的问题
    TCP报头中有一个字段叫做目的端口号,TCP就是通过目的端口号将有效载荷交付给上层进程的

3.1.1 16位紧急指针(标记位URG)

TCP会将接收到的报文保存在以队列形式组织起来的接收缓冲区中,如果说队列中已经存在了很多报文,此时有一个紧急任务需要处理,由于缓冲区的存在,这个任务也需要在缓冲区中排队,这显然是不合理的,紧急任务就是需要被尽快处理的,所以这个报文需要插队。

将报文中的URG标记位置为1,则说明该报文时紧急任务,报头中16位紧急指针表示的就是紧急数据在有效载荷的偏移量。仅仅数据的大小为1字节,也就是说TCP允许插队,但是不允许大量的插队。

那么什么是紧急任务呢?举个例子:假设用户正在想服务器中上传数据,但是此时用户发现自己想要上传的数据不是这个,所以用户需要终止上传行为,有了URG和紧急指针,即使服务器的接收缓冲区中有大量的报文,服务器已经可以优先处理这个紧急任务。


3.1.2 标记位PSH

在流量控制中会讲到报文中窗口大小表示的就是接收方当前可用的接收缓冲区的大小,假设TCP对应的上层执行任务非常消耗时间,导致其接收缓冲区被填满,此时接收方就需要通过窗口大小来告诉发送方自己的接收缓冲区已经不能接收数据了,此时发送到就需要等待接收方上层读取缓冲区的数据。

但是发送方一直这么等下去也不是办法,所以发送方会发送探测报文,以检查接收方是否已恢复接收能力,等待接收方报文中窗口大小,如果窗口大小不足时,发送方依旧会停止发送数据,发送方就会每过一段时间就重复这个过程,如果重复的次数多了,报文就会将PSH标记位置为1,告诉接收方尽快将接收缓冲区中的内容交付给上层。

还有一种方式就是发送方在等待的过程中,接收方上层读取了接收缓冲区内的数据,接收缓冲区就可以接收发送方的数据,接收方就会向发送方发送报文,报文中更新了自己的窗口大小。

无论是发送方发送的探测报文报文后,接收方回复报文,还是接收方直接发送报文,这两种方式都是告诉发送方,接收方已经能够接收数据,发送方可以继续发送数据了。


3.1.3 标记位RST

TCP是保证可靠性的,那么是否就能保证TCP的三次握手一定是成功的呢?

并不是,TCP保证可靠性并不是指TCP的三次握手一定是成功的,它指的是接收方收到了发送方发送的哪些数据,发送方是知道的,发送方发送的哪些数据是有问题的,发送方是知道的。

所以,TCP的三次握手是有可能失败的,当TCP第一次和第二次失败的时候,由于有响应的存在,客户端和服务器是报文是否被对方接收了的,但是第三次握手是没有响应的,所以客户端时无法保证服务器接收到了报文。

第三次握手时,客户端认为自己发送出报文,三次握手就完成了,而服务器需要接收到客户端的报文,服务器才认为三次握手完成。假设第三次握手的报文丢失了,客户端认为三次握手完成了,服务器认为三次握手没有完成。

客户端认为三次握手完成了,然后开始向服务器发送数据,然后服务器就感到疑惑,没有完成三次握手,客户端怎么就向我发送数据了,然后服务器就向客户端发送报文,报文中将RST标记位置为1,表示


3.2 流量控制(16位窗口大小)

TCP协议是拥有显示意义上的发送缓冲区和接收缓冲区的,当发送方应用层通过write/send函数向接收方发送数据时,由于write/send函数本质上就是拷贝函数,它会将应用层的数据拷贝到TCP的发送缓冲区中,通过一系列操作,将数据拷贝到接收方的接收缓冲区,最终接收方的应用层通过read/recv函数将数据读取上去。

在下图中,我就将这过程进行简单的演示,对于处在同一层协议栈的双方,发送方在同一层发出的数据,就是接收方收到的数据,并且下面的协议我们还没讲到,就先跳过一下。

在这里插入图片描述

这里假设发送方疯狂的向接收方发送数据,导致接收方来不及读取,那么接收方存储不下的报文应该怎么办?

丢弃吗?这里并不是报文的问题,并且报文传输也是消耗资源了的,所以丢弃报文显然是不合理的。

所以不能丢弃,那么接收方在自己接收缓冲区保存不下时,就需要告诉发送方不要再发送数据了。这就是我这里要将的流量控制,流量控制是谁做的?用户好像并没有管过,实际上流量控制是发送方的TCP做的,本质上就是操作系统做的。

发送方的操作系统如何进行流量控制呢?发送方一定需要知道接收方的当前可用的接收缓冲区的大小。

发送方怎么知道接收方当前可用的接收缓冲区的大小呢?一般来说发送方发送一个TCP报文,接收方就需要返回一个TCP报文,这就是确认应答机制(后面会讲),又TCP报头中有一个字段16位窗口大小,它就是记录接收方当前可用的接收缓冲区的大小。


有了流量控制,当接收方可用的接收缓冲区的大小不足时,发送方就会减少或停止发送数据,但是用户在一直发,这就会导致发送方的发送缓冲区被填满,最终导致进程阻塞,只有当操作系统将发送缓冲区中的数据发送出去时,进程才会被移除阻塞队列。

同样当接收方的接收缓冲区为空时,进程也会被阻塞,只有发送方有数据发送到接收缓冲区中,进程才会被移出阻塞队列。

但是用户并不需要关心这个过程,TCP(操作系统)会自己管理进程是否发送的问题。

这与我们在操作系统中文件读写相关知识很类似,当我们向文件中写入数据时,实际上是向的文件的内核基本缓冲区中,数据什么时候刷新到磁盘中,一次刷新多少,都不需要用户操心。

而这里同样如此,用户将数据发送出去,实际上就是将数据拷贝到发送缓冲区中,什么时候发,一次发多少,这都是TCP(操作系统)需要操心的事,用户就不需要管了。


3.3 确认应答机制(32位序号和32确认序号)

对于TCP而言,当客户端发送了一个报文以后,服务器就需要回应一个报文,当客户端接收到了服务器的报文就知道服务器收到了客户端的报文,但是服务器却不知道自己发送的报文客户端是否收到,所以客户端就又向服务器发送一个报文,当服务器收到了客户端发的报文以后,服务端就知道它发送的报文客户端收到了,此时客户端又不知道自己发送的报文服务器是否收到,一直重复这样的操作,最终最后发生报文的一方,无法知道对方是否接受到报文。所以,TCP暂时做不到100%可靠的网络通信,但是可以做到局部上的可靠通信,当一方收到了响应,就能100%保证历史上最近的一个报文是被对方收到了的

下图是为了大家方便理解而画的图,这是TCP发送数据的一种方式,下图中客户端发送报文时,是串行发送的,但是这样有一个缺点就是慢。

在这里插入图片描述

在TCP中,TCP是允许同时发送多个TCP报文的。由于报文在网络传输中各种因素的影响,可能会导致服务器收到的报文顺序是乱序的,乱序是一直不可靠的一种,TCP报头中的32位序号就可以解决报文顺序乱序的问题,保证报文的按序到达(按序到达是对TCP的上层来说的)。

在这里插入图片描述

TCP会将数据中的每一个字节进行编号,并将数据中第一个序号保存在TCP报头中的32位序号中,当客户端同时发送多个报文时,经过网络的传输可能导致服务器接收报文的顺序是乱序的,但是由于有报头中序号的存在,TCP就可以通过序号的大小进行排序,从而使报文变得有序。

而报头的32位确认序号表示的是确认序号之前的所有报文都被对方全部接收了。以下图为例,服务器收到四个报文,就需要响应四个报文,其中四个报文中的确认序号为101、201、301和401,假设除了确认序号为401的报文,其他的报文全部丢失,只要客户端收到确认序号为401的报文,就可以确定客户端发送的四个报文已经被服务器收到了。这样做了以后,TCP协议就能够允许少量报文的丢失

在这里插入图片描述


3.4 捎带应答(32位序号和32确认序号)

上面我们讲到了请求报文中的32序号能够让接收方接收到的报文按序到达,响应报文中的32位确认序号可以让发送方知道自己发送的哪些报文已经被接收方收到了。

这时候就有一个问题了,请求报文和响应报文,是两个分开的报文,为什么报文中需要存在序号和确认序号呢?为什么不可以将它们合并在一起呢?

这是因为上面我们讲到的只有服务器确认的情况,实际上服务器也会发送数据。例如,当客户端发送请求报文以后,服务器需要对客户端发送ACK,表示自己已经收到了报文,然后服务器也想给客户端发送数据,这样服务器就对客户端的一个报文,响应了两个报文,为什么不将这两个报文合并呢?将服务器想要发送数据的报文的ACK置为1,就可以将两个报文合并为一个报文了,这就是捎带应答因为序号和确认序号可能会被同时使用,所以不能合并

捎带应答就是通过将确认信息与待发送的数据结合在一起传输,从而减少通信开销和降低延迟。
在这里插入图片描述


3.5 超时重传机制

超时重传机制就是主机A向主机B发送数据,在特定的时间间隔中没有收到主机B的确认应答,主机A就会向主机B重新发送未被确认的数据。

主机A向主机B发送数据,但是可能由于网络中的各种原因,导致数据没有送达到主机B,主机A在一定时间内没有收到主机B的应答,就会将未被确认的数据进行重发。

在这里插入图片描述

由于主机A需要收到主机B的应答才能够确定,自己发送的数据被主机B收到了,所以除了上面的情况,还有主机B发送的确认应答由于网络的各种原因,导致主机A没有收到主机B的应答,主机A在一段时间内没有收到主机B的应答,就会将未被确认的数据进行重发。

在该情况下,主机B就会收到主机A相同的报文,主机B就会根据报文中的序号,对报文进行去重,保留新的还是旧的就需要看操作系统了。

在这里插入图片描述

在上面两种情况中,无论是主机A发送的请求报文丢失了,还是主机B发送的应答报文丢失了,最终结果都是主机A进行超时重传

由于主机A需要得到主机B的确认应答,才能保证数据没主机B接收了,所以主机A发送数据后,不能立即从发送缓冲区移除,需要暂时保留一段时间,直到主机B发送确认应答后,才能够移除。

暂时保存在发送缓冲区的数据是在滑动窗口的区域中,收到应答后,通过滑动窗口的移动,移除指定的数据(滑动窗口在后面讲解)。

那么超时时间是多久呢?

  • 如果超时时间太长会导致效率降低
  • 如果超时时间太短会导致过于频繁的进行重传

并且由于网络环境是动态变化的,所以超时时间也需要是浮动的。

TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间。

Linux中超时以500ms为一个单位进行控制,每次判定超时重发的超时
时间都是500ms的整数倍。

如果重发一次之后,仍然得不到应答,等待 2*500ms 后再进行重传。

如果仍然得不到应答,等待 4*500ms 进行重传。依次类推,以指数形式递增

累计到一定的重传次数,TCP认为网络或者对端主机出现异常,强制关闭连接


3.6 三次握手和四次挥手

主机可能在同一时间内存在不同状态的连接,所以操作系统就需要对连接进行管理,管理的方式就是先描述再组织,先通过连接的各种属性,将连接描述为一个结构体,再使用特定的数据结构将所有的结构体管理起来,当建立一个连接以后,操作系统就为这个连接创建一个结构体对象,在将对方放入到数据结构中,最终对连接的管理就转变为了对数据结构的管理。

在这里插入图片描述

3.6.1 三次握手中服务器和客户端状态变化

服务端状态转化:

  • [CLOSED -> LISTEN],服务器端调用listen后进入LISTEN状态,等待客户端连接。
  • [LISTEN -> SYN_RCVD] ,一旦监听到连接请求,就将该连接放入到内核的等待队列中,并向客户端发送SYN+ACK确认报文。
  • [SYN_RCVD -> ESTABLISHED],服务端一旦收到客户端的确认报文,就进入ESTABLISHED状态,可以进行读写数据了。
  • [ESTABLISHED -> CLOSE_WAIT],当客户端主动关闭连接(调用close),服务器会收到结束报文段,服务器返回确认报文段并进入CLOSE_WAIT。
  • [CLOSE_WAIT -> LAST_ACK],进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据),当服务器真正调用close关闭连接时,会向客户端发送FIN,此时服务器进入LAST_ACK状态,等待最后一个ACK到来(这个ACK是客户端确认收到了FIN)。
  • [LAST_ACK -> CLOSED],服务器收到了对FIN的ACK,彻底关闭连接。

客户端状态转化:

  • [CLOSED -> SYN_SENT],客户端调用connect,发送同步报文段。
  • [SYN_SENT -> ESTABLISHED],connect调用成功,则进入ESTABLISHED状态,开始读写数据。
  • [ESTABLISHED -> FIN_WAIT_1],客户端主动调用close时,向服务器发送结束报文段,同时进入FIN_WAIT_1。
  • [FIN_WAIT_1 -> FIN_WAIT_2],客户端收到服务器对结束报文段的确认,则进入FIN_WAIT_2,开始等待服务器的结束报文段。
  • [FIN_WAIT_2 -> TIME_WAIT],客户端收到服务器发来的结束报文段,进入TIME_WAIT,并发出LAST_ACK。
  • [TIME_WAIT -> CLOSED],客户端要等待一个2MSL(Max Segment Life,报文最大生存时间)的时间,才会进入CLOSED状态。

3.6.2 三次握手(建立连接)

3.6.2.1 为什么要进行三次握手?

TCP为什么需要三次握手,为什么不是一次、两次和四次呢?

  • TCP为什么不是一次握手

    1. 如果TCP一次握手就建立连接,只需要客户端向服务器发送SYN就能建立连接,客户端无法得知服务器是否收到SYN,若是客户端发送的报文丢失,就会造成双方建立不一致的问题。
    2. 攻击者可以伪造SYN,会导致SYN洪水的问题,并且服务器维护连接是需要成本的,会造成资源浪费。
  • TCP为什么不是两次握手

    1. 两次握手不能避免旧的重复连接初始化造成混乱的问题。若客户端发送的SYN因网络延迟滞留,随后重发SYN并建立新连接,而滞留的SYN稍后到达服务器,服务器会误认为是新连接请求并响应SYN+ACK。此时客户端已忽略旧SYN,导致服务器资源被无效占用。
    2. 攻击者可以伪造SYN,会导致SYN洪水的问题,并且服务器维护连接是需要成本的,会造成资源浪费
    3. 无法保证双方的通信能力,客户端向服务器发送SYN,证明客户端的发送能力,服务器收到SYN,并向客户端发送SYN+ACK,证明了服务器的接收能力和发送能力。所以两次握手不能证明客户端的接收能力。
    4. 两次握手不能保证双方初始序号同步
  • TCP为什么是三次握手

    1. 三次握手可以避免旧的重复连接初始化造成混乱的问题。若客户端发送的SYN因网络延迟滞留,随后重发SYN并建立新连接,而滞留的SYN稍后到达服务器,服务器会误认为是新连接请求并响应SYN+ACK,并向。此时客户端已忽略旧SYN,导致服务器资源被无效占用。
    2. 三次握手以最小成本验证全双工,保证双方的通信能力。客户端向服务器发送SYN,证明客户端的发送能力,服务器收到SYN,并向客户端发送SYN+ACK,证明了服务器的接收能力和发送能力,客户端接收到SYN+ACK,并向客户端发送ACK,就能证明客户端的接收能力。
    3. 奇数次握手,服务器能够减少资源的浪费,最后一个报文时客户端发出的,客户端发出报文认为连接建立成功,服务器则是收到了报文才认为连接建立成功。建立连接是需要消耗资源的,客户端先认为连接建立成功,所以资源是需要客户端先消费的,当服务器收到报文后才会消费资源,假设报文在传输过程中丢失了,服务器就任务连接建立失败,就不会消耗资源。
    4. 三次握手可以保证双方序号同步
  • TCP为什么不是四次握手

    1. 四次握手的过程:
      1. 客户端向服务器发送SYN
      2. 服务器收到SYN,向客户端发送ACK
      3. 服务器向客户端发送SYN
      4. 客户端收到SYN,向服务器发送ACK
    2. 服务器收到客户端的SYN,就必须向客户端发送ACK和SYN,所以服务器为了提高效率和节省资源,将ACK和SYN合并为一个报文。本质上三次握手就是四次握手+捎带应答
      在这里插入图片描述

3.6.3 四次挥手(断开连接)

3.6.3.1 为什么要进行四次挥手
  • 为什么要进行四次挥手
    1. 四次挥手的过程
      • 由于TCP双方是平等的,所以断开连接需要双方同意
      1. 客户端向服务器发送FIN,表示客户端已经发送完数据,不会再向服务器发送数据了(此时客户端还可以接收数据)
      2. 服务器收到FIN,向客户端发送ACK,表示自己已经收到报文(此时服务器还可以向服务器发送数据)
      3. 服务器向客户端发送FIN,表示服务器已经发送完数据,不会再向客户端发送数据了(由于服务器收到FIN后,此时服务器还没将数据发送完毕,所以通常不将ACK与FIN进行合并)
      4. 客户端收到FIN,向服务器发送ACK,表示自己已经收到报文
    2. 四次挥手可以变为三次挥手吗?
      • 可以的,但是现实中四次挥手更常见。四次挥手可以通过延迟应答机制,将ACK和FIN合并。首先服务器收到客户端的FIN,准备向客户端发送ACK,此时服务器会等待一段时间,若此时服务器需要向客户端发送FIN,就将ACK和FIN合并为一个报文发送给客户端,否则就先将ACK先发送给客户端。(延迟应答会在后面讲解)
        在这里插入图片描述

3.6.3.2 CLOSE_WAIT 和 TIME_WAIT

在这里插入图片描述

CLOSE_WAIT状态只有被动断开连接的一方才会拥有的状态,当被动断开连接一方收到FIN并返回ACK后,会将状态变为CLOSE_WAIT状态,直到它调用close函数,并向主动断开连接的一方发送FIN后,才会变为LAST_ACK状态。

服务器出现大量 CLOSE_WAIT 状态的连接原因有哪些?

  1. 服务器的代码存在问题,服务器在处理完数据以后,并没有调用关闭连接的函数(close()函数或shutdown()函数),导致服务器中存在大量CLOSE_WAIT状态的连接。
  2. 服务器在处理客户端发送的数据时,可能由于某些复杂的业务逻辑导致处理过程阻塞。在这种情况下,服务器无法及时处理完数据并发送FIN报文关闭连接,从而使连接长时间处于CLOSE_WAIT状态。

服务器中存在大量 CLOSE_WAIT 状态的连接,会导致服务器网络应用越来越卡。


在这里插入图片描述

TIME_WAIT是只有主动断开连接的一方才会拥有的状态。

进入TIME_WAIT状态的连接,此时它绑定的IP和端口号并未被彻底释放,通常此时重启服务器绑定同样的IP和端口号就会报错。

下图我关闭服务器后,立刻重启服务器并绑定相同IP和端口号,此时服务器并未启动,错误原因为IP和端口号已经被其他进程绑定了。

在这里插入图片描述

解决这个问题的方法就是使用setsockopt函数,设置套接字选项以允许地址重用。
在这里插入图片描述


TIME_WAIT的时间是2MSL,MSL是报文的最长存活时间。TIME_WAIT为两倍MSL,能够保证两个方向上未被接收到的报文和迟到的报文已经消失。

同时也保证了最后一个报文的可靠到达,假设最后一个ACK丢失了,服务器会重发FIN,此时客户端已经不在了,但是操作系统以及维护着TCP连接,依旧可以重复ACK。

TIME_WAIT存在的意义:

  1. 防止网络中历史报文对新连接的影响
    • 报文三次握手时,初始的序号是随机的,所以并不是判断报文是新的还是旧的,如果是没有TIME_WAIT,旧报文会对新连接有影响。
    • 报文的最长存活时间MSL,TIME_WAIT能够等待历史报文在网络中消散,就没有旧报文影响。
  2. TIME_WAIT能够保证最后一个报文的可靠到达,当最后一个报文丢失后
    • 没有TIME_WAIT的情况,客户端(主动断开连接一方)发送ACK后就进入CLOSE状态,由于服务器(被动断开连接一方)没有收到ACK,就会重发FIN,由于客户端连接已经关闭,则会发送RST给服务器,这样服务器就会将连接强制关闭,强制关闭连接并不是很好。
    • 有TIME_WAIT的情况,客户端(主动断开连接一方)发送ACK后就进入CLOSE状态,由于服务器(被动断开连接一方)没有收到ACK,就会重发FIN,由于客户端的连接并没有关闭,所以收到FIN以后会重置TIME_WAIT时间,并向服务器发送ACK,这样能够保证服务器正常关闭连接。

3.7 滑动窗口

滑动窗口就是TC并发多个数据暂时不需要ACK的解决方案,从而提高TCP的效率,滑动窗口还是流量控制的解决方案。

在确认应答部分我就讲到过,对于每一个报文,都要收到它对应的ACK,假设发送一个报文,就要等待收到ACK再发送下一个报文,这样TCP的效率就比较低了。

那么一次发送多个报文,就可以大大提高效率(多个报文的等待时间重叠了)。

在这里插入图片描述


3.7.1 滑动窗口在哪里?

滑动窗口就是发送缓冲区中的一个区域。
在这里插入图片描述


3.7.2 如何理解滑动窗口?

发送缓冲区我们可以理解为一个数组,滑动窗口则可以理解为两个指针,两个指针指向的区间就是滑动窗口,滑动窗口向右滑动就是指针向右移动

需要注意的是,滑动窗口只能向右滑动,不能向左滑动。

这时候就有人问了,按照数组形式理解,滑动窗口越界了怎么办?
操作系统中对其有复杂的处理方式,大家可以将滑动窗口为环形结构即可。

在这里插入图片描述

  • 滑动窗口的大小是由谁决定呢?
    • 滑动窗口的大小是由接收方可用的接收缓冲区的大小决定的。
    • 滑动窗口的大小还与拥塞窗口有关。
    • 滑动窗口的大小就是接收方窗口大小与拥塞窗口中小的一个。
  • 滑动窗口是如何更新的呢?
    • 滑动窗口是根据确认序号和接收方窗口大小决定的
    • win_start = 确认序号,win_end = win_start + win(窗口大小)

在这里插入图片描述


3.7.3 滑动窗口的大小变化吗?

滑动窗口的大小是可以变化的,滑动窗口的大小是根据对方可用的接收缓冲区大小来决定的,滑动窗口既可以变大,也可以变小,甚至可以为0。

当主机A将数据发送给主机B后,假设主机B的上层并未将接收缓冲区中读走,此时可以缓冲区就会变小,主机A的滑动窗口也会变小。主机A多次向发送数据,假设主机B上层一直没有读走数据,主机B的可以缓冲区就为0,主机A的滑动窗口也会变为0。
在这里插入图片描述

当主机A再次将数据发送给主机B后,假设主机B的上层一次将接收缓冲区中的数据全部读走,此时可以缓冲区就会变大,对应主机A的滑动窗口也会变大。
在这里插入图片描述


3.7.4 报文/响应报文丢失了怎么办?(快重传)

  1. 最左侧的报文/响应报文丢失了

    • 当最左侧的报文丢失时,其他的报文被主机B接收到,主机B会对所有的报文进行响应,但由于主机B并未收到数据1001~2000,所以所有相应的报文的确认序号都为1001,当主机A收到3个及以上的响应报文的确认序号相同时,就会立即对该数据进行重发,这就是快重传。如果主机A没有收到3个及以上的响应报文的确认序号相同时,主机A则会等到超时时间,将对应数据进行超时重传。
    • 当最左侧的报文对应的相应报文丢失了,但由于其他的响应报文并未丢失,确认序号的定义是确认序号之前的数据都收到了,所以并未有影响。
      在这里插入图片描述
  2. 中间的报文丢失了

    • 中间的报文丢失了,由于前面的报文已经被主机B接收到了,那说明滑动窗口就可以向右滑动,将已经被接收到的数据移除,则中间报文就变成了最左侧报文,中间报文丢失的问题就转变为了最左侧报文丢失的问题了。
    • 中间报文对应的响应报文丢失,会转化为最左侧报文对应的响应报文丢失的问题。
      在这里插入图片描述
  3. 最右侧报文/响应报文丢失了

    • 最右侧报文丢失了,由于前面的报文已经被主机B接收到了,那说明滑动窗口就可以向右滑动,将已经被接收到的数据移除,则中间报文就变成了最左侧报文,最右侧报文丢失的问题就转变为了最左侧报文丢失的问题了。
    • 最右侧报文对应的响应报文丢失,会被转化为最左侧报文对应的响应报文丢失的问题。

3.8 拥塞控制

上面讲述的机制都是与双方主机相关的,但是主机A向主机B发送数据需要通过网络,所以TCP还考虑了网络的状态,当网络中少量报文丢失,发送方就会将报文进行重传,但网络中大量的报文丢失了,发送方则会判断出是网络出现了问题 。

TCP引入 慢启动 机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据。少量数据探路,有响应后,说明网络状态已经好了一些,就需要尽快的恢复网络通信。

在这里插入图片描述

此处引入一个概念为拥塞窗口

  • 发送开始的时候,定义拥塞窗口大小为1
  • 每次收到一个ACK应答,拥塞窗口加1
  • 也就是先发送一个报文,收到应答后,下次就可以发送两个。发送两个报文,两个报文都收到了应答,则下次可以发送四个,以此类推
  • 每次发送报文的时候,将拥塞窗口和接收端窗口大小做比较,取较小的值作为滑动窗口的大小

像上面这样的拥塞窗口增长速度,是指数级别的。“慢启动” 只是指初使时慢,但是增长速度非常快。

  • 为了不增长的那么快,因此不能使拥塞窗口单纯的加倍
  • 此处引入一个叫做慢启动的阈值
  • 当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长

在这里插入图片描述

  • 当TCP开始启动的时候,以慢启动的方式增加拥塞窗口,慢启动阈值等于窗口最大值
  • 当拥塞窗口达到慢启动的阈值后,以线性方式增加拥塞窗口
  • 在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置回1

在这里插入图片描述
拥塞窗口的过程:

  1. 慢启动
    • 发送方以指数方式逐渐增加拥塞窗口的大小。
  2. 拥塞避免
    • 当拥塞窗口超过阈值后,发送方以线性方式缓慢增加拥塞窗口,避免网络过载。
  3. 快重传
    • 发送方收到3个重复的ACK时,立即重传丢失的数据包,而不必等待超时。
    • cwnd = cwnd /2,ssthresh = cwnd
  4. 快恢复
    • 在快重传后,发送方不进入慢启动阶段,而是进入拥塞避免阶段,调整拥塞窗口。主机还能收到 3 个重复 ACK 说明网络也不那么糟糕,所以没有必要像超时重传那么强烈。
    • cwnd = ssthresh + 3
    • 如果再收到重复的 ACK,那么 cwnd 增加 1
    • 如果收到新的的 ACK,把 cwnd 设置为第一步中的 ssthresh 的值
  5. 超时重传
    • 发送方的重传定时器超时,说明可能发生了严重的网络拥塞
    • ssthresh = cwnd / 2
    • cwnd = 1

3.9 面相字节流

创建一个TCP的socket,同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区

  • 调用write时,数据会先写入发送缓冲区中
  • 如果发送的字节数太长,会被拆分成多个TCP的数据包发出
  • 如果发送的字节数太短,就会先在缓冲区里等待,等到缓冲区长度差不多了,或者其他合适的时机发送出去
  • 接收数据的时候,数据也是从网卡驱动程序到达内核的接收缓冲区
  • 然后应用程序可以调用read从接收缓冲区拿数据
  • 另一方面,TCP的一个连接,既有发送缓冲区,也有接收缓冲区,那么对于这一个连接,既可以读数据,也可以写数据,这个概念叫做 全双工

由于缓冲区的存在,TCP程序的读和写不需要一一匹配,例如:

  • 写100个字节数据时,可以调用一次write写100个字节,也可以调用100次write,每次写一个字节
  • 读100个字节数据时,也完全不需要考虑写的时候是怎么写的,既可以一次read 100个字节,也可以一次read一个字节,重复100次

如何理解面向字节流呢?

  • 发送缓冲区

    • 发送方实际发送的数据是 “滑动窗口” 内的字节,已发送但未确认的字节会保留在缓冲区中,当收到接收方的 ACK 就会通过将滑动窗口向右滑动,将已被接收的数据移除,看上去发送缓冲区中的数据就像是“流动的”。
  • 接收缓冲区

    • 发送方的发送的数据会按照顺序保存在接收缓冲区,而接收方的上层可以按顺序从接收缓冲区中读取数据,将数据读走后,接收缓冲区中的数据看起来就是“流动的”。

3.10 粘包问题

粘包问题中的 “包”,是指的应用层的数据包

  • 在TCP的协议头中,没有如同UDP一样的 “报文长度” 这样的字段,但是有一个序号这样的字段
  • 站在传输层的角度,TCP是一个一个报文过来的,按照序号排好序放在缓冲区中
  • 站在应用层的角度,看到的只是一串连续的字节数据
  • 那么应用程序看到了这么一连串的字节数据,就不知道从哪个部分开始到哪个部分,是一个完整的应用层数据包

那么如何避免粘包问题呢?归根结底就是一句话,明确两个包之间的边界

  • 对于定长的包,保证每次都按固定大小读取即可。例如上面的Request结构,是固定大小的,那么就从缓冲区从头开始按sizeof(Request)依次读取即可
  • 对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位置
  • 对于变长的包,还可以在包和包之间使用明确的分隔符

思考:对于UDP协议来说,是否也存在 “粘包问题” 呢?

  • 对于UDP,如果还没有上层交付数据,UDP的报文长度仍然在。同时,UDP是一个一个把数据交付给应用层,就有很明确的数据边界
  • 站在应用层的站在应用层的角度,使用UDP的时候,要么收到完整的UDP报文,要么不收,不会出现"半个"的情况

3.11 TCP异常情况

  1. 进程终止
    • 进程终止会释放文件描述符,仍然可以发送FIN,和正常关闭没有什么区别
  2. 机器重启
    • 机器重启时,操作系统会询问是否终止进程,所以和进程终止的情况相同
  3. 机器掉电(无法发送FIN)
    • 发送方机器掉电
      • 发送方所有未发送的数据和连接状态丢失,无法继续传输
      • 接收方长时间未收到数据或 FIN,可能默认关闭连接
    • 接收方机器掉电
      • 发送方会启动重传定时器。若在定时器超时后仍未收到数据,发送方会持续重传确认报文,在多次重传无果后,发送方会认为连接出现问题,最终会选择关闭连接。
      • 接收方所有数据和连接状态丢失,无法继续传输

3.12 半连接队列和全连接队列(listen 的第二个参数)

  • 半链接队列(用来保存处于SYN_SENT和SYN_RECV状态的请求)
  • 全连接队列(用来保存处于 ESTABLISHED 状态,但是应用层没有调用accept取走的请求)

半连接队列工作流程

  • 客户端向服务器发送 SYN 包,请求建立连接。
  • 服务器收到 SYN 包后,为这个连接创建一个新的条目,并将其放入半连接队列中。
  • 服务器向客户端发送 SYN + ACK 包,等待客户端的 ACK 确认。
  • 如果在一定时间内没有收到客户端的 ACK 确认,服务器会重发 SYN + ACK 包,达到一定重传次数后仍未收到 ACK,则会从半连接队列中移除该条目。

全连接队列工作流程

  • 客户端收到服务器的 SYN + ACK 包后,向服务器发送 ACK 确认包。
  • 服务器收到 ACK 确认包后,将连接信息从半连接队列移除,并加入到全连接队列中。
  • 服务器进程调用 accept() 函数从全连接队列中取出一个连接进行处理。
  • 如果全连接队列已满,新的连接将被暂时拒绝,客户端可能会收到连接超时的错误,服务器也可能会采取一些策略(如丢弃连接请求等)来处理这种情况。

全连接队列的容量 = listen 的第二个参数(backlog)+ 1。


3.13 文件、Socket、系统和网络之间的关系

进程在启动后,操作系统会为进程创建对应的PCB和文件描述符表,当进程创建套接字后,会返回一个文件描述符并保存在文件描述符表中。

数据通过网络后,最先到达网卡,网卡会向CPU对应的针脚发送中断信号,CPU中的寄存器会将其转化为中断号,操作系统可以通过中断号找到对应中断向量表中的方法,操作系统可以根据对应的方法,将网卡中的数据读取到传输层的接收缓冲区中,再通过文件的操作方法表中的方法将接收缓冲区中的数据读取到应用层。

每一个文件描述符指向一个 file 结构体, file 结构体中的一个字段指向 socket 结构体。socket 结构体中的一个字段指向 file 结构体,还有一个字段指向相关套接字函数。socket 结构体中有一个 struct sock* 字段,指向 sock 结构体(实际上指向的是 udp_sock 结构体或 tcp_sock 结构体)。

  • tcp_sock 嵌套了 inet_connection_sock,inet_connection_sock 嵌套了 inet_sock,inet_sock 又嵌套了 sock。所以通过 tcp_sock 可以依次访问到其内部嵌套的各层结构体
  • 同理,udp_sock 嵌套了 inet_sock,inet_sock 嵌套了 sock,通过 udp_sock 可以访问 sock 和 inet_sock

struct sock * 字段,既可以指向的是 udp_sock 结构体也可以指向 tcp_sock 结构体。将 struct sock * 进行强制类型转换,就可以分别访问 tcp_sock 和 udp_sock。所以Linux内核中,使用指针操作和结构体嵌套实现了多态,这样就使用一种方式就实现了TCP和UDP。

在这里插入图片描述


四、UDP与TCP的对比

4.1 UDP与TCP的特点对比

可靠传输/不可靠传输不是优缺点,而是协议的特点。

UDP特点

  • 面向数据报:将每一个独立的报文作为一个整体发送,保留报文边界
  • 不建立连接:知道对方的IP和端口号就可以直接传输,不需要建立链接
  • 不可靠传输:没有确认机制,没有重传机制,数据传输发生错误的时候,没有如何反馈

TCP特点

  • 面向字节流:将数据设置为连续的字节流,不保留报文边界
  • 建立连接:在传输数据之前需要建立连接,连接过程我们称之为三次握手
  • 可靠传输:通过确认应对、超时重传、拥塞控制、数据校验和、流量控制等机制保证了数据的可靠传输

4.2 用UDP实现可靠传输

参考TCP的可靠性机制,在应用层实现类似的逻辑

  1. 引入序号,保证数据按序到达
  2. 引入确认应对机制,确认数据是否对方收到
  3. 引入超时重传机制,无法保证对方收到数据,则将数据进行重传,尽可能的保证数据被对方收到
  4. 引入滑动窗口与流量控制,使用滑动窗口机制控制数据传输速率,避免接收方缓存溢出,导致数据丢失
  5. 引入拥塞控制,通过拥塞控制避免网络拥塞。

结尾

如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。
希望大家以后也能和我一起进步!!🌹🌹
如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹

在这里插入图片描述

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

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

相关文章

mybatisX的自定义模板生成

在idea中使用mybtais的自定义模板生成,可以帮我们省去很多重复的代码。 打开一个项目,我们要修改的主要就两个文件,一个是生成的mapper接口,另一个是xml文件: 相应的mapper接口模板为: package ${mapper…

miniz:一个轻量级、高性能的开源压缩库

目录 1.简介 2.核心特性 3.基本使用示例 4.与 ZLIB 的对比 5.使用场景 6.注意事项 1.简介 miniz 是一个轻量级、高性能的开源压缩库,专注于提供 ZLIB/GZIP 兼容的压缩和解压缩功能。它的核心优势在于体积小巧(单文件实现)、跨平台支持和…

Jenkins接口自动化测试(构建)平台搭建

Python接口自动化测试零基础入门到精通(2025最新版)自动化测试流程 在进行平台搭建前,我们首先要问自己:我需要搭建的平台的功能是什么,要实现什么目标? 在我的理解中,自动化构建平台的执行流…

Day 22: 复习

机器学习数据处理与降维技术复习总结 前言 经过6天的学习,我们系统地学习了从基础的Numpy数组操作到高级的降维算法,这些内容构成了机器学习数据预处理的重要知识体系。本文将对这一系列学习内容进行全面复习和总结,帮助大家建立完整的知识…

力扣 hot100 Day56

46. 全排列 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 //抄的 class Solution { private:vector<vector<int>>result;vector<int> path; public:void backtracking(vector<int>& nu…

Android 编码规范全指南

在 Android 开发领域&#xff0c;代码不仅是功能实现的载体&#xff0c;更是团队协作与项目迭代的基础。一套完善的编码规范&#xff0c;能让代码从 “可运行” 升级为 “易维护、可扩展、低风险”。本文基于 Google、Square 等顶尖团队的实践经验&#xff0c;结合国内 Android…

[RPA] Excel中的字典处理

案例1一个Excel文件总共有2个Sheet页&#xff0c;分别为总表和对照表通过对照表sheet页&#xff0c;设置价格对照字典对照表循环总表sheet页&#xff0c;根据循环到的商品名称&#xff0c;找到对应字典中的价格&#xff0c;并计算出总价总表将总价写入到Excel表中C列&#xff0…

基于NSGAII优化算法的车间生产调度matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.部分程序 4.算法理论概述 5.参考文献 6.完整程序 1.程序功能描述 车间生产调度是制造业的核心环节&#xff0c;其目标是在满足设备约束、工序优先级等条件下&#xff0c;优化多个相互冲突的生产指标&#xff08;如…

Cmake、VS2019、C++、openGLopenCV环境安装

在 CMake 和 Visual Studio 2019 环境下安装和配置 OpenGL、OpenCV 以及 CUDA 可能会有些复杂&#xff0c;因为涉及的组件多且相互依赖。以下是一个详细的指南&#xff0c;帮助您逐步完成安装和配置。 1. 前提条件 在开始之前&#xff0c;请确保您已安装以下软件&#xff1a; …

视频二维码在产品设备说明书中的应用

在当今数字化的时代&#xff0c;传统的产品设备说明书正面临着一场变革。文字和图片虽然能提供基本信息&#xff0c;但在复杂设备的安装、操作和故障排除方面&#xff0c;往往显得力不从心。而视频二维码的出现&#xff0c;为这一困境提供了完美的解决方案&#xff0c;它将冰冷…

【Pytest 使用教程】

pytest 使用 test_basic.py Pytest 完全实战手册 一、核心概念与基础 1、在pytest框架下运行测试用例&#xff0c;最基础的一共有三点。导入pytest的包写一个方法&#xff0c;或者类。后面运行的时候&#xff0c;相当于运行这个方法&#xff0c;或者类里的方法&#xff0c;无需…

基于OpenOCD 的 STM32CubeIDE 开发烧录调试环境搭建 DAPLINK/STLINK

需要部署一个开发环境,实现h7的板子通过daplink功能给目标板烧写程序(同事要将这个过程用fpga实现),需要通过openocd+gdb+daplink stm32; 总结:单条命令执行太麻烦,参考4写成脚本文件: 独立脚本使用Openocd ​ 在**“在Stm32CubeIDE环境下使用DAP-Link仿真”**一文中…

嵌入式硬件篇---zigbee无线串口通信问题

使用 ZigBee 进行无线串口通信时&#xff0c;接收异常&#xff08;如丢包、乱码、完全无法接收&#xff09;是常见问题&#xff0c;其原因涉及射频通信特性、网络机制、硬件配置、环境干扰等多个层面。以下从具体机制出发&#xff0c;详细分析可能的原因&#xff1a;一、射频层…

【AI周报】2025年7月26日

【AI周报】2025年7月第四周观察&#xff1a;GitHub Spark重塑开发范式&#xff0c;中美AI政策对垒升级 省流版静态页面周报&#xff0c;为方便各位看官快速食用&#xff0c;我准备了摘要版周报&#xff0c;欢迎访问&#xff1a;20250726周报 引言&#xff1a;本周焦点速览 2…

HTML:从 “小白” 到 “标签侠” 的修炼手册

目录 一、HTML&#xff1a;网页的 “骨架” 不是骷髅架 二、文本标签&#xff1a;文字的 “华丽变身” 术 1. 标题标签&#xff1a;文字界的 “领导班子” 2. 段落标签&#xff1a;文字的 “专属保姆” 3. 文本格式化标签&#xff1a;给文字 “穿花衣” 三、链接标签&…

python3GUI--基于YOLO的火焰与烟雾检测系统By:PyQt5(详细图文介绍)

文章目录一&#xff0e;前言1.引言2.正文二&#xff0e;核心内容1.数据集2.模型训练3.界面窗口1.登录注册界面2.核心功能界面3.检测告警提示窗口三&#xff0e;.核心界面模块介绍1.顶部信息区域2.数据输入3.参数配置4.告警设置5.操作台6.关于7.指标变化8.异常速览9.日志输出10.…

基于Transform、ARIMA、LSTM、Prophet的药品销量预测分析

文章目录有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主一、项目背景二、数据准备与预处理三、模型选择与方法设计1. ARIMA 模型&#xff1a;传统统计方法的基线构建2. LSTM 模型&#xff1a;引入记忆机制的深度学习方法3. Transformer 模型&#…

LLM隐藏层与logits数值的简单理解

LLM的“隐藏层数值”和“logits数值” 代表什么,范围是多少 “隐藏层数值”和“logits数值”是两个关键概念——它们分别对应模型“理解信息”和“输出决策”的核心环节。 一、先明确基础:LLM的“思考”流程 LLM本质是“输入文本→处理信息→输出结果”的神经网络。简单说…

Vue》》@ 用法

使用 别名导入 // 导入 src/components/Button.vue import Button from /components/Button.vue// 导入 src/utils/helper.js import { helperFunc } from /utils/helper// 导入 src/store/index.js import store from /store

20250726-1-Kubernetes 网络-Service存在的意义_笔记

一、Service控制器 1. Service存在的意义 1)基本场景  动态IP问题:Pod IP具有短暂性,销毁重建后IP会变化(示例:原IP 169.130重建后变为169.132) 服务发现需求:需要稳定入口访问同一服务的多个Pod,避免因Pod变动导致服务中断 负载均衡需求:多个Pod副本需要统一访…