【Linux】从零开始认识五种IO模型 --- 理解五种IO模型,开始使用非阻塞IO

五种IO模型与阻塞IO
- 1 前言
- 2 五种IO模型
- 3 非阻塞IO
1 前言
通过网络通信的学习,我们能够理解网络通信的本质是进程间通信,而进程间通信的本质就是IO。
IO就是input与output,站在进程角度,IO就是将数据从进程间输入输出数据;站在软件层面,IO就是与操作系统进行交互!以IO中常用的接口:read为例,当底层没有数据时,就会阻塞 。这种阻塞的本质是等待事件就绪。write写入数据时,会将数据拷贝到缓冲区中,当缓冲区满了之后,也会进行阻塞等待。
所以我们平时使用的IO都可以总结为等 + 拷贝!所以什么叫做高效的IO呢?IO中可以理解为等 + 拷贝,拷贝的效率是很快的,那么高效的IO就是"等"的效率高!如果调用read,write不需要等待,那么效率自然而然的就高了!
针对这个高效的IO,我们介绍一下五种IO模型
2 五种IO模型
五种IO模型是程序员们经过长时间的使用总结出来的常用情况。接下来我们使用钓鱼的例子来介绍这五种IO模型
- 张三今天去钓鱼,他带着一根鱼竿,鱼漂,小马扎。走到河边扎好座椅,甩杆钓鱼。张三目不转睛的盯着鱼漂,一有动静就收杆,其他事情一概不考虑,只盯着鱼漂看!
- 李四今天也去钓鱼,他也带着一根鱼竿,鱼漂,小马扎。他也扎好座椅,甩杆钓鱼。李四布置好之后,不时刷手机,聊天,看书。间断性的看一看检测鱼漂有没有动,鱼漂动了就需要收杆。
- 王五今天也来钓鱼,他是个聪明人,他布置好杆子后拿出来一个神器:铃铛,放在鱼竿上。之后就开始耍手机,看书,聊天,一有鱼上钩,铃铛就会响,就可以进行收杆!
- 赵六也来钓鱼了,他是附近一带的首富,带着一车鱼竿来钓鱼了,放好100根鱼竿后,赵六就开始巡逻式的检查鱼竿情况。有鱼的鱼竿就进行收杆。
- 田七是整个城镇的首富,他带着管家来钓鱼。田七知道自己想要的是河流里鱼,而不是钓鱼。所以他就安排司机钓鱼,他回家等待鱼,小王怎么钓鱼,田七并不关心。
上面五个人就是经典的五种IO模型,每个人都代表一种系统调用,鱼竿就是文件描述符,鱼就是数据,河是操作系统内部,鱼漂浮动就代表数据就绪,收杆就代表进行拷贝:
- 张三的方式称之为阻塞IO
- 李四的方式称之为非阻塞IO
- 王五的方式称之为信号驱动IO
- 赵六的方式称之为多路复用/多路转接IO
- 田七的方式叫做异步IO
阻塞IO和非阻塞IO的区别就是等待的方式不同,拷贝数据时一模一样的!上面五种钓鱼效率最高的是赵六:多路复用IO,毕竟人家鱼竿多,成功等到数据的概率就高!
而非阻塞IO的高效是与阻塞IO进行对比的,张三李四一天钓的鱼最终可能差不多,但李四看完一本书,追了4集电视剧…非阻塞IO的高效体现在可以在等待IO的同时处理其他事情!
异步IO就是两件事情互不影响,等待数据与获取数据就是异步进行!同步IO与异步IO的区别就是是否自身参与IO。
阻塞 IO 是最常见的 IO 模型:
-
阻塞 IO:在内核将数据准备好之前,系统调用会一直等待。所有的套接字,默认都是阻塞方式.

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

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

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

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

任何 IO 过程中, 都包含两个步骤: 第一是等待,第二是拷贝。 而且在实际的应用场景中, 等待消耗的时间往往都远远高于拷贝的时间。让 IO 更高效, 最核心的办法就是让等待的时间尽量少。
这里强调两组概念:
同步通信 vs 异步通信(synchronous communication / asynchronouscommunication)同步和异步关注的是消息通信机制。
- 所谓同步, 就是在发出一个调用时, 在没有得到结果之前, 该调用就不返回.但是一旦调用返回, 就得到返回值了;换句话说, 就是由调用者主动等待这个调用的结果;
- 异步则是相反, 调用在发出之后, 这个调用就直接返回了, 所以没有返回结果;换句话说, 当一个异步过程调用发出后, 调用者不会立刻得到结果;而是在调用发出后, 被调用者通过状态,通知来通知调用者, 或通过回调函数处理这个调用。另外,我们回忆在讲多进程多线程的时候,也提到同步和互斥。这里的同步通信和进程之间的同步是完全不相干的概念。
- 进程/线程同步也是进程/线程之间直接的制约关系, 是为完成某种任务而建立的两个或多个线程, 这个线程需要在某些位置上协调他们的工作次序而等待、 传递信息所产生的制约关系. 尤其是在访问临界资源的时候。
以后在看到 “同步” 这个词, 一定要先搞清楚大背景是什么。 这个同步, 是同步通信异步通信的同步, 还是线程同步与互斥的同步.
阻塞 vs 非阻塞
- 阻塞和非阻塞关注的是程序在等待调用结果(消息, 返回值) 时的状态。
- 阻塞调用是指调用结果返回之前, 当前线程会被挂起。调用线程只有在得到结果之后才会返回。
- 非阻塞调用指在不能立刻得到结果之前, 该调用不会阻塞当前线程。
3 非阻塞IO
实现非阻塞IO的方式有很多种:
当使用open打开一个文件时,可以传入一个标志位O_NONBLOCK or O_NDELAY
int open(const char *pathname, int flags);int open(const char *pathname, int flags, mode_t mode);
这样读取时就是非阻塞的进行读取。
同样的recv和send系列接口也有对应的非阻塞标志位MSG_DONTWAIT :
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
但是这些方式都不太通用,我们可以通过fcntl接口将文件描述符设置为非阻塞的文件描述符:
Linux Programmer's Manual
FCNTL(2)NAMEfcntl - manipulate file descriptorSYNOPSIS#include <unistd.h>#include <fcntl.h>int fcntl(int fd, int cmd, ... /* arg */ );
fcntl 函数有 5 种功能:
- 复制一个现有的描述符(cmd=F_DUPFD) .
- 获得/设置文件描述符标记(cmd=F_GETFD 或 F_SETFD).
- 获得/设置文件状态标记位(cmd=F_GETFL 或 F_SETFL).
- 获得/设置异步 I/O 所有权(cmd=F_GETOWN 或 F_SETOWN).
- 获得/设置记录锁(cmd=F_GETLK,F_SETLK 或 F_SETLKW).
通过这个系统调用,可以写一个demo来看看效果。
这是j经典的阻塞IO:
#include <iostream>
#include <unistd.h>
#include<cstdio>
// 阻塞IO
int main()
{char buffer[1024];// 读取标准输入中的数据while (true){printf("Enter#");fflush(stdout);int n = read(0, buffer, sizeof(buffer) - 1);if (n == 0){// 说明没有读取到continue;}else if(n > 0){//读取到了数据buffer[n] = 0;std::cout << buffer ;}else{//读取出现错误了perror("error!\n");}}return 0;
}
读取效果是这样的:

如果没有数据输入就会阻塞等待数据输入。
那么如何更改为非阻塞IO呢?我们设置一个接口函数
#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 ;
}
通过这个接口可以快速将文件描述符设置为非阻塞。我们将标准输入设置为非阻塞我们再来运行一下:

- 如果是非阻塞 , 底层数据没有就绪,IO 接口会以出错形式返回。
那么如何区分是真的出错了还是底层不就绪的非阻塞IO返回呢?仅仅通过返回值是无法区分的了,但是read接口中的返回值是这么描述的:
RETURN VALUEOn success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number. It is not an error if this number issmaller than the number of bytes requested; this may happen for example because fewer bytes are actually available right now (maybe because we were close to end-of-file, or because we are reading from a pipe, or from a terminal), or because read() was interrupted by a signal. See also NOTES.On error, -1 is returned, and errno is set appropriately. In this case, it is left unspecified whether the file position (if any) changes.
出错时,read接口会设置全局的errno!同样的recv,send…IO系列接口都是会设置errno:

可以看到errno被设置为了11,那11代表什么呢?11就是EWOULDBLOCK错误:
#define EAGAIN 11 /* Try again */
...
#define EWOULDBLOCK EAGAIN /* Operation would block */
这就表示底层数据不就绪,可以try again,如果真的出错了,就会被设置成其他格式。
// 非阻塞IO
int main()
{char buffer[1024];SetNonBlock(0);// 读取标准输入中的数据while (true){printf("Enter#");fflush(stdout);ssize_t n = read(0, buffer, sizeof(buffer) - 1);if (n == 0){printf("read done\n");break;}else if (n > 0){// 读取到了数据buffer[n] = 0;printf("echo# %s" , buffer);}else{// 读取出现错误了if(errno == EWOULDBLOCK){std::cout << "底层数据没有就绪,开始轮询检测" <<std::endl;//do other thing//可以做其他事情!continue;}else{//真的出错了!perror("read error!\n");break;}}//sleep(1);}return 0;
}
这样就可以进行正常的非阻塞轮询了!

注意:操作系统的两个缓冲区:输入与输出,在我们键盘进行输入时会到操作系统的输入缓冲区,然后再到进程的输入缓冲区。而键盘输入时,操作系统会判断是否需要回显,回显就会将输入缓冲区的数据拷贝到输出缓冲区一份,这里就是可以回显的原因。
当我们进行IO拷贝时,如果突然接收到一个信号,导致IO拷贝中断了,那么这个读取的返回可能并没有读取完毕!这种情况的错误码是EINTR,我们可以进行判断!
相关文章:
【Linux】从零开始认识五种IO模型 --- 理解五种IO模型,开始使用非阻塞IO
恐惧让你沦为囚犯, 希望让你重获自由。 --- 《肖申克的救赎》--- 五种IO模型与阻塞IO 1 前言2 五种IO模型3 非阻塞IO 1 前言 通过网络通信的学习,我们能够理解网络通信的本质是进程间通信,而进程间通信的本质就是IO。 IO就是input与outp…...
Spring Boot 集成阿里云直播点播
在当今数字化时代,视频直播和点播服务已经成为许多应用的核心功能。阿里云提供了强大的直播和点播服务,能够满足各种规模的应用需求。而 Spring Boot 作为一种流行的 Java 开发框架,能够快速构建高效的应用程序。本文将详细介绍如何在 Spring…...
舍伍德业务安全架构(Sherwood Applied Business Security Architecture, SABSA)
舍伍德业务安全架构(Sherwood Applied Business Security Architecture, SABSA)是一个企业级的安全架构框架,它提供了一个全面的方法来设计和实现信息安全策略。SABSA模型将业务需求与安全控制相结合,确保企业的信息安全措施能够支…...
论可以对抗ai编程的软件开发平台(直接把软件需求描述变成软件的抗ai开发平台)的设计
论可以对抗ai编程的软件开发平台(直接把软件需求描述变成软件的抗ai开发平台)的设计 大家知道,传统的数学密码,都可以被量子计算机破解,但是这些年发展出很多数学密码,量子计算机也破解不了,叫…...
饿了么数据库表设计
有商家表、商品表、商品规格表、购物车表,不难分析出表是不够全面的。 (1)首先分析需要补充的表 1.对于购物车而言肯定有对应的用户,因此要添加一个用户表。 2.商品规格是冷,热,半分糖、全糖,对于冷热和半分糖是可以分…...
Flink处理乱序的数据的最佳实践
目录 网络延迟和分布式系统 事件时间与处理时间的差异 事件时间和水位线(Watermark) 时间窗口(TimeWindow) 滚动窗口(Tumbling Window) 滑动窗口(Sliding Window) 会话窗口(Session Window) 自定义Watermark生成策略 设置允许延迟和侧输出 设置允许的最大延迟时间 使…...
Android OpenGL ES详解——模板Stencil
目录 一、概念 1、模板测试 2、模板缓冲 二、模板测试如何使用 1、开启和关闭模板测试 2、开启/禁止模板缓冲区写入 3、模板测试策略函数 4、更新模板缓冲 5、模板测试应用——物体轮廓 三、模板缓冲如何使用 1、创建模板缓冲 2、使用模板缓冲 3、模板缓冲应用——…...
vscode在cmake config中不知道怎么选一个工具包?select a kit
vscode在cmake config中不知道怎么选一个工具包,或者发现一直在用VS的工具包想换成自己的工具包。select a kit vscode在cmake config中不知道怎么选一个工具包,或者发现一直在用VS的工具包想换成自己的工具包。select a kit 1.在VSCode中 按ctrlshift…...
无人机之无线电监测设备技术篇
一、技术原理 无人机的无线电监测设备主要通过捕捉和分析无人机发出的无线电信号来实现对无人机的监测和定位。这些信号包括无人机的上行遥控信号、下行数据图传信号等。设备采用多种技术手段,如频谱分析、信号解调、定位算法等,对接收到的信号进行处理和…...
【系统架构设计师】预测试卷一:案例分析
更多内容请见: 备考系统架构设计师-专栏介绍和目录 文章目录 试题一(共25分)【问题 1】(12分)【问题 2】(13分)试题二(共 25分)【问题 1】(12分)【问题 2】(7分)【问题 3】(6分)试题三(共25分)【问题 1】(9分)【问题 2】(16分)试题四(共25分)【问题 1】…...
一篇文章教会你I2C通信(软件I2C和硬件I2C)以读取MPU6050为例,附STM32代码示例
目录 一、I2C通信介绍: (1)基本概念: (2)特点: (3)工作原理: 二、I2C通信原理: (1)I2C 物理层: &…...
Python实现SPFA算法
目录 Python实现SPFA算法引言一、SPFA算法的理论基础1.1 最短路径问题1.2 SPFA算法的基本原理1.3 SPFA算法的复杂度 二、SPFA算法的Python实现2.1 基本实现2.2 案例一:使用SPFA算法进行城市交通最短路径计算2.2.1 实现代码 2.3 案例二:负权重边的处理2.3…...
MYSQL安装(ubuntu系统)
rpm -qa 查询安装软件包 ps axj 查询服务 卸载mysql(万不得已) ps axj | grep mysql 查看是否存在mysql服务 systemctl stop mysqld 关闭该服务 rpm -qa | grep mysql 查安装mysql安装包 rmp -qa | grep mysql | xargs (yum apt) -y remove进行批量…...
Cpp二叉搜索树的讲解与实现(21)
文章目录 前言一、二叉搜索树的概念定义特点 二、二叉树的实现基本框架查找插入删除当只有0 ~ 1个孩子的时候当有2个孩子的时候 三、二叉树的应用K模型KV模型 四、二叉树的性能分析总结 前言 这是全新的一个篇章呢,二叉搜索树是我们接下来学习set、map的前提 迈过它…...
微服务设计模式 — 补偿事务模式(Compensating Transaction Pattern)
微服务设计模式 — 补偿事务模式(Compensating Transaction Pattern) 定义 在云计算和分布式系统中,管理跨多个微服务或组件的事务一致性是一项极具挑战性的任务,补偿事务模式Compensating Transaction Pattern)是一种…...
20 实战:形状编码、运动补偿和纹理编码的实现(基于python)
在当今多媒体时代,视频处理与编码已经成为各个领域中不可或缺的一部分。无论是视频编辑、流媒体传输,还是计算机视觉应用,视频编码技术都扮演着关键角色。本文将详细解析一个基于Python的图形用户界面(GUI)视频编码器。通过对代码的逐行讲解、功能分析以及参数调节方法的探…...
区块链-C++挖矿软件XMRIG源码分析
C++挖矿软件源码分析 3rdpartybackendgrgon2Obfusheader.hmain 程序 xmrig.cppxmrig命名空间process类Entry::IdApp类CoreControllerbasetoolkernelinterfacesDonateStrategy.cppdonate.h/2/dmiCmake 跨平台的自动化构建系统CMakeLists.txt.cmake 13个引入算力哈希率 HashrateE…...
C语言指针的介绍
零.导言 在日常生活中,我们常常在外出时居住酒店,细心的你一定能发现酒店不同的房间上有着不同的门牌号,上面写着像308,512之类的数字。当你定了酒店之后,你就会拿到一个写有门牌号的钥匙,凭着钥匙就能进入…...
八大排序算法——堆排序
目录 前言 一、向上调整算法建堆 二、向下调整算法建堆 三、堆排序 前言 堆排序是基于堆结构的一种排序思想,因此要为一个乱序的数组进行排序的前提是数组必须要是一个堆,所以要先对数组进行建堆操作 一、向上调整算法建堆 时间复杂度:O…...
U盘文件不翼而飞?这些数据恢复工具帮你找回!
U盘因其便携性是我们日常工作和生活中不可或缺的工具。不过有时候它也会出点小状况。如果你U盘里的数据突然不见了,不要着急,可以先试试这几款数据恢复工具! 福昕数据恢复 直达链接:www.pdf365.cn/foxit-restore/ 操作教程&…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...
Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...
