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

五种IO模型

1、通信的本质:

通过网络通信的学习,我们能够理解网络通信的本质是进程间通信,而进程间通信的本质就是IO。

IO也就是input和output。当读取条件不满足的时候,recv会阻塞。write写入数据时,会将数据拷贝到缓冲区中,当缓冲区满了之后,也会进行阻塞等待。

recv实际上干了几件事情呢?

  1. 拷贝

所以这也可以映射出IO的本质就是

IO = 等 + 拷贝

那什么是高效的IO呢?

单位时间内,等的比重越低,IO效率就越高!

什么是高效的IO代码?

减少IO的比重,或者把等待的时间利用起来。

针对高效的IO,我们来讲解一下五种IO模型!

2、五种IO模型

我们平时在钓鱼的时候,一般分为两步:

钓鱼 = 等 + 钓。这正好对应着我们的IO本质。

所以我们用钓鱼的例子来分别引入这五种IO模型。

有五名角色,他们采用钓鱼的方法分别对应的五种不同的IO模型。

  1. 张三:他将鱼竿放在水里之后,眼睛就一直盯着鱼竿,其他什么事都不干,只一动不动的盯着鱼竿,这就是阻塞IO
  2. 李四:他将鱼竿放在水里之后,一直在动,一会看看鱼有没有上钩,一会看看张三在干嘛(但张三一直不理他,一直阻塞式一心一意的盯着自己的鱼竿),一会又看看报纸,一会又刷刷抖音,非常的忙。这也就是非阻塞IO!
  3. 王五:他在鱼竿上绑了一个铃铛,当鱼上钩的时候,就会发出信号提醒王五有鱼上钩了,这就是信号驱动IO
  4. 赵六:他是当地的有钱人,他拉来了一卡车的鱼竿,有200根鱼竿同时进行钓鱼,这个赵六呢就来回在岸边检测轮询。这就是多路复用,多路转接式的IO
  5. 田七:他不想自己去钓鱼,他本质想吃鱼,所以他让别人(操作系统)去钓鱼,最后坐收渔翁之利。这就是异步IO

这里的湖水就是OS内部缓冲区,水桶是用户的缓冲区,鱼是数据,而鱼竿是一个sockfd,文件描述符。

阻塞 IO: 在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式.
阻塞 IO 是最常见的 IO 模型.

非阻塞 IO: 如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK 错误码.
非阻塞 IO 往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对 CPU 来说是较大的浪费, 一般只有特定场景下才使用.

信号驱动 IO: 内核将数据准备好的时候, 使用 SIGIO 信号通知应用程序进行 IO操作.

IO 多路转接: 虽然从流程图上看起来和阻塞 IO 类似. 实际上最核心在于 IO 多路转接能够同时等待多个文件描述符的就绪状态.

异步 IO: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据).

小结
任何 IO 过程中, 都包含两个步骤. 第一是等待, 第二是拷贝. 而且在实际的应用场景中, 等待消耗的时间往往都远远高于拷贝的时间. 让 IO 更高效, 最核心的办法就是让等待的时间尽量少.

什么是异步IO,什么是同步IO?

这个答案有争议,但是我们按照教材官方的解释就是只要参与IO,就是同步IO,参照上面的例子就是只要自己亲自参与钓鱼这个过程,就是同步IO,因此前面4种IO模型都是同步IO,只有最后田七没有自己参与IO,所以只有最后一种是异步IO

阻塞IO和非阻塞IO有什么区别呢?

区别就是等待的方式不一样。

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

  • 阻塞调用是指调用结果返回之前,当前线程会被挂起. 调用线程只有在得到结果之后才会返回.
  • 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程.

3.非阻塞IO

一个文件描述符, 默认都是阻塞 IO.

那我们如何实现非阻塞IO呢?

我们使用一个函数fcntl(),函数原型如下.

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );

传入的 cmd 的值不同, 后面追加的参数也不相同.

fcntl 函数有 5 种功能:

  1. 复制一个现有的描述符(cmd=F_DUPFD).
  2. 获得/设置文件描述符标记(cmd=F_GETFD 或 F_SETFD).
  3. 获得/设置文件状态标记(cmd=F_GETFL 或 F_SETFL).
  4. 获得/设置异步 I/O 所有权(cmd=F_GETOWN或 F_SETOWN).
  5. 获得/设置记录锁(cmd=F_GETLK,F_SETLK 或 F_SETLKW).

我们此处只是用第三种功能, 获取/设置文件状态标记, 就可以将一个文件描述符设置为非阻塞.

实现函数 SetNoBlock
基于 fcntl, 我们实现一个 SetNoBlock 函数, 将文件描述符设置为非阻塞.

#pragma once
#include <unistd.h>
#include <fcntl.h>
#include <iostream>//int fcntl(int fd, int cmd, ... /* arg */);void SetNonBlock(int fd)
{//首先获取原来标志位int fl = ::fcntl(fd , F_GETFL);if(fl < 0){std::cout<< "fcntl error " << std::endl;return ;} //设置非阻塞标志位int n = ::fcntl(fd , F_SETFL , fl | O_NONBLOCK);if(n < 0){perror("fcntl error \n");return ;}return ;
}
  • 使用 F_GETFL 将当前的文件描述符的属性取出来(这是一个位图).
  • 然后再使用 F_SETFL 将文件描述符设置回去. 设置回去的同时, 加上一个O_NONBLOCK 参数,就将该文件描述符设置为非阻塞

我们将标准输入设置为非阻塞的时候,运行程序:

如果是非阻塞,底层数据没有就绪,IO接口会以出错的形式返回。

那么如何区分是真的出错了还是底层不就绪的非阻塞IO返回呢?仅仅通过返回值是无法区分的了,但是我们可以参考read函数中的errno全局错误码,将错误详细的输出,所以我们根据errno具体情况进行区分!

同样的recv,send…IO系列接口都是会设置errno全局错误码。

可以看到errno被设置为了11,那11代表什么呢?11就是EWOULDBLOCK错误:

#define    EAGAIN        11    /* Try again */
...
#define    EWOULDBLOCK    EAGAIN    /* Operation would block */

这就表示底层数据不就绪,可以try again,如果真的出错了,errno就会被设置成其他格式。

所以我们根据这一特性来重新编写我们的代码

#include <iostream>
#include <unistd.h>
#include "Comm.hpp"int main()
{char buffer[1024];SetNonBlock(0); // 将0设置为非阻塞while (true){ssize_t s = ::read(0, buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0;std::cout << "Echo# " << buffer << std::endl;}else{// 问题:我怎么知道是底层IO条件不就绪,还是读取错误了呢???//  底层IO条件就绪和读取错误采用的是同样返回值操作的if (errno == EWOULDBLOCK || errno == EAGAIN){std::cout << "底层数据没有就绪, 下次在试试吧! 做做其他事情!" << std::endl;sleep(1);continue;}else if (errno == EINTR){continue;}else{std::cout << "读取错误... s : " << s << " errno: " << errno << std::endl;sleep(1);}}}
}

运行结果:

这里必须线删除fd,因为如果先关闭fd的话,再删除fd,这里的fd就不是合法的了

循环读取的时候,为什么会出现阻塞呢

大概率会让滑动窗口变大,并行就会发送更多的数据

http://www.xdnf.cn/news/172963.html

相关文章:

  • 【数据挖掘】时间序列预测-时间序列预测策略
  • Kubernetes/KubeSphere 安装踩坑记:从 context deadline exceeded 到成功部署的完整排障笔记
  • 同样开源的自动化工作流工具n8n和Dify对比
  • Docker compose 部署微服务项目(从0-1出发纯享版无废话)
  • 代数拓扑和黎曼几何有什么联系吗?
  • 【深度好文】4、Milvus 存储设计深度解析
  • 公网域名如何解析到内网ip服务器?自己域名映射外网访问
  • 3. 使用idea将一个git分支的部分提交记录合并到另一个git分支
  • Golang | 集合求交
  • 常用的性能提升手段--提纲
  • 二叉树的前序、中序和后序遍历:详解与实现
  • 非计算机专业如何利用AI开展跨学科和交叉研究
  • 智能硬件行业售后服务管理:提升客户体验的关键所在
  • Java:网络编程
  • CesiumEarth更新至1.14.0版本,重新设计了图层设置页面,优化了许多界面交互问题
  • K8S Pod 常见数据存储方案
  • Lua 第12部分 日期和时间
  • PH热榜 | 2025-04-27
  • HTML倒数
  • java 类的实例化过程,其中的相关顺序 包括有继承的子类等复杂情况,静态成员变量的初始化顺序,这其中jvm在干什么
  • xe-upload上传文件插件
  • WPF常用技巧汇总 - Part 2
  • Qt项目全局设置UTF-8编码方法(MSVS编译中文报错解决办法)
  • 新能源汽车运动控制器核心芯片选型与优化:MCU、DCDC与CANFD协同设计
  • 设计一个新能源汽车控制系统开发框架,并提供一个符合ISO 26262标准的模块化设计方案。
  • Java高频常用工具包汇总
  • [特殊字符]实战:使用 Canal + MQ + ES + Redis + XXL-Job 打造高性能地理抢单系统
  • Spark Mllib 机器学习
  • 第二章,网络类型及数据链路层协议
  • SMART:大模型在关键推理步骤辅导小模型,在保持高推理效率的同时,显著提升小模型的推理能力!!