JAVA 中的 I/O 模型 - 多路复用
背景
上一章节中讲解了
NIO
相关的知识点,知道了代码中通过配置configureBlocking
配置不阻塞,让程序能够一直运行下去(底下也是内核系统进行支持,监听是否有FD
发生状态变化)。
环境相关介绍:
1.8 - JDK (1.6 前后有版本变化)
CentOS Linux release 7.8.2003 (Core)
多路复用 及 Reactor 模式
为何需要
在上一节中我们讲解到NIO
中如何解决阻塞以及更好的进行客户端数据的读取数据。
但是同样也依旧会存在以下问题:
代码中维护客户端连接。
服务器在不断的将客户端
FD
传递进行轮询判断是否有事件(涉及线程上下文切换)。
Demo
多路复用 - 预先需知
在之前讲解需要了解到的知识点,后期的任何网络
IO
的变化其实大部分都是依赖于底层操作系统的支持,多路复用也是基于底层操作系统函数的支持。
select
函数 - > 同步多路复用IO
方法
返回值中会返回三个集合数据包含 readfds
,writefds
以及exceptfds
文件描述符集合。(fds
有1024
限制)poll
函数 - > 同步多路复用IO
方法
返回值中返回对应有响应的fds
集合。epoll_create
函数 - > 打开epoll
文件描述符
该方法将会返回一个epoll
实例(该实例用于接收IO
事件通知)。epoll_ctl
函数 - > epoll
描述符的控制接口
接收fd
绑定对应事件到epoll
实例上。epoll_wait
函数 - > 等待epoll
文件描述符上IO
事件
返回对应有IO
事件的fd
。
上述方法中,前两个都是基于多路复用进行的,下面三个方法则完全归属于epoll
方式(个人觉得他也使用到了多路复用,但是更偏向于Reactor
模型)。
不知道有没有小伙伴对于多路复用与Reactor
模型这两个概念有没有疑问(我起初看这两个词经常在一起,以为是指的是一个意思),后来经过学习查阅才明白这两个是有一定的差距的:
前两个方法是多路复用的主要调用方法,而
epoll
则是Reactor
模型的代表了。
多路复用的过程即使将产生的fd
全部传递至方法中。即抽象可以理解为select(int[] fds)
。每次不停的进行轮询判断是否有事件产生,产生之后再进行client
的非阻塞事件操作,但是服务端的socket
依旧会进行遍历集合。
epoll
也是有多路复用的一个概念在其中,但是为什么会叫Reactor
模式呢?那是因为通过划分事件进行分别注册到对应的epoll
实例上(如下图)。将不同的事件交给不同的epoll
实例,最后会交给对应的业务线程去进行处理
代码运行 - 过程详解
项目启动:
上面最开始的三板斧都是固定不变的(针对于server
端)。之后我们就可以看到他是采用了epoll
方式。epoll_ctl
这个函数就是将对应创建的server
的fd
添加epoll
实例中。接下来就是epoll_wait
不断的对其实例上注册的fd
进行循环。
启动客户端进行连接并进行数据传输:
可以看到同样进行的client
连接后的fd
同样也放到epoll
实例中(这里可以理解为单Reactor
模型)。传输数据的时候监听到fd=10
上有事件发生,即产生对应的读写事件。
实验结果
上面的讲解主要是讲了epoll
,关于多路复用的也是涉及到一部分。底层还是由系统层面进行支撑的,当然,也并不是多路复用就是完美的解决方案,不然后面也不会有一主 N 从的Reactor
模型出现。
多路复用器:
优势:
通过一次系统调用,把 fds,传递给内核,内核进行遍历,减少了系统调用的次数!!
弊端:
重复传递
fd
。每次都要重新遍历全量
fd
。
epoll
:
优势:
客户端连接的
fds
内核存放空间。不同的事件注册到不同的
epoll
实例上,性能得到了极大的提升。
弊端:
服务资源开销增加(多Reactor
情况下)
总结
上面讲述了多路复用以及
Reactor
模型。二者皆都谈及了一些,当然对于其中的组成部分还有许多没有提及(例如channel
,buffer
等)。
不知道小伙伴们有没有想过epoll
单个实例的时候其实跟select
与poll
一样的效果,那为什么多个实例就能得到极大的提升?
其实道理也很简单,就是在针对一个事件监听的数组要查询上面某个 fd 状态发生改变,其实底层就是遍历操作。只有当前数组元素较少,遍历的时长则会减少,对应的响应就更能及时(这个时候就能很好的理解下面这张图了)。
作者:Montos
链接:https://juejin.cn/post/6937913439801212959
来源:掘金
评论