09_五大IO模型

news/2025/9/25 15:42:20/文章来源:https://www.cnblogs.com/z1yang/p/19110931

要深入的理解各种IO模型,那么必须先了解下产生各种IO的原因是什么,要知道这其中的本质问题那么我们就必须要知道一条消息是如何从一个人发送到另外一个人的。
以两个应用程序通讯为例,我们来了解一下当“A”向"B" 发送一条消息,简单来说会经过如下流程:

  1. 应用A把消息发送到 TCP发送缓冲区;
  2. TCP发送缓冲区再把消息发送出去,经过网络传递后,消息会发送到B服务器的TCP接收缓冲区;
  3. B再从TCP接收缓冲区去读取属于自己的数据。

如图所示:

image

一、阻塞 IO和非阻塞IO

我们把视角切换到上面图中的第三步, 也就是应用B从TCP缓冲区中读取数据。

思考:

TCP缓冲区还没有接收到应用B该读取的消息时,此时应用B向TCP缓冲区发起读取申请,TCP接收缓冲区是应该马上告诉应用B 现在没有你的数据,还是说让应用B在这里等着,直到有数据再把数据交给应用B。

1.1 阻塞IO

什么是阻塞IO:

从上面的思考中,可以知道:阻塞IO就是当应用B发起读取数据申请时,在内核数据没有准备好之前,应用B会一直处于等待数据状态,直到内核把数据准备好了交给应用B才结束。

术语描述:

在应用调用recvfrom读取数据时,其系统调用直到数据包到达且被复制到应用缓冲区中或者发送错误时才返回,在此期间一直会等待,进程从调用到返回这段时间内都是被阻塞的称为阻塞IO。

流程:

  1. 应用进程向内核发起recfrom读取数据;
  2. 准备数据报(应用进程阻塞);
  3. 将数据从内核复制到应用空间;
  4. 复制完成后,返回成功提示。

image

1.2 非阻塞IO

什么是非阻塞IO:

非阻塞IO就是当应用B发起读取数据申请时,如果内核数据没有准备好会即刻告诉应用B,不会让B在这里等待。

术语描述:

非阻塞IO是在应用调用recvfrom读取数据时,如果该缓冲区没有数据的话,就会直接返回一个EWOULDBLOCK错误,不会让应用一直等待中。在没有数据的时候会即刻返回错误标识,那也意味着如果应用要读取数据就需要不断的调用recvfrom请求,直到读取到它数据要的数据为止。

流程:

  1. 应用进程向内核发起recvfrom读取数据;
  2. 没有数据报准备好,即刻返回EWOULDBLOCK错误码;
  3. 应用进程向内核发起recvfrom读取数据;
  4. 已有数据包准备好就进行一下 步骤,否则还是返回错误码;
  5. 将数据从内核拷贝到用户空间;
  6. 完成后,返回成功提示。

image

二、IO多路复用

继续把视角放到应用B从TCP缓冲区中读取数据这个环节。如果在并发的环境下,可能会N个人向应用B发送消息,这种情况下我们的应用就必须创建多个线程去读取数据,每个线程都会自己调用recvfrom 去读取数据。

image

上面服务端通过多线程的方式处理客户端请求实现了主线程的非阻塞,使用不同线程处理不同的连接请求,但是我们并没有那么多的线程资源,并且等待读就绪的过程是耗时最多的,那么有没有什么办法可以将连接保存起来,等读已就绪时我们再进行处理。

基于非阻塞式 IO ,可以这么写:

arr = new Arr[];
listenfd = socket();   // 打开一个网络通信套接字
bind(listenfd);        // 绑定
listen(listenfd);      // 监听
while(1) {connfd = accept(listenfd);  // 阻塞 等待建立连接arr.add(connfd);
}// 异步线程检测 连接是否可读
new Tread(){for(connfd : arr){// 还有一个弊端:可读 connfd 只能串行处理// 获取直接开多线程处理连接 但线程资源有限int n = read(connfd, buf);  // 检测 connfd 是否可读if(n != -1){newThreadDeal(buf);   // 创建新线程处理close(connfd);        // 关闭连接 arr.remove(connfd);   // 移除已处理的连接}}
}newTheadDeal(buf){doSomeThing(buf);  // 处理数据
}

上面的实现看着很不错,但是却存在一个很大的问题,我们需要不断的调用 read() 进行系统调用,这里的系统调用我们可以理解为分布式系统的 RPC 调用,性能损耗十分严重,因为这依然是在用户层的操作。

2

这时我们自然而然就会想到把上述循环检测连接(文件描述符)可读的过程交给操作系统去做,从而避免频繁的进行系统调用。当然操作系统给我们提供了这样的函数:select、poll、epoll。

2.1 select

select 是操作系统提供的系统函数,通过它我们可以将文件描述符发送给系统,让系统内核帮我们遍历检测是否可读,并告诉我们进行读取数据。

3

arr = new Arr[];
listenfd = socket();   // 打开一个网络通信套接字
bind(listenfd);        // 绑定
listen(listenfd);      // 监听
while(1) {connfd = accept(listenfd);  // 阻塞 等待建立连接arr.add(connfd);
}// 异步线程检测 通过 select 判断是否有连接可读
new Tread(){while(select(arr) > 0){for(connfd : arr){if(connfd can read){// 如果套接字可读 创建新线程处理newTheadDeal(connfd);arr.remove(connfd);   // 移除已处理的连接}}}
}newTheadDeal(connfd){int n = read(connfd, buf);  // 阻塞读取数据doSomeThing(buf);  // 处理数据close(connfd);        // 关闭连接 
}

从上面我们可以看出 select 运行的整个流程:

image

术语描述:

进程通过将一个或多个fd传递给select,阻塞在select操作上,select帮我们侦测多个fd是否准备就绪,当有fd准备就绪时,select返回数据可读状态,应用程序再调用recvfrom读取数据。

虽然减少了大量系统调用但也存在一些问题:

  • 每次调用需要在用户态和内核态之间拷贝文件描述符数组,在高并发场景下这个拷贝的消耗是很大的。
  • 内核检测文件描述符可读还是通过遍历实现,当文件描述符数组很长时,遍历操作耗时也很长。
  • 内核检测完文件描述符数组后,当存在可读的文件描述符数组时,用户态需要再遍历检测一遍。

2.2 poll

poll 和 select 原理基本一致,最大的区别是去掉了最大 1024 个文件描述符的限制

select 使用固定长度的 BitsMap,表示文件描述符集合,而且所支持的文件描述符的个数是有限制的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制, 默认最大值为 1024,只能监听 0~1023 的文件描述符。

poll 不再用 BitsMap 来存储所关注的文件描述符,取而代之用动态数组,以链表形式来组织,突破了 select 的文件描述符个数限制,当然还会受到系统文件描述符限制。

2.3 epoll

epoll 主要优化了上面三个问题实现。

方案1:内核中保存一份文件描述符,无需用户每次传入,而是仅同步修改部分。

方案2:通过事件唤醒机制唤醒替代遍历。

方案3:仅将可读部分文件描述符同步给用户态,不需要用户态再次遍历。

epoll 基于高效的红黑树结构,提供了三个核心操作,主要流程如下所示:

image

边缘触发和水平触发:

  • select/poll 只有水平触发模式;
  • epoll 支持两种事件触发模式,分别是边缘触发(ET)和水平触发(LT),epoll 默认的触发模式是水平触发。

边缘触发:使用边缘触发模式时,当被监控的 Socket 描述符上有可读事件发生时,服务器端只会从 epoll_wait 中苏醒一次,即使进程没有调用 read 函数从内核读取数据,也依然只苏醒一次,因此我们程序要保证一次性将内核缓冲区的数据读取完。

水平触发:使用水平触发模式时,当被监控的 Socket 上有可读事件发生时,服务器端不断地从 epoll_wait 中苏醒,直到内核缓冲区数据被 read 函数读完才结束,目的是告诉我们有数据需要读取。

三、信号驱动IO模型

IO多路复用解决了一个线程可以监控多个fd的问题,但是select是采用轮询的方式来监控多个fd的,通过不断的轮询fd的可读状态来知道是否有可读的数据;

有人就想,能不能我发出请求后等你数据准备好了就通知我,所以就衍生了信号驱动IO模型。

于是信号驱动IO不是用循环请求询问的方式去监控数据就绪状态,而是在调用sigaction时候建立一个SIGIO的信号联系,当内核数据准备好之后再通过SIGIO信号通知线程数据准备好后的可读状态;

当线程收到可读状态的信号后,此时再向内核发起recvfrom读取数据的请求,因为信号驱动IO的模型下应用线程在发出信号监控后即可返回,不会阻塞,所以这样的方式下,一个应用线程也可以同时监控多个fd。

术语描述:

首先开启套接口信号驱动IO功能,并通过系统调用sigaction执行一个信号处理函数,此时请求即刻返回,当数据准备就绪时,就生成对应进程的SIGIO信号,通过信号回调通知应用线程调用recvfrom来读取数据。

四、异步IO

通过观察发现,不管是IO复用还是信号驱动,要读取一个数据总是要发起两阶段的请求,第一次发送select请求,询问数据状态是否准备好,第二次发送recevform请求读取数据。

思考:为什么明明是想读取数据,而却非得要先发起一个select询问数据状态的请求,然后再发起真正的读取数据请求,能不能有一种一劳永逸的方式,只要发送一个请求告诉内核要读取数据,然后内核去帮我去完成剩下的所有事情?

因此设计了一种方案,应用只需要向内核发送一个read 请求,告诉内核它要读取数据后即刻返回;内核收到请求后会建立一个信号联系,当数据准备就绪,内核会主动把数据从内核复制到用户空间,等所有操作都完成之后,内核会发起一个通知告诉应用,这种一劳永逸的模式为异步IO模型。

image

术语描述:

应用告知内核启动某个操作,并让内核在整个操作完成之后,通知应用,这种模型与信号驱动模型的主要区别在于,信号驱动IO只是由内核通知我们合适可以开始下一个IO操作,而异步IO模型是由内核通知我们操作什么时候完成。

IO模型里面的同步异步:

在IO模型里面如果请求方从发起请求到数据最后完成的这一段过程中都需要自己参与,那么这种我们就称为同步请求;反之,如果应用发送完指令后就不再参与过程了,只需要等待最终完成结果的通知,那么这就属于异步。

为什么只有异步非阻塞而没有异步阻塞呢,因为异步模型下请求指定发送完后就即刻返回了,没有任何后续流程了,所以它注定不会阻塞,所以也就只会有异步非阻塞模型了。

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

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

相关文章

wsl Ubuntu 使用cmake

安装cmake及其工具链CMakeLists.txt编写规范 略 构建与编译流程 生成makefile点击查看代码 mkdir build && cd build cmake ..此步骤会根据CMakeList.txt生成平台相关的构建文件 编译项目 执行make命令生成目标…

黄龙云 加强网站建设网络营销方案如何写

元类在测试框架中的运用 书接上回 我们知道了元类的基本用法,也写了一个小demo,接下来我们就尝试运用进我们测试框架。 #一款无需编码且易用于二次开发的接口测试框架。 #我写的我写的我写的我写的 pip install mwj-apitest #这里面就用到了元类&…

河南小学网站建设养生网站源码下载

不得不说RTL SDR真是神器,直接把SDR的入门门槛拉低到了几十块钱。对于RTL SDR的学习开发,有大佬写的《Software_Defined_Radio_using_MATLAB_Simulink_and_the_RTL-SDR》,另外,除了MATLAB,近些年爆火的PYTHON当然也是可…

AI元人文思想体系:从哲学基础到价值原语博弈的微观机制

AI元人文思想体系:从哲学基础到价值原语博弈的微观机制 图片 AI元人文思想体系:从哲学基础到价值原语博弈的微观机制 引言:时代的岔路口——从工具对齐到主体共生 我们正站在一个文明史的奇点上。通用人工智能(AGI…

做题笔记16

9.24 P8331 [ZJOI2022] 简单题 幽默题 这张图肯定是若干个杏仁拼在一起,证明?随便拿一个杏仁出来,如果我们加边,要么会有一个 \(K_4\) 同胚,要么会有至少一组平行的环,要么仍然是一个杏仁,前面两种情况容易分讨…

条件判断语句

条件判断语句编程的时候经常需要检查一系列的条件,根据判断的条件决定采取什么措施。接下来学习一下IF-ELSE 语句。 1. 条件测试 判断一个表达式是为True或False的行为就是条件测试。 # -*- coding: utf-8 -*- print(…

嵌入式铁头山羊STM32-各章节详细笔记-查阅传送门 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

网站建设皖icp做网站的价格

​ 2023年9月20~22日,深圳唯创知音电子将在 深圳宝安国际会展中心(9号馆9B1)为您全面展示最新的芯片产品及应用方案,助力传感器行业的发展。 作为全球领先的芯片供应商之一,深圳唯创知音电子一直致力于为提供高质量、…

网站开发与设计期末考试网站优化宝

SD-WAN作为一种先进的网络技术,为企业提供了更加灵活和高效的网络连接方案。然而,在异地组网的过程中,SD-WAN也面临一些挑战。本文将探讨SD-WAN异地组网所面临的难题,并提供相应的解决方案。 挑战一:网络延迟和不稳定性…

网站流量是怎么赚钱的挣钱最快的小游戏

椭圆曲线密码学 (ECC) 是一种基于椭圆曲线数学的公开密钥加密算法。 它提供了一种执行密钥交换、数字签名和加密等加密操作的安全方式。 ECC 为 1977 年首次发布的 Rivest-Shamir-Adleman (RSA) 加密算法提供了一种替代性方案。 继续阅读,进一步了解椭圆曲线密码学…

深入解析:实验室:将 XSS 反映到 HTML 上下文中,大多数标记和属性都被阻止

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

深入解析:【Qt】信号和槽

深入解析:【Qt】信号和槽2025-09-25 15:31 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; f…

做题笔记6

小王精心做题笔记,堂堂连载! 5.21 昨天讲的网络流 连边时都是形如 \(u\rightarrow v,(cap,cost)\) 的格式 CF2046D For the Emperor! 首先缩点,一下对缩点后的 DAG 考虑,直接费用流建模 考虑记一个很大的数 \(B\),…

第17章 Day20-Day21 逆向爬虫之瑞数6

逆向爬虫之补环境专题 一、补环境的原理 浏览器环境和node环境对比:浏览器下:node.js下当我们辛苦将浏览器环境的加密或者解密入口找到,把加密或者解密的JS的代码拷贝到本地,由node解释器驱动执行的时候,会因为拷…

建一个网站大概需要多少钱同城购物网站建设成本

【🐋和鲸冬令营】通过数据打造爆款社交APP用户行为分析报告 文章目录 【🐋和鲸冬令营】通过数据打造爆款社交APP用户行为分析报告1 业务背景2 数据说明3 数据探索性分析4 用户行为分析4.1 用户属性与行为关系分析4.2 转化行为在不同用户属性群体中的分布…

做网站 域名如何要回android编程

1.同步解释 1.1 同步基础概念 触发器:触发器是控制采集的命令。您可以使用触发器来启动、停止或暂停采集。触发信号可以源自软件或硬件源。 时钟:时钟是用于对数据采集计时的周期性数字信号。根据具体情况,您可以使用时钟信号直接控制数据采…

基于多假设跟踪(MHT)算法的MATLAB实现

一、核心代码 %% MHT多假设跟踪主函数 function mht_demo()% 参数设置num_targets = 3; % 真实目标数量num_scans = 50; % 总扫描次数detection_prob = 0.9; % 检测概率clutter_rate = 0.1; % 杂波密度(…

ROS2之消息接口

ROS2 的三大消息接口 1. 消息(Message, msg)定义文件后缀:.msg作用:用于 话题 (Topic) 通信,节点之间以“流”的方式交换数据。特点:一对多(一个话题可以有多个订阅者/发布者)单向通信(发布 → 订阅)异步(发…