Linux 【C编程】IO进阶— 阻塞IO、非阻塞IO、 多路复用IO、 异步IO

news2024/9/28 3:24:37

文章目录

  • 1.阻塞IO与非阻塞IO
    • 1.1为什么有阻塞式?
    • 1.2非阻塞
  • 2.阻塞式IO的困境
  • 3.并发IO的解决方案
    • 3.1非阻塞式IO
    • 3.2多路复用IO
      • 3.2.1什么是多路复用IO?
      • 3.2.1多路复用IO select原理
      • 3.2.1多路复用IO poll原理
    • 3.3异步IO

1.阻塞IO与非阻塞IO

1.1为什么有阻塞式?

1.常见的阻塞:wait pause sleep 等函数 ; read或write某些文件
2.阻塞式的好处:在某些情况下,阻塞式 I/O 可以更有效地利用系统资源。在一些高负载场景下,阻塞式 I/O 可以避免频繁的上下文切换,降低系统开销。 阻塞式 I/O 的编程模型通常比较直观和易于理解。代码顺序执行,不需要太多复杂的逻辑来处理异步操作和事件回调。

1.2非阻塞

1.为什么要实现非阻塞?
非阻塞 I/O 允许程序在进行 I/O 操作时不被阻塞,可以继续执行其他任务。这对于高并发的应用程序尤其重要,可以充分利用系统资源,提高系统的吞吐量和性能。 在一些场景下,程序需要快速响应并处理多个客户端请求。如果使用阻塞 I/O,一个慢速的 I/O 操作可能会导致整个程序被阻塞,无法及时响应其他请求,而非阻塞 I/O 可以避免这种情况。非阻塞 I/O 可以与异步 I/O 结合,使得程序可以发起一个 I/O 操作后继续执行其他任务,当 I/O 完成时,系统通知程序并处理完成的数据。这种模式可以提高系统的并发性和性能。
2.如何实现非阻塞IO访问: O_NONBLOCK和fcntl

2.阻塞式IO的困境

以在程序中读取键盘为例

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
int main(){
    char buf[199];
    memset(buf,0,sizeof(buf));
    //读取键盘
    // 键盘就是标准输入, stdin
    printf("before read.\n");
    read (0,buf,2);  //从键盘读取两个字节 就是0号文件标识符 read 本身就是阻塞式的
    printf("读出来的内容是 :【%s】\n",buf);
    return 0;
}

此时运行程序后,发现已经堵塞住了 正在等待输入
在这里插入图片描述
在程序中读取鼠标
鼠标设备本质上也是字符设备 在/dev/input中,使用cat 读取 mouse 可能有多个 哪个晃动鼠标能得到数据就说明是鼠标设备,至于乱码是因为读取的是二进制数据,不是普通的字符。
在这里插入图片描述
代码演示:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
    char buf[199];
    memset(buf,0,sizeof(buf));
    //读取鼠标
    // 鼠标不是标准输入 需要open 打开
    int fd = -1;
    fd = open("/dev/input/mouse0",O_RDONLY);
    if(fd<0){
        perror("open:");
        return -1;
    }

    printf("before read mouse.\n");
    read (fd,buf,2);  //从鼠标读取两位
    printf("mouse读出来的内容是 :【%s】\n",buf);




    return 0;
}

如果程序同时读取键盘和鼠标

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
    char buf[199];
    memset(buf,0,sizeof(buf));
    //读取鼠标
    // 鼠标不是标准输入 需要open 打开
    int fd = -1;
    fd = open("/dev/input/mouse0",O_RDONLY);
    if(fd<0){
        perror("open:");
        return -1;
    }

    printf("before read mouse.\n");
    read (fd,buf,2);  //从鼠标读取两位
    printf("mouse读出来的内容是 :【%s】\n",buf);

    memset(buf,0,sizeof(buf));
    printf("before keyboard read.\n");
    read (0,buf,2);  //从键盘读取两个字节 就是0号文件标识符 read 本身就是阻塞式的
    printf("keyboard 读出来的内容是 :【%s】\n",buf);


    return 0;
}

如果用户使用这个程序,先使用鼠标,再使用键盘输入,程序没有问题
在这里插入图片描述
当用户先输入键盘的时候,阻塞IO就出现问题了,程序必须先等待鼠标事件,当前程序已经被阻塞住了,无论怎么输入键盘,程序也不会有响应。例如下图
在这里插入图片描述

3.并发IO的解决方案

3.1非阻塞式IO

使用fcntl 修改0号文件标识符的属性,添加非阻塞属性。
鼠标是通过open 打开的,直接添加非阻塞属性即可。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
    char buf[199];
    memset(buf,0,sizeof(buf));
    int ret = -1;
    // 鼠标不是标准输入 需要open 打开
    int fd = -1;
    fd = open("/dev/input/mouse0",O_RDONLY|O_NONBLOCK);  //添加非阻塞属性
    if(fd<0){
        perror("open:");
        return -1;
    }
    int flag=  -1;
    //把0号描述符变成非阻塞式的
    flag = fcntl(0,F_GETFL); //获取原来的flag
    flag |= O_NONBLOCK ; //添加非阻塞属性
    fcntl(0,F_SETFL,flag); //更新flag
    while (1){
        ret  = read (fd,buf,2);  //从鼠标读取
        if(ret>0){
            printf("mouse读出来的内容是 :【%s】\n",buf);
            memset(buf,0,sizeof(buf));
        }
        ret  = read (0,buf,10);  //从键盘读取 
        if(ret>0){
            printf("keyboard 读出来的内容是 :【%s】\n",buf);
            memset(buf,0,sizeof(buf));
        }
    }
   


    return 0;
}

与阻塞相比,想读入键盘就读入键盘,想读入鼠标就读入鼠标,提高并发性
在这里插入图片描述

3.2多路复用IO

3.2.1什么是多路复用IO?

多路复用 I/O 是一种机制,允许一个进程能够同时监视和处理多个 I/O 源,例如文件描述符、sockets 或其他文件 I/O。这些多路复用的系统调用允许程序等待多个 I/O 事件中的任何一个就绪,从而避免了阻塞等待单个 I/O 完成的情况,提高了程序的效率和并发处理能力。对外部还是阻塞式的,内部非阻塞式自动轮询多路IO看看哪个有数据,就先用哪个。

3.2.1多路复用IO select原理

在Linux系统上常见的多路复用IO技术包括 select poll

1.文件描述符集合:
select 使用三个文件描述符集合来表示待检查的文件描述符。这三个集合分别是读文件描述符集合(readfds)、写文件描述符集合(writefds)和异常文件描述符集合(exceptfds)。
2.超时设置:
select 允许设置一个超时时间,表示最长等待时间。当超时时间达到时,select 将返回,不再等待事件的发生。
3.轮询:
select 通过轮询检查文件描述符的状态,判断是否有可读、可写或异常事件发生。它会遍历指定的文件描述符集合,检查每个文件描述符的状态。
4.阻塞:
当没有任何文件描述符就绪时,select 可以阻塞程序,等待文件描述符变得可读、可写或发生异常。在这种情况下,它会一直等待,直到有文件描述符就绪或超时发生。
5.就绪文件描述符集合:
select 在返回时会修改传入的文件描述符集合,标识出哪些文件描述符已经就绪,可以进行相应的IO操作。

在使用 select 时,需要注意其效率问题,特别是在大规模的文件描述符集合中。因为 select 是线性扫描所有文件描述符的,当文件描述符数量增加时,性能可能会下降。在一些操作系统上,存在文件描述符数量的限制。
使用select实现同时读取键盘和鼠标数据,并且设置了3s超时 如果一直没有等到IO到达,就直接退出程序。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>

int main() {
    char buf[199];
    memset(buf, 0, sizeof(buf));

    // 打开鼠标设备
    int mouse_fd = open("/dev/input/mouse0", O_RDONLY);
    if (mouse_fd < 0) {
        perror("open mouse:");
        return -1;
    }
    struct timeval timeout;
    timeout.tv_sec = 3;   // 设置秒数
    timeout.tv_usec = 0;  // 设置微秒数
    printf("before select.\n");

    while (1) {
        fd_set read_fds;
        FD_ZERO(&read_fds);
        FD_SET(0, &read_fds);   // 标准输入(键盘)
        FD_SET(mouse_fd, &read_fds);  // 鼠标

        // 使用select监听多个文件描述符
        int result = select(mouse_fd + 1, &read_fds, NULL, NULL, &timeout);

        if (result > 0) {
            //判断出来是键盘IO到了
            if (FD_ISSET(0, &read_fds)) {
                // 从键盘读取
                read(0, buf, sizeof(buf));
                printf("keyboard 读出来的内容是:%s\n", buf);
                memset(buf, 0, sizeof(buf));
            }
            //判断出是鼠标到了
            if (FD_ISSET(mouse_fd, &read_fds)) {
                // 从鼠标读取
                read(mouse_fd, buf, 2);  // 从鼠标读取两位
                printf("mouse 读出来的内容是:%s\n", buf);
                memset(buf, 0, sizeof(buf));
            }
        }
        if(result == 0){
            printf("select 等待超时\n");
            return -1;
        }
    }

    // 关闭鼠标设备
    close(mouse_fd);

    return 0;
}

3.2.1多路复用IO poll原理

poll 是 Linux 中用于多路复用 I/O 操作的系统调用之一,它允许一个进程等待多个文件描述符上的事件发生。poll 的原理如下:
1.准备文件描述符集合和事件关注列表: 在调用 poll 之前,应用程序需要创建一个 struct pollfd 数组,该数组包含了要监听的文件描述符以及对每个文件描述符关注的事件。每个 struct pollfd 结构体包括以下字段:

  • fd
  • events
  • revents

2.调用 poll 函数: 应用程序调用 poll 函数,传递准备好的 struct pollfd 数组及数组的长度。

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  • fds:指向struct pollfd数组的指针
  • nfds: 数组中结构体的数量
  • timeout:设置超时时间,-1代表一直等待

代码演示:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>

int main() {
    char buf[199];
    memset(buf, 0, sizeof(buf));

    // 打开鼠标设备文件
    int mouse_fd = open("/dev/input/mouse0", O_RDONLY);
    if (mouse_fd < 0) {
        perror("open mouse:");
        return -1;
    }

    // 使用poll监听标准输入和鼠标输入
    struct pollfd fds[2];
    fds[0].fd = STDIN_FILENO;  // 标准输入
    fds[0].events = POLLIN ;  //事件为可读

    fds[1].fd = mouse_fd;  // 鼠标输入
    fds[1].events = POLLIN ;//事件为可读

    printf("Waiting for input...\n");

    while (1) {
        //这里的timeout 设置-1 代表一直等待
        int result = poll(fds, 2, -1);  // 阻塞等待事件发生

        if (result > 0) {
            if (fds[0].revents & (POLLIN | POLLPRI)) {
                // 从标准输入读取数据
                read(STDIN_FILENO, buf, sizeof(buf));
                printf("Keyboard input: %s\n", buf);
            }

            if (fds[1].revents & (POLLIN | POLLPRI)) {
                // 从鼠标设备读取数据
                read(mouse_fd, buf, sizeof(buf));
                printf("Mouse input: %s\n", buf);
            }
        } else if (result < 0) {
            perror("poll:");
            break;
        }
    }

    // 关闭文件描述符
    close(mouse_fd);

    return 0;
}

与select效果一致
在这里插入图片描述

3.3异步IO

在Linux中,异步IO(Asynchronous I/O)是一种文件IO操作的模型,与传统的同步IO模型(例如使用read和write函数)不同。在异步IO模型中,IO操作的请求被提交后,程序可以继续执行其他任务,而无需等待IO操作完成。
异步IO的关键特点包括:

  • 非阻塞: 异步IO允许程序在等待IO操作完成的同时继续执行其他任务,不会阻塞整个进程。

  • 回调机制: 异步IO通常通过回调机制来处理IO操作完成的通知。当IO操作完成时,系统会调用预先注册的回调函数,以便程序可以处理IO的结果。

  • 提高并发性: 异步IO适用于需要同时处理大量IO操作的场景,能够提高程序的并发性能。
    在Linux中,异步IO可以通过以下几种机制来实现:

  • fcntl+signal:使用fcntl中的F_SETOWN和O_ASYNC选项来设置异步IO的所有者,然后结合signal来注册信号处理函数,以便在IO事件发生时得到通知。

  • aio_ 函数族:* 提供了一组异步IO相关的系统调用,例如aio_read、aio_write等。这些函数使用结构体struct aiocb来描述IO操作,并可以设置回调函数。

  • epoll: epoll本身是一个多路复用机制,但也可以与异步IO结合使用,通过epoll监听IO事件,当IO操作完成时,通过回调机制处理。

  • libuv: 是一个跨平台的异步IO库,它封装了不同操作系统的异步IO机制,使得在不同平台上能够使用相似的异步IO接口。

使用fcntl+signa完成异步IO操作

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
#include <fcntl.h>
#include<signal.h>
int mouse_fd = -1;
//设置信号回调函数  鼠标时间设置为一个异步IO
void func(int sig){
    char buf[100] = {0};
    if (sig !=SIGIO)
    read (mouse_fd,buf,2);  //从鼠标读取两位
    printf("mouse读出来的内容是 :【%s】\n",buf);
}
int main(){
    char buf[100] = {0};
    //读取鼠标
    int flag = -1;
    mouse_fd = open("/dev/input/mouse0",O_RDONLY);
    if(mouse_fd<0){
        perror("open:");
        return -1;
    }
    //注册异步通知 把鼠标设置为异步IO事件
    flag  = fcntl(mouse_fd,F_GETFL);
    flag |= O_ASYNC;
    fcntl(mouse_fd,F_SETFL,flag);
    //把当前进程设置异步IO接收的进程
    fcntl(mouse_fd,F_SETOWN,getpid());
    // 注册信号处理函数
    signal(SIGIO, func);
    memset(buf,0,sizeof(buf));
    printf("before keyboard read.\n");
    read (0,buf,2);  //从键盘读取两个字节 就是0号文件标识符 read 本身就是阻塞式的
    printf("keyboard 读出来的内容是 :【%s】\n",buf);
    return 0;
}

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

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

相关文章

Msa全球最新研究:多系统萎缩特效药全球最新进展

多系统萎缩是一种罕见的神经系统疾病&#xff0c;它涉及到多个系统的功能减退或丧失。对于这种疾病&#xff0c;传统的西医治疗方法往往束手无策。然而&#xff0c;中国著名中医刘家峰大夫&#xff0c;却通过中药治疗&#xff0c;为多系统萎缩患者带来了新的希望。 刘家峰大夫…

idea使用docker-compose发布应用程序

非常重要的话说在前头 idea要想使用docker-compose&#xff0c;不能使用ssh创建idea Docker&#xff0c;而需要使用socket创建idea Docker。 socket docker是不安全的&#xff0c;任何人都可以访问你的docker&#xff0c;所以只能测试环境使用&#xff0c;请勿在正式环境使用s…

TreesVariety

树木品种 - 树木和植物捆绑包。与“植被引擎”兼容的包装 通用和HDRP的树木包在这里 树木品种: ● 支持Unity Wind; ● 11种树木,7种植物; ● Unity树创建器树(可编辑); ● 与内置管道配合使用; ● 支持地形广告牌。 树木: ● 8棵桦树; ● 4块枫木; ● 8块橡木; ●…

代码随想录-刷题第五十五天

72. 编辑距离 题目链接&#xff1a;72. 编辑距离 思路&#xff1a;本题是用动规来解决的经典题目&#xff0c;这道题目看上去好像很复杂&#xff0c;但用动规可以很巧妙地算出最少编辑距离。动态规划五步曲分析&#xff1a; dp[i][j]表示以下标i-1为结尾的字符串word1&#x…

2024年甘肃省职业院校技能大赛信息安全管理与评估 样题一 理论题

竞赛需要完成三个阶段的任务&#xff0c;分别完成三个模块&#xff0c;总分共计 1000分。三个模块内容和分值分别是&#xff1a; 1.第一阶段&#xff1a;模块一 网络平台搭建与设备安全防护&#xff08;180 分钟&#xff0c;300 分&#xff09;。 2.第二阶段&#xff1a;模块二…

Git的简单使用说明

Git入门教程 git的最主要的作用&#xff1a;版本控制&#xff0c;协助开发 一.版本控制分类 ​​ 1.本地版本控制 ​​ 2.集中版本控制 ​​ 所有的版本数据都存在服务器上&#xff0c;用户的本地只有自己以前所同步的版本&#xff0c;如果不连网的话&#xff0c;用户就看不…

Springboot+vue整合 支付宝沙箱支付

可以完成的功能&#xff1a; 一、拉起支付 二、异步回调 三、掉单处理 四、超时关闭 五、订单退款

ssh远程登录

ssh协议 ---基于 tcp 的 22 号端口 确认是否有ssh包&#xff1a; [rootserver ~] rpm -qa | grep sshopenssh-clients-8.7p1-24.el9_1.x86_64openssh-server-8.7p1-24.el9_1.x86_64 1、 ssh的验证过程 第一阶段&#xff1a;版本协商以及tcp三次握手 第二阶段&#xff1a;秘钥和…

C# 图解教程 第5版 —— 第22章 命名空间和程序集

文章目录 22.1 引用其他程序集22.2 命名空间22.2.1 命名空间名称22.2.2 命名空间的补充22.2.3 命名空间跨文件伸展22.2.4 嵌套命名空间 22.3 using 指令22.3.1 using 命名空间指令22.3.2 using 别名指令22.3.3 using static 指令 22.4 程序集的结构22.5 程序集标识符22.6 强命名…

微信小程序(二)事件绑定

注释很详细&#xff0c;直接上代码 新增内容&#xff1a; 点击事件绑定注册页面设置页面初始化数据事件处理函数的实现更新数据并更新视图 源码&#xff1a; index.wxml <!-- 页面的数据绑定 --> <view>{{msg}}</view> <!-- 绑定点击事件 --> <but…

高防ip是什么,防护效果好吗

DDoS攻击是互联网最常见的网络攻击方式之一&#xff0c;通过大量虚假流量对目标服务器进行攻击&#xff0c;堵塞网络耗尽服务器性能&#xff0c;导致服务器崩溃&#xff0c;真正的用户无法访问。以前企业常用的防御手段就是高防服务器&#xff0c;也就是硬防&#xff0c;但由于…

【微信小程序独立开发1】项目提出和框架搭建

前言&#xff1a;之前学习小程序开发时仿照别人的页面自己做了一个商城项目和小说项目&#xff0c;最近突发奇想&#xff0c;想从0开发一个关于《宠物日记》的小程序&#xff0c;需求和页面都由自己设计&#xff0c;将在这记录开发的全部流程和过程中遇到的难题等... 1、搭建小…

MySQL夯实之路-查询性能优化深入浅出

MySQL调优分析 explain&#xff1b;show status查看服务器状态信息 优化 减少子任务&#xff0c;减少子任务执行次数&#xff0c;减少子任务执行时间&#xff08;优&#xff0c;少&#xff0c;快&#xff09; 查询优化分析方法 1&#xff0e;访问了太多的行和列&#xff1…

【教学类-43-18】A4最终版 20240111 数独11.0 十宫格X*Y=Z套(n=10),套用没有分割行列的A4横版模板

作品展示&#xff1a; 撑满格子的10宫格数独50%难度 50空 背景需求&#xff1a; 大4班有3位男孩做9宫格数独&#xff08;81格子&#xff0c;30%难度 24空&#xff09;非常娴熟&#xff0c;我观察他们基本都在10分钟内完成&#xff0c;其中一位男孩把九宫格题目给我看时表达自…

Windows安全基础:UAC

目录 UAC原理介绍 UAC的四个安全级别定义 UAC的触发条件 UAC用户登录过程 UAC虚拟化 配置UAC UAC原理介绍 用户账号控制&#xff08;User Account Control&#xff09; 为Windows Vista推出的一项安全技术&#xff0c;其原理是通过限制安全应用软件对系统层级的访问&…

从零到一保姆级深度学习Docker镜像配置教程

文章目录 前言一、Docker相关工具安装1.1 Docker安装和配置1.2 Nvidia-Docker安装1.3 Docker-Compose安装 二、深度学习环境Docker镜像配置2.1 基础镜像拉取与启动2.2 深度学习Docker环境配置2.2.1 更换软件源2.2.2 安装常见命令2.2.3 安装SSH2.2.4 安装Miniconda2.2.5 安装Pyt…

计算n的平方根m 进而将m向下取整 math.isqrt()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 计算n的平方根m 进而将m向下取整 math.isqrt() 选择题 请问执行math.isqrt(10)的运行结果是&#xff1a; import math print("【执行】math.sqrt(10)") print (math.sqrt(10)) pr…

thinkphp6报错Driver [Think] not supported.

thinkphp6报错Driver [Think] not supported. 问题解决方法测试 问题 直接使用 View::fetch();渲染模板报错 解决方法 这个报错是由于有安装视图驱动造成的 运行如下命令安装即可 composer require topthink/think-view官方文档中是这么写的 视图功能由\think\View类配合视…

视频SDK的技术架构优势和价值

为了满足企业对于高质量视频的需求&#xff0c;美摄科技推出了一款强大的视频SDK&#xff08;软件开发工具包&#xff09;&#xff0c;旨在帮助企业轻松实现高效、稳定的视频功能&#xff0c;提升用户体验&#xff0c;增强企业竞争力。 一、美摄视频SDK的技术实现方式 美摄视…

Flutter之配置环境创建第一个项目

随着时代发展&#xff0c;使用Flutter开发的项目越来越多&#xff0c;于是踏上了Flutter开发之路。 作为一个Android开发人员&#xff0c;也只能被卷到与时俱进&#xff0c;下面一起创建一个Flutter项目吧。 一、Android开发&#xff0c;电脑上已经具备了的条件&#xff1a; …