当前位置: 首页 > ds >正文

【Linux高级IO】多路转接之epoll

多路复用之epoll

  • 一,认识epoll
  • 二,epoll的相关接口
    • 1. epoll_create
    • 2. epoll_ctl
    • 3. epoll_wait
  • 三,epoll的原理
  • 四,epoll的两种工作模式(ET和LT)
    • 1. 两种工作模式
    • 2. 对比ET和LT
  • 五,总结

在了解到select和poll的缺点后,我们现在来看看epoll。epoll虽然是改进后的poll,但是在底层已经完全不一样了。

一,认识epoll

epoll有三个系统调用,但是在使用上还是非常的方便,下面来直接看看epoll的接口

二,epoll的相关接口

1. epoll_create

使用 epoll 时先调用这个,用于创建一个 epoll模型,返回值是个 文件描述符,至于原理我们原理部分解释

 int epoll_create(int size);

这里要注意一下:

  1. 自从linux2.6.8之后,size参数是被忽略的.
  2. 用完之后, 必须调用close()关闭

2. epoll_ctl

再创建完 epoll 句柄后,就要来进行事件注册了。这个接口是用户告诉内核,关心哪个 fd 的哪个 events事件,op 参数表示对 fd 的操作,epfdepoll_create 的返回值。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

对于第二个参数的取值:

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

第三个参数:
epoll特有的数据类型:

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 */
};

这个 epoll_event 结构中有两个,一个是events表示要关心的哪个事件,另一个
poll_data_t data 是一个联合体,里面可以存放文件描述符等,只不过是提供给用户用的。

events是下面几个宏的集合:

  • EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);
  • EPOLLOUT : 表示对应的文件描述符可以写;
  • EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来);
  • EPOLLERR : 表示对应的文件描述符发生错误;
  • EPOLLHUP : 表示对应的文件描述符被挂断;
  • EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.
  • EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要再次把这个socket加入到EPOLL队列里

3. epoll_wait

epoll这里用户到内核,内核到用户是两个独立的接口,所以这个接口是内核告诉用户,你要我关心的这些fd的哪些事件就绪了

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  1. 我们在使用时需要定义一个 struct epoll_event结构的数组,然后传入这个接口。epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)
  2. maxevents 是告诉内核这个 events 有多大,这个 maxevents 的值不能大于创建 epoll_create时 的 size
  3. 参数timeout是超时时间 (毫秒,0会立即返回,-1是永久阻塞)
  4. 返回值和poll一样

三,epoll的原理

知道了epoll的一些接口后,我们来看看epoll的原理

首先,OS是知道底层网卡上是否有数据的,当网卡有数据时,会向OS发送硬件中断,而selectpoll不知道,因为遍历的是 fd 对应的 struct file 中是否有数据

当epoll_create时,内核中会创建一个红黑树,红黑树中每一个节点都表示用户要求关心的fd上的事件。同时还会维护一个就绪队列,每一个节点表示内核告诉用户,要求关心的哪些fd上的事件已经就绪。

也就是说epoll_create时,内核中会创建一个epoll模型,创建一个红黑树和就绪队列

同时,在网卡驱动层,会有一个回调机制,用来直接检测网卡的数据

  1. 当网卡有数据时,确认是否关心
  2. 在就绪队列中,形成新节点,表示哪个 fd 上的哪个事件已经就绪

其次,应用层面上 epoll_ctl 就是在向底层的红黑树通过 op 参数进行增删改,和 selectpoll 自己维护的数组类似,只不过 epoll 是在内核中维护不用上层自己维护。

epoll_wait 就是向就绪队列中拿取已经就绪的节点

  • epoll_wait检测底层是否有数据,时间复杂度为O(1)
  • epoll_wait获取所有就绪事件的时间复杂度是O(N),必然的,因为要遍历就绪队列

细节1:
如果epoll_wait是传入的数组满了怎么办? 满了就会返回,同时就绪队列会保存就绪的事件节点,只要队列有节点,就绪事件就会一直产生,epoll_wait继续取就可以

细节2:
epoll_wait的返回值表示 所有的返回事件会严格按照顺序放在events中,返回值表示就绪个数

那么关于epoll_create的返回值?其实就是文件描述符!

epoll模型 也就是一个 数据结构 ,里面指向了红黑树和就绪队列和注册的回调,叫eventpoll

  • 那么一个进程可以用epoll_create,其他进程也可以,所以OS对这些eventpoll要进行先描述再组织
  • 另一方面,linux下一切皆文件,当进程创建 epoll模型 时,和文件操作一样,该进程的文件描述符表中会分配文件描述符给 epoll ,其 struct file结构体 中就会指向 eventpoll 。这样就和其他文件一样对 epoll 进行了管理

在这里插入图片描述

总结一下, epoll的使用过程就是:

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

四,epoll的两种工作模式(ET和LT)

1. 两种工作模式

epoll的工作模式就是 epoll 给用户提供的事件就绪通知的策略

epoll有2种工作模式:

  • 水平触发(LT)
  • 边缘触发(ET)

epoll在默认下是LT模式
LT模式(默认):就是在epoll时,底层事件就绪后,epoll就会一直通知上层 ,
ET模式:底层有数据只通知一次,直到下次数据发生变化(收到新数据)再通知

2. 对比ET和LT

ET模式比更高效,为什么?

  • 因为在ET通知策略中,没有无效的通知,LT可能会重复通知
  • ET只通知一次,就会倒逼上层把本轮的数据取完,这样就会导致tcp底层给发送方通告一个更大的窗口,使发送方滑动窗口更大,那么从概率上提高双方通信效率

如何保证ET模式下,把fd缓冲区数据全部取完?
循环读取,直到读完,但是读完后不想阻塞,就要采用非阻塞读取
所以ET模式下必须以非阻塞状态进行IO

假设有下面一个场景:服务器接受到一个10k的请求, 会向客户端返回一个应答数据. 如果客户端收不到应答, 不会发送第二个10k请求

在这里插入图片描述

如果服务端写的代码是阻塞式的read, 并且一次只 read 1k 数据的话(read不能保证一次就把所有的数据都读出来,参考 man 手册的说明, 可能被信号打断), 剩下的9k数据就会待在缓冲区中
在这里插入图片描述
此时由于 epoll 是ET模式, 并不会认为文件描述符读就绪. epoll_wait 就不会再次返回. 剩下的 9k 数据会一直在缓冲区中. 直到下一次客户端再给服务器写数据. epoll_wait 才能返回。
但是服务端只有在读取完10K后才会给客户端响应,客户端收不到响应,也就不会再次发送请求,那么服务端也不会继续读取。
在这里插入图片描述
所以, 为了解决上述问题(阻塞read不一定能一下把完整的请求读完), 于是就可以使用非阻塞轮训的方式来读缓冲区,保证一定能把完整的请求都读出来。
如果是LT没这个问题. 只要缓冲区中的数据没读完, 就能够让 epoll_wait 返回文件描述符读就绪。

五,总结

学习完了多路转接epoll,我们就可以做到让一个进程或者线程去同时处理多个IO,那么在对于服务器高性能的要求场景下使用epoll就是个很好的选择。

http://www.xdnf.cn/news/6312.html

相关文章:

  • Linux——mysql主从复制与读写分离
  • 人工智能+ERP:政策新规下企业智能化转型路径
  • 【vue】axios网络请求介绍
  • 【2025版】Spring Boot面试题
  • C语言_自定义类型:结构体
  • (4)python开发经验
  • (十七)Java日期时间API全面解析:从传统Date到现代时间处理
  • Ros2 - Moveit2 - DeepGrasp(深度抓握)
  • golang -- 如何让main goroutine等一等
  • 数智驱动——AI:企业数字化转型的“超级引擎”
  • FreeRTOS学习笔记
  • 【Java学习笔记】finalize方法
  • 前后端分离博客 Weblog 项目实战
  • 【AI大模型】赋能【传统业务】
  • Java基础语法之数组
  • Windows下Docker安装portainer
  • 64. 最小路径和
  • Shell 脚本中的通道号(文件描述符)
  • maven项目, idea右上角一直显示gradle的同步标识, 如何去掉
  • 计算机网络:什么是计算机网络?它的定义和组成是什么?
  • 开源Heygem本地跑AI数字人视频教程
  • python使用matplotlib画图
  • IDEA编辑器设置的导出导入
  • why FPGA喜欢FMC子卡?
  • Vue3学习(组合式API——计算属性computed详解)
  • 使用Word2Vec算法实现古诗自动生成实战
  • Linux514 rsync 解决方案环境配置
  • 2025年渗透测试面试题总结-360[实习]安全工程师(题目+回答)
  • 三维CAD皇冠CAD(CrownCAD)建模教程:工程图模块二
  • 52页PPT | 企业数字化转型L1-L5数据架构设计方法论及案例数字化转型解决方案数字化规划方案