Linux下的IO模型

阻塞与非阻塞IO(Input/Output)

阻塞与非阻塞IO(Input/Output)是计算机操作系统中两种不同的文件或网络通信方式。它们的主要区别在于程序在等待IO操作完成时的行为。

阻塞IO(Blocking IO)

在阻塞IO模式下,当一个线程发起IO请求(如读取数据或写入数据)时,它会一直等待直到IO操作完成。这意味着在等待数据的过程中,该线程不能执行其他任务,因此被称为“阻塞”。这种模式下,每个IO请求都需要一个独立的线程来处理,因为每个线程都会被阻塞直到IO操作完成。

优点:

  • 编程模型简单,易于理解和实现。
  • 对于IO操作较少的应用,资源消耗相对较小。

缺点:

  • 线程利用率低,因为线程在等待IO操作完成时不能执行其他任务。
  • 可扩展性差,随着并发IO请求的增加,需要更多的线程来处理,这会导致资源消耗增加

常见的阻塞IO函数:

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

非阻塞IO(Non-blocking IO)

在非阻塞IO模式下,当一个线程发起IO请求时,它会立即返回,不会等待IO操作完成。如果IO操作尚未完成,系统会返回一个特定的错误码(如EWOULDBLOCK)。线程可以继续执行其他任务,直到IO操作准备好,此时系统会通知线程,线程可以再次尝试进行IO操作

优点:

  • 线程可以处理多个IO请求,提高了线程的利用率。
  • 可扩展性好,适用于高并发的IO操作场景。

缺点:

  • 编程模型复杂,需要处理更多的错误码和状态检查。
  • 需要额外的机制(如事件循环、IO多路复用等)来管理多个IO请求

fcntl()函数:

函数头文件:
#include <unistd.h>
#include <fcntl.h>函数原型:
int fcntl(int fd, int cmd, ... /* arg */ );函数参数:
fd: 要操作的文件描述符
cmd: 一个命令代码,它指定了要执行的操作
arg: 一个可变参数,它的类型和值取决于 cmd 的值cmd命令:
F_DUPFD(int):复制文件描述符。指定新文件描述符的最小值
F_GETFD(void):获取文件描述符的状态。
F_SETFD(int):设置文件描述符的状态。可以设置 FD_CLOEXEC 标志
F_GETFL(void):获取文件描述符的状态标志。
F_SETFL(int):设置文件描述符的状态标志。常用的标志有 O_NONBLOCK 设置文件描述符为非阻塞模式 
和 O_APPEND设置文件描述符为追加模式。
F_GETLK(struct flock*):获取文件锁的状态。用于存储当前的锁信息
F_SETLK(struct flock*):设置文件锁。
F_SETLKW(struct flock*):设置文件锁,并等待锁就绪。
...函数返回值:
成功:
F_DUPFD:新的文件描述符
F_GETFD:文件描述符标志的值
F_GETFL:文件描述符状态的值 
...    
失败:返回-1,并设置错误原因

 在Linux中,可以通过将文件描述符设置为非阻塞模式来实现非阻塞IO。这可以通过fcntl()系统调用完成

int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);

IO多路复用 

基本思想:由内核来监控多个文件描述符是否可以进行I/O操作,如果有就绪的文件描述符,将结果 告知给用户进程,则用户进程在进行相应的I/O操作

IO多路复用的主要优点:

提高资源利用率:通过减少线程或进程的数量,降低了操作系统在线程上下文切换和维护线程状态上的开销。

提高系统性能:线程不需要在等待IO操作时被阻塞,而是可以继续执行其他任务,直到IO操作准备好。

可扩展性:可以处理大量的并发连接

IO多路复用的机制:

a.select多路复用I/O

在Linux中,select() 系统调用是一种实现IO多路复用的方法,它允许程序同时监视多个文件描述符,以确定是否有文件描述符已经准备好进行IO操作。这意味着程序可以等待多个输入或输出通道,当至少有一个通道可以进行操作时,select() 调用会返回。

函数描述:
函数头文件:
#include <sys/select.h>函数原型:
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);struct timeval {time_t      tv_sec;         /* seconds */suseconds_t tv_usec;        /* microseconds */};函数参数:
nfds:最大文件描述符加1
readfds:指向读文件描述符集合的指针。
如果某个文件描述符在返回时有数据可读,该描述符会在这个集合中被设置
writefds:指向写文件描述符集合的指针。
如果某个文件描述符在返回时可以无阻塞地写入数据,该描述符会在这个集合中被设置
exceptfds:指向异常文件描述符集合的指针。通常用于检测异常条件,如错误状态
timeout:指定等待时间,可以指定为NULL,表示无限期等待直到有文件描述符就绪函数返回值:
成功:返回已经就绪的文件描述符的个数。如果设置timeout,超时就会返回0
失败:-1,并设置errno确定错误原因
 操作文件描述符集合的宏定义:
从文件描述符集合set中移除文件描述符fd
void FD_CLR(int fd, fd_set *set);检查文件描述符fd是否在文件描述符集合set中
int  FD_ISSET(int fd, fd_set *set);
返回值:如果fd在集合set中,返回非零值;否则返回零。将文件描述符fd添加到文件描述符集合set中
void FD_SET(int fd, fd_set *set);初始化或清空文件描述符集合set
void FD_ZERO(fd_set *set);
工作原理:
  1. 内核监控: 当select()被调用时,内核会接管文件描述符集合,并开始监控这些文件描述符的状态。

  2. 数据准备: 当有网络数据到达或文件准备好被读取时,内核会检测到这些状态的变化。

  3. 通知进程: 一旦某个文件描述符就绪,内核会通知调用select()的进程。

  4. 返回就绪集合select()返回时,会返回就绪的文件描述符数量,并更新传递给它的文件描述符集合,以反映哪些文件描述符已经就绪。

  5. 用户空间处理: 应用程序在用户空间检查哪些文件描述符就绪,并进行相应的IO操作。

  6. 超时处理: 如果在指定的超时时间内没有任何文件描述符就绪,select()将返回0,表示超时。

 示例代码:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/select.h>#define NFDS 0 
int main()
{// 可读的文件描述符集合fd_set readfds;// 清空可读的文件描述符集合FD_ZERO(&readfds);// 添加要监控的文件描述符到集合中FD_SET(0,&readfds);// 设置超时时间struct timeval timeout = {.tv_sec=3,.tv_usec=0};int result;struct timeval timeout_bak;fd_set readfds_bak;while(1){timeout_bak = timeout;readfds_bak = readfds;int result=select(NFDS+1, &readfds_bak,NULL,NULL, &timeout_bak);if(result==-1){perror("select");exit(EXIT_FAILURE);}else if(result==0){printf("Overtime\n");}else if(result>0){for(int i=0;i<result;i++){if(FD_ISSET(0,&readfds)){char buf[128]={0};fgets(buf,sizeof(buf),stdin);printf("buf:%s\n",buf);}}}}
}
注意事项:
  • select()的所有文件描述符集合都需要在用户空间和内核空间之间复制,这可能会带来性能开销。
  • select()有一个限制,即可以监视的文件描述符数量通常有一个上限(通常是1024),这取决于具体的系统配置。
  • 超时时间在select()返回后可能会被修改,如果需要再次使用原来的超时时间,需要备份timeval结构体。

 b.poll多路复用I/O

select() 类似,但它没有文件描述符的数量限制。这意味着 poll() 可以处理任意数量的文件描述符,只要系统资源(如内存)允许。这使得 poll() 在处理大量并发连接时更为有效

poll():使用一个 pollfd 结构体数组来跟踪文件描述符和事件。每个 pollfd 结构体包含一个文件描述符、期望的事件(events)和实际发生的事件(revents)。这种方式使得 poll() 在处理大量文件描述符时更为高效,因为它直接操作文件描述符数组,而不需要复制整个集合

函数描述:

函数头文件:
#include <poll.h>函数原型:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);struct pollfd {int   fd;         /* file descriptor */short events;     /* requested events */short revents;    /* returned events */};函数参数:
fds:指向pollfd结构体数组的指针,该数组包含了需要监控的文件描述符。
nfds:fds 数组的长度。
timeout:超时时间,以毫秒为单位。如果设置为-1,则poll()会无限期等待;如果设置为0,则不会等待,立即返回fd:需要监控的文件描述符。
events:需要监控的事件类型,可以是POLLIN(可读)、POLLOUT(可写)、POLLERR(错误)、POLLHUP(挂起)等。
revents:实际发生的事件,由poll()函数填充函数返回值:
成功:返回revents就绪的文件描述符的数量,如果超时,返回0
失败:返回-1,并设置error错误原因

 工作原理:

  1. 监控多个文件描述符poll() 通过监控多个文件描述符的状态,允许程序在单个线程内处理多个输入输出源。这通过使用一个文件描述符集合来实现,其中每个文件描述符都可以设置为监控读、写或异常事件。

  2. 事件驱动poll() 采用事件驱动的方式工作。程序指定哪些事件(如可读、可写)感兴趣,poll() 则等待这些事件发生。当任何一个文件描述符上发生了感兴趣的事件时,poll() 调用返回。

  3. 水平触发(Level Triggered)poll() 默认的工作方式是水平触发(LT),这意味着只要文件描述符的状态没有改变,poll() 会持续报告该文件描述符就绪,即使数据已经被读取或写入。

  4. 超时处理: 如果在指定的超时时间内没有任何文件描述符就绪,poll()将返回0,表示超时。

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <poll.h>
#define MAX_FDS 10
int main()
{struct pollfd fds[MAX_FDS]={0};// 将标准输入文件描述符封装成struct pollfd结构体对象struct pollfd fd={.fd = 0,.events=POLLIN};fds[0]=fd;nfds_t nfds = 1;int result;while(1){result = poll(fds, nfds, 2000);if(result==-1){perror("poll");exit(EXIT_FAILURE);}else if(result == 0){printf("overtime\n");}else if(result>0){for(int i=0;i<nfds;i++){if(fds[i].revents == POLLIN){char buf[128]={0};fgets(buf,sizeof(buf),stdin);printf("buf:%s\n",buf);}}}}return 0;
}

c. epoll多路复用I/O

epoll相对于selectpoll有较大的不同,主要是针对前面两种多路复用 IO 接口的不足

epoll能够更有效地处理大量并发文件描述符,因为它不需要在每次调用时复制整个文件描述符集合,也不会受到文件描述符数量的限制

epoll 使用了两种主要的数据结构:红黑树和双向链表。

  • 红黑树epoll 在内核中使用红黑树来高效地管理文件描述符。每个注册到 epoll 的文件描述符都会存储在这颗树中,以便快速地进行查找、插入和删除操作。

  • 双向链表epoll 维护了一个就绪列表,这是一个双向链表,用于存储所有准备好进行 IO 操作的文件描述符。当一个文件描述符变得可读或可写时,它会被添加到这个链表中

事件通知机制:

  1. 回调机制(callback):当一个文件描述符的状态发生变化(例如,从不可读变为可读),epoll 会通过回调机制将这个事件添加到就绪列表中。这种机制避免了对所有注册的文件描述符进行轮询,从而提高了效率。

  2. 就绪列表epoll 维护了一个就绪列表,这是一个双向链表,用于存储所有准备好进行 IO 操作的文件描述符。当 epoll_wait() 被调用时,它只需检查这个就绪列表,而不是遍历整个文件描述符集合。

  3. 边缘触发(ET)模式:在边缘触发模式下,epoll 只在文件描述符的状态发生变化时通知应用程序。这意味着应用程序必须读取或写入所有可用的数据,直到遇到 EAGAIN 错误,否则该文件描述符不会被再次报告为就绪。

函数描述:

1.epoll创建函数
函数头文件:
#include <sys/epoll.h>函数原型:
int epoll_create(int size);函数参数:
size:这个参数是一个提示,告诉内核预计要监控的文件描述符数量
从Linux 2.6.8开始,这个参数被忽略,epoll_create可以处理的文件描述符数量只受限于系统资源,如内存和内核配置。函数返回值:
成功:返回一个非负的文件描述符
失败:返回-1,并设置errno以指示错误原因
ENOMEM(内存不足)
ENFILE(打开的文件数量超过限制)
2.epoll控制函数
函数头文件:
#include <sys/epoll.h>函数原型:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);typedef union epoll_data {void        *ptr;int          fd;uint32_t     u32;uint64_t     u64;} epoll_data_t;
struct epoll_event {uint32_t     events;   /* Epoll events *//* 监控的事件类型 */epoll_data_t data;     /* User data variable *//* 与文件描述符相关联的数据 */};函数参数:
epfd:由 epoll_create() 调用返回的 epoll 实例的文件描述符。
op:要执行的操作类型,可以是以下值之一:
EPOLL_CTL_ADD:添加新的文件描述符到epoll实例。
EPOLL_CTL_MOD:修改已经添加到epoll实例中的文件描述符的监控事件。
EPOLL_CTL_DEL:从epoll实例中删除文件描述符。
fd:要监控的文件描述符。
event:指向epoll_event结构体的指针,该结构体定义了文件描述符上感兴趣的事件events:指定文件描述符上感兴趣的事件类型,可以是以下值的组合:
EPOLLIN:文件描述符可读。
EPOLLOUT:文件描述符可写。
EPOLLERR:文件描述符发生错误。
EPOLLHUP:文件描述符被挂起。
EPOLLET:设置边缘触发模式(默认是水平触发模式)。
data:用于传递与文件描述符相关联的特定数据,可以是文件描述符本身或其他自定义数据函数返回值:
成功时:返回0
失败时:返回-1,并设置errno以指示错误原因
3.epoll等待函数
函数头文件:
#include <sys/epoll.h>函数原型:
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);函数参数:
epfd:由epoll_create()调用返回的epoll实例的文件描述符。
events:指向epoll_event结构体数组的指针,该数组用于从内核接收发生的事件。
maxevents:events数组的最大长度,即可以返回的最大事件数量。
timeout:等待时间,单位为毫秒。如果设置为-1,则无限期等待直到至少有一个文件描述符就绪;如果设置为 0,则不等待,立即返回。函数返回值:
成功:返回就绪的文件描述符的数量,即 events 数组中填充的事件数量
失败:返回-1,并设置errno以指示错误原因
EBADF(无效的文件描述符)
EFAULT(无效的内存访问)
EINTR(被信号打断)...
​

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>#define MAX_FDS 10
#define MAX_EVENTS 10
int main()
{//创建epoll使用 epoll_create 函数int epfd = epoll_create(1);if(epfd==-1){perror("epoll_create");exit(EXIT_FAILURE);}//添加文件描述符使用 epoll_ctl 函数将文件描述符注册到epollstruct epoll_event event;event.events = EPOLLIN; // 监听可读事件event.data.fd = 0; // 关联文件描述符int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &event);if(ret == -1){perror("epoll_ctl");exit(EXIT_FAILURE);}//等待 使用 epoll_wait 函数等待事件int nfds;struct epoll_event events[MAX_FDS];while(1){nfds = epoll_wait(epfd, events,MAX_EVENTS,2000);if(nfds==-1){perror("epoll_wait");exit(EXIT_FAILURE);}else if(nfds == 0){printf("overtime\n");}else if(nfds>0){// epoll_wait返回就绪文件描述符的个数for(int i=0;i<nfds;i++){if(events[i].data.fd==0){char buf[128]={0};fgets(buf,sizeof(buf),stdin);printf("buf:%s\n",buf);}}}}return 0;
}

结语:

无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力

 

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

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

相关文章

详细介绍:API 和 SPI 的区别

文章目录 Java SPI (Service Provider Interface) 和 API (Application Programming Interface) 的区别详解目录1. 定义和目的1.1 API (Application Programming Interface)1.2 SPI (Service Provider Interface) 2. 使用场景2.1 API 的应用场景2.2 SPI 的应用场景 3. 加载和调…

PyGWalker:让你的Pandas数据可视化更简单,快速创建数据可视化网站

1、PyGWalker应用: 在数据分析的过程中,数据的探索和可视化是至关重要的环节,如何高效地将分析结果展示给团队、客户,甚至是公众,是很多数据分析师和开发者面临的挑战,接下来介绍的两大工具组合——PyGWalker与Streamlit,可以帮助用户轻松解决这个问题,即使没有复杂的代…

调用智谱AI,面试小助手Flask简单示例

文章目录 1.接入AI获取API密钥Python代码 2.小助手的实现流程3.Flask应用示例Python文件.pyindex.html运行Flask应用地址栏输入 http://localhost:5000/ 1.接入AI 获取API密钥 在智谱AI的官方网站上注册&#xff0c;右上角点击API密钥&#xff0c;新建并复制一个 API Key&…

AI不可尽信

看到某项目有类似这样的一段代码 leaves : make([]int, 10) leaves leaves[:0]没理解这样的连续两行,有何作用? 初始化一个长度和容量都为10的切片,接着把切片长度设置为0 即如下demo: (在线地址) package mainimport "fmt"func main() {leaves : make([]int, 1…

MongoDB-aggregate流式计算:带条件的关联查询使用案例分析

在数据库的查询中&#xff0c;是一定会遇到表关联查询的。当两张大表关联时&#xff0c;时常会遇到性能和资源问题。这篇文章就是用一个例子来分享MongoDB带条件的关联查询发挥的作用。 假设工作环境中有两张MongoDB集合&#xff1a;SC_DATA&#xff08;学生基本信息集合&…

Flask-2

文章目录 请求全局钩子[hook]异常抛出和捕获异常abort 主动抛出HTTP异常errorhandler 捕获错误 context请求上下文(request context)应用上下文(application context)current_appg变量 两者区别&#xff1a; 终端脚本命令flask1.0的终端命令使用自定义终端命令 flask2.0的终端命…

⌈ 传知代码 ⌋ 将一致性正则化用于弱监督学习

&#x1f49b;前情提要&#x1f49b; 本文是传知代码平台中的相关前沿知识与技术的分享~ 接下来我们即将进入一个全新的空间&#xff0c;对技术有一个全新的视角~ 本文所涉及所有资源均在传知代码平台可获取 以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦&#x…

2款.NET开源且免费的Git可视化管理工具

Git是什么&#xff1f; Git是一种分布式版本控制系统&#xff0c;它可以记录文件的修改历史和版本变化&#xff0c;并可以支持多人协同开发。Git最初是由Linux开发者Linus Torvalds创建的&#xff0c;它具有高效、灵活、稳定等优点&#xff0c;如今已成为软件开发领域中最流行…

如何使用EventChannel

文章目录 1 知识回顾2 示例代码3 经验总结我们在上一章回中介绍了MethodChannel的使用方法,本章回中将介绍EventChannel的使用方法.闲话休提,让我们一起Talk Flutter吧。 1 知识回顾 我们在前面章回中介绍了通道的概念和作用,并且提到了通道有不同的类型,本章回将其中一种…

使用Apifox创建接口文档,部署第一个简单的基于Vue+Axios的前端项目

前言 在当今软件开发的过程中&#xff0c;接口文档的创建至关重要&#xff0c;它不仅能够帮助开发人员更好地理解系统架构&#xff0c;还能确保前后端开发的有效协同。Apifox作为一款集API文档管理、接口调试、Mock数据模拟为一体的工具&#xff0c;能够大幅度提高开发效率。在…

我为什么决定关闭ChatGPT的记忆功能?

你好&#xff0c;我是三桥君 几个月前&#xff0c;ChatGPT宣布即将推出一项名为“记忆功能”的新特性&#xff0c;英文名叫memory。 这个功能听起来相当吸引人&#xff0c;宣传口号是让GPT更加了解用户&#xff0c;仿佛是要为我们每个人量身打造一个专属的AI助手。 在记忆功…

用Arduino单片机读取PCF8591模数转换器的模拟量并转化为数字输出

PCF8591是一款单芯片&#xff0c;单电源和低功耗8位CMOS数据采集设备。博文[1]对该产品已有介绍&#xff0c;此处不再赘述。但该博文是使用NVIDIA Jetson nano运行python读取输入PCF8591的模拟量的&#xff0c;读取的结果显示在屏幕上&#xff0c;或输出模拟量点亮灯。NVIDIA J…

Ubuntu下Kafka安装及使用

Kafka是由Apache软件基金会开发的一个开源流处理平台&#xff0c;同时也是一个高吞吐量的分布式发布订阅消息系统。它由Scala和Java编写&#xff0c;具有多种特性和广泛的应用场景。 Kafka是一个分布式消息系统&#xff0c;它允许生产者&#xff08;Producer&#xff09;发布消…

docker 部署nacos

目录 一、拉取镜像 二、部署 三、访问&#xff08;默认是用内嵌数据库&#xff09; 四、配置 五、重启容器 一、拉取镜像 docker pull nacos/nacos-server 二、部署 docker run --name nacos -d -p 8848:8848 -p 9848:9848 -p 9849:9849 --restartalways --privilegedt…

软考鸭微信小程序:助力软考备考的便捷工具

一、软考鸭微信小程序的功能 “软考鸭”微信小程序是一款针对软考考生的备考辅助工具&#xff0c;提供了丰富的备考资源和功能&#xff0c;帮助考生提高备考效率&#xff0c;顺利通过考试。其主要功能包括&#xff1a; 历年试题库&#xff1a;小程序内集成了历年软考试题&…

加油站智能视频监控预警系统(AI识别烟火打电话抽烟) Python 和 OpenCV 库

加油站作为存储和销售易燃易爆油品的场所&#xff0c;是重大危险源之一&#xff0c;随着科技的不断发展&#xff0c;智能视频监控预警系统在加油站的安全保障方面发挥着日益关键的作用&#xff0c;尤其是其中基于AI的烟火识别、抽烟识别和打电话识别功能&#xff0c;以及其独特…

云服务架构与华为云架构

目录 1.云服务架构是什么&#xff1f; 1.1 云服务模型 1.2 云部署模型 1.3 云服务架构的组件 1.4 云服务架构模式 1.5 关键设计考虑 1.6 优势 1.7 常见的云服务架构实践 2.华为云架构 2.1 华为云服务模型 2.2 华为云部署模型 2.3 华为云服务架构的核心组件 2.4 华…

实时语音交互,打造更加智能便捷的应用

随着人工智能和自然语言处理技术的进步&#xff0c;用户对智能化和便捷化应用的需求不断增加。语音交互技术以其直观的语音指令&#xff0c;革新了传统的手动输入方式&#xff0c;简化了用户操作&#xff0c;让应用变得更加易用和高效。 通过语音交互&#xff0c;用户可以在不…

Label-Studio ML利用yolov8模型实现自动标注

引言 Label Studio ML 后端是一个 SDK&#xff0c;用于包装您的机器学习代码并将其转换为 Web 服务器。Web 服务器可以连接到正在运行的 Label Studio 实例&#xff0c;以自动执行标记任务。我们提供了一个示例模型库&#xff0c;您可以在自己的工作流程中使用这些模型&#x…

基于SpringCloud的微服务架构下安全开发运维准则

为什么要进行安全设计 微服务架构进行安全设计的原因主要包括以下几点&#xff1a; 提高数据保护&#xff1a;微服务架构中&#xff0c;服务间通信频繁&#xff0c;涉及到大量敏感数据的交换。安全设计可以确保数据在传输和存储过程中的安全性&#xff0c;防止数据泄露和篡改。…