IO多路转接poll和epoll

news2025/1/11 0:13:22

文章目录

    • poll
    • poll和select的区别
    • poll的底层实现方式
    • 什么是epoll
    • epoll执行原理
    • epoll接口
      • struct epoll_event是什么结构
    • epoll的两种触发方式
    • epoll底层实现
    • epoll总结

poll

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

在这里插入图片描述

参数:

  • fds是一个poll函数监听的结构列表.也就是说,这是一个数组指针,数组的每个元素是struct pollfd类型。 每一个元素中, 包含了三部分内容: 文件描述符, 监听的事件集合, 返回的事件集合.(这个数组类似于select里的存储fd的数组,只不过里面多包含了需要关心的事件,events相当于用户告诉内核,revent相当于内核告诉用户)
  • nfds表示fds数组的长度.
  • timeout表示poll函数的超时时间, 单位是毫秒(ms)

返回值:

  • 返回值小于0, 表示出错;
  • 返回值等于0, 表示poll函数等待超时;
  • 返回值大于0, 表示poll由于监听的文件描述符就绪而返回

poll和select的区别

那么可以看的出来:

  1. 操作系统只会对events进行检测而不进行改动,而是将输出放在revents中,就达到了输入输出的分离;而select中的是用户和操作系统都关心同一个输入输出,即输入输出放在同一个地方,所以在输入的时候要改一次,输出的时候也要改一次。

  2. poll的可监听文件描述符无限制,因为它能监听多少是取决于用户自己定义的结构体数组的大小,这个结构体数组是动态定义所以无大小限制;而select有限制,因为它能监听多少fd是取决于位图的大小,位图是有最大值限制的。

  3. 最大的区别其实还是这个存储fd的数组,有限制和无限制。

poll的底层实现方式

为什么很多地方都说poll是基于链表实现,而此时查看手册又看见它是基于数组的呢?

这是因为我们用户级别看到的并且传入的参数fds就是数组,但是到了操作系统层面,会用一些手段将数组的元素用链表连接起来,形成一个新的链表。

这样的好处是:链表可以动态地插入或者删除元素,虽然访问元素这方面来说数组更胜一筹,但是对于底层来说,前者的操作会更多,所以链表会更好。

再有就是,内核不需要自己去维护一个数组的大小,只需要维护好链表的头指针即可。

什么是epoll

假设你是一家电商平台的客服人员,每天需要处理数千个用户的咨询和请求。你需要实时地回答用户的问题、处理订单、解决客户投诉等。这个过程中,你能同时处理的客户请求有限,因为人的处理速度是有限的。

这时,你可以将每一个用户的请求看作是一个连接,在处理用户请求时,可以采用以下两种不同的方式:

  1. 传统方式(select/poll):你会不断地询问每一个用户是否有新的问题或请求,等待他们的回应。这样做会造成你需要不停地轮询每一个用户,即使他们没有新的请求。这种方式效率低下,容易出现资源浪费。

  2. 使用 epoll 的方式:你注册所有用户的连接到 epoll 实例中,并指定你关心的事件类型,比如等待用户发送消息的事件(EPOLLIN)。当用户发送消息到来时,epoll 实例会通知你去处理这个事件。这样,你只需要在事件发生时进行处理,而不需要主动地轮询所有连接。这种方式可以高效地处理大量并发连接,只关注活跃的事件,减少了不必要的资源消耗。

epoll执行原理

epoll最重要的就是三个接口:

调用epoll_create创建一个epoll句柄;
调用epoll_ctl, 将要监控的文件描述符进行注册;
调用epoll_wait,等待文件描述符就绪

和两个数据结构:

红黑树和链表

  • epoll执行原理

在这里插入图片描述

epoll接口

  1. 创建:

随着创建的对象eventpoll产生,红黑树和就绪队列建立。

int epoll_create(int size)
  • 作用:创建一个 epoll 实例,并返回一个用于操作 epoll 的文件描述符。
  • 参数 size:指定创建的 epoll 实例能同时监视的最大文件描述符数量。该参数在新版本的内核中已经忽略,但仍需传递一个大于0的值以保证兼容性。
  • 返回值:成功时返回一个非负整数,表示生成的 epoll 文件描述符;失败时返回-1,并设置errno为相应的错误码。

用完之后, 必须调用close()关闭

  1. 注册:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型

这个函数其实就是类似对红黑树的操作。 将被监听的描述符添加到红黑树或从红黑树中删除或者对监听事件进行修改

作用:用于向指定的 epoll 实例中注册、修改或删除文件描述符对应的事件。

  • 参数

参数epfd:指定需要进行操作的 epoll 实例的文件描述符,也就是epoll_create的返回值。

参数 op:表示动作,用三个宏来表示:

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

参数 fd:需要添加、修改或删除的文件描述符。是需要监听的fd

参数 event:指向 epoll_event 结构体的指针,用于描述事件的类型和其他属性。

struct epoll_event是什么结构

简而言之,它就是在用户空间和内核空间之间传递事件信息的数据结构。它用于描述注册的文件描述符上发生的事件类型以及与事件相关联的数据。

epoll_event结构:

struct epoll_event {
    uint32_t events;  // 表示注册的事件类型
    epoll_data_t data;  // 与事件相关联的数据,可以是文件描述符或指针
};

其中,epoll_data_t结构:

typedef union epoll_data {
    void *ptr; // 用于存储与事件关联的指针数据
    int fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;
  • 返回值:成功时返回0;失败时返回-1,并设置errno为相应的错误码。
  1. 等待:
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

作用:等待事件的发生,当有事件发生时,将就绪的文件描述符从 epoll 实例中返回。

  • 参数 epfd:指定需要等待事件的 epoll 实例的文件描述符。

参数 events:指向 epoll_event 结构体数组的指针,用于保存就绪的文件描述符及其对应的事件。

参数 maxevents:指定 events 数组的容量,即最多可以返回多少个就绪的事件。

参数 timeout:指定等待的超时时间,以毫秒为单位。设置为-1时表示无限等待,直到有事件发生;设置为0时表示立即返回,不阻塞;设置为正整数时表示等待指定的毫秒数。

  • 返回值:

返回就绪的事件数目,若超时时间到达且没有任何事件发生,则返回0;若发生错误,则返回-1,并设置errno为相应的错误码。

epoll的两种触发方式

select和poll只支持LT工作模式,epoll的默认的工作模式是LT模式。

epoll底层实现

epoll总结

  1. 内核数据结构: 在内核中,epoll 使用了三个主要的数据结构来管理事件和文件描述符:
  • epoll_event 结构体: 在用户空间和内核空间之间传递事件信息的数据结构。它用于描述注册的文件描述符上发生的事件类型以及与事件相关联的数据。
  • 红黑树: 使用红黑树数据结构来存储被监视的文件描述符和事件。红黑树可以快速查找、插入和删除节点,保持树的平衡性。
  • 就绪链表: 当文件描述符上的事件就绪时,将其添加到就绪链表中,以便在用户空间进行处理。
  1. 注册与管理: 用户通过 epoll_create 系统调用创建一个 epoll 实例,返回一个文件描述符。然后使用 epoll_ctl 系统调用来注册需要监视的文件描述符,并指定感兴趣的事件类型。内核会将注册的文件描述符和相关事件信息添加到内部的数据结构中。
  2. 事件触发与通知: 当监视的文件描述符上的事件就绪时,内核会将就绪的事件添加到就绪链表中,并通知用户空间。用户可以使用 epoll_wait 系统调用来等待就绪事件的发生。这样,用户不再需要不断地轮询所有的文件描述符,而是只关注已经就绪的事件,提高了事件的处理效率。
  3. 边缘触发与水平触发: epoll 支持两种工作模式:边缘触发(Edge-Triggered,EPOLLET)和水平触发(Level-Triggered,默认模式)。边缘触发模式仅在状态变化时通知事件,而水平触发模式会在文件描述符上的事件还未处理完之前持续通知事件。
  4. 高效的事件批量获取: epoll_wait 系统调用支持一次性获取多个就绪事件,通过传递一个事件数组来指定返回的事件集合。这种批量的事件获取方式,减少了系统调用的次数,提高了效率。

总的来说,epoll 的底层实现利用了内核中的数据结构、红黑树和就绪链表,以及事件触发和通知机制,实现了高效的大规模并发连接管理和事件处理能力。这些设计和优化使得 epoll 在处理高并发情况下表现出色,并成为 Linux 系统中常用的事件通知机制。

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

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

相关文章

大数据(八):Pandas的基础应用详解(五)

专栏介绍 结合自身经验和内部资料总结的Python教程&#xff0c;每天3-5章&#xff0c;最短1个月就能全方位的完成Python的学习并进行实战开发&#xff0c;学完了定能成为大佬&#xff01;加油吧&#xff01;卷起来&#xff01; 全部文章请访问专栏&#xff1a;《Python全栈教…

MySQL表的CURD

CRUD : Create(创建), Retrieve(读取)&#xff0c;Update(更新)&#xff0c;Delete&#xff08;删除&#xff09; Create 语法 INSERT [INTO] table_name [(column [, column] ...)] VALUES (value_list) [, (value_list)] ... value_list: value, [, value] ... 示例&#x…

第 2 章 线性表(学生健康登记表实现)

1. 示例代码 1) status.h /* DataStructure 预定义常量和类型头文件 */#ifndef STATUS_H #define STATUS_H/* 函数结果状态码 */ #define TRUE 1 /* 返回值为真 */ #define FALSE 0 /* 返回值为假 */ #define RET_OK 0 /* 返回值正确 */ #define INFEASI…

Datax抽取mysql的bit类型数据

背景&#xff1a;使用datax抽取mysql的一张表&#xff0c;里面有两个bit类型的字段&#xff0c;抽取出来显示如下&#xff1a; 需要在抽取reader里面进行处理配置 最终生成的datax的json文件reader的配置会转换为具体的数值 最终查询效果&#xff1a;

python1图书点评推荐系统的设计与实现vue

本次设计任务是要设计一个图书点评网&#xff0c;通过这个系统能够满足用户对图书信息下载、收藏功能。系统的主要功能包括&#xff1a;首页、个人中心、用户管理、图书分类管理、图书信息管理、留言板管理、系统管理等功能。 管理员可以根据系统给定的账号进行登录&#xff0c…

新版edge浏览器读取谷歌浏览器上的历史记录

上一篇&#xff1a;(3条消息) 新版edge浏览器读取谷歌浏览器上的历史记录_learningbilibili的博客-CSDN博客https://blog.csdn.net/learningbilibili/article/details/123662218 关于上次的读取历史记录的问题是现在的edge浏览器最近的版本更新后出现了每次启动时从 Google Chr…

为IT服务台构建自定义Zia操作

Zia是manageengine的商业人工智能助手&#xff0c;是ServiceDesk Plus Cloud的虚拟会话支持代理。使用Zia&#xff0c;您可以优化帮助台管理&#xff0c;还可以缩小最终用户与其帮助台之间的差距&#xff0c;Zia通过执行预配置的操作来帮助用户完成他们的服务台任务。 例如&…

【Linux】线程池读写锁

文章目录 线程池应用场景线程池原理构造线程池代码实现 读写锁应用场景读写锁的三种状态读写锁的接口初始化接口销毁接口以读模式加锁以写模式加锁解锁接口 常见问题 乐观锁/悲观锁乐观锁悲观锁自旋锁 线程池 应用场景 线程池不仅要提高程序运行效率&#xff0c;还要提高程序…

读SQL学习指南(第3版)笔记13_读后总结与感想兼导读

1. 基本信息 SQL学习指南(第3版) Learning SQL, Third Edition [美] 艾伦博利厄 &#xff08;Alan Beaulieu&#xff09; 人民邮电出版社,2022年4月出版 1.1. 读薄率 书籍总字数424千字&#xff0c;笔记总字数25969字。 读薄率25969424000≈6.13% 1.2. 读厚方向 SQL入门经…

探索多线程编程:线程的本质、状态和属性

目录 什么是线程线程状态新建线程可运行线程阻塞和等待线程终止线程 线程属性优先级线程名守护线程中断线程未捕获异常的处理器 在现代计算机编程中&#xff0c;多线程是一个重要而强大的概念。它使得我们能够更有效地利用多核处理器、提高程序性能并实现并发操作。 什么是线程…

docker从零部署jenkins保姆级教程(下)

上一篇文章&#xff0c;我们完成了以下工作。 1)、docker部署jenkins 2)、建立第一个jenkins job 3)、通过jenkins job自动编译构建我们的github项目 上面所做的3个工作&#xff0c;其实都是为了这一篇文章打基础&#xff0c;不管是部署docker还是部署jenkins&#xff0c;我们最…

d435i 相机和imu标定

一、IMU 标定 使用 imu_utils 功能包标定 IMU&#xff0c;由于imu_utils功能包的编译依赖于code_utils&#xff0c;需要先编译code_utils&#xff0c;主要参考 相机与IMU联合标定_熊猫飞天的博客-CSDN博客 Ubuntu20.04编译并运行imu_utils&#xff0c;并且标定IMU_学无止境的…

补码:将减法运算转化为另一种形式的加法运算

文章目录 解析 个人见解&#xff0c;如有错误&#xff0c;请多包涵。 解析 对于人来说&#xff0c;减法是简单容易的。 被减数和减数列式相减&#xff0c;从低位到高位分别计算&#xff0c;有需要的借位就可以了。 这是一种可以在计算机上成立的理论方案&#xff0c;但是由于…

SpringMVC常用注解介绍及参数传递说明

前言 上一篇文章介绍了SpringMVC是什么以及它的工作流程和核心组件&#xff0c;介绍入门示例时&#xff0c;提到了RequestMapping注解&#xff0c;那么这篇文章就来介绍SpringMVC中更多的常用的注解&#xff0c;以及它的参数传递。 一. SpringMVC常用注解 1.1 RequestParam …

Homebrew安装cocoapods: zsh: command not found: brew解决方法

问题描述: 通过Homebrew安装cocoapods时,输入命令行 brew install cocoapods出现如下报错: zsh: command not found: brew zsh:找不到命令&#xff1a;brew 问题解决: 使用以下命令,重新安装Homebrew. /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/ra…

永恒之黑_CVE-2020-0796漏洞复现

永恒之黑&#xff1a;CVE-2020-0796漏洞复现 目录 永恒之黑&#xff1a;CVE-2020-0796漏洞复现漏洞介绍漏洞影响范围漏洞复现1.环境准备2.复现过程 漏洞介绍 本漏洞源于SMBv3没有正确处理压缩的数据包&#xff0c;在解压数据包的时候使用客户端传过来的长度进行解压时&#xf…

机器学习——boosting之提升树(未完)

提升树和adaboost基本流程是相似的 我看到提升树的时候&#xff0c;懵了 这…跟adaboost有啥区别&#xff1f;&#xff1f;&#xff1f; 直到看到有个up主说了&#xff0c;我才稍微懂 相当于&#xff0c;我在adaboost里的弱分类器&#xff0c;换成CART决策树就好了呗&#xff1…

springboot + activiti实现activiti微服务化

概述 本文介绍如何将springbootactiviti进行整合,并配合eureka,zuul和feign实现activiti的微服务化,将流程控制和业务逻辑分离. 并实现了几个比较特殊的功能,比如时间段委托(某人请假或出差,出差时间内,所有待办交给被委托人处理),比如节点的无限级加签功能(流程本身有不确定性…

php 获取今日、昨日、上周、本月的起始时间戳和结束时间戳的方法非常简单

php 获取今日、昨日、上周、本月的起始时间戳和结束时间戳的方法&#xff0c;主要使用到了 php 的时间函数 mktime。下面首先还是以示例说明如何使用 mktime 获取今日、昨日、上周、本月的起始时间戳和结束时间戳&#xff0c;然后在介绍一下 mktime 函数作用和用法。非常简单哦…

vue3组件通信学习笔记

1、Prop 父组件 <template><div class"parent"><h1>我是父元素</h1><Child :msg"msg"></Child></div> </template><script setup> import Child from ./Child.vue let msg ref(我是父组件的数据…