IO多路转接poll和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 系统中常用的事件通知机制。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

久菜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值