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

49.多路转接epoll

select缺陷:支持的文件描述符数量太少
poll缺陷:poll随着用户fd的增多,效率会发生一定程度的降低

epoll核心定位:基于多个fd等待的就绪事件通知机制 --- 等,同select,poll

按照man手册说法:是为了处理大批量句柄(资源的代表,例如sockfd)而做了改进的poll

认识epoll接口

int epoll_create(int size);  // 创建一个epoll模型

  • size:弃用,epoll_create()  creates  a new epoll(7) instance.  Since Linux 2.6.8, the size argument is ignored, but must be greater than zero;see HISTORY.
  • 返回值:epoll的文件描述符

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *_Nullable event);  //用户告诉内核,你要帮我关心哪一个fd,上面的event事件

  • epfd:epoll_create的返回值文件描述符
  • op:操作,EPOLL_CTL_ADD,EPOLL_CTL_MOD,EPOLL_CTL_DEL(注:DEL只能移除合法的文件描述符
  • fd:需要关心的文件描述符
  • event:输入型参数,events表示用户告诉内核需要关心的事件
    events取值:
  • 返回值:成功0返回,失败-1返回,错误码指明错误(同非阻塞调用)

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); //内核通知用户:你让我关心的fd们,事件已经就绪(输出的就是已经就绪的,不需要轮询判断)

  • epfd:epoll文件描述符
  • events:输出型参数,用户定义的数组或申请好的一块内存
  • maxevents:events数组的长度
  • timeout:等待时间,同poll
  • 返回值:同select和poll

        特别注意:输出型参数struct epoll_event结构内uint32_t events是指的已经就绪的事件,和add时添加的有区别,因为可以添加WR,但只就绪了R,用的时候要判断。

        这就引出另一个问题,不是说底层红黑树和就绪队列的节点是共享的,如果这个就绪队列中events事件会被修改,那么底层红黑树节点(我epoll_ctl时add进去的)不是跟着变了?

        不会的,在epitem中,add的event和revents是分别存储的(rb_tree只关心event,就绪队列只关心revents)

epoll的原理

  • epoll_create创建一个epoll模型(红黑树、就绪队列、回调队列),得到epfd


            epoll_create -> 创建一个struct eventpoll(存放着红黑树、就绪队列)-> 对于每一个事件struct epitem(存放着其对应的 rb_node 和 就绪队列节点)-> 红黑树和就绪队列存的都是eptiem
  • epoll_ctl对于红黑树的增删改(用户拷贝到内核),本质:用户告诉内核,你要帮我关心哪个fd上的哪些事件
  • epoll_wait将就绪队列中的数据由内核拷贝到用户,本质:内核告诉用户,你要我关心的哪些fd对应的事件已经就绪了

        获取就绪事件时间复杂度:O(1)(只需要关心就绪队列即可)

        检测是否有就绪事件:O(1)(红黑树每一个节点fd都有与之对应的struct epoll_entry,里面都注册了回调机制,将来事件就绪,直接回调“激活”rb_node到就绪队列中,暂时)

        网络协议栈中,存在一种回调机制:底层,特定的fd上有数据就绪,自动进行回调:“激活”红黑树中的节点,链入到就绪队列中。

        所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系(每一个rb_node都有与之对应的回调关系,struct epoll_entry),当响应的事件发生时会调用这个回调方法(这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中)

  • 就绪队列,epoll本质:一个基于事件就绪的生产者消费者模型(epoll接口是线程安全的)
  • 获取就绪事件,如果缓冲区大小不够了怎么办?--- 不影响,没拿完,默认给你保留着,下次拿 -> 细节:内核会严格按照0下标开始,依次拷贝保存就绪事件和fd -> 应用层处理就绪事件的时候,处理的全部是就绪的,不用轮询判断是否合法了
  • epoll_ctl:1)在红黑树中插入节点  2)向底层回调注册回调方法

epoll内核结构

1)为什么epoll是和文件关联的?

        stuct file中的void *private_data指针指向struct event_poll对象(类比TCP中,private_data指向struct socket)

2)rb_tree和就绪队列用的是同一个结构struct epitem

epitem里面的struct epoll_filefd,事件对应的文件描述符

3)回调机制原理

        事件激活不需要遍历,每个红黑树节点都在驱动程序注册了回调机制,有对应红黑树节点的地址。

sys_epoll_ctl系统调用执行插入时,回调机制具体做什么 -> 将红黑树节点链入就绪队列

以读事件为例,当有数据到来,读事件就绪,回调机制的流程是什么?

暂留

echoserver --- epoll版本

https://gitee.com/its-quite-six/linux-remote/tree/master/25_9_30/1.EpollServer

Epoll的优点和缺点

epoll的优点:

  • 接口使用方便
  • 数据拷贝轻量:只在合适时调用EPOLL_CTL_ADD将文件描述符拷贝到内核中,这个操作不频繁(select/poll都是每次循环都要进行拷贝
  • 事件回调机制:避免使用遍历,而是精准使用回调函数,将就绪的文件描述符结构加入到就绪队列中(回调将红黑树节点链入到就绪队列中,时间复杂度O(1),不需要遍历,暂定)。epoll_wait返回直接访问就绪队列就知道哪些文件描述符就绪,时间复杂度O(1)(不会因为文件描述符增多,效率收到影响
  • 文件描述符数量无上限

        注意:struct epoll_event*是我们在用户空间分配好的内存,势必还是需要将内核的数据拷贝到用户空间的内存中的。

LT和ET理解

水平触发:有事件就绪,但没有处理(没有来得及处理),EPOLL模型会一直通知我们!

边缘触发:以读为例,从无到有,从有到多,变化了才通知,如果没有处理完,也不通知了

        举例:张三和李四是学校附近站点的快递员。他们平时的工作就是骑着三轮车去学生宿舍派发快递。

        张三是个老好人,他去派发快递的时候,如果三轮车的快递没有派发完,他就会一直等一直打电话给学生叫他下来拿。如果派发完了,恰好李四这时让他帮忙派发,李四快递放到他车上了,他会继续重复等和打电话,让学生下来拿直到拿完

        李四的性格恰好相反,他去派发快递的时候,打电话告诉学生,对学生统一说,我只给你打这一次电话,你不来拿我就走了,不拿完也是你的事,我不管。如果学生没有拿完,李四准备走了,这时张三让他帮忙派发,张三快递放他车上了,他会再打一次电话,说新快递来了,但我还是只打只一次。

        张三:LT模式,只要底层有报文,一直通知上层

        李四:ET模式,底层数据从无到有,从有到多(只通知一次)-> 因为从网络中拿到数据,导致底层数据变化时,才会通知上层 -> 即便上层没读完,也不通知 -> 倒逼上层,必须收到通知,把本轮数据取完。

        epoll的epoll_wait每次进行读取,底层就绪队列会被拿走,但是在下一次epoll_wait时,红黑树对应节点的回调队列里就绪事件依旧会触发,还是能“激活”对应节点(LT)

两种工作模式模拟了示波器的叫法。

ET+非阻塞理由

        ET通知就绪,用户必须把缓冲区本轮数据全部读完 -> 循环读取 -> recv的时候,万一读完,用户并不清楚底层缓冲区已经被读完了,recv还会被调用(细节点:recv可以通过返回值来判断是否还有数据,返回值小于缓冲区大小,那么就表示本次读完。但是如果刚好,buff大小是本轮数据大小的约数,用阻塞调用读取完下一次读取会被阻塞)-> ET模式,必须把fd设置为非阻塞的工作模式(ET+fd)

细节:

1)LT模式需要设置为非阻塞吗?可以,但也没有必要,recv一次一定不会被阻塞

2)不考虑返回值,即阻塞判断返回值的方法(有bug),无脑循环EAGAIN(非阻塞)

ET高效的原因

        关于ET的工作特性,LT也能实现 -> 但LT没有强制性,约束不了程序员 -> ET设置了,必须一次读完,不然代码就有bug

LT vs ET,高效:

1)ET通知效率更高:有效通知数量多

2)ET尽快读完所有的数据,可以给对方更新一个更大的win窗口,提高对方滑动窗口大小,提高网络发送的报文并发量(核心)

细节:

  • 接受缓冲区有低水位线这种设计,不是说只要有数据读事件能就绪。
  • 请求方可以在TCP报头设置标志位PSH,让接收方fd就绪。
  • ET模式倒逼程序员的本质:为给对方在概率上提供一个更大的应答win,提高TCP传输的效率

epoll使用场景

epoll的高性能, 是有一定的特定场景的. 如果场景选择的不适宜, epoll的性能可能适得其反.
        对于多连接, 且多连接中只有一部分连接比较活跃时, 比较适合使用epoll.
        例如, 典型的一个需要处理上万个客户端的服务器, 例如各种互联往APP的入口服务器, 这样的服务器就很适合epoll.

        如果只是系统内部, 服务器和服务器之间进行通信, 只有少数的几个连接, 这种情况下用epoll就并不合适. 具体要根据需求和场景特点来决定使用哪种IO模型.

http://www.dtcms.com/a/426881.html

相关文章:

  • flash网站源码下载北京网站建设案例
  • 景德镇做网站代理游戏平台赚钱吗
  • 开源AI大模型、AI智能名片与S2B2C商城小程序:用户需求满足的底层逻辑与实践路径
  • Git 命令配置别名、Git命令缩写(Mac版)
  • 部署Tomcat11.0.11(Kylinv10sp3、Ubuntu2204、Rocky9.3)
  • SCL-90症状自评量表抖音快手微信小程序看广告流量主开源
  • 合肥网站公司微信商城开发需要多少钱
  • 企业在拥抱 Al技术过程中面临安全风险是什么
  • Claude Sonnet 4.5:一次面向落地的常规升级(性能、安全、开发者工具)
  • Linux内核命名空间隔离机制在容器安全强化中的实战应用标准
  • 灵雀云六度入选 Gartner 中国 ICT 技术成熟度曲线报告,ACP以安全、稳定、智能三大核心能力定义企业级云原生数字底座
  • 中文的网站做不成二维码记事本做网站怎么改字体颜色
  • 近场通讯(NFC)在工厂智能化中的应用:从工具管理到身份识别
  • 计算机网络-RIP协议
  • Cortex-M3深入理解
  • python进阶_Day2
  • Python14-SVM⽀持向量机
  • 无锡做网站价格手机网站排名怎么做
  • 大模型价格战背后的技术革命:剖析DeepSeek-V3.2的DSA稀疏注意力
  • 用 AI 驱动 Unity 开发:Unity3d MCP
  • 微信小程序学习(四)
  • VIM和Linux命令速查表
  • TDengine 时序函数 STATECOUNT 用户手册
  • GitHub Spec Kit:官方规格驱动开发工具包深度解析
  • 商标图片大全 设计图网站过度优化的表现
  • 精读C++20设计模式——行动型设计模式:责任链
  • transformers音频实战01-音频概念
  • 方寸网站建设如何建立免费个人网站
  • Spring Boot 实战 Redis 分布式锁:从原理到高并发落地
  • nodejs做网站的弊端马来西亚网站后缀