何谓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详解)
服务端:
1 package cn.blog.test.NioTest; 2 3 4 import java.io.IOException; 5 import java.net.InetSocketAddress; 6 import java.nio.ByteBuffer; 7 import java.nio.channels.*; 8 import java.nio.charset.Charset; 9 import java.util.Iterator; 10 import java.util.Set; 11 12 13 public class MyNioServer { 14 private Selector selector; //创建一个选择器 15 private final static int port = 8686; 16 private final static int BUF_SIZE = 10240; 17 18 private void initServer() throws IOException { 19 //创建通道管理器对象selector 20 this.selector=Selector.open(); 21 22 //创建一个通道对象channel 23 ServerSocketChannel channel = ServerSocketChannel.open(); 24 channel.configureBlocking(false); //将通道设置为非阻塞 25 channel.socket().bind(new InetSocketAddress(port)); //将通道绑定在8686端口 26 27 //将上述的通道管理器和通道绑定,并为该通道注册OP_ACCEPT事件 28 //注册事件后,当该事件到达时,selector.select()会返回(一个key),如果该事件没到达selector.select()会一直阻塞 29 SelectionKey selectionKey = channel.register(selector,SelectionKey.OP_ACCEPT); 30 31 while (true){ //轮询 32 selector.select(); //这是一个阻塞方法,一直等待直到有数据可读,返回值是key的数量(可以有多个) 33 Set keys = selector.selectedKeys(); //如果channel有数据了,将生成的key访入keys集合中 34 Iterator iterator = keys.iterator(); //得到这个keys集合的迭代器 35 while (iterator.hasNext()){ //使用迭代器遍历集合 36 SelectionKey key = (SelectionKey) iterator.next(); //得到集合中的一个key实例 37 iterator.remove(); //拿到当前key实例之后记得在迭代器中将这个元素删除,非常重要,否则会出错 38 if (key.isAcceptable()){ //判断当前key所代表的channel是否在Acceptable状态,如果是就进行接收 39 doAccept(key