Su的技术博客

  • 首页
  • Java
  • MySQL
  • DDD
  • 事故复盘
  • 架构方案
  • Other
  • 工具
  • 打赏
  • 关于
  1. 首页
  2. Java
  3. 正文
                           

【原创】笔记08 | 搜狗面试题:IO多路复用之select、poll、epoll的区别

2021-05-30 32点热度 0人点赞 0条评论






I/O多路复用(multiplexing)的本质是通过一种机制(系统内核缓冲I/O数据),让单个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作。

01

select


int select (int n, fd_set *readfds, fd_set *writefds,                    fd_set *exceptfds, struct timeval *timeout);
// fd_set 结构体简化为:typedef struct{ long int fds_bits[32];}fd_set;

select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。


select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。

缺点:

1、 单个进程可监视的fd数量被限制,即能监听端口的大小有限。

      一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.

2、 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低:

当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。

3、需要维护一个用来存放大量fd的数据结构,每次调用select时把fd集合从用户态拷贝到内核态,这样会使得用户空间和内核空间在传递该结构时复制开销大。


02

poll


int poll (struct pollfd *fds, unsigned int nfds, int timeout);
struct pollfd { int fd; /* file descriptor */ short events; /* requested events to watch */ // 请求监视的事件 short revents; /* returned events witnessed */ // 返回发生的事件};
和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。
它没有最大连接数的限制,原因是它是基于链表来存储的。
缺点:

1、大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。                   

2、poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。


LT模式:level trigger。当epoll_wait检测到描述符事件发生并将此事件通知应用程序,

应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。

ET模式:edge trigger。当epoll_wait检测到描述符事件发生并将此事件通知应用程序,

应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。


03

epoll


int epoll_create(int size);int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

epoll_create:创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。参数size并不是限制了epoll所能监听的描述符最大个数,只是对内核初始分配内部数据结构的一个建议。

epoll_ctl:对指定描述符fd执行op操作。

-epfd:是epoll_create()的返回值。

-op操作:对应宏:添加EPOLL_CTL_ADD,删除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD,对应添加、删除和修改对fd的监听事件。

- fd:是需要监听的fd(文件描述符)。

- epoll_event:是告诉内核需要监听什么事件(读、写事件等)。

epoll_wait:等待epfd上的io事件,最多返回maxevents个事件。

-events:用来从内核得到事件的集合,

-maxevents:告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,

-timeout:是超时时间。


epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。LT模式下,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作,而在ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无论fd中是否还有数据可读。所以在ET模式下,read一个fd的时候一定要把它的buffer读光,也就是说一直读到read的返回值小于请求值,或者遇到EAGAIN错误。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。


epoll为什么要有EPOLLET触发模式?


如果采用EPOLLLT模式的话,系统中一旦有大量你不需要读写的就绪文件描述符,它们每次调用epoll_wait都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率.。而采用EPOLLET这种边沿触发模式的话,当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符。

epoll优点:

1、没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口);

2、效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;

      即Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。

3、 内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。

04

区别



0、底层数据结构
select:数组,poll:链表,epoll:红黑树。

1、支持一个进程所能打开的最大连接数
select 单个进程所能打开的最大连接数有FD_SETSIZE宏定义,其大小是32个整数的大小(在32位的机器上,大小就是32*32,同理64位机器上FD_SETSIZE为32*64),当然我们可以对进行修改,然后重新编译内核,但是性能可能会受到影响,这需要进一步的测试。
poll本质上和select没有区别,但是它没有最大连接数的限制,原因是它是基于链表来存储的。
epoll 虽然连接数有上限,但是很大,1G内存的机器上可以打开10万左右的连接,2G内存的机器可以打开20万左右的连接。

2、FD剧增后带来的IO效率问题
select/poll 因为每次调用时都会对连接进行线性遍历,所以随着FD的增加会造成遍历速度慢的“线性下降性能问题”。
epoll 因为epoll内核中实现是根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll没有前面两者的线性下降的性能问题,但是所有socket都很活跃的情况下,可能会有性能问题。

3、消息传递方式
select/poll 内核需要将消息传递到用户空间,都需要内核拷贝动作。
epoll通过内核和用户空间共享一块内存来实现的。

select、poll与epoll之间的区别总结图:

历史背景:

1)select出现是1984年在BSD里面实现的。

2)14年之后也就是1997年才实现了poll,其实拖那么久也不是效率问题, 而是那个时代的硬件实在太弱,一台服务器处理1千多个链接简直就是神一样的存在了,select很长段时间已经满足需求 。

3)2002, 大神 Davide Libenzi 实现了epoll。


参考资料:

https://www.cnblogs.com/Anker/p/3265058.html

https://www.cnblogs.com/aspirant/p/9166944.html

https://www.cnblogs.com/dhcn/p/12731883.html


笔记系列

笔记 | Java对象探秘
笔记 | JVM内存区域结构:一计两栈一堆一区

笔记 | 面试官问我高并发的问题:并发编程的三大挑战

笔记 | 面试官问我:TCP与UDP的区别
笔记 | 网络编程基础:TCP如何保证可靠性
笔记 | 面试又挂了,只因问了:TCP三次握手和四次挥手
笔记 | 5种网络IO模型
回复公众号【资料】获得干货资料集锦:技术ppt、IT大会资料、架构、分布式资料等。
推荐好文

1、互联网Code Review最佳实践分享

2、dubbo面试题!会这些,说明你看懂了dubbo源码

3、Kafka面试题!掌握它才说明你真正懂Kafka
4、
Netty 5.0为啥被舍弃?原因竟然是...
5、
中台之上——业务架构系列【汇总】

6、必备瑞士军刀IDEA插件,你使用了哪些

7、线上热更新代码只需3步 Arthas实战

8、Eureka源码剖析之七:架构&面试题【总结】

9、互联网工程师应该用这种姿势打印日志

-关注搬运工来架构,与优秀的你一同进步-

原创不易,如果喜欢这篇文章可以点在看哦↘


本文仅供学习!所有权归属原作者。侵删!文章来源: 搬运工来架构

标签: 原创 面试题 多路复用 搜狗 笔记 IO
最后更新:2023-02-19

Cocodroid

专注Java后端,分享技术。

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

最新 热点 推荐
最新 热点 推荐
殷浩详解DDD 第四讲:领域层设计规范 既生@Resource,何生@Autowired? Go整洁架构实践 接口优化的常见方案实战总结 QQ音乐高可用架构体系 构建一个布隆过滤器 —— Building a Bloom filter
殷浩详解DDD 第四讲:领域层设计规范Redis为什么这么快?构建一个布隆过滤器 —— Building a Bloom filterQQ音乐高可用架构体系接口优化的常见方案实战总结Go整洁架构实践
Eureka源码剖析之三:服务拉取 笔记 | 网络编程基础:TCP如何保证可靠性 猪八戒网DevOps之Java组件安全检测 Go整洁架构实践 线上问题处理案例1:出乎意料的数据库连接池 全链路压测之影子库及ShardingSphere实现影子库源码剖析

@Autowired (1) @Resource (1) API网关 (1) ddd (6) DP (1) ElasticSearch (1) eureka (7) go (1) HTTP (1) IDEA (1) iOS (1) Java (8) JSR (1) QQ音乐 (1) repository (1) Spring (1) SQL优化 (1) 代理 (1) 依赖注入 (1) 同城双活 (1) 垃圾回收 (1) 定时任务 (1) 容灾 (1) 布隆过滤器 (1) 异地双活 (1) 接口优化 (1) 故障转移 (1) 数据库 (2) 整洁架构 (1) 文件网关 (1) 方案 (2) 服务续约 (1) 注册中心 (7) 流水账 (1) 流量 (1) 第五 (1) 线上案例 (1) 线上问题 (2) 缓存 (1) 缓存击穿 (1) 编译 (3) 网络 (3) 聊聊 (1) 订单 (1) 设计规范 (1) 详解 (1) 连接池 (1) 限流 (1) 领域驱动设计 (4) 高可用 (1)

COPYRIGHT © 2014-2023 verysu.com . ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

粤ICP备15033072号-2