【Linux高级IO】五种IO模型 多路转接(select)

目录

1. 五种IO模型

 1.1 阻塞式IO

 1.2 非阻塞IO

1.3 信号驱动IO

1.4  多路转接

 1.5 异步IO

2. 同步通信与异步通信

3. 多路转接

3.1 select

总结


在这里插入图片描述

1. 五种IO模型

 1.1 阻塞式IO

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

 最常见的就是C语言中调用scanf,程序启动,等待输入,不输入一直阻塞等待;

 比如:小明去钓鱼,阻塞式IO就类似于小明一直盯着鱼竿,什么都不干就等着鱼上钩;

 1.2 非阻塞IO

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

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

比如:小王也去钓鱼,在等待鱼上钩的这段时间,小王可以看看书,吃点零食...做其他的事情;

1.3 信号驱动IO

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

 比如:小聪也去钓鱼,他在鱼竿加了一个铃铛,在等待鱼上钩的期间,他可以干其他事,等有鱼上钩铃铛就会提醒有鱼了;

1.4  多路转接

IO多路转接:多路转接能够同时等待多个文件 描述符的就绪状态.  

从流程图上看起来和阻塞IO类似,如何理解?

比如:小张也是去钓鱼,他拿了很多鱼竿,同时使用多个鱼竿钓鱼;一个人盯着多个鱼竿;

 1.5 异步IO

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

 怎么去理解?

比如:小李是一个老板,走到鱼塘边看到这么多人在钓鱼,他想吃鱼了,但是他又不想自己钓,于是和自己的司机说,你去钓鱼,钓上来鱼给我,然后通知另一个司机来接他;小李全程不参与钓鱼,由他的司机给他钓;司机就相当于操作系统,小李相当于是一个线程,钓鱼是IO,鱼就是数据;

2. 同步通信与异步通信

同步通信

在同步通信中,发送方和接收方在通信过程中是时间上密切关联的。发送方发送消息后,需要等待接收方确认已经接收到消息,才能继续进行后续操作。

特点

  • 阻塞:发送方在发送消息后会被阻塞,直到接收方处理完毕并回复。这意味着发送方的执行流会暂停,直至接收方确认。
  • 实现简单:因其简单的请求、响应模式,容易实现和理解。
  • 实时性:适用于需要即时反馈的场景,比如电话通话或某些即时消息服务。

 比如:打电话;

 异步通信

 在异步通信中,发送方和接收方之间的通信不需要严格的时间同步。发送方在发送完消息后,不必等待接收方的确认,可以继续执行其他任务。

特点

  • 非阻塞:发送方在发送消息后不会被阻塞,可以自由进行其他操作。接收方会在适当的时候处理接收到的消息。
  • 复杂性高:由于涉及到消息的队列、存储和后续处理,通常实现起来比同步通信复杂。
  • 适应性强:适用于高并发、跨网络或需要高响应性的场景,比如事件驱动的编程模型、某些消息队列系统。

 比如:电子邮箱;

 两种通信的关键点在于阻塞与非阻塞;

  • 阻塞调用是指调用结果返回之前,当前线程会被挂起. 调用线程只有在得到结果之后才会返回;
  • 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程. 等待需要时可以再获取执行结果;

 如何设置非阻塞?

fcntl

文件描述符, 默认都是阻塞IO,但是可以通过接口设置为非阻塞; 

#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );

 参数:

  • fd:文件描述符
  • cmd:操作命令,指定要执行的操作类型。
  • arg:可选参数,根据 cmd 的不同而有所不同,某些操作不需要这个参数。

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

 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).

 使用示例:

将文件描述符设置为非阻塞; 

void SetNoBlock(int fd) { 
    int fl = fcntl(fd, F_GETFL); 

    if (fl < 0) { 
        perror("fcntl");
        return; 
    }

    fcntl(fd, F_SETFL, fl | O_NONBLOCK); 
}

F_GETFL将当前的文件描述符的属性取出来(这是一个位图);该位图表示文件描述符的各种状态和行为;这里是获取出它的状态和行为,然后添加一个非阻塞的状态;

文件访问模式: 

  • O_RDONLY:以只读模式打开文件。
  • O_WRONLY:以只写模式打开文件。
  • O_RDWR:以读写模式打开文件。

 文件状态标志:

  • O_APPEND:文件指针在每次写操作后移到文件末尾。这意味着任何写入都将附加到文件的现有内容后面。
  • O_NONBLOCK:文件描述符被设置为非阻塞模式。对该文件描述符的读写操作不会导致进程阻塞。
  • O_SYNC:写入操作会在返回前完成同步,这样就可以确保数据已经写入磁盘。
  • O_DSYNC:确保数据已写入磁盘。

 验证一下非阻塞IO:

#include <iostream>
#include <cstdlib>
#include <fcntl.h>
#include <unistd.h>
#include <cerrno>

void SetNonBlock(int fd)
{
    int fl = fcntl(fd, F_GETFL);
    if(fl < 0)
    {
        std::cerr << "fcntl error" << std::endl;
        exit(0);
    }
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}

int main()
{
    SetNonBlock(0);
    while (true)
    {
        char buffer[1024];
        ssize_t s = read(0, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << "echo# " << buffer << std::endl;
        }
        else if (s == 0)
        {
            std::cout << "end stdin" << std::endl;
            break;
        }
        else
        // 非阻塞等待, 如果数据没有准备好,返回值会按照出错返回, s == -1
        // 数据没有准备好 vs 真的出错了 : 处理方式一定不是一样的。 s无法区分!
        // 数据没有准备好,算读取错误吗?不算。read,recv以出错的形式告知上层,数据还没有准备好
        {
            if (errno == EWOULDBLOCK)
            {
                std::cout << "OS的底层数据未准备就绪, errno: " << errno << std::endl;
            }
            else if(errno == EINTR)//信号导致中断
            {
                std::cout << "IO interrupted by signal, try again" << std::endl;
            }
            else
            {
                std::cout << "read error!" << std::endl;
            }
        }
        sleep(1);
    }
    return 0;
}

3. 多路转接

多路转接有什么优势?

比如:TCPServer,服务端需要创建一个监听套接字,然后接收客户端发来的连接请求,然后返回新的fd,服务端需要维护多个连接,对于每个连接都可能需要读数据/写数据;也就是说,服务端需要监控多个fd的IO事件;没有多路转接:

  • 把所有的fd都设置非阻塞,然后不停的循环遍历每个fd,判断是否有IO事件;
  • 创建多个线程,让线程去监控IO事件;
  • 基于信号以及子进程的方式,让子进程去执行处理对应的IO操作,设置忽略SIGCHLD
  • 使用孙子进程,子进程退出,自动回收资源

 这几种方式都有很大的效率和内存开销的问题:

  • 循环遍历的方式:CPU 资源浪费严重,因为即使没有 I/O 事件发生,CPU 也会不断地进行循环检查;可扩展性差,随着连接数的增加,性能会急剧下降;
  • 线程/进程的创建和销毁开销较大,过多的线程/进程会导致系统资源耗尽;

 应对多个连接的IO时,多路转接无疑是最合适的方式;

3.1 select

  IO总结就两个过程:

  • 等(阻塞,等待数据)
  • 就是拷贝(读/写数据,read、write、recv、send)

 多路转接帮我们解决的就是IO中的等;有IO事件就绪就返回;并且它可以一次等待多个文件描述符;

接口原型:

#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

 参数:

nfds:需要监控的最大文件描述符编号加 1。select 函数会检查从 0 到 nfds - 1 的所有文件描述符。例如,如果要监控的文件描述符是 3、5、7,那么 nfds 应该设置为 8(最大文件描述符 7 加 1)。

readfds:指向一个 fd_set 类型的集合,该集合包含了需要监控读事件的文件描述符。当集合中的某个文件描述符,select 函数会将其标记为就绪。如果不需要监控读事件,可以将其设置为 NULL;

writefds:指向一个 fd_set 类型的集合,该集合包含了需要监控写事件的文件描述符。当集合中的某个文件描述符可以进行写操作时,select 函数会将其标记为就绪。如果不需要监控写事件,可以将其设置为 NULL。

exceptfds:指向一个 fd_set 类型的集合,该集合包含了需要监控异常事件的文件描述符。当集合中的某个文件描述符发生异常时,select 函数会将其标记为就绪。如果不需要监控异常事件,可以将其设置为 NULL;

timeout:指向一个 struct timeval 类型的结构体,用于设置 select 函数的超时时间。struct timeval 结构体定义如下:

struct timeval {
    time_t      tv_sec;         /* seconds */
    suseconds_t tv_usec;        /* microseconds */
};

// 示例
struct timeval timeout ={5,0};// 5秒以内阻塞等待,5秒以后非阻塞;(阻塞+非阻塞)
// 5秒以内阻塞式等待,5秒内就绪了select正常返回,5秒内没有就绪,返回值就为0(超时返回)

struct timeval timeout ={0,0};// 立即检测,有就绪的直接返回,没有返回0(非阻塞)
struct timeval timeout= nullptr;// (阻塞)

 返回值:

  •  大于 0:表示就绪的文件描述符的总数,即 readfds、writefds 和 exceptfds 三个集合中就绪的文件描述符数量之和。
  • 0:表示超时,即在指定的时间内没有文件描述符就绪。
  • -1:表示发生错误,此时会设置相应的 errno 变量,可以通过 perror 函数输出错误信息

 设置位图不允许私自设置,系统也提供了接口:

void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位
int  FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真
void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位
void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位

 示例:

简易的SelectServer,详细可见我的码云:SelectSever示例

Select的优缺点
优点:select只负责等待,可以等待多个fd,IO的时候效率比较高;
缺点:

  • 每次都要对select的参数重置;
  • 编写程序的时候,select要是有第三方数组,所以充满遍历,可能会影响select的效率
  • 用户到内核,内核到用户,每次select调用和返回,都要对位图进行重新设置,内核和用户之间要一直进行数据拷贝
  • select让OS在底层遍历要关心的所有fd,这也会导致效率低下,内核在检查文件描述符状态时,采用的是线性遍历的方式,即从 0 到 nfds - 1 逐个检查每个文件描述符是否就绪。随着要监控的文件描述符数量增加,遍历的时间也会线性增长;
  • fd_set是系统提供的类型,fd_set大小是固定的;也就是说位图的大小也是固定的,select最多能检测的fd总数是有上限的;

后续会介绍其他实现多路转接的接口,他们解决了select中存在的一些问题,也是现在最为常用的方式;


总结

        以上便是本文的全部内容,希望对你有所帮助,感谢阅读!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux服务升级:Almalinux 升级 DeepSeek-R1

目录 一、实验 1.环境 2.Almalinux 部署 Ollama 3.Almalinux 升级 DeepSeek-R1 4.Almalinux 部署 docker 5. docker 部署 DeepSeek-R1 6.Almalinux 部署 Cpolar (内网穿透) 7.使用cpolar内网穿透 二、问题 1.构建容器失败 一、实验 1.环境 &#xff08;1&#xff09…

深度剖析数据分析职业成长阶梯

一、数据分析岗位剖析 目前&#xff0c;数据分析领域主要有以下几类岗位&#xff1a;业务数据分析师、商业数据分析师、数据运营、数据产品经理、数据工程师、数据科学家等&#xff0c;按照工作侧重点不同&#xff0c;本文将上述岗位分为偏业务和偏技术两大类&#xff0c;并对…

CosyVoice2整合包 特殊声音标记,声音克隆更逼真,新增批量生成

新增批量生成,可用于制作直播话术音频 特殊声音标记 符号示例1_语气加强<strong> </strong>每天都<strong>付出</strong>和<strong>精进</strong>&#xff0c;才能达到巅峰。2_呼吸声[breath][breath] 吸气,[breath] 呼气! [breath] 吸,[b…

vector习题

完数和盈数 题目 完数VS盈数_牛客题霸_牛客网 一个数如果恰好等于它的各因子(该数本身除外)之和&#xff0c;如&#xff1a;6321。则称其为“完数”&#xff1b;若因子之和大于该数&#xff0c;则称其为“盈数”。 求出2到60之间所有“完数”和“盈数”。 输入描述&#xff…

如何保证 Redis 缓存和数据库的一致性?

如何保证 Redis 缓存和数据库的一致性&#xff1f; 1. 问题出现场景 先修改数据库&#xff0c;再删除缓存 删除数据库数据成功了&#xff0c;但是删除缓存却失败了&#xff0c;缓存中仍保留的是旧数据 先删除缓存&#xff0c;再删除数据库 如果 Redis 缓存删除成功后&#xf…

信刻光盘安全隔离与信息交换系统让“数据摆渡”安全高效

随着数据传输、存储及信息技术的飞速发展&#xff0c;信息安全保护已成为重中之重。各安全领域对跨网数据交互的需求日益迫切&#xff0c;数据传输的安全可靠性成为不可忽视的关键。为满足业务需求并遵守保密规范&#xff0c;针对于涉及重要秘密信息&#xff0c;需做到安全的物…

网络原理--TCP/IP(2)

我们在之前已经介绍到TCP协议的核心机制二,接下来我们将继续介绍其他的核心机制。 核心机制三:连接管理 即建立连接,断开连接,在正常情况下,TCP要经过三次握⼿建⽴连接,四次挥⼿断开连接。 建立连接:TCP是通过“三次握手” 在生活中的握手就是打招呼,,但握手操作没有…

Windows PicPick Professional-v7.3.2-中文版

Windows PicPick Professional-中文版 链接&#xff1a;https://pan.xunlei.com/s/VOKGwGVGWUDl7L8cW4D1A1W4A1?pwdw5qz# - 更新了中文翻译&#xff0c;默认取消检测升级&#xff0c;删除多国语言

校园二手交易微信小程序的设计与实现(论文源码调试讲解)

第4章 系统设计 一个成功设计的系统在内容上必定是丰富的&#xff0c;在系统外观或系统功能上必定是对用户友好的。所以为了提升系统的价值&#xff0c;吸引更多的访问者访问系统&#xff0c;以及让来访用户可以花费更多时间停留在系统上&#xff0c;则表明该系统设计得比较专…

数据结构之各类排序算法代码及其详解

1. 排序的概念 排序是一种常见的算法概念&#xff0c;用于将一组数据按照特定的顺序进行排列。排序算法的目的是将一组数据按照递增或递减的顺序重新排列。常见的排序算法包括冒泡排序、插入排序、选择排序、快速排序、归并排序等。排序算法的选择通常取决于数据规模、数据分布…

6.6.6 嵌入式SQL

文章目录 2个核心问题识别SQL语句主语言和SQL通信完整导图 2个核心问题 SQL语句嵌入高级语言需要解决的2个核心问题是&#xff1a;如何识别嵌入语句&#xff1f;如何让主语言&#xff08;比如C,C语言&#xff09;和SQL通信&#xff1f; 识别SQL语句 为了识别主语言中嵌入的SQL…

keil主题(vscode风格)

#修改global.prop文件&#xff0c;重新打开keil即可 # Keil uVision Global Properties File # This file is used to customize the appearance of the editor# Editor Font editor.font.nameConsolas editor.font.size10 editor.font.style0# Editor Colors editor.backgro…

医疗AR眼镜:FPC如何赋能科技医疗的未来之眼?【新立电子】

随着科技的飞速发展&#xff0c;增强现实&#xff08;AR&#xff09;技术在医疗领域的应用逐渐成为焦点。医疗AR眼镜作为一种前沿的智能设备&#xff0c;正在为医疗行业带来深刻的变革。它不仅能够提升医生的工作效率&#xff0c;还能改善患者的就医体验&#xff0c;成为医疗科…

【异地访问本地DeepSeek】Flask+内网穿透,轻松实现本地DeepSeek的远程访问

写在前面&#xff1a;本博客仅作记录学习之用&#xff0c;部分图片来自网络&#xff0c;如需引用请注明出处&#xff0c;同时如有侵犯您的权益&#xff0c;请联系删除&#xff01; 文章目录 前言依赖Flask构建本地网页访问LM Studio 开启网址访问DeepSeek 调用模板Flask 访问本…

GPIO(嵌入式学习)

GPIO 通用输入输出口&#xff1a; 可分为八种输入输出模式 输出模式 下端可控制端口输出高低电平&#xff0c;用以驱动LED&#xff0c;控制蜂鸣器&#xff0c;模拟通信协议输出时序 输入模式 读取高低电平或电压&#xff0c;用与读取按键输入&#xff0c;外界模块电平信号…

AI助理精准匹配------助力快速搭建Stable Difussion图像生成应用

AI助理精准匹配------助力快速搭建Stable Difussion图像生成应用 背景信息搭建Stable Difussion图像生成应用释放资源 背景信息 过去你在阿里云社区搭建Stable Difussion图像生成应用&#xff0c;你可能还需要去在线实验室或者是官方文档去查找部署步骤&#xff0c;找到部署步…

火山引擎 DeepSeek R1 API 使用小白教程

一、火山引擎 DeepSeek R1 API 申请 首先需要三个要素&#xff1a; 1&#xff09;API Key 2&#xff09;API 地址 3&#xff09;模型ID 1、首先打开火山引擎的 DeepSeek R1 模型页面 地址&#xff1a;账号登录-火山引擎 2、在页面右下角&#xff0c;找到【推理】按钮&#…

排序算法(3):

这是我们的最后一篇排序算法了&#xff0c;也是我们的初阶数据结构的最后一篇了。 我们来看&#xff0c;我们之前已经讲完了插入排序&#xff0c;选择排序&#xff0c;交换排序&#xff0c;我们还剩下最后一个归并排序&#xff0c;我们今天就讲解归并排序&#xff0c;另外我们还…

【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码

&#x1f44b;hi&#xff0c;我不是一名外包公司的员工&#xff0c;也不会偷吃茶水间的零食&#xff0c;我的梦想是能写高端CRUD &#x1f525; 2025本人正在沉淀中… 博客更新速度 &#x1f44d; 欢迎点赞、收藏、关注&#xff0c;跟上我的更新节奏 &#x1f4da;欢迎订阅专栏…

微信小程序:完善购物车功能,购物车主页面展示,详细页面展示效果

一、效果图 1、主页面 根据物品信息进行菜单分类&#xff0c;点击单项购物车图标添加至购物车&#xff0c;记录总购物车数量 2、购物车详情页 根据主页面选择的项&#xff0c;根据后台查询展示到页面&#xff0c;可进行多选&#xff0c;数量加减等 二、代码 1、主页面 页…