高级IO涉及的编程模型

news2025/1/11 7:40:28

目录

  • 五种IO模型
    • 引入
    • IO模型
      • 阻塞IO
      • 非阻塞IO
      • 信号驱动IO
      • O多路转接
      • 异步IO
    • IO重要概念
      • 同步通信 vs 异步通信
      • 阻塞 vs 非阻塞
    • 其他高级IO
  • 非阻塞IO
    • fcntl
    • 基于fcntl将文件描述符设置为非阻塞
    • 轮询方式读取标准输入
  • I/O多路转接之select
    • 初识select
    • select函数
      • 原型
      • 参数timeout取值
      • fd_set结构及输入输出型参数
      • select编写需要第三方数组
      • socket就绪条件
    • select优缺点
    • 代码
  • I/O多路转接之poll
    • poll函数接口
    • 参数说明
    • 参数fds及poll优缺点
    • 代码
  • I/O多路转接之epoll
    • epoll的相关系统调用
      • epoll_create
      • epoll_ctl
      • epoll_wait
  • epoll工作原理
    • 原理
    • 细节理解
    • epoll的优点(和 select 的缺点对应)
    • 注意
  • epoll工作方式
  • epoll的使用场景
  • epoll中的惊群问题
  • 代码

五种IO模型

引入

在这里插入图片描述

IO模型

阻塞IO

阻塞IO: 在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式
在这里插入图片描述

非阻塞IO

非阻塞IO: 如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码.

非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对CPU来说是较大的浪费, 一般只有特定场景下才使用.
在这里插入图片描述

信号驱动IO

信号驱动IO: 内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作
在这里插入图片描述

O多路转接

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

异步IO

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

IO重要概念

同步通信 vs 异步通信

同步和异步关注的是消息通信机制.

  • 所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回. 但是一旦调用返回,就得到返回值了; 换句话说,就是由调用者主动等待这个调用的结果;
  • 异步则是相反, 调用在发出之后,这个调用就直接返回了,所以没有返回结果; 换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果; 而是在调用发出后, 被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用.

多进程多线程, 也提到同步和互斥. 这里的同步通信和进程之间的同步是完全不想干的概念.

  • 进程/线程同步也是进程/线程之间直接的制约关系
  • 是为完成某种任务而建立的两个或多个线程,这个线程需要在某些位置上协调他们的工作次序而等待、传递信息所产生的制约关系. 尤其是在访问临界资源的时候.

阻塞 vs 非阻塞

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

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

其他高级IO

非阻塞IO,纪录锁,系统V流机制, I/O多路转接(也叫I/O多路复用) ,readv和writev函数以及存储映射IO(mmap),这些统称为高级IO

非阻塞IO

fcntl

一个文件描述符, 默认都是阻塞IO.
在这里插入图片描述

基于fcntl将文件描述符设置为非阻塞

bool SetNonBlock(int fd)
{
    int fl = fcntl(fd, F_GETFL); // 在底层获取当前fd对应的文件读写标志位
    if (fl < 0)
        return false;
    fcntl(fd, F_SETFL, fl | O_NONBLOCK); // 设置非阻塞
    return true;
}
  • 使用F_GETFL将当前的文件描述符的属性取出来(这是一个位图).
  • 然后再使用F_SETFL将文件描述符设置回去. 设置回去的同时, 加上一个O_NONBLOCK参数.

轮询方式读取标准输入

bool SetNonBlock(int fd)
{
    int fl = fcntl(fd, F_GETFL); // 在底层获取当前fd对应的文件读写标志位
    if (fl < 0)
        return false;
    fcntl(fd, F_SETFL, fl | O_NONBLOCK); // 设置非阻塞
    return true;
}

int main()
{
    // 将0号文件描述符设置为非阻塞
    SetNonBlock(0); //只要设置一次,后续就都是非阻塞了

    char buffer[1024];
    while (true)
    {
        sleep(1);
        errno = 0;
        // 非阻塞的时候,我们是以出错的形式返回,告知上层数据没有就绪:通过errno来辨别出错类型
        // 0号设置为非阻塞的时候,read不再阻塞等待键盘输入内容,而是会直接返回
        ssize_t s = read(0, buffer, sizeof(buffer) - 1); 

        // s > 0 代表读取到数据了,否则代表出错了,出错包含两种,一种代表正常continue再次循环,一种代表异常
        // 出错,不仅仅是错误返回值,errno变量也会被设置,表明出错原因,可以通过errno来确定出错类型
        if (s > 0)
        {
            buffer[s-1] = 0;
            std::cout << "echo# " << buffer << " errno[---]: " << errno << " errstring: " << strerror(errno) << std::endl;
        }
        else
        {
            // 如果失败的errno值是11,代表其实没错,只不过是底层数据没就绪,非阻塞直接返回而已
            // EWOULDBLOCK 和  EAGAIN 就是 11
            if(errno == EWOULDBLOCK || errno == EAGAIN)
            {
                std::cout << "当前0号fd数据没有就绪, 请下一次再来试试吧" << std::endl;
                continue;
            }
            // EINTR 也是代表正常
            else if(errno == EINTR)
            {
                std::cout << "当前IO可能被信号中断,在试一试吧" << std::endl;
                continue;
            }
            else
            {
                //进行差错处理
            }
        }
    }
    return 0;
}

I/O多路转接之select

初识select

系统提供select函数来实现多路复用输入/输出模型.

  • select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;
  • 程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变;

select函数

原型

在这里插入图片描述

参数timeout取值

在这里插入图片描述

fd_set结构及输入输出型参数

在这里插入图片描述

select编写需要第三方数组

在这里插入图片描述

socket就绪条件

读就绪

  • socket内核中, 接收缓冲区中的字节数, 大于等于低水位标记SO_RCVLOWAT. 此时可以无阻塞的读该文件描述符, 并且返回值大于0;
  • socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;
  • 监听的socket上有新的连接请求;
  • socket上有未处理的错误;

写就绪

  • socket内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小), 大于等于低水位标记
  • SO_SNDLOWAT, 此时可以无阻塞的写, 并且返回值大于0;
  • socket的写操作被关闭(close或者shutdown). 对一个写操作被关闭的socket进行写操作, 会触发SIGPIPE信号;
  • socket使用非阻塞connect连接成功或失败之后;
  • socket上有未读取的错误;

异常就绪

  • socket上收到带外数据. 关于带外数据, 和TCP紧急模式相关(TCP协议头中, 有一个紧急指针的字段),

select优缺点

在这里插入图片描述

代码

I/O多路转接之poll

poll函数接口

在这里插入图片描述
在这里插入图片描述

参数说明

在这里插入图片描述

参数fds及poll优缺点

在这里插入图片描述

代码

I/O多路转接之epoll

按照man手册的说法: 是为处理大批量句柄而作了改进的poll.
被公认为Linux2.6下性能最好的多路I/O就绪通知方法.

epoll的相关系统调用

在这里插入图片描述

epoll_create

创建一个epoll的句柄.

  • 自从linux2.6.8之后, size参数是被忽略的.
  • 用完之后, 必须调用close()关闭.

epoll_ctl

epoll的事件注册函数.

  • 它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型.
  • 第一个参数是epoll_create()的返回值(epoll的句柄).
  • 第二个参数表示动作,用三个宏来表示.
  • 第三个参数是需要监听的fd.
  • 第四个参数是告诉内核需要监听什么事.

第二个参数的取值:

  • EPOLL_CTL_ADD :注册新的fd到epfd中;
  • EPOLL_CTL_MOD :修改已经注册的fd的监听事件;
  • EPOLL_CTL_DEL :从epfd中删除一个fd;

struct epoll_event结构如下:
在这里插入图片描述

events可以是以下几个宏的集合:

  • EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);
  • EPOLLOUT : 表示对应的文件描述符可以写;
  • EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来);
  • EPOLLERR : 表示对应的文件描述符发生错误;
  • EPOLLHUP : 表示对应的文件描述符被挂断;
  • EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.
  • EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要再次把这个socket加入到EPOLL队列里.

epoll_wait

  • int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

收集在epoll监控的事件中已经发送的事件.

  • 参数events是分配好的epoll_event结构体数组.
  • epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存).
  • maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size.
  • 参数timeout是超时时间 (毫秒, 0会立即返回, -1是永久阻塞).
  • 如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时, 返回小于0表示函数失败.

epoll工作原理

原理

在这里插入图片描述

  • 当某一进程调用epoll_create方法时, Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关.

在这里插入图片描述
在这里插入图片描述

细节理解

在这里插入图片描述
在这里插入图片描述

epoll的优点(和 select 的缺点对应)

在这里插入图片描述

注意

epoll中使用了内存映射机制
内存映射机制: 内核直接将就绪队列通过mmap的方式映射到用户态. 避免了拷贝内存这样的额外性能开销.

这种说法是不准确的. 我们定义的struct epoll_event是我们在用户空间中分配好的内存. 势必还是需要将内核的数据拷贝到这个用户空间的内存中的

epoll工作方式

epoll有2种工作方式-水平触发(LT)和边缘触发(ET)
在这里插入图片描述
对比LT和ET
在这里插入图片描述

epoll的使用场景

在这里插入图片描述

epoll中的惊群问题

参考 http://blog.csdn.net/fsmiy/article/details/36873357

代码

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

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

相关文章

React Native中防止滑动过程中误触

React Native中防止滑动过程中误触 在使用React Native开发的时&#xff0c;当我们快速滑动应用的时候&#xff0c;可能会出现误触&#xff0c;导致我们会点击到页面中的某一些点击事件&#xff0c;误触导致页面元素响应从而进行其他操作,表现出非常不好的用户体验。 一、问题…

Kafka安装以及入门基本命令操作

文章目录 1.单节点搭建1.1 下载安装包1.2 配置环境变量1.3 配置配置文件1.4 启动启动zookeeper启动kafka 1.5 创建启动脚本startKafka.sh 2.简单的使用2.1 创建topic2.2 查看topic2.3 producer生产数据2.4 consumer消费者拉取数据 1.单节点搭建 1.1 下载安装包 #解压 tar -xz…

Android framework的底层原理,再不看就真有可能被淘汰

前言 从事Android开发的人都知道&#xff0c;目前市面上有各种类型跨平台技术诞生&#xff0c;严重冲击了Android市场&#xff0c;越来越多的Android开发者不再做移动应用开发&#xff0c;而另一方面&#xff0c;系统开发由于其复杂的逻辑&#xff0c;形成了独有的核心竞争力&…

Java 基础进阶篇(一)—— String 与 StringBuilder

文章目录 一、String 类概述二、String 创建对象的方式2.1 创建对象的两种方式2.2 面试&#xff1a;两种方式的区别 ★2.3 常见面试题 ★ 三、String 类常用方法3.1 字符串内容比较3.2 常用 API&#xff1a;遍历、截取、替换、分割 四、StringBuilder 字符串操作类4.1 构造器4.…

JavaScript 数据类型判断

&#xff08;生活的道路一旦选定&#xff0c;就要勇敢地走到底&#xff0c;决不回头。——左拉&#xff09; typeof typeof是在javascript编码过程中最常用的判断数据类型的方法&#xff0c;常用来判断数字&#xff0c;字符串&#xff0c;布尔值和undefind console.log(typeo…

ROS Noetic版本 rosdep找不到命令 不能使用的解决方法

使用rosdep指令来安装开源包所需的依赖是很方便的&#xff0c;本文主要介绍ROS Noetic版本中使用rosdep&#xff0c;报错找不到命令 &#xff0c;rosdep不能使用的解决方法。 rosdep&#xff1a;找不到命令 Command rosdep not found, but can be installed with:sudo apt ins…

怎么取消parallels更新,关闭Parallels Desktop 更新提示

自动更新问题 使用Parallels Desktop一段时间后&#xff0c;会主动提示更新&#xff0c;MacOS上最好的虚拟机&#xff1a;Parallels Desktop&#xff0c;但在使用过程中每次启动都要检查更新&#xff0c;比较烦人 如图&#xff1a; 解决方法 取消parallels更新 点击Parall…

v4l2框架

v4l2框架 文章目录 v4l2框架框架1.硬件相关层uvc_probeuvc_register_chainsuvc_register_termsuvc_register_video 2.核心层__video_register_device 3.虚拟视频驱动vivid分析入口vivid_init注册vivid平台驱动vivid_probevivid_create_instance 框架 1.硬件相关层 driver/medi…

2023 hnust 大三下 嵌入式 期中考试复习笔记

前言 ★&#xff1a;重点※&#xff1a;补充内容❓&#xff1a;还没搞懂的内容主要来源&#xff1a;教材、PPT、百度百科、AI重点来源&#xff1a;4-6班感谢&#xff1a;lyf总结得很草率&#xff0c;因为没听过课&#xff0c;也玩不懂 重点汇总 考试题型 选择&#xff08;40…

C#_Struct与Class的差异

简述 struct属于值类型&#xff0c;class属于引用类型 存储地址 struct储存于栈&#xff0c;class储存于堆&#xff08;class于栈中储存引用&#xff09; 传参性质 struct属于值传递&#xff0c;在函数内对参数进行修改&#xff0c;不会修改struct class处于引用传递&…

day40—选择题

文章目录 1.上网的时候&#xff0c;访问某个网页却突然出现了某个运营商的网页&#xff08;如联通、电信&#xff09;。出现此问题可能的原因是&#xff08;A&#xff09;2.某浏览器发出的HTTP 请求报文如下&#xff0c;下列叙述中&#xff0c;错误的是&#xff08;C&#xff0…

阿里云CentOS服务器安装Redis教程(一步步操作)

使用阿里云服务器ECS安装Redis数据库流程&#xff0c;操作系统为CentOS 7.6镜像&#xff0c;在CentOS上安装Redis 4.0.14&#xff0c;云服务器选择的是持久内存型re6p实例&#xff0c;新手站长分享阿里云CentOS服务器安装Redis流程方法&#xff1a; 目录 在CentOS系统中部署R…

【内网】面试·HVV专题

【内网】面试HVV专题 1.目标主机没有nc时如何获取反向shell2.说说你了解的隧道技术SSH隧道HTTP/HTTPS协议DNS协议1.目标主机没有nc时如何获取反向shell Python反向shell执行如下命令,在VPS上监听本地2222端口 nc -lvp 2222在目标主机上执行如下命令: bash反向shell执行如下命…

数据结构/栈实现队列

前言 在学习数据结构的过程当中&#xff0c;我们会学到栈和队列&#xff0c;在本篇文章中&#xff0c;重点讲解的是栈实现队列&#xff0c;故关于栈和队列的讲解只是简单带过。 一、栈 栈是一种后进先出的线性表&#xff0c;即只能在固定的一端进行插入和删除。 栈 方法 Stac…

(2)Qt的UI入门

目录 1. QWidget类&#xff08;重点&#xff09; 2. 子组件 1. QWidget类&#xff08;重点&#xff09; QWidget类是Qt中所有组件和窗口的基类&#xff0c;其内部规定了组件和窗口的基本规则和功能。 Qt中每个属性的文档中都有Access functions部分&#xff0c;表示其支持的读写…

机器学习实战教程(十二):聚类算法Kmeans

聚类概念 聚类是一种无监督的机器学习方法&#xff0c;它主要是通过在数据集中找到相似的样本并将它们分组来发现数据中的模式和结构。聚类算法可以将数据分成具有相似特征的组&#xff0c;每个组被称为一个簇。 常见的聚类算法有以下几种&#xff1a; K-means聚类算法&#…

python面向对象三大特性详解 - 封装 继承 多态

前言 面向对象编程有三大特性&#xff1a;封装、继承、多态&#xff0c;本文带大家来认识和了解这三个特性~ 补充 - 新式类 & 经典类 在python2.x中&#xff0c;新式类是继承了object类的子类&#xff0c;以及该子类的子类 子子类...&#xff1b;经典类就是没有继承没有…

java基础入门-05

Java基础入门-05 13、面向对象进阶&#xff08;static&继承&#xff09;1.1 如何定义类1.2 如何通过类创建对象1.3 封装1.3.1 封装的步骤1.3.2 封装的步骤实现 1.4 构造方法1.4.1 构造方法的作用1.4.2 构造方法的格式1.4.3 构造方法的应用 1.5 this关键字的作用1.5.1 this关…

MapReduce 源码分析-1

源码追踪 Class Job 作为使用Java语言编写的MapReduce城西&#xff0c;其入口方法位main方法&#xff0c;在MapReduce Main方法中&#xff0c;整个核心都在围绕着job类&#xff0c;中文称之为作业。 public class WordDriver {public static void main(String[] args) throw…

解决vue中父组件通过props向子组件传递数据,子组件接收不到

问题&#xff1a;父组件在挂载时向后端发起请求获取数据&#xff0c;然后将获取到的数据传递给子组件&#xff0c;子组件想要在挂载时获取数据&#xff0c;获取不到。 代码示例&#xff1a; //父组件 <template><div><HelloWorld :message"message"…