上一篇文章我们了解了Unix标准的5种网络I/O模型,知道了它们的核心区别与各自的优缺点。尤其是I/O多路复用模型,在高并发场景下,有着非常好的优势。而Netty也采用了I/O多路复用模型。
那Netty是如何实现I/O多路复用的呢?
Netty实际上也是一个封装好的框架,它的本质上还是使用了Java的NIO包(New IO,不是网络I/O模型的NIO,Nonblocking IO)包,Java NIO包里面使用了I/O多路复用。
所以,本文作为一个 前置知识 + 高频面试题 章节(手动狗头),一起来深入了解下I/O多路复用模型吧。
本文预计阅读时间 5分钟,将重点回答以下两个问题:
1.I/O多路复用模式的实现
这是我们上一篇讲I/O多路复用使用的图,可以再回顾一下I/O多路复用模型。
多个的进程的IO可以注册到一个复用器(selector)上,然后用一个进程调用select,select会监听所有注册进来的IO。
举个例子。
在BIO模式中,一个老师(应用进程/线程)只能同时处理一个同学(IO流)的问题。如果有10个同学,就需要配置10个老师来做一对一的讲解。
在IO多路复用模型中。我们给 老师 配置了一个 班长(复用器Selector)。班长 负责观察班级里的10个同学谁要提问,一旦有同学举手,班长就反馈老师去处理这个举手同学的问题。
这样一来,只需要1个老师,老师 只需要注意 班长 的反馈,就能及时处理对应的 同学 的问题了。
下面我们具体来看看I/O多路复用的三种实现:select、poll、epoll。
2. select
Linux系统提供了一个函数select来供开发者使用select多路复用机制。
该函数的作用是:
通过轮询,可以同时监视多个文件描述符是否发生了读、写、异常这三类IO事件。
最后返回发生IO事件的文件描述符数量,以及读事件、写事件、异常事件这三种事件分别发生在哪些文件描述符中(readfds、writefds、errorfds三个参数)。
我们结合 老师-班长-同学 的模型来理解下这个过程。
特别注意,在select函数下,老师仅仅知道有学生发生变化了,但到底是哪些学生发生变化,他需要 轮询 一遍同学名单(xxxfds),找出举手的同学,然后和他进行交流。
select的缺点比较明显:
3. poll
poll的实现和select非常相似,我们就不重复说明了,直接介绍一下区别。poll函数如下:
主要是描述fd集合的方式不同,poll使用pollfd结构而不是fd_set结构,pollfd结构使用链表而非数组,这导致pollfd的长度没有限制。但是如果pollfd长度过大,会导致性能下降。
除此之外,二者的原理基本一致,即对多个描述符也是进行轮询,根据描述符的状态进行处理。
因此,二者的缺陷也基本一致。
4. epoll
epoll的全称是eventpoll,它是基于event事件进行实现的,是linux特有的I/O复用函数。
它在实现和使用上和select\poll有很大差别:
不同于select使用三个fd_set来对应读/写/异常的IO变化,epoll专门定义了一个epoll_event结构体,将其作为读/写/异常的IO变化的逻辑封装,称为事件(event)。
4.1 epoll的三个核心函数
epoll把原先的select/poll调用分成了3个函数。
4.2 epoll的实现原理
当某一进程调用 epoll_create()方法 时,内核空间会创建一个eventpoll结构体,这个结构体中有两个成员变量与epoll的使用方式密切相关,结构体如下所示:
用 epoll_ctl()方法 将新添加的监控事件event加入到 红黑树rbr 中。还会给内核中断处理程序注册一个 回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。
一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,被触发的事件会被 回调函数 加入eventpoll的 链表rdlist 中。
当调用 epoll_wait()方法 检查是否有事件发生时,只需要检查eventpoll对象中的rdlist链表中是否有元素即可。如果链表中有数据的话,就把对应有修改的事件event复制到epoll_wait()方法的events数组变量中,用户就能获得了。
如此一来,epoll_wait的效率就非常高了。因为调用epoll_wait时,不需要向操作系统复制所有的连接的句柄数据,内核也不需要去遍历全部的连接。
4.3 epoll中有使用共享内存吗?
很多博客提到了这点:
但是事实确实如此吗?
源码面前无密码,我们直接看下源码吧。
参考eventpoll.c的源码。
https://github.com/torvalds/linux/blob/master/fs/eventpoll.c
具体的epoll_wait调用关系如下图所示。
我们可以在put_user中看到具体的说明。
因此,事件确实是从内核空间拷贝到用户空间的,并没有使用共享内存。
5.三种实现对比
通过上面的分析,相信大家都已经了解了select/poll/epoll的实现。
下面通过一个表格来总结他们的主要区别。
从整体来看,epoll的实现性能是比select/poll更好的。
当然,如果保持活跃的连接一直非常多,epoll_wait的效率就不一定高了,因为此时epoll_wait的回调函数触发过于频繁。
因此,epoll最适合的场景是连接数量很多,但是活跃连接数量不多的情况。
参考书目:
《Linux高性能服务器编程》
往期热门笔记合集推荐:
作为数亿互联网用户当中的一员,你可曾想过,办理信贷业务完全不用跑到线下营业...
根据调研机构Gartner公司日前发布的一份调查报告,由于数据质量不良的问题,40%...
关于机器人接管人类工作的持续辩论引起了全球媒体的关注。几位辛勤工作的员工认...
我们每天都在用微信,大家有没有发现,有时候微信消息明明来了,却没有提示,等...
1.我在也不吃醋再也不胡思乱想我累了,你爱跟谁好就好去吧。 2.其实,爱不是寻...
谷歌今天预告了即将在 Android 中引入的诸多新功能,包括密码检查工具、定时短信...
据外媒报道,T-Mobile计划到2023年底向90%的美国人提供超容量5G。所谓超容量5G指...
在近日发表的一篇论文中,图灵奖得主 Yoshua Bengio 等详细介绍了其团队当前的研...
( 映维网 2021年01月26日 )现在不少VR头显都会利用前置摄像头纳入AR透视功能。...
据外媒,美国电信运营商Verizon近期引起了休斯敦部分居民的不满,据了解,该公司...