也谈Reactor模式

何谓Reactor模式?它是实现高性能IO的一种设计模式。网上资料有很多,有些写的也很好,但大多不知其所以然。这里博主按自己的思路简单介绍下,有不对的地方敬请指正。 BIO Java1.4(2002年)以前,IO都是Blocking的,也就是常说的BIO,它在等待请求、读、写(返回)三个环节都是阻塞的。在等待请求阶段,系统无法知道请求何时到达,因此需要一个主线程一直守着,当有请求进来时,将请求分发给读写线程。如图: 代码如下: 复制代码 ExecutorService executor = Excutors.newFixedThreadPollExecutor(100);//线程池 ServerSocket serverSocket = new ServerSocket(); serverSocket.bind(8088); while(!Thread.currentThread.isInturrupted()){//主线程死循环等待新连接到来 Socket socket = serverSocket.accept(); executor.submit(new ConnectIOnHandler(socket));//为新的连接创建新的线程 } class ConnectIOnHandler extends Thread{ private Socket socket; public ConnectIOnHandler(Socket socket){ this.socket = socket; } public void run(){ while(!Thread.currentThread.isInturrupted()&&!socket.isClosed()){//死循环处理读写事件 String someThing = socket.read()....//读取数据 if(someThing!=null){            ......//处理数据            socket.write()....//写数据 } } } 复制代码 需知,请求进来(accept),并不表示数据马上达到了,可能隔一段时间才会传进来,这个时候socket.read()也是一直阻塞的状态。socket.write()也同理,当向磁盘或其它socket写数据时,也要等对方准备好才能写入,在对方准备阶段,socket.write()也是阻塞的。这两个环节可能的无效阻塞导致读写线程的低效。 NIO Java1.4开始,引入了NIO。NIO有三个概念:Selector、Buffer、Channel。与BIO的区别是,请求进来后,并不会马上分派IO线程,而是依靠操作系统底层的多路复用机制(select/poll/epoll等),在监听到socket读写就绪之后,再分配IO线程(实际可由当前线程[使用Buffer和Channel]直接读写,因为读写本身的效率很高),这就避免了线程等待。且与BIO多线程方式相比,使用I/O多路复用技术,系统不必创建和维护庞大的线程池,从而大大减小了开销。这部分工作是NIO的核心,由Selector负责,本质上是多路复用的Java封装。而Buffer和Channel又封装了一层socket的读写,应该为的是将IO与业务代码彻底分离。以下图示为本人理解: 如图示,与BIO中监听线程职责不同,Selector监听的不只是连接请求,还有读写就绪事件,当某个事件发生时,即通知注册了该事件的Channel,由Channel操作socket读写Buffer。虚线表示需要具体的NIO框架或业务代码自己处理,比如Channel如何注册以及注册何种事件,Channel处理IO的方式(如在当前线程处理还是新开线程,若新开线程,则可看作是AIO模式)等。NIO只是提供了一套机制,具体使用还是需要编程实现(Reactor模式就是OO的一种实现)。 示例代码(摘自Java NIO详解) 服务端: View Code 客户端: View Code 在早期的JDK1.4和1.5 update10版本之前,Selector基于select/poll模型实现,是基于IO复用技术的非阻塞IO,不是异步IO。在JDK1.5 update10和linux core2.6以上版本,sun优化了Selctor的实现,底层使用epoll替换了select/poll。另据说Buffer指向的并非堆内内存,NIO使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆的 DirectByteBuffer 对象作为这块内存的引用进行操作,避免了在 Java 堆和 Native 堆中来回复制数据。 NIO的实现解析可参看:深入浅出NIO Socket实现机制 Reactor模式 NIO为实现Reactor模式提供了基础,上面的NIO图示其实就是Reactor模式的雏形,只是Reactor以OO的方式抽象出了几个概念,使得职责划分更加明确。 Reactor:Reactor是IO事件的派发者,对应NIO的Selector; Acceptor:Acceptor接受client连接,建立对应client的Handler,并向Reactor注册此Handler,对应NIO中注册Channel和事件触发时的判断分支(上述NIO服务端示例代码的38-46行); Handler:IO处理类,对应NIO中Channel[使用socket]操作Buffer的过程。 基于上述三个角色画出Reactor模式图如下: 如此,Reactor模式便非常清晰地展现在我们眼前。那么业务线程如何与Reactor交互呢?由前文所知,数据存取于Buffer,具体操作由Handler负责。socket.read()将数据读入Buffer,需要一种机制将Buffer引用推送给业务线程;同样,业务线程返回的数据需要写入Buffer,按Reactor模式,写入后还需要注册write事件,socket可写后write()。如果直接调用的话,至少Handler和业务代码会耦合在一起,常见的解耦方式是定义接口,或使用消息中间件。 其它 话说回来,由于相对短暂的历史以及相对封闭的环境,.Net社区缺少很多概念的演化、探究和讨论,这也导致了.Neter们这些概念的缺失。虽然从语言层面上来说,C#和Java大同小异,前者甚至一定程度的有语法上的便利,然而只有认识到了其背后的思想和模式,才能真正用好这门语言,这就是.Neter需要了解Java及其历史的原因,毕竟.Net一开始就是参照着Java来的。 比如.Net里的堆栈概念,就算一些经典书籍都没有非常深入的说明,而Java方面的资料就很多了,参看深入理解JVM—JVM内存模型 其它参考资料: NIO浅析 深入理解Java NIO 网络通信socket连接数上限 转载请注明本文出处:https://www.cnblogs.com/newton/p/9776821.htmlhttps://www.cnblogs.com/newton/p/9776821.html
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信