【Linux】I/O多路复用模型 select、poll、epoll

2024-06-21 03:44

本文主要是介绍【Linux】I/O多路复用模型 select、poll、epoll,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

创作不易,本篇文章如果帮助到了你,还请点赞 关注支持一下♡>𖥦<)!!
主页专栏有更多知识,如有疑问欢迎大家指正讨论,共同进步!
🔥Linux系列专栏:Linux基础 🔥

给大家跳段街舞感谢支持!ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ

在这里插入图片描述


目录

    • 多路:
    • 复用:
  • 一、select 模型
  • 二、poll 模型
  • 三、epoll 模型
    • 水平触发模式 EPOLLLT
    • 边缘触发模式 EPOLLET
    • 示例

在监听 socket 时,需要分配多个线程/进程维护多个 socket 连接,I/O 多路复用技术就是用来使用一个进程来监听维护多个 socket 连接

多路:

I/O 状态:可读、可写

复用:

使用一个线程/进程监听处理 I/O 事件,复用多个 socket 请求


一、select 模型

select 原理:

  • 1.创建一个文件描述符集合 fd_set set 0 不监听 1 监听

  • 2.设置文件描述符的状态

    • FD_ZERO(&set); 初始化监听集合,将位码初始化为 0
    • FD_SET(int sockfd,&set); 将 sockfd 在集合中对应的位设置成 1
    • FD_CLR(int sockfd,&set); 将 sockfd 在集合中对应的位设置成 0
    • int code = FD_ISSET(int sockfd,&set); 返回 sockfd 在文件描述符集合中的位码
  • 3.调用 select 函数,传入文件描述符集合 set


select 函数:

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

参数:

  • nfds: 需要监视的最大文件描述符值加 1
  • readfds:监听读事件,设为 NULL 不监听
  • writefds:监听写事件,设为 NULL 不监听
  • errorfds:异常事件
  • timeout:阻塞时间,设为 NULL 为阻塞监听,定义 timeval 结构体将成员设置为 0 为非阻塞。定时阻塞支持微妙级别定时

返回值:返回就绪的 socket 数量

select 函数会将整个文件描述符集合 set 拷贝到内核中,让内核检查是否有读/写事件,内核遍历文件描述符集合,
如果有事件发生,内核将对应的 socket 标记位可读/可写,然后将整个文件描述符集合 set 拷贝回用户空间,用户再遍历文件描述符集合找到就绪的 socket 进行后续处理


select 的缺点:

  • 1.Linux 平台受最大文件描述符数量限制,select 的最大监听数为 1024
  • 2.select 监听就绪后只返回就绪的数量,需要用户遍历找到就绪的 socket
  • 3.监听集合会在监听到就绪后被修改,需要用户自行分离传入传出设置
  • 4.轮询监听,轮询数量大, CPU 处理性能下降
  • 5.拷贝和挂载开销大
  • 6.设置监听不灵活,无法对不同的 socket 设置不同的监听类型

二、poll 模型

poll 监听事件种类更丰富,对监听和就绪数组进行了传入传出分离

poll 不受最大文件描述符数量限制,支持用户自定义长度结构体数组作为集合

#include <poll.h>int ready = poll(struct pollfd* fds, int nfds, int timeout);

参数:

  • fds:监听数组
    • struct pollfd listen_array[4096]; //监听数组
    • listen_array[0].fd = sockfd; //监听 sockfd,-1 取消监听
    • listen_array[0].events = POLLIN|POLLOUT|POLLERR; //设置监听事件
    • listen_array[0].revents; //如果监听的 socket 就绪,系统将就绪事件传到 revents 中
  • nfds:监听数组大小
  • timeout:阻塞时间,-1 为阻塞监听,0 为非阻塞。>0 定时阻塞 只支持毫妙级定时

poll 的缺点

  • 1.轮询监听,轮询数量大, CPU 处理性能下降
  • 2.拷贝和挂载开销大
  • 3.只有特定的 Linux 版本才能使用

三、epoll 模型

结合了 select 和 poll 的优势

创建监听树(创建于内核中,使用红黑树):

#include <sys/epoll.h>
int epfd = epoll_create(int size);

参数:size 为监听数量。返回值:指向监听树的描述符。


监听树节点

struct epolevent node;
node.data.fd = sockfd;
node.events = EPOLLIN|EPOLLOUT|EPOLLERR;

epoll 监听到就绪直接返回就绪节点,用户遍历处理这些就绪 socket 即可


向监听树中添加节点:

epoll_ctl(int epfd,EPOLL_CTL_ADD,int sockfd,struct epollevent* node);

在监听树中删除节点:

epoll_ctl(int epfd,EPOLL_CTL_DEL,int sockfd,NULL);

修改监听树中的节点(修改监听的事件):

epoll_ctl(int epfd,EPOLL_CTL_MOD,int sockfd,&node);

监听事件发生:

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

参数:

  • epfd:监听树
  • events:监听事件数组
  • maxevents:最大就绪数大小
  • timeout:阻塞时间,-1 为阻塞监听,0 为非阻塞。>0 定时阻塞

epoll 的优点:

  • 没有监听数量限制,不用担心轮询问题
  • epoll 使用了事件驱动的方式,监听集合在内核层,避免了每次调用时的重复设置和遍历操作。内核会将就绪的文件描述符直接返回
  • 监听事件种类更丰富,可以对不同的 socket 设置不同的事件监听,对监听和就绪数组进行了传入传出分离


水平触发模式 EPOLLLT

  • 当可读或可写事件发生时,epoll 会持续通知用户处理直到用户处理。
  • 以在任何时候处理这些通知,不必立即响应
  • 水平触发适用于处理数据量较大或需要缓冲数据的情况,因为可以确保用户不会错过任何数据

多个事件同时发生可能导致重复处理,可以使用 EPOLLONESHOT设置为只触发一次通知


边缘触发模式 EPOLLET

  • 当可读或可写事件发生时,epoll 会立即通知(只会通知 1 次)
  • 应用程序需要在收到通知后立即处理相应的 I/O 事件,否则可能会丢失事件
  • 边缘触发适用于处理大量并发连接且数据量较小的情况,可以减少不必要的系统调用和上下文切换。

边缘触发模式一般和非阻塞 I/O 搭配使用,直到系统调用(如 read 和 write)返回错误,错误类型为 EAGAIN 或 EWOULDBLOCK


示例

    //创建监听树并将serversock设置监听读事件if((epfd = epoll_create(EPOLL_MAX)) == -1){perror("epoll_initializer: create epoll tree failed");}struct epoll_event node;node.data.fd = sockfd;node.events = EPOLLIN;//添加节点epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&node);int readycode;struct epoll_event readyarray[EPOLL_MAX];int i = 0;int flag = 1;while(flag){if((readycode = epoll_wait(epfd,readyarray,EPOLL_MAX,-1)) == -1){perror("epoll_wait failed");}else{i = 0;//根据readycode就绪数量处理就绪while(readycode){//判断就绪if (readyarray[i].data.fd == server_sockfd){//server sock ready//添加tcp连接任务}else{//client sock ready//添加响应处理任务}--(readycode);++i;}}}

在这里插入图片描述

大家的点赞、收藏、关注将是我更新的最大动力! 欢迎留言或私信建议或问题。
大家的支持和反馈对我来说意义重大,我会继续不断努力提供有价值的内容!如果本文哪里有错误的地方还请大家多多指出(●'◡'●)

这篇关于【Linux】I/O多路复用模型 select、poll、epoll的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1080118

相关文章

Linux卸载自带jdk并安装新jdk版本的图文教程

《Linux卸载自带jdk并安装新jdk版本的图文教程》在Linux系统中,有时需要卸载预装的OpenJDK并安装特定版本的JDK,例如JDK1.8,所以本文给大家详细介绍了Linux卸载自带jdk并... 目录Ⅰ、卸载自带jdkⅡ、安装新版jdkⅠ、卸载自带jdk1、输入命令查看旧jdkrpm -qa

Linux samba共享慢的原因及解决方案

《Linuxsamba共享慢的原因及解决方案》:本文主要介绍Linuxsamba共享慢的原因及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux samba共享慢原因及解决问题表现原因解决办法总结Linandroidux samba共享慢原因及解决

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

新特性抢先看! Ubuntu 25.04 Beta 发布:Linux 6.14 内核

《新特性抢先看!Ubuntu25.04Beta发布:Linux6.14内核》Canonical公司近日发布了Ubuntu25.04Beta版,这一版本被赋予了一个活泼的代号——“Plu... Canonical 昨日(3 月 27 日)放出了 Beta 版 Ubuntu 25.04 系统镜像,代号“Pluc

基于Flask框架添加多个AI模型的API并进行交互

《基于Flask框架添加多个AI模型的API并进行交互》:本文主要介绍如何基于Flask框架开发AI模型API管理系统,允许用户添加、删除不同AI模型的API密钥,感兴趣的可以了解下... 目录1. 概述2. 后端代码说明2.1 依赖库导入2.2 应用初始化2.3 API 存储字典2.4 路由函数2.5 应

Linux安装MySQL的教程

《Linux安装MySQL的教程》:本文主要介绍Linux安装MySQL的教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux安装mysql1.Mysql官网2.我的存放路径3.解压mysql文件到当前目录4.重命名一下5.创建mysql用户组和用户并修

Linux上设置Ollama服务配置(常用环境变量)

《Linux上设置Ollama服务配置(常用环境变量)》本文主要介绍了Linux上设置Ollama服务配置(常用环境变量),Ollama提供了多种环境变量供配置,如调试模式、模型目录等,下面就来介绍一... 目录在 linux 上设置环境变量配置 OllamPOgxSRJfa手动安装安装特定版本查看日志在

Linux系统之主机网络配置方式

《Linux系统之主机网络配置方式》:本文主要介绍Linux系统之主机网络配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、查看主机的网络参数1、查看主机名2、查看IP地址3、查看网关4、查看DNS二、配置网卡1、修改网卡配置文件2、nmcli工具【通用

Linux系统之dns域名解析全过程

《Linux系统之dns域名解析全过程》:本文主要介绍Linux系统之dns域名解析全过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、dns域名解析介绍1、DNS核心概念1.1 区域 zone1.2 记录 record二、DNS服务的配置1、正向解析的配置

Linux修改pip和conda缓存路径的几种方法

《Linux修改pip和conda缓存路径的几种方法》在Python生态中,pip和conda是两种常见的软件包管理工具,它们在安装、更新和卸载软件包时都会使用缓存来提高效率,适当地修改它们的缓存路径... 目录一、pip 和 conda 的缓存机制1. pip 的缓存机制默认缓存路径2. conda 的缓