反应器模式Reactor
What,Reactor模式是什么
反应器模式是一种编程模型,由Reactor反应器和Handlers处理器两部分组成,一个负责处理对外交互,一个负责内部事务处理
Reactor反应器线程的职责:负责响应IO事件,并且分发到Handlers处理器
Handlers处理器的职责:非阻塞的执行业务处理逻辑
Why,为什么需要反应器模式
在传统IO即BIO模型下,socket编程是通过不断增加线程的方式来扩展服务器性能的,但是线程资源在操作系统中是有限的,如何更好的利用cpu、内存、硬盘等资源是我们需要解决的问题,《scalable-io-in-java》书中提到分而治之通常是实现任何可伸缩性的目标最佳方案,我们的方案就是将处理分为多个任务,每个任务执行一个非阻塞的操作,当任务被激活时就执行它(也就是一个IO事件被触发时,实时上java的nio就是这样设计的);反应器模式对线程数量进行控制,做到一个线程处理大量的连接,从而使得计算机资源被更好的利用
HOW,怎么使用反应器模式
传统的javaIO实现就不再贴代码了,可以看下上篇<<java网络编程之Netty(二)>>
参照《scalable-io-in-java》中单线程版的Reactor模式实现一个服务器处理程序
服务器处理一个网络请求大致步骤是建立连接、读取客户端请求数据、处理数据、将响应数据写回客户端,java的SelectionKey中设计了OP_CONNECT(连接就绪事件)、OP_ACCEPT(接受连接就绪事件)、OP_READ(读就绪事件)、OP_WRITE(写就绪事件),其中OP_READ和OP_WRITE不管是服务器还是客户端都会发生的事件,OP_ACCEPT用于服务器接收前端请求,OP_CONNECT用于客户端和服务器建立连接,我们在考虑服务器时应该有一个准备服务器的Reactor和一个用于处理连接事件的Accept和处理具体请求的Handler
创建一个Reactor时需要一些基本的配置,javanio的选择器Selector、服务器通道ServerSocketChannel等都应该在构建反应器时准备好,并将Channel注册到Selector上,同时需要附加一个处理器到选择器上;
Reactor的代码:
package com.crs.reactor; 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.util.Iterator; import java.util.Set; /** * @author administrator * @version 1.0 * @date 2021/8/15 15:02 **/ public class Reactor implements Runnable{ final Selector selector; final ServerSocketChannel serverSocketChannel; Reactor(int port) throws IOException { // 创建一个选择器 selector = Selector.open(); // 打开serversocket连接 serverSocketChannel = ServerSocketChannel.open(); // 绑定一个端口 serverSocketChannel.bind(new InetSocketAddress(port)); // 设置通道为非阻塞的 serverSocketChannel.configureBlocking(false); SelectionKey sk = serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT); // 将连接处理器附加到sk选择键上,在获取就绪事件选择键时就可以取出此处理器执行 sk.attach(new Accept(selector,serverSocketChannel)); System.out.println("服务器已启动,绑定端口为"+port); } @Override public void run() { while (!Thread.interrupted()){ try { int num; while ((num=selector.select()) == 0) { Thread.sleep(1000); System.out.println("没有就绪事件请安心等待"); } // 获取就绪事件集合 Set<SelectionKey> selected = selector.selectedKeys(); System.out.println("已获取就绪事件"+selected.toString()); Iterator<SelectionKey> itr = selected.iterator(); while (itr.hasNext()){ // 反应器负责分发收到的事件 dispatch(itr.next()); } // 清理掉处理过的事件 selected.clear(); } catch (IOException | InterruptedException e) { e.printStackTrace(); } } } private void dispatch(SelectionKey key) { // 获取前面绑定到选择键的处理器 Runnable r = (Runnable)key.attachment(); if(r != null){ // 执行连接处理器 r.run(); } } }
服务器端的连接事件Accept:
package com.crs.reactor; import java.io.IOException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; /** * @author administrator * @version 1.0 * @date 2021/8/15 15:44 **/ public class Accept implements Runnable{ final Selector selector; final ServerSocketChannel serverSocketChannel; public Accept(Selector selector, ServerSocketChannel serverSocketChannel) { this.selector = selector; this.serverSocketChannel = serverSocketChannel; } @Override public void run() { try { SocketChannel socketChannel = serverSocketChannel.accept(); if(socketChannel != null){ new Handler(selector,socketChannel); // socketChannel.close(); // new Request(socketChannel,selectionKey); } } catch (IOException e) { e.printStackTrace(); } } }
具体的读写事件处理器Handler
package com.crs.reactor; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; /** * @author administrator * @version 1.0 * @date 2021/8/15 15:44 **/ public class Handler implements Runnable{ final SelectionKey selectionKey; final SocketChannel socketChannel; static final int RECIVE =0,SEND=1; int state = RECIVE; final ByteBuffer buffer = ByteBuffer.allocate(1024); public Handler(Selector selector, SocketChannel socketChannel) throws IOException { this.socketChannel = socketChannel; this.socketChannel.configureBlocking(false); // 注册读就绪事件并获取得选择键 selectionKey = socketChannel.register(selector,SelectionKey.OP_READ); selectionKey.attach(this); // 唤醒阻塞在选择器上的线程 selector.wakeup(); } @Override public void run() { try { if(state == SEND){ // 将数据写入通道 socketChannel.write(buffer); // 写完数据后准备读取数据因此将缓冲区设置为写状态以便于下一次连接可读 buffer.clear(); // 写完后注册读就绪事件 selectionKey.interestOps(SelectionKey.OP_READ); // 写完数据切换到读取状态 state = RECIVE; socketChannel.close(); } else if(state == RECIVE){ // 读取数据 int len = 0; // 构建返回的数据 // 构建请求头响应码 String head1="HTTP/1.0 200 OK"+"\r\n"; // 构建请求头返回数据格式 String head2="Content-Type:application/json"+"\r\n"; buffer.put(head1.getBytes()); buffer.put(head2.getBytes()); buffer.put("\r\n".getBytes()); while ((len=socketChannel.read(buffer))>0){ System.out.println(new String(buffer.array(), 0, len)); } // 读数据完成后准备将数据写入通道 buffer.flip(); // 注册写就绪事件 selectionKey.interestOps(SelectionKey.OP_WRITE); // 修改状态为发送数据 state = SEND; } }catch (IOException e) { e.printStackTrace(); } } }
在系统IO这块除了Reactor模式外,还有Proactor模式
Proactor模式也就是前摄器模式,是利用系统提供的异步IO函数进行IO操作的异步处理,需要依赖操作系统,它的模型是提前向操作注册句柄,所有的IO操作由系统内核完成,用户线程只需要处理业务逻辑即刻;和Reactor模型一样也是通过监听事件,读写事件就绪然后系统通知用户进程进行IO操作,只是在Reactor模型中是由用户在数据准备好后再调用系统read函数读取数据,而Proactor模型则是由系统内核直接把数据写入用户进程的缓冲区中,用户进程直接使用数据处理业务即可
参考
Reactor(反应器)模式初探:https://blog.csdn.net/pistolove/article/details/53152708
Proactor模式详解:https://blog.csdn.net/u013354486/article/details/82086084
彻底搞懂Reactor模型和Proactor模型:https://cloud.tencent.com/developer/article/1488120