彻底搞懂 select/poll/epoll,就这篇了
wptr33 2024-12-14 15:35 18 浏览
之前已经把网络 I/O 相关要点都盘了,还剩 select/poll/epoll 这几个区别没说,这篇就来搞搞它们,并且是从完全理解原理的角度来区分它们。
本来是要上源码的,但是感觉没啥必要,身为应用开发者我觉得理解原理就行了,源码反正看了就忘了,理解才是最重要!所以我就尽量避免代码且用大白话来盘一盘这三个玩意。
话不多说,发车。
小思考
首先,我们知道 select/poll/epoll 是用来实现多路复用的,即一个线程利用它们即可 hold 诸多个 socket。
按照这个思路,线程不可能被任何一个被管理的 Socket 阻塞,且任一个 Socket 来数据之后都得告知 select/poll/epoll 线程。
想想看,这应该如何实现呢?
我们拿 select 的逻辑来分析下
按照我们的理解,select 管理多个 Socket 的模型如下图所示:
这里要注意一下内核态和用户态的交互,用户程序访问不了内核空间。
所以,我们调用 select 会把所有要管理的 socket 的 fd (文件描述符,Linux下皆为文件,简单理解就是通过 fd 能找到这个 socket)传到内核中。
此时,要遍历所有 socket,看看是否有感兴趣的事件发生。如果没有一个 socket 有事件发生,那么 select 一上线程就需要让出 cpu 阻塞等待,这个等待可以是不设置超时时间的死等,也可以是设置 timeout 的有超时时间的等待。
假设此时客户端发送了数据,网卡接收到的数据塞到对应的 socket 的接收队列中,此时 socket 知道来数据了,那如何唤醒 select 呢?
其实每个 socket 有个属于自己的睡眠队列,select 会安排一个内应,即在被管理的 socket 的睡眠队列里面塞入一个 entry。
当 socket 接收到网卡的数据后,就会去它的睡眠队列里遍历 entry,调用 entry 设置的 callback 方法,这个 callback 方法里就能唤醒 select !
所以 select 在每个被它管理的人 socket 每个睡眠队列里都塞入一个与它相关的 entry,这样不论哪个 socket 来数据了,它立马就能被唤醒然后干活!
但是,select 的实现不太好,因为唤醒的 select 此时只知道来活了,并不知道具体是哪个 socket 来数据了,所以只能傻傻地遍历所有 socket ,看看到底是哪个 scoket 来活了,然后把所有来活的 socket 封装成事件返回。
这样用户程序就能获得发生的事件,然后进行 I/O 和业务处理了。
这就是 select 的实现逻辑,理解起来应该不难。
这里再提一嘴 select 的限制,因为被管理的 socket fd 需要从用户空间拷贝到内核空间,为了控制拷贝的大小而做了限制,即每个 select 能拷贝的 fds 集合大小只有1024。
然后要改的话只能修改宏..再重新编译内核。网上很多文章都是这样说的,但是(没错有个但是)。
我看了一篇文章,确实有这个宏愿,值也是 1024,但内核根本没有限制 fds 集合的大小。然后托人问了个内核大佬,大佬说内核确实没做限制,glibc那层做了。
所以..重新编译内核?那篇文章放在文末。
poll
poll 这玩意相比于 select 主要就是优化了 fds 的结构,不再是 bit 数组了,而是一个叫 pollfd 的玩意,反正就是不用管啥 1024 的限制了。
不过现在也没人用 poll,我就不多说了。
epoll
这个就是重点了。
相信看了 select 的确,我们稍微思考下,就能想出几个可以优化的点。
比如,为什么每次 select 需要把监控的 fds 传输到内核里?不能在内核里维护么?
为什么 socket 只唤醒 select,不能告诉它是哪个 socket 来数据了?
epoll 主要就是基于上面两点做了优化。
首先,搞了个叫 epoll_ctl 的方法,这方法就是用来管理维护 epoll 所监控的那些 socket。
如果你的 epoll 要新加一个 socket 来管理,那就调用 epoll_ctl,要删除一个 socket 也调用 epoll_ctl,通过不同的入参来控制增删改。
这样,在内核里面就维护了此 epoll 管理的 socket 集合,这样就不用每次调用的时候都得把所有管理的 fds 拷贝到内核了。
对了,这个 socket 集合是用红黑树实现的。
然后和 select 类似,每个 socket 的睡眠队列里都会加个 entry,当每个 socket 来数据之后,同样也会调用 entry 对应的 callback。
与 select 不同的是,引入了一个 ready_list 双向链表,callback 里面会把当前的 socket 加入到 ready_list 然后唤醒 epoll。
这样被唤醒的 epoll 只需要遍历 ready_list 即可,这个链表里一定是有数据可读的 socket,相比于 select 就不会做无用的遍历了。
同时收集到的可读的 fd 按理是要拷贝到用户空间的,这里又做了个优化,利用了 mmp,让用户空间和内核空间映射到同一块内存中,这样就避免了拷贝。
完美啊~
这就是 epoll 基于 select 所作的优化,还有一些差别没细说,比如 epoll 是阻塞睡眠在一个 single_epoll_wait_list 而不是 socket 的睡眠队列等等,我就不提了,理解上面的这些已经够了。
ET<
都谈到 epoll 了,避免不了要扯扯 ET 和 LT 两个模式。
ET,边沿触发。
按照上面的逻辑就是 epoll 遍历 ready_list 的时候,会把 socket 从 ready_list 里面移除,然后读取这个 scoket 的事件。
而 LT,水平触发,有点不一样。
在这个模式下 epoll 遍历 ready_list 的时候,会把 socket 从 ready_list 里面移除,然后读取这个 scoket 的事件,如果这个 socket 返回了感兴趣的事件,那么当前这个 socket 会再被加入到 ready_list 中,这样下次调用 epoll_wait 的时候,还能拿到这个 socket。
这就是这两者最本质的区别了。
看到这有人会问,这两种模式的使用会造成哪种不一样的结果?
如果此时一个客户端同时发来了 5 个数据包,按正常的逻辑,只需要唤醒一次 epoll ,把当前 socket 加一次到 ready_list 就行了,不需要加 5 次。然后用户程序可以把 socket 接收队列的所有数据包都读完。
但假设用户程序就读了一个包,然后处理报错了,后面不读了,那后面的 4 个包咋办?
如果是 ET 模式,就读不了了,因为没有把 socket 加入到 ready_list 的触发条件了。除非这个客户端发了新的数据包过来,这样才会再把当前 socket 加入到 ready_list,在新包过来之前,这 4 个数据包都不会被读到。
而 LT 模式不一样,因为每次读完有感兴趣的事件发生之后,会把当前 socket 再加入到 ready_list,所以下次肯定能读到这个 socket,所以后面的 4 个数据包会被访问到,不论客户端是否发送新包。
至此,我想你应该理解什么是 ET ,什么是 LT 了,而不用对着一些什么状态变更触发这些不易理解的名词而发晕。
最后
好了,今天的分析到此完毕,我个人觉得对 select/poll/epoll 的理解到这个程度就差不多了,当然还有很多细节,需要自行去看源码探究,问我我也不懂,这些都是阅读网上的源码分析文章得出的结论。
我也不建议读的那么深,毕竟人的精力有限对吧,有涉及到相关底层优化的时候,再去研究也不迟。
相关推荐
- redis的八种使用场景
-
前言:redis是我们工作开发中,经常要打交道的,下面对redis的使用场景做总结介绍也是对redis举报的功能做梳理。缓存Redis最常见的用途是作为缓存,用于加速应用程序的响应速度。...
- 基于Redis的3种分布式ID生成策略
-
在分布式系统设计中,全局唯一ID是一个基础而关键的组件。随着业务规模扩大和系统架构向微服务演进,传统的单机自增ID已无法满足需求。高并发、高可用的分布式ID生成方案成为构建可靠分布式系统的必要条件。R...
- 基于OpenWrt系统路由器的模式切换与网页设计
-
摘要:目前商用WiFi路由器已应用到多个领域,商家通过给用户提供一个稳定免费WiFi热点达到吸引客户、提升服务的目标。传统路由器自带的Luci界面提供了工厂模式的Web界面,用户可通过该界面配置路...
- 这篇文章教你看明白 nginx-ingress 控制器
-
主机nginx一般nginx做主机反向代理(网关)有以下配置...
- 如何用redis实现注册中心
-
一句话总结使用Redis实现注册中心:服务注册...
- 爱可可老师24小时热门分享(2020.5.10)
-
No1.看自己以前写的代码是种什么体验?No2.DooM-chip!国外网友SylvainLefebvre自制的无CPU、无操作码、无指令计数器...No3.我认为CS学位可以更好,如...
- Apportable:拯救程序员,IOS一秒变安卓
-
摘要:还在为了跨平台使用cocos2d-x吗,拯救objc程序员的奇葩来了,ApportableSDK:FreeAndroidsupportforcocos2d-iPhone。App...
- JAVA实现超买超卖方案汇总,那个最适合你,一篇文章彻底讲透
-
以下是几种Java实现超买超卖问题的核心解决方案及代码示例,针对高并发场景下的库存扣减问题:方案一:Redis原子操作+Lua脚本(推荐)//使用Redis+Lua保证原子性publicbo...
- 3月26日更新 快速施法自动施法可独立设置
-
2016年3月26日DOTA2有一个79.6MB的更新主要是针对自动施法和快速施法的调整本来内容不多不少朋友都有自动施法和快速施法的困扰英文更新日志一些视觉BUG修复就不翻译了主要翻译自动施...
- Redis 是如何提供服务的
-
在刚刚接触Redis的时候,最想要知道的是一个’setnameJhon’命令到达Redis服务器的时候,它是如何返回’OK’的?里面命令处理的流程如何,具体细节怎么样?你一定有问过自己...
- lua _G、_VERSION使用
-
到这里我们已经把lua基础库中的函数介绍完了,除了函数外基础库中还有两个常量,一个是_G,另一个是_VERSION。_G是基础库本身,指向自己,这个变量很有意思,可以无限引用自己,最后得到的还是自己,...
- China's top diplomat to chair third China-Pacific Island countries foreign ministers' meeting
-
BEIJING,May21(Xinhua)--ChineseForeignMinisterWangYi,alsoamemberofthePoliticalBureau...
- 移动工作交流工具Lua推出Insights数据分析产品
-
Lua是一个适用于各种职业人士的移动交流平台,它在今天推出了一项叫做Insights的全新功能。Insights是一个数据平台,客户可以在上面实时看到员工之间的交流情况,并分析这些情况对公司发展的影响...
- Redis 7新武器:用Redis Stack实现向量搜索的极限压测
-
当传统关系型数据库还在为向量相似度搜索的性能挣扎时,Redis7的RedisStack...
- Nginx/OpenResty详解,Nginx Lua编程,重定向与内部子请求
-
重定向与内部子请求Nginx的rewrite指令不仅可以在Nginx内部的server、location之间进行跳转,还可以进行外部链接的重定向。通过ngx_lua模块的Lua函数除了能实现Nginx...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
因果推断Matching方式实现代码 因果推断模型
-
git pull命令使用实例 git pull--rebase
-
git pull 和git fetch 命令分别有什么作用?二者有什么区别?
-
面试官:git pull是哪两个指令的组合?
-
git 执行pull错误如何撤销 git pull fail
-
git fetch 和git pull 的异同 git中fetch和pull的区别
-
git pull 之后本地代码被覆盖 解决方案
-
还可以这样玩?Git基本原理及各种骚操作,涨知识了
-
git命令之pull git.pull
-
- 最近发表
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)