Reactor模式模块之间的交互
简单描述一下Reactor各个模块之间的交互流程,先从序列图开始:
1. 初始化InitiationDispatcher,并初始化一个Handle到EventHandler的Map。
2. 注册EventHandler到InitiationDispatcher中,每个EventHandler包含对相应Handle的引用,从而建立Handle到EventHandler的映射(Map)。
3. 调用InitiationDispatcher的handle_events()方法以启动Event Loop。在Event Loop中,调用select()方法(Synchronous Event Demultiplexer)阻塞等待Event发生。
4. 当某个或某些Handle的Event发生后,select()方法返回,InitiationDispatcher根据返回的Handle找到注册的EventHandler,并回调该EventHandler的handle_events()方法。
5. 在EventHandler的handle_events()方法中还可以向InitiationDispatcher中注册新的Eventhandler,比如对AcceptorEventHandler来,当有新的client连接时,它会产生新的EventHandler以处理新的连接,并注册到InitiationDispatcher中。
援引大神的文章:https://www.cnblogs.com/luxiaoxun/archive/2015/03/11/4331110.html
Basic Reactor Design
不引入线程池时服务端代码如下
/**
* 创建 SocketHandler 的时候 会注册 对应的 读写事件,后续 如果有 事件发生, 会 由 reactor 调用 run方法
*
* 一个 socketHandler 对应 一个 selectionKey,也 对应 一组 ( sockelChannel 和 关注的 事件)
* 当发生 selectionKey 对应的 事件 时,在 com.chunfen.tomcat.nio.reactor.Reactor#dispatch(java.nio.channels.SelectionKey)
* 方法中 会调用run 方法,根据 channel 的 状态 进行 发送/接收数据
*/
public class SocketHandler implements Runnable{
private SocketChannel socketChannel;
private SelectionKey selectionKey;
private ObjectMapper objectMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
ByteBuffer input = ByteBuffer.allocate(1024);
ByteBuffer output = ByteBuffer.allocate(1024);
public SocketHandler(Selector selector, SocketChannel socketChannel) throws IOException {
this.socketChannel=socketChannel;
socketChannel.configureBlocking(false);
this.selectionKey = this.socketChannel.register(selector, 0);
//将SelectionKey绑定为本Handler 下一步有事件触发时,将调用本类的run方法。
//参看dispatch(SelectionKey key)
selectionKey.attach(this);
//同时将SelectionKey标记为可读,以便读取。
selectionKey.interestOps(SelectionKey.OP_READ);
// 可以 立即wakeup 也可不调用
// selector.wakeup();
}
/**
* 处理读取数据
*/
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " SocketHandler running");
if(selectionKey.isReadable()){
read();
} else if(selectionKey.isWritable()){
send();
}
} catch (IOException e) {
e.printStackTrace();
}
}
void read() throws IOException {
input.clear();
socketChannel.read(input);
if (inputIsComplete()) {
process();
// Normally also do first write now
selectionKey.interestOps(SelectionKey.OP_WRITE); //第三步,接收write事件
// 可以 立即wakeup 也可不调用
// selectionKey.selector().wakeup();
}
}
void send() throws IOException {
output.clear();
Message<Date> dateMessage = new Message<Date>();
dateMessage.setData(new Date());
dateMessage.setCode(11);
System.out.println(Thread.currentThread().getName() + " SocketHandler send message" + objectMapper.writeValueAsString(dateMessage));
socketChannel.write(ByteBuffer.wrap(objectMapper.writeValueAsBytes(dateMessage)));
//write完就结束了, 关闭select key
if (outputIsComplete()) {
selectionKey.cancel();
}
}
private boolean inputIsComplete() {
/* ... */
return true;
}
private boolean outputIsComplete() {
/* ... */
return true;
}
private void process() throws IOException {
/* ... */
input.flip();
byte[] bytes = new byte[input.remaining()];
input.get(bytes);
// Message obj = objectMapper.readValue(bytes, Message.class);
System.out.println(Thread.currentThread().getName() + " SocketHandler read message"+new String(bytes, "utf-8"));
return;
}
}
/**
* 初始 reactor 关注 accept 事件,dispatch 会 调用 accept 的run 方法
* 拿到 socketChannel 的时候 创建 socketHandler 让其 执行 连接完成后的 后续方法
*/
public class Acceptor implements Runnable {
private Reactor reactor;
public Acceptor(Reactor reactor){
this.reactor=reactor;
}
@Override
public void run() {
try {
SocketChannel socketChannel=reactor.getServerSocketChannel().accept();
System.out.println(Thread.currentThread().getName() + " Acceptor accept a client");
//调用Handler来处理channel
if(socketChannel!=null){
new SocketHandler(reactor.getSelector(), socketChannel);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Getter
@Setter
public class Reactor implements Runnable{
private ObjectMapper objectMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
private Selector selector;
private ServerSocketChannel serverSocketChannel;
public Reactor(int port){
try {
selector=Selector.open();
serverSocketChannel=ServerSocketChannel.open();
InetSocketAddress inetSocketAddress=new InetSocketAddress(InetAddress.getLocalHost(),port);
serverSocketChannel.bind(inetSocketAddress);
serverSocketChannel.configureBlocking(false);
//向selector注册该channel
SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//利用selectionKey的attache功能绑定Acceptor 如果有事情,触发Acceptor
selectionKey.attach(new Acceptor(this));
System.out.println(Thread.currentThread().getName() + " Reactor starting");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " Reactor started");
while(!Thread.interrupted()){
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
//Selector如果发现channel有OP_ACCEPT或READ事件发生,下列遍历就会进行。
while(iterator.hasNext()){
//来一个事件 第一次触发一个accepter线程
//以后触发SocketReadHandler
SelectionKey selectionKey = iterator.next();
System.out.println(Thread.currentThread().getName() + " Reactor from client select" + objectMapper.writeValueAsString(selectionKey));
//所有的关心的 io 事件 都 通过 dispatch 进行分发
dispatch(selectionKey);
// selectionKeys.clear();
iterator.remove();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 运行Acceptor或SocketReadHandler
* @param key
*/
void dispatch(SelectionKey key) {
// 不同的 key 触发不同的 事件
// 注意 此处 attachment 不一定 必须是 Runnable 接口的实现,也可以是公共的接口
Runnable r = (Runnable)(key.attachment());
if (r != null){
r.run();
}
}
}
客户端代码如下
@Test
public void reactorTest() throws Exception{
new Thread(new Reactor(8889)).start();
System.out.println(Thread.currentThread().getName() + " ReactorTest reactor starting");
Thread.sleep(300);
try (SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(InetAddress.getLocalHost(), 8889))){
System.out.println(Thread.currentThread().getName() + " ReactorTest client connected");
socketChannel.write(Charset.forName("utf-8").encode("Hi~ server have a nice 假日"));
ByteBuffer input = ByteBuffer.allocate(1024);
socketChannel.read(input);
input.flip();
byte[] bytes = new byte[input.remaining()];
input.get(bytes);
Message obj = objectMapper.readValue(bytes, Message.class);
System.out.println(Thread.currentThread().getName() + " " + obj.toString());
// 维持 main
Thread.sleep(20 * 1000);
} catch (Exception e){
e.printStackTrace();
}
}
日志输出如下
main Reactor starting
main ReactorTest reactor starting
Thread-0 Reactor started
main ReactorTest client connected
Thread-0 Reactor from client select{"valid":true,"selector":{"open":true},"readable":false,"writable":false,"connectable":false,"acceptable":true}
Thread-0 Acceptor accept a client
Thread-0 Reactor from client select{"valid":true,"selector":{"open":true},"readable":true,"writable":false,"connectable":false,"acceptable":false}
Thread-0 SocketHandler running
Thread-0 SocketHandler read messageHi~ server have a nice 假日
Thread-0 Reactor from client select{"valid":true,"selector":{"open":true},"readable":false,"writable":true,"connectable":false,"acceptable":false}
Thread-0 SocketHandler running
Thread-0 SocketHandler send message{"code":11,"data":1569831928922}
main Message(code=11, data=1569831928922, message=null)
socketHanlder的两个判断可以改为状态模式(State-Object Pattern)
/**
*
* 处理读取数据
*
*/
@Override
public void run() {
// 方式一: 回调对象只有 socketHandler 所以 要进行 selectKey 可读可写状态判断
// try {
// System.out.println(Thread.currentThread().getName() + " SocketHandler running");
// if(selectionKey.isReadable()){
// read();
// } else if(selectionKey.isWritable()){
// send();
// }
// } catch (IOException e) {
// e.printStackTrace();
// }
// 方式二 使用状态模式(State-Object Pattern)
// selectionKey 是 context 环境 attachment 就是 state
// 更换 attachment reactor 的 dispatch 方法 会执行不同的回调
try {
socketChannel.read(input);
if (inputIsComplete()) {
process();
selectionKey.attach(new Sender()); //状态迁移, Read后变成write, 用Sender作为新的callback对象
selectionKey.interestOps(SelectionKey.OP_WRITE);
selectionKey.selector().wakeup();
}
} catch (IOException e) {
e.printStackTrace();
}
}
class Sender implements Runnable {
public void run(){
try {
send();
} catch (IOException e) {
e.printStackTrace();
}
}
}
也可单独分离readHandler和sendHandler,详见git代码
Worker Thread Pools
对acceptor引入线程池
reactor中更换acceptor如下
public Reactor(int port){
try {
selector=Selector.open();
serverSocketChannel=ServerSocketChannel.open();
InetSocketAddress inetSocketAddress=new InetSocketAddress(InetAddress.getLocalHost(),port);
serverSocketChannel.bind(inetSocketAddress);
serverSocketChannel.configureBlocking(false);
//向selector注册该channel
SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//利用selectionKey的attache功能绑定Acceptor 如果有事情,触发Acceptor
// selectionKey.attach(new Acceptor(this));
//使用线程池 的 acceptor
selectionKey.attach(new PoolAcceptor(this));
System.out.println(Thread.currentThread().getName() + " Reactor starting");
} catch (IOException e) {
e.printStackTrace();
}
}
acceptor改写如下:
/**
* acceptor 使用线程池 处理 读时间
*/
public class PoolAcceptor implements Runnable {
private Reactor reactor;
private ThreadPoolExecutor pool;
static final int PROCESSING = 3;
public PoolAcceptor(Reactor reactor){
this.reactor=reactor;
pool = new ThreadPoolExecutor(3, 5, 1000, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100), new ThreadPoolExecutor.AbortPolicy());
}
@Override
public void run() {
try {
SocketChannel socketChannel=reactor.getServerSocketChannel().accept();
System.out.println(Thread.currentThread().getName() + " Acceptor accept a client");
//调用Handler来处理channel
if(socketChannel!=null){
if(PROCESSING == 3) {
pool.execute(new ReadHandler(reactor.getSelector(), socketChannel));
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Using Multiple Reactors
主Reactor只进行accept,从Reactor进行读写操作
增加SubReactor如下:
public class SubReactor extends Thread{
private ObjectMapper objectMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
private Selector selector;
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 1000,TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100), new ThreadPoolExecutor.AbortPolicy());
public SubReactor(){
try {
selector = Selector.open();
System.out.println(Thread.currentThread().getName() + " SubReactor selector open");
} catch (Exception e) {
e.printStackTrace();
}
}
public void register(SocketChannel socketChannel){
try {
System.out.println(Thread.currentThread().getName() + " SubReactor register socketChannel");
// 特别提示: 一个坑 如果 selector 优先 被select() 方法阻塞, 要优先调用 wakeup()方法,再进行 register 的动作,如果是 线程池里面进行register,那么在注册前调用wakeup()方法
// selector.wakeup();
// new ReadHandler(selector, socketChannel);
pool.execute(new ReadHandler(selector, socketChannel));
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void run() {
//同原来 reactor 方法
//selector 从这里阻塞
}
/**
* 运行Acceptor或SocketReadHandler
* @param key
*/
void dispatch(SelectionKey key) {
//同原来 reactor 方法
}
}
增加MainReactor如下:
public class MainReactor implements Runnable{
private ObjectMapper objectMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
private Selector selector;
private ServerSocketChannel serverSocketChannel;
public MainReactor(int port){
try {
selector=Selector.open();
serverSocketChannel=ServerSocketChannel.open();
InetSocketAddress inetSocketAddress=new InetSocketAddress(InetAddress.getLocalHost(),port);
serverSocketChannel.bind(inetSocketAddress);
serverSocketChannel.configureBlocking(false);
//向selector注册该channel
SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//注意这里,从这里创建 subReactor
SubReactor subReactor = new SubReactor();
subReactor.start();
selectionKey.attach(new MainSubAcceptor(this, subReactor));
System.out.println(Thread.currentThread().getName() + " Reactor starting");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
//同原来的reactor方法
}
/**
* 运行Acceptor或SocketReadHandler
* @param key
*/
void dispatch(SelectionKey key) {
//同原来的reactor方法
}
}
增加MainSubAcceptor如下
/**
* 初始 reactor 关注 accept 事件,dispatch 会 调用 accept 的run 方法
* 拿到 socketChannel 的时候 创建 socketHandler 让其 执行 连接完成后的 后续方法
*/
public class MainSubAcceptor implements Runnable {
private MainReactor reactor;
private SubReactor subReactor;
public MainSubAcceptor(MainReactor reactor, SubReactor subReactor){
this.reactor=reactor;
this.subReactor = subReactor;
}
@Override
public void run() {
try {
SocketChannel socketChannel= reactor.getServerSocketChannel().accept();
System.out.println(Thread.currentThread().getName() + " MainSubAcceptor accept a client");
//调用Handler来处理channel
if(socketChannel!=null){
// 使用 分离的 reactor 进行 后续 处理
subReactor.register(socketChannel);
}
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " MainSubAcceptor run end");
}
}
遇到的坑:再做左后一个demo时遇到一个坑,如果SubReactor优先启动,selector 会优先调用 select() 阻塞,这时如果要再进行 register 那么在调用前必须先 进行 selector.wakeup() 才能继续进行数据发送和接收。
完整的代码演示请移步gitee https://gitee.com/xic/chunfenwx/tree/master/tomcat-demo
参考资料:
https://www.cnblogs.com/yueweimian/p/6262211.html
https://www.cnblogs.com/luxiaoxun/archive/2015/03/11/4331110.html