JAVA IO模型演进及Reactor模式
来源:互联网 发布:超级基因优化液无弹窗 编辑:程序博客网 时间:2024/06/14 23:43
一、传统BIO模型
在基于传统同步阻塞模型中:ServerSocket负责绑定IP地址,启动监听端口;Socket负责发起连接操作。连接成功后,双方通过输入输出流进行同步阻塞式通信。
通信过程:
1)服务端通常由一个独立的Acceptor线程负责监听客户端的连接;
2)Acceptor监听到客户端的连接请求后,为每个客户端创建一个新的线程进行链路处理;
3)链路处理线程完成客户端请求的处理后,通过输出流返回应答给客户端,然后线程销毁。
模型缺点:
1)服务端线程个数与客户端并发访问连接数是1:1的关系;
2)随着客户端并发访问量增大,服务端线程个数线性膨胀,系统性能急剧下降。
二、优化后的BIO模型
服务端通过线程池来处理多个客户端的接入请求,通过线程池约束及调配服务端线程资源。形成客户端个数M:服务端线程池最大线程数N的比例关系。
通信过程:
1)当有新的客户端接入时,将客户端Socket封装成一个Task投递到服务端任务队列;
2)服务端任务线程池中的多个线程对任务队列中的Task进行并行处理;
3)任务线程处理完当前Task后,继续从任务队列中取新的Task进行处理。
模型缺点:
1)BIO的读和写操作都是同步阻塞的,阻塞时间取决于对端IO线程的处理速度和网络IO的传输速度,可靠性差;
2)当线程池中所有线程都因对端IO线程处理速度慢导致阻塞时,所有后续接入的客户端连接请求都将在任务队列中排队阻塞堆积;
3)任务队列堆积满后,新的客户端连接请求将被服务端单线程Acceptor阻塞或拒绝,客户端会发生大量连接失败和连接超时。
三、NIO模型
多路复用器Selector是NIO模型的基础,一个多路复用器Selector可以同时轮询多个注册在它上面的Channel,服务端只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端连接。
模型优点:
1)NIO中Channel是全双工的,Channel比流可以更好地映射底层操作系统的API(UNIX网络编程模型中,底层操作系统的通道都是全双工的,同时支持读写操作);
2)客户端发起的连接操作是异步的,不需要像之前的客户端那样被同步阻塞;
3)JDK的Selector在Linux等主流操作系统上通过epoll实现,它没有连接句柄数的限制,使得一个Selector线程可以同时处理成千上万个客户端连接,而且性能不会随客户端连接的增加而线性下降,适合做高性能高负载的网络服务器方案。
四、AIO模型
NIO2.0的异步套接字通道是真正的异步非阻塞IO,对应于UNIX网络编程中的事件驱动IO(AIO)。
它不需要通过多路复用器Selector对注册的通道进行轮询操作即可实现异步读写。通过事件驱动+回调函数的方式完成。
五、几种IO模型的功能特性对比
六、NIO模型+Reactor模式的网络服务端
1、Reactor模式思想:分而治之+事件驱动
1)分而治之
一个connection里完整的网络处理过程一般分为accept、read、decode、process、encode、send这几步。
Reactor模式将每个步骤映射为一个Task,服务端线程执行的最小逻辑单元不再是一次完整的网络请求,而是Task,且采用非阻塞方式执行。
2)事件驱动
每个Task对应一个特定事件,当Task准备就绪时,对应的事件通知就会发出。
Reactor收到事件通知后,分发给绑定了对应事件的Handler执行Task。
2、单线程版本Reactor模式
1)结构图
Reactor:负责响应事件,将事件分发给绑定了该事件的Handler处理;
Handler:事件处理器,绑定了某类事件,负责执行对应事件的Task对事件进行处理;
Acceptor:Handler的一种,绑定了connect事件。当客户端发起connect请求时,Reactor会将accept事件分发给Acceptor处理。
2)客户端连接服务端
a、服务端将绑定了accept事件的Acceptor注册到Reactor中,准备accept新的connection;
b、服务端循环执行Reactor的多路复用器Selector的select功能,监听就绪事件;
c、客户端connect服务器;
d、Reactor的多路复用器监听到accept事件,分发给Acceptor处理事件;
e、Acceptor执行事件Task,接收建立与客户端的连接,并创建一个Handler用于执行该连接的后续请求事件;
f、Handler绑定连接的read事件,并将自己注册到Reactor的Selector中监听。
3)服务端处理客户端请求
a、客户端发送请求;
b、客户端请求到达服务端时,Reactor监听到read事件,将事件分发给对应Handler处理;
c、Handler处理read事件,异步读取客户端请求数据;
d、Handler解析(decode)客户端请求数据;
e、Handler处理(process)客户端请求;
f、Handler重新绑定write事件;
g、当连接可以开始write时,Reactor监听到write事件,将事件分发给Handler处理;
h、Handler处理write事件,异步写出服务端响应数据。
4)模型优缺点
a、单线程版本Reactor模型优点是不需要做并发控制,代码实现简单清晰;
b、缺点是不能利用多核CPU,一个线程需要执行处理所有的accept、read、decode、process、encode、send事件,如果其中decode、process、encode事件的处理很耗时,则服务端无法及时响应其他客户端的请求事件。
3、Reactor模式的其他版本
1)Worker threads
a、使用线程池执行数据的具体处理过程decode、process、encode,提高数据处理过程的响应速度;
b、Reactor所在单线程只需要专心监听处理客户端请求事件accept、read、write;
c、因为Reactor仍是单线程,无法并行响应多个客户端的请求事件(比如同一时刻只能read一个客户端的请求数据)。
2)Multiple reactor threads
a、采用多个Reactor,每个Reactor在自己单独线程中执行,可以并行响应多个客户端的请求事件;
b、Netty采用类似这种模式,boss线程池就是多个mainReactor,worker线程池就是多个subReactor。
注:以上内容参照《Netty权威指南》和网络文章
4、NIO单线程Reactor模式示例代码
package com.zhangyiwen.study.nio.reactor_demo;import java.io.IOException;import java.nio.channels.SelectionKey;/** * Created by zhangyiwen on 16/11/8. */public interface Handler { void handle(SelectionKey sk) throws IOException;}
package com.zhangyiwen.study.nio.reactor_demo;import java.io.IOException;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.SocketChannel;import java.nio.charset.Charset;/** * Created by zhangyiwen on 16/11/7. */public class EventHandler implements Handler{ SocketChannel socketChannel; SelectionKey selectionKey; ByteBuffer readBuffer = ByteBuffer.allocate(MAXIN); ByteBuffer outBuffer = ByteBuffer.allocate(MAXOUT); static final int MAXIN = 256*1024; static final int MAXOUT = 256*1024; static final Charset charset = Charset.forName("UTF-8"); EventHandler(SocketChannel socketChannel, Selector selector) throws IOException { this.socketChannel = socketChannel; // 用selector注册套接字,并返回对应的SelectionKey,同时设置Key的interest set为监听该连接上得read事件 this.selectionKey = socketChannel.register(selector, SelectionKey.OP_READ); // 绑定handler this.selectionKey.attach(this); } @Override public void handle(SelectionKey sk) throws IOException{ if (sk.isReadable()) { System.out.println("[event]read"); read(); } else if (sk.isWritable()) { System.out.println("[event]write"); write(); } } /** * 处理read事件 * @throws IOException */ private void read() throws IOException{ // 读取数据 readBuffer.clear(); StringBuilder content = new StringBuilder(); int readNum = socketChannel.read(readBuffer); if(readNum==0){ return; }else if(readNum<0){ throw new IOException("exception."); }else { readBuffer.flip(); content.append(charset.decode(readBuffer)); //decode } while(socketChannel.read(readBuffer) > 0) { readBuffer.flip(); content.append(charset.decode(readBuffer)); //decode } // 处理数据 process(content.toString()); // selectionKey.interestOps(SelectionKey.OP_WRITE); } /** * 处理客户端请求数据 * @param content */ private void process(String content) throws IOException{ System.out.println("[receive from client] -> client:" + socketChannel.getRemoteAddress() + ", content: " + content); outBuffer = ByteBuffer.wrap(content.toUpperCase().getBytes()); } /** * 处理write事件 * @throws IOException */ private void write() throws IOException { // 写数据 socketChannel.write(outBuffer); if (outBuffer.remaining() > 0) { return; } // selectionKey.interestOps(SelectionKey.OP_READ); }}
package com.zhangyiwen.study.nio.reactor_demo;import java.io.IOException;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.nio.charset.Charset;/** * Created by zhangyiwen on 16/11/8. */public class Acceptor implements Handler{ static final Charset charset = Charset.forName("UTF-8"); private ServerSocketChannel serverChannel; private Selector selector; public Acceptor(ServerSocketChannel serverChannel, Selector selector) { this.serverChannel = serverChannel; this.selector = selector; } @Override public void handle(SelectionKey sk) throws IOException{ System.out.println("[event]connect"); // 建立连接 SocketChannel socketChannel = serverChannel.accept(); System.out.println("[new client connected] client:" + socketChannel.getRemoteAddress()); // 设置为非阻塞 socketChannel.configureBlocking(false); // 创建Handler,专门处理该连接后续发生的OP_READ和OP_WRITE事件 new EventHandler(socketChannel, this.selector); // 发送欢迎语 socketChannel.write(charset.encode("welcome my client.")); }}
package com.zhangyiwen.study.nio.reactor_demo;import java.io.IOException;import java.net.InetAddress;import java.net.InetSocketAddress;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.spi.SelectorProvider;import java.util.Iterator;/** * Created by zhangyiwen on 16/11/7. * 服务端,会先发欢迎语,后续会将客户端发来的消息转成大写后返回 */public class NioServer { private InetAddress hostAddress; private int port; private Selector selector; private ServerSocketChannel serverChannel; public NioServer(InetAddress hostAddress, int port) throws IOException { this.hostAddress = hostAddress; this.port = port; //初始化selector.绑定服务端监听套接字,感兴趣事件,对应的handler initSelector(); } public static void main(String[] args) { try { // 启动服务器 new NioServer(null, 9090).start(); } catch (IOException e) { e.printStackTrace(); } } /** * 初始化selector,绑定服务端监听套接字、感兴趣事件及对应的handler * @return * @throws IOException */ private void initSelector()throws IOException { // 创建一个selector selector = SelectorProvider.provider().openSelector(); // 创建并打开ServerSocketChannel serverChannel = ServerSocketChannel.open(); // 设置为非阻塞 serverChannel.configureBlocking(false); // 绑定端口 serverChannel.socket().bind(new InetSocketAddress(hostAddress, port)); // 用selector注册套接字,并返回对应的SelectionKey,同时设置Key的interest set为监听客户端连接事件 SelectionKey selectionKey = serverChannel.register(selector, SelectionKey.OP_ACCEPT); // 绑定handler selectionKey.attach(new Acceptor(serverChannel,selector)); } public void start() { while (true) { /* * 选择事件已经ready的selectionKey,该方法是阻塞的. * 只有当至少存在selectionKey,或者wakeup方法被调用,或者当前线程被中断,才会返回. */ try { selector.select(); } catch (IOException e) { e.printStackTrace(); } // 循环处理每一个事件 Iterator<SelectionKey> items = selector.selectedKeys().iterator(); while (items.hasNext()) { SelectionKey key = items.next(); items.remove(); if (!key.isValid()) { continue; } // 事件处理分发 dispatch(key); } } } /** * 事件处理分发 * @param sk 已经ready的selectionKey */ private void dispatch(SelectionKey sk){ // 获取绑定的handler Handler handler = (Handler) sk.attachment(); try { if (handler != null) { handler.handle(sk); } } catch (IOException e) { e.printStackTrace(); sk.channel(); try { if(sk.channel()!=null){ sk.channel().close(); } } catch (IOException e1) { e1.printStackTrace(); } } }}
package com.zhangyiwen.study.nio.reactor_demo;import java.io.IOException;import java.net.InetAddress;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.SocketChannel;import java.nio.channels.spi.SelectorProvider;import java.nio.charset.Charset;import java.util.Iterator;import java.util.Scanner;/** * Created by zhangyiwen on 16/11/8. * 手动输入客户端 */public class NioClient { private InetAddress hostAddress; private int port; private Selector selector; private SocketChannel socketChannel; private ByteBuffer readBuffer = ByteBuffer.allocate(8192); static final Charset charset = Charset.forName("UTF-8"); public NioClient(InetAddress hostAddress, int port) throws IOException { this.hostAddress = hostAddress; this.port = port; initSelector(); } public static void main(String[] args) { try { new NioClient(InetAddress.getByName("localhost"), 9090); } catch (IOException e) { e.printStackTrace(); } } private void initSelector() throws IOException { // 创建一个selector selector = SelectorProvider.provider().openSelector(); // 打开SocketChannel socketChannel = SocketChannel.open(); // 设置为非阻塞 socketChannel.configureBlocking(false); // 连接指定IP和端口的地址 socketChannel.connect(new InetSocketAddress(this.hostAddress, this.port)); // 用selector注册套接字,并返回对应的SelectionKey,同时设置Key的interest set为监听服务端已建立连接的事件 socketChannel.register(selector, SelectionKey.OP_CONNECT); // 开启新线程执行 new Thread(new ClientThread()).start(); //在主线程中 从键盘读取数据输入到服务器端 Scanner scan = new Scanner(System.in); while(scan.hasNextLine()) { String line = scan.nextLine(); if("".equals(line)) continue; //不允许发空消息 socketChannel.write(charset.encode(line));//sc既能写也能读,这边是写 } } private class ClientThread implements Runnable { @Override public void run() { while (true) { try { selector.select(); } catch (Exception e) { e.printStackTrace(); } Iterator<?> selectedKeys = selector.selectedKeys().iterator(); while (selectedKeys.hasNext()) { SelectionKey key = (SelectionKey) selectedKeys.next(); selectedKeys.remove(); if (!key.isValid()) { continue; } dispatch(key); } } } /** * 事件处理分发 * @param key 已经ready的selectionKey */ private void dispatch(SelectionKey key){ try { if (key.isConnectable()) { System.out.println("[event]connect."); finishConnection(key); } else if (key.isReadable()) { System.out.println("[event]read"); read(key); } } catch (IOException e) { e.printStackTrace(); key.channel(); try { if(key.channel()!=null){ key.channel().close(); } } catch (IOException e1) { e1.printStackTrace(); } } } } /** * 完成与服务端连接 * @param key * @throws IOException */ private void finishConnection(SelectionKey key) throws IOException { SocketChannel socketChannel = (SocketChannel) key.channel(); // 判断连接是否建立成功,不成功会抛异常 socketChannel.finishConnect(); // 设置Key的interest set为OP_WRITE事件 key.interestOps(SelectionKey.OP_READ); } /** * 处理read * @param key * @throws IOException */ private void read(SelectionKey key) throws IOException { // 读取数据 SocketChannel socketChannel = (SocketChannel) key.channel(); readBuffer.clear(); StringBuilder content = new StringBuilder(); int readNum = socketChannel.read(readBuffer); if(readNum==0){ return; }else if(readNum<0){ throw new IOException("exception."); }else { readBuffer.flip(); content.append(charset.decode(readBuffer)); //decode } while(socketChannel.read(readBuffer) > 0) { readBuffer.flip(); content.append(charset.decode(readBuffer)); } // 处理数据 process(content.toString(), key); // 设置Key的interest set为OP_READ事件// key.interestOps(SelectionKey.OP_READ); } /** * 处理服务端响应数据 * @param content */ private void process(String content,SelectionKey key) { System.out.println("[Client receive from server] -> content: " + content); }}
- JAVA IO模型演进及Reactor模式
- java网络编程—基石:五种IO模型及原理(多路复用\Reactor\epoll)
- java NIO的多路复用及reactor模式
- java io演进
- IO多路复用和Reactor模式
- IO多路复用和Reactor模式
- Reactor及proactor模式
- reactor模式---事件触发模型
- java模式之Reactor
- java模式之Reactor
- Java NIO Reactor模式
- java模式之Reactor
- 【Java】Reactor模式
- 【java】Reactor模式详解
- 5种网络IO模型、Reactor、Proactor
- java设计模式-reactor模式
- 高性能IO模式--Reactor和Proactor
- IO设计模式:Reactor和Proactor对比
- 区域链技术文章
- 一个关于数论中拉格朗日定理的证明
- CODE[VS] 3304 水果姐逛水果街I(线段树求区间最大最小值)
- TCP三次握手四次握手详解
- 增强for循环
- JAVA IO模型演进及Reactor模式
- 拷贝本地文件至HDFS异常:No FileSystem for scheme: file
- 1645: [Usaco2007 Open]City Horizon 城市地平线 (并查集)
- |洛谷|动态规划|P1757 通天之分组背包
- struts2自定义标签
- RMQ算法
- Java设计模式之桥接模式
- C#中线程间操作无效: 从不是创建控件 txtBOX 的线程访问它
- [深度学习论文笔记][Semantic Segmentation] Learning Deconvolution Network for Semantic Segmentation