高并发四种IO模型的底层原理

1 IO读写的基本原理

为了避免用户进程直接操作内核,保证内核安全,操作系统将内存(虚拟内存)划分为两部分:一部分是内核空间(Kernel-Space),另一部分是用户空间(User-Space)。在Linux系统中,内核模块运行在内核空间,对应的进程处于内核态;用户程序运行在用户空间,对应的进程处于用户态。

操作系统的核心是内核程序,它独立于普通的应用程序,既有权限访问受保护的内核空间,也有权限访问硬件设备,而普通的应用程序并没有这样的权限。内核空间总是驻留在内存中,是为操作系统的内核保留的。应用程序不允许直接在内核空间区域进行读写,也不允许直接调用内核代码定义的函数。每个应用程序进程都有一个单独的用户空间,对应的进程处于用户态,用户态进程不能访问内核空间中的数据,也不能直接调用内核函数,因此需要将进程切换到内核态才能进行系统调用。

内核态进程可以执行任意命令,调用系统的一切资源,而用户态进程只能执行简单的运算,不能直接调用系统资源,那么问题来了:用户态进程如何执行系统调用呢?答案是:用户态进程必须通过系统调用(System Call)向内核发出指令,完成调用系统资源之类的操作。

说明:
如果没有特别声明,本书后文所提到的内核是指操作系统的内核。

用户程序进行IO的读写依赖于底层的IO读写,基本上会用到底层的read和write两大系统调用。虽然在不同的操作系统中read和write两大系统调用的名称和形式可能不完全一样,但是它们的基本功能是一样的。

操作系统层面的read系统调用并不是直接从物理设备把数据读取到应用的内存中,write系统调用也不是直接把数据写入物理设备。上层应用无论是调用操作系统的read还是调用操作系统的write,都会涉及缓冲区。具体来说,上层应用通过操作系统的read系统调用把数据从内核缓冲区复制到应用程序的进程缓冲区,通过操作系统的write系统调用把数据从应用程序的进程缓冲区复制到操作系统的内核缓冲区。

简单来说,应用程序的IO操作实际上不是物理设备级别的读写,而是缓存的复制。read和write两大系统调用都不负责数据在内核缓冲区和物理设备(如磁盘、网卡等)之间的交换。这个底层的读写交换操作是由操作系统内核(Kernel)来完成的。所以,在应用程序中,无论是对socket的IO操作还是对文件的IO操作,都属于上层应用的开发,它们在输入(Input)和输出(Output)维度上的执行流程是类似的,都是在内核缓冲区和进程缓冲区之间进行数据交换。

2 内核缓冲区与进程缓冲区

为什么设置那么多的缓冲区,导致读写过程那么麻烦呢?

缓冲区的目的是减少与设备之间的频繁物理交换。计算机的外部物理设备与内存和CPU相比,有着非常大的差距,外部设备的直接读写涉及操作系统的中断。发生系统中断时,需要保存之前的进程数据和状态等信息,结束中断之后,还需要恢复之前的进程数据和状态等信息。为了减少底层系统的频繁中断所导致的时间损耗、性能损耗,出现了内核缓冲区。

操作系统会对内核缓冲区进行监控,等待缓冲区达到一定数量的时候,再进行IO设备的中断处理,集中执行物理设备的实际IO操作,通过这种机制来提升系统的性能。至于具体什么时候执行系统中断(包括读中断、写中断)则由操作系统的内核来决定,应用程序不需要关心。

上层应用使用read系统调用时,仅仅把数据从内核缓冲区复制到应用的缓冲区(进程缓冲区)​;上层应用使用write系统调用时,仅仅把数据从应用的缓冲区复制到内核缓冲区。

内核缓冲区与应用缓冲区在数量上也不同。在Linux系统中,操作系统内核只有一个内核缓冲区。每个用户程序(进程)都有自己独立的缓冲区,叫作用户缓冲区或者进程缓冲区。在大多数情况下,Linux系统中用户程序的IO读写程序并没有进行实际的IO操作,而是在用户缓冲区和内核缓冲区之间直接进行数据的交换。

3 典型的系统调用流程

用户程序所使用的系统调用read和write并不是使数据在内核缓冲区和物理设备之间交换:read调用把数据从内核缓冲区复制到应用的用户缓冲区,write调用把数据从应用的用户缓冲区复制到内核缓冲区。两个系统调用的大致流程如图2-1所示。

在这里插入图片描述

这里以read系统调用为例,看一下一个完整输入流程的两个阶段:

  • 应用程序等待数据准备好。
  • 从内核缓冲区向用户缓冲区复制数据。

如果是读取一个socket(套接字)​,那么以上两个阶段的具体处理流程如下:

  • 第一个阶段,应用程序等待数据通过网络到达网卡,当所等待的分组到达时,数据被操作系统复制到内核缓冲区中。这个工作由操作系统自动完成,用户程序无感知。
  • 第二个阶段,内核将数据从内核缓冲区复制到应用的用户缓冲区。

再具体一点,如果是在Java客户端和服务端之间完成一次socket请求和响应(包括read和write)的数据交换,其完整的流程如下:

  • 客户端发送请求:Java客户端程序通过write系统调用将数据复制到内核缓冲区,Linux将内核缓冲区的请求数据通过客户端机器的网卡发送出去。在服务端,这份请求数据会从接收网卡中读取到服务端机器的内核缓冲区。
  • 服务端获取请求:Java服务端程序通过read系统调用从Linux内核缓冲区读取数据,再送入Java进程缓冲区。
  • 服务端业务处理:Java服务器在自己的用户空间中完成客户端的请求所对应的业务处理。
  • 服务端返回数据:Java服务器完成处理后,构建好的响应数据将从用户缓冲区写入内核缓冲区,这里用到的是write系统调用,操作系统会负责将内核缓冲区的数据发送出去。
  • 发送给客户端:服务端Linux系统将内核缓冲区中的数据写入网卡,网卡通过底层的通信协议将数据发送给目标客户端。

由于生产环境的Java高并发应用基本都运行在Linux操作系统上,因此以上案例中的操作系统以Linux作为实例。

4 四种主要的IO模型

服务端高并发IO编程往往要求的性能都非常高,一般情况下需要选用高性能的IO模型。

4.1 同步阻塞IO

首先,解释一下阻塞与非阻塞。阻塞IO指的是需要内核IO操作彻底完成后才返回到用户空间执行用户程序的操作指令。​“阻塞”指的是用户程序(发起IO请求的进程或者线程)的执行状态。可以说传统的IO模型都是阻塞IO模型,并且在Java中默认创建的socket都属于阻塞IO模型。

其次,解释一下同步与异步。简单来说,可以将同步与异步看成发起IO请求的两种方式。同步IO是指用户空间(进程或者线程)是主动发起IO请求的一方,系统内核是被动接收方。异步IO则反过来,系统内核是主动发起IO请求的一方,用户空间是被动接收方。

同步阻塞IO(Blocking IO)指的是用户空间(或者线程)主动发起,需要等待内核IO操作彻底完成后才返回到用户空间的IO操作。在IO操作过程中,发起IO请求的用户进程(或者线程)处于阻塞状态。

在Java中

在Java应用程序进程中所有对socket连接进行的IO操作都是同步阻塞IO。在阻塞式IO模型中,从Java应用程序发起IO系统调用开始,一直到系统调用返回,这段时间内发起IO请求的Java进程(或者线程)是阻塞的。直到返回成功后,应用进程才能开始处理用户空间的缓冲区数据。

同步阻塞IO的具体流程如图2-2所示。

在这里插入图片描述

举个例子,在Java中发起一个socket的read操作的系统调用,流程大致如下:

  • (1)从Java进行IO读后发起read系统调用开始,用户线程(或者线程)就进入阻塞状态。
  • (2)当系统内核收到read系统调用后就开始准备数据。一开始,数据可能还没有到达内核缓冲区(例如,还没有收到一个完整的socket数据包)​,这时内核就要等待。
  • (3)内核一直等到完整的数据到达,就会将数据从内核缓冲区复制到用户缓冲区(用户空间的内存)​,然后内核返回结果(例如返回复制到用户缓冲区中的字节数)​。
  • (4)直到内核返回后用户线程才会解除阻塞的状态,重新运行起来。阻塞IO的特点是在内核执行IO操作的两个阶段,发起IO请求的用户进程(或者线程)被阻塞了。

阻塞IO的优点是:应用程序开发非常简单;在阻塞等待数据期间,用户线程挂起,基本不会占用CPU资源。

阻塞IO的缺点是:一般情况下会为每个连接配备一个独立的线程,一个线程维护一个连接的IO操作。在并发量小的情况下,这样做没有什么问题。在高并发的应用场景下,阻塞IO模型需要大量的线程来维护大量的网络连接,内存、线程切换开销会非常巨大,性能很低,基本上是不可用的。

4.2 同步非阻塞IO

非阻塞IO(Non-Blocking IO, NIO)指的是用户空间的程序不需要等待内核IO操作彻底完成,可以立即返回用户空间去执行后续的指令,即发起IO请求的用户进程(或者线程)处于非阻塞状态,与此同时,内核会立即返回给用户一个IO状态值。

阻塞和非阻塞的区别是什么呢?阻塞是指用户进程(或者线程)一直在等待,而不能做别的事情;非阻塞是指用户进程(或者线程)获得内核返回的状态值就返回自己的空间,可以去做别的事情。在Java中,非阻塞IO的socket被设置为NONBLOCK模式。

说明:
同步非阻塞IO也可以简称为NIO,但是它不是Java编程中的NIO。Java编程中的NIO(New IO)类库组件所归属的不是基础IO模型中的NIO模型,而是IO多路复用模型。

同步非阻塞IO指的是用户进程主动发起,不需要等待内核IO操作彻底完成就能立即返回用户空间的IO操作。在IO操作过程中,发起IO请求的用户进程(或者线程)处于非阻塞状态。

在java中

在Linux系统下,socket连接默认是阻塞模式,可以将socket设置成非阻塞模式。在NIO模型中,应用程序一旦开始IO系统调用,就会出现以下两种情况:

  • (1)在内核缓冲区中没有数据的情况下,系统调用会立即返回一个调用失败的信息。
  • (2)在内核缓冲区中有数据的情况下,在数据的复制过程中系统调用是阻塞的,直到完成数据从内核缓冲区复制到用户缓冲区。复制完成后,系统调用返回成功,用户进程(或者线程)可以开始处理用户空间的缓冲区数据。

同步非阻塞IO的流程如图2-3所示。

在这里插入图片描述

举个例子,发起一个非阻塞socket的read操作的系统调用,流程如下:

  • (1)在内核数据没有准备好的阶段,用户线程发起IO请求时立即返回。所以,为了读取最终的数据,用户进程(或者线程)需要不断地发起IO系统调用。
  • (2)内核数据到达后,用户进程(或者线程)发起系统调用,用户进程(或者线程)阻塞。内核开始复制数据,它会将数据从内核缓冲区复制到用户缓冲区,然后内核返回结果(例如返回复制到的用户缓冲区的字节数)​。
  • (3)用户进程(或者线程)读到数据后,才会解除阻塞状态,重新运行起来。也就是说,用户空间需要经过多次尝试才能保证最终真正读到数据,而后继续执行。

同步非阻塞IO的特点是应用程序的线程需要不断地进行IO系统调用,轮询数据是否已经准备好,如果没有准备好就继续轮询,直到完成IO系统调用为止。

同步非阻塞IO的优点是每次发起的IO系统调用在内核等待数据过程中可以立即返回,用户线程不会阻塞,实时性较好。

同步非阻塞IO的缺点是不断地轮询内核,这将占用大量的CPU时间,效率低下。

总体来说,在高并发应用场景中,同步非阻塞IO是性能很低的,也是基本不可用的,一般Web服务器都不使用这种IO模型。在Java的实际开发中,不会涉及这种IO模型,但是此模型还是有价值的,其作用在于其他IO模型中可以使用非阻塞IO模型作为基础,以实现其高性能。

4.3 IO多路复用

概念理解

为了提高性能,操作系统引入了一种新的系统调用,专门用于查询IO文件描述符(含socket连接)的就绪状态。在Linux系统中,新的系统调用为 select/epoll 系统调用。通过该系统调用,一个用户进程(或者线程)可以监视多个文件描述符,一旦某个描述符就绪(一般是内核缓冲区可读/可写)​,内核就能够将文件描述符的就绪状态返回给用户进程(或者线程)​,用户空间可以根据文件描述符的就绪状态进行相应的IO系统调用。

IO多路复用(IO Multiplexing)属于一种经典的Reactor模式实现,有时也称为异步阻塞IO, Java中的Selector属于这种模型。

系统应用

如何避免同步非阻塞IO模型中轮询等待的问题呢?答案是采用IO多路复用模型。

目前支持IO多路复用的系统调用有select、epoll等。几乎所有的操作系统都支持select系统调用,它具有良好的跨平台特性。epoll是在Linux 2.6内核中提出的,是select系统调用的Linux增强版本。

在IO多路复用模型中通过select/epoll系统调用,单个应用程序的线程可以不断地轮询成百上千的socket连接的就绪状态,当某个或者某些socket网络连接有IO就绪状态时就返回这些就绪的状态(或者说就绪事件)​。

举个例子来说明IO多路复用模型的流程。发起一个多路复用IO的read操作的系统调用,流程如下:

  • (1)选择器注册。首先,将需要read操作的目标文件描述符(socket连接)提前注册到Linux的select/epoll选择器中,在Java中所对应的选择器类是Selector类。然后,开启整个IO多路复用模型的轮询流程。
  • (2)就绪状态的轮询。通过选择器的查询方法,查询所有提前注册过的目标文件描述符(socket连接)的IO就绪状态。通过查询的系统调用,内核会返回一个就绪的socket列表。当任何一个注册过的socket中的数据准备好或者就绪了就说明内核缓冲区有数据了,内核将该socket加入就绪的列表中,并且返回就绪事件。
  • (3)用户线程获得了就绪状态的列表后,根据其中的socket连接发起read系统调用,用户线程阻塞。内核开始复制数据,将数据从内核缓冲区复制到用户缓冲区。
  • (4)复制完成后,内核返回结果,用户线程才会解除阻塞的状态,用户线程读取到了数据,继续执行。

说明:
在用户进程进行IO就绪事件的轮询时,需要调用选择器的select查询方法,发起查询的用户进程或者线程是阻塞的。
当然,如果使用了查询方法的非阻塞的重载版本,发起查询的用户进程或者线程也不会阻塞,重载版本会立即返回。

IO多路复用模型的read系统调用流程如图2-4所示。

在这里插入图片描述

IO多路复用模型的特点是:IO多路复用模型的IO涉及两种系统调用,一种是IO操作的系统调用,另一种是select/epoll就绪查询系统调用。IO多路复用模型建立在操作系统的基础设施之上,即操作系统的内核必须能够提供多路分离的系统调用select/epoll。

和NIO模型相似,多路复用IO也需要轮询。负责select/epoll状态查询调用的线程,需要不断地进行select/epoll轮询,以找出达到IO操作就绪的socket连接。

IO多路复用模型与同步非阻塞IO模型是有密切关系的,具体来说,注册在选择器上的每一个可以查询的socket连接一般都设置成同步非阻塞模型,只是这一点对于用户程序而言是无感知的。

IO多路复用模型的优点是一个选择器查询线程可以同时处理成千上万的网络连接,所以用户程序不必创建大量的线程,也不必维护这些线程,从而大大减少了系统的开销。与一个线程维护一个连接的阻塞IO模式相比,这一点是IO多路复用模型的最大优势。

通过JDK的源码可以看出,Java语言的NIO组件在Linux系统上是使用epoll系统调用实现的。所以,Java语言的NIO组件所使用的就是IO多路复用模型。

IO多路复用模型的缺点是,本质上 select/epoll 系统调用是阻塞式的,属于同步IO,需要在读写事件就绪后由系统调用本身负责读写,也就是说这个读写过程是阻塞的。要彻底地解除线程的阻塞,就必须使用异步IO模型。

4.4 异步IO

异步IO(Asynchronous IO, AIO)指的是用户空间的线程变成被动接收者,而内核空间成为主动调用者。在异步IO模型中,当用户线程收到通知时,数据已经被内核读取完毕并放在了用户缓冲区内,内核在IO完成后通知用户线程直接使用即可。

异步IO类似于Java中典型的回调模式,用户进程(或者线程)向内核空间注册了各种IO事件的回调函数,由内核去主动调用。

系统应用

异步IO模型的基本流程是:用户线程通过系统调用向内核注册某个IO操作。内核在整个IO操作(包括数据准备、数据复制)完成后通知用户程序,用户执行后续的业务操作。

在异步IO模型中,在整个内核的数据处理过程(包括内核将数据从网络物理设备(网卡)读取到内核缓冲区、将内核缓冲区的数据复制到用户缓冲区)中,用户程序都不需要阻塞。

异步IO模型的流程如图2-5所示。

在这里插入图片描述

举个例子,发起一个异步IO的read操作的系统调用,流程如下:

  • (1)当用户线程发起了read系统调用后,立刻就可以去做其他的事,用户线程不阻塞。
  • (2)内核开始IO的第一个阶段:准备数据。准备好数据,内核就会将数据从内核缓冲区复制到用户缓冲区。
  • (3)内核会给用户线程发送一个信号(Signal),或者回调用户线程注册的回调方法,告诉用户线程read系统调用已经完成,数据已经读入用户缓冲区。
  • (4)用户线程读取用户缓冲区的数据,完成后续的业务操作。

异步IO模型的特点是在内核等待数据和复制数据的两个阶段,用户线程都不是阻塞的。用户线程需要接收内核的IO操作完成的事件,或者用户线程需要注册一个IO操作完成的回调函数。正因为如此,异步IO有的时候也被称为信号驱动IO。

异步IO模型的缺点是应用程序仅需要进行事件的注册与接收,其余的工作都留给了操作系统,也就是说需要底层内核提供支持。

理论上来说,异步IO是真正的异步输入输出,它的吞吐量高于IO多路复用模型的吞吐量。就目前而言,Windows系统下通过IOCP实现了真正的异步IO。在Linux系统下,异步IO模型在2.6版本才引入,JDK对它的支持目前并不完善,因此异步IO在性能上没有明显的优势。

大多数高并发服务端的程序都是基于Linux系统的。因而,目前这类高并发网络应用程序的开发大多采用IO多路复用模型。大名鼎鼎的Netty框架使用的就是IO多路复用模型,而不是异步IO模型。

参考

  • 书名:Java高并发核心编程 卷1:NIO、Netty、Redis、ZooKeeper
  • 作者:尼恩

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

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

相关文章

腾讯云短信实战:Spring Boot接入YML配置与签名/模板/发送/统计/状态/号码包工具类详解

下面是一个Spring Boot集成腾讯云短信服务的详细示例&#xff0c;包含配置和6个工具类&#xff08;签名、模板、发送、统计、状态&#xff09;&#xff0c;采用YML配置&#xff1a; 1. 添加Maven依赖 <dependency><groupId>com.tencentcloudapi</groupId>&…

【Java篇】IntelliJ IDEA 安装与基础配置指南

序 本篇文章将介绍IDEA 2023 版本。 提高开发人员的生产力。无论您是刚开始接触编程的新手&#xff0c;还是经验丰富的开发专家。 一&#xff1a;官网下载安装包&#xff1a; IDEA下载链接 这个版本可以根据自己的需要选择。 二、安装方法 双击进这个.exe文件 这里要选择合…

2-Nodejs运行JS代码

2-Nodejs运行JS代码 创建一个 js 文件编写 JS 代码 要注意的是&#xff0c;在nodejs环境中不能操作浏览器 DOM 对象相关的api&#xff0c;在Nodejs 中运行 JS 代码 按住 shift 键&#xff0c;在 js 文件所在文件夹空白处右键&#xff0c;选择 Powershell 窗口执行如下命令&…

vue中使用西瓜播放器xgplayer (封装)+xgplayer-hls 播放.m3u8格式视频

1.西瓜播放器官网 http://h5player.bytedance.com/guide/2.安装 # 最新稳定版 $ npm install xgplayer对于已有项目也可以通过 CDN 引入&#xff0c;代码如下&#xff1a; <script src"//unpkg.byted-static.com/xgplayer/2.31.2/browser/index.js" type"tex…

2025-07-15通过边缘线检测图像里的主体有没有出血

本节观点&#xff1a;一个好的提问就已经解决了问题的90%。 对于问题的描述正确与否决定了解决问题的方法和路径&#xff0c;所以我们在AI时代必须要学会正确的描述问题和表达问题&#xff0c;否则即使有AI辅助也是很难精准的解决问题。 我的问题&#xff1a; 如何利用代码从图…

【Docker基础】Dockerfile指令速览:文件与目录操作指令详解

目录 引言 1 ADD&#xff1a;高级文件复制与解压 1.1 指令简介 1.2 语法 1.3 功能详解 1.4 使用场景 1.5 执行流程 1.6 示例 1.7 注意事项 2 WORKDIR&#xff1a;设置工作目录 2.1 指令简介 2.2 语法 2.3 使用场景 2.4 创建流程 2.5 示例 2.6 注意事项 3 VOLU…

Python 程序设计讲义(2):Python 概述

Python 程序设计讲义&#xff08;2&#xff09;&#xff1a;Python 概述 一、Python 语言的发展史 Python 语言诞生于 1990 年。 2002 年 10 月&#xff0c;Python2.0 正式发布。 2008 年 12 月&#xff0c;Python3.0 正式发布。 Python3.0 在语法层面和解释器内部做了很多重大…

多租户SaaS系统中设计安全便捷的跨租户流程共享

四维协同架构​​,结合动态授权、加密传输、行为审计和智能策略 一、​​权限控制体系​​ 1. ​​动态权限模型​ ​ 2. ​​授权策略实现​​ ​​RBAC+ABAC混合模型​​ 在流程表增加shared_tenants字段存储授权信息,结合属性动态校验: CREATE TABLE workflow_process…

Spring Ioc Bean 到底是什么

Bean 到底是什么&#xff1f; 简单来说&#xff0c;Spring Bean 就是一个由 Spring IoC 容器负责创建、管理和装配的 Java 对象。 它不是一种新的技术&#xff0c;它本质上还是一个普普通通的 Java 对象&#xff08;POJO - Plain Old Java Object&#xff09;&#xff0c;但它的…

【PCIe 总线及设备入门学习专栏 5.1.1 -- PCIe PERST# 信号的作用】

文章目录 PCIe PERSTN#PERST# 信号作用概述简要定义PERST# 的关键功能PERST# 的时序图示意Synopsys PCIe EP IP 中 PERST# 的作用关键信号接口典型复位流程示例代码(Verilog for Synopsys PCIe)PERST# 使用场景举例(Synopsys PCIe EP)1. 系统上电初始化2. 热复位特定设备3.…

使用python的pillow模块将图片转化为灰度图,获取值和修改值

使用python的pillow模块可以将图片转化为灰度图&#xff0c; 可以获取灰度图的特定点值&#xff0c;区域值&#xff0c; 修改值并保存到图片 图片转换为灰度图 from PIL import Image# 打开图片 image Image.open("d://python//2//1.jpg")gray_image image.convert…

记忆力训练day41

通常是一个地点记2组词 数字和人体记忆宫殿更注重 即时性&#xff1b;地点记忆宫殿是长久性

自动微分模块

一.前言本章节我们是要学习梯队计算&#xff0c;⾃动微分&#xff08;Autograd&#xff09;模块对张量做了进⼀步的封装&#xff0c;具有⾃动求导功能。⾃动微分模块是构成神经⽹络 训练的必要模块&#xff0c;在神经⽹络的反向传播过程中&#xff0c;Autograd 模块基于正向计算…

深度学习·目标检测和语义分割基础

边缘框 不是标准的x&#xff0c;y坐标轴。边缘框三种表示&#xff1a;左上右下下坐标&#xff0c;左上坐标长宽&#xff0c;中心坐标长宽 COCO 目标检测数据集的格式&#xff1a;注意一个图片有多个物体&#xff0c;使用csv或者文件夹结构的格式不可取。 锚框算法 生成很多…

ffmpeg音视频处理大纲

FFmpeg 是一个功能强大的开源音视频处理工具集&#xff0c;其核心代码以 C 语言实现。下面从源码角度分析 FFmpeg 如何实现转码、压缩、提取、截取、拼接、合并和录屏等功能&#xff1a; 一、FFmpeg 核心架构与数据结构 FFmpeg 的源码结构围绕以下核心组件展开&#xff1a; lib…

网络安全小练习

一、docker搭建 1.安装 2.改变镜像源&#xff08;推荐国内镜像源&#xff1a;阿里云镜像源&#xff09; 登录阿里云容器镜像源服务&#xff08; 阿里云登录 - 欢迎登录阿里云&#xff0c;安全稳定的云计算服务平台 &#xff09; 复制系统分配的专属地址 配置 sudo mkdir …

数据结构——顺序表的相关操作

一、顺序表基础认知​1.顺序表的定义与特点​顺序表是数据结构中一种线性存储结构&#xff0c;它将数据元素按照逻辑顺序依次存储在一片连续的物理内存空间中。简单来说&#xff0c;就是用一段地址连续的存储单元依次存放线性表的元素&#xff0c;且元素之间的逻辑关系通过物理…

2025最新国产用例管理工具评测:Gitee Test、禅道、蓝凌测试、TestOps 哪家更懂研发协同?

在快节奏的 DevOps 时代&#xff0c;测试用例管理已不再是 QA 的独角戏&#xff0c;而是穿透需求—开发—测试—交付全流程的核心枢纽。想象一下&#xff0c;如果用例结构混乱&#xff0c;覆盖不全&#xff0c;甚至丢失版本变更历史&#xff0c;不仅协作乱&#xff0c;还影响交…

在线评测系统开发交流

https://space.bilibili.com/700332132?spm_id_from333.788.0.0 实验内容爬虫Web系统设计数据分析实验指导爬虫Web系统设计自然语言处理与信息检索数据可视化评分标准FAQ实验二&#xff1a;在线评测系统实验概述实验内容Step1&#xff1a;题目管理Step2&#xff1a;题目评测S…

Linux操作系统从入门到实战(十)Linux开发工具(下)make/Makefile的推导过程与扩展语法

Linux操作系统从入门到实战&#xff08;十&#xff09;Linux开发工具&#xff08;下&#xff09;make/Makefile的推导过程与扩展语法前言一、 make/Makefile的推导过程1. 先看一个完整的Makefile示例2. make的工作流程&#xff08;1&#xff09;寻找Makefile文件&#xff08;2&…