javaNIO对于多路复用io(同步非阻塞io)的实现
package test; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; public class NioPractice { public static void main(String[] args) throws IOException { // TODO Auto-generated method stub //开启一个ServerSocketChannel在端口8080监听 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress("localhost", 8080)); //开启一个多路复用器,可以实现用一个线程监控所有IO连接的IO就绪事件 Selector selector=Selector.open(); //将severSocketChannel注册到多路复用器上,并声明关注其accept就绪事件 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while(selector.select()!=0) { Iterator<SelectionKey> iterator=selector.selectedKeys().iterator(); while(iterator.hasNext()) { SelectionKey key=iterator.next(); if(key.isReadable()) { //将Channel里的数据读出,当有大量数据需要读取时,导致监控线程一直为这个io服务, //因此可以专门开一个线程池来对io读写处理(不由监控线程来处理,由新开的线程池来处理,所以监控线程可以去处理别的就绪事件) } if(key.isWritable()) { } if(key.isAcceptable()) { //统一由selector多路复用器来监听,当socketChannel为非阻塞时,省去了单独判断它是否成功接受连接 SocketChannel socketChannel=serverSocketChannel.accept(); //将socketChannel注册到多路复用器上,并声明关注其read事件 socketChannel.register(selector, SelectionKey.OP_READ); } iterator.remove(); } } } }
基于 Java NIO 的事件分离模式
采用 Java NIO 技术实现的典型的两种高效、应用广泛的事件分离模式:一个是反应器(Reactor)模式,一个是前摄器(Proactor)模式。
同步非阻塞io
反应器(Reactor)模式目的是使服务器具有并发处理服务请求的能力。服务器首先注册多个事件处理器(Event Handler),事件分离器(Event Demultiplexer)负责接收一个或者多个客户端发来的请求,并将事件分发到对应的事件处理器上。
一个典型的读操作的过程是:
1) 服务器程序注册 Accept 事件,事件分离器等待客户端的连接请求;
2) 客户端请求到来后,将连接事件分发到 Acceptor 事件处理器中,建立连接。
3) 连接建立完成后服务器程序再注册一个读操作事件。事件分离器继续等待事件发生;
4) 当读操作在对应的连接上就绪后,事 件分离器分发事件到对应的Read Handler 事件处理器中基于同步 I/O 进行阻塞读取;
5) 读取完成后再根据业务需要做进一步处理。 整个过程是由服务器感兴趣的事件触发的,因此形象的叫做反应器模式。
异步非阻塞io
前摄器(Proactor)模式也是一种事件分离模式,与 Reactor 不同的是基于异步 I/O 的,实现了真正的无阻塞异步的 I/O 操作。前摄器模式同样是分离事件给对应的事件处理器,然而这里的事件不再是就绪事件,如就绪读或者就绪写,而是异步操作的完成事件。
一个典型的读操作处理过程如下:
1) 服务器程序注册 Accept 事件,事件分离器等待客户端的连接请求;
2) 客户端请求到来后,将连接事件分发到 Acceptor 事件处理器中,建立连接。
3) 连接建立完成后服务器程序再注册一个读操作完成事件。事件分离器继续读操作完成事件的发生;
4) 当读操作在对应的连接上就绪后,由操作系统触发一个异步读取操作,等读取完成后,会将读取内容放在用户指定的缓冲区,并通知应用程序事件完成。
5) 事件分离器接收到完成事件后,将事件分发到 ReadHandler 事件处理器中直接从指定缓冲区中取出数据,而不需要进行实际的 I/O 操作。
6) 缓冲区数据取出后再根据不同的业务做进一步的处理
典型的异步模式实现,都建立在操作系统支持异步 API 的基础之上,这种实现称为系统级异步,因为应用程序完全依赖操作系统执行真正的 I/O 工作
这两个模式都使用了事件驱动模型:一个是在数据准备好时通知事件处理器去进行io处理,一个 是在已经完成了io处理后通知事件处理器进行业务处理
事件驱动模型图
使用JavaAIO对文件进行异步读写的实现如下:
package test; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousFileChannel; import java.nio.channels.CompletionHandler; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; public class AIOtest1 { public static void main(String[] args) throws IOException { // TODO Auto-generated method stub Path path = Paths.get("test-write.txt"); if(!Files.exists(path)){ Files.createFile(path); } AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE); ByteBuffer buffer = ByteBuffer.allocate(1024); long position = 0; buffer.put("test data".getBytes()); buffer.flip(); fileChannel.write(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() { @Override public void completed(Integer result, ByteBuffer attachment) { System.out.println("bytes written: " + result); } @Override public void failed(Throwable exc, ByteBuffer attachment) { System.out.println("Write failed"); exc.printStackTrace(); } }); } }
参考:
论文:基于Netty的高可服务消息中间件的研究与实现_崔晓旻