一、什么是Reactor模式
1.1概念
The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers
这是维基百科上对于Reactor的定义。
大致意思是说,reactor设计模式是一种事件处理模式,这种模式针对同时有一个或多个请求发送到事件处理器(service handler),这个事件处理器会采用多路复用(demultiplexes )方法,同步的将这些请求分发到请求处理器(request handlers)
听起来可能有些拗口,但其中的精髓部分就是多路复用的模式进行请求的分发,而对于服务器来说,这些请求其实就是Socket
通信中的I/O
请求。
说到这里,就和我们前面所说到的JavaIO
模型中的I/O多路复用,建立关系了。
没错,Reactor
模式其实就是依靠这种I/O
模型来实现请求的分发。
1.2NIO如何实现Reactor模式
根据上面提到的,Reactor
模式要依靠多路复用的分发模式,联想到Java
中的多路复用I/O是采用基于NIO
中Selector
实现,所以要实现Reactor
模式,还是要依靠JavaNIO
的核心组件。
这就是一个基础的Reactor
模式的模型图,可以和定义中的Reactor
模式图进行对比,client相当于inputer
,而Reactor
本身相当于ServiceHandler
,而其通过Selector实现的多路复用进行分发请求到具体的Request Handler
,用于进行读取消息,编码解码,返回消息等操作。
二、JavaNIO实现基于Reactor模式的ECHO服务器
2.1初始化事件处理器(service handler)
public class NioReactor {
private Selector selector;
private ServerSocketChannel serverSocketChannel;
public NioReactor(int port) throws IOException {
// 初始化Selector和Channel,并完成注册
selector = Selector.open();
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 这里注意绑定到key上
selectionKey.attach(new Acceptor());
serverSocketChannel.bind(new InetSocketAddress(port));
}
/**
* 内部类Accept,实际TCP连接的建立和SocketChannel的获取在这个类中实现
* 根据类的实现,可以发现一个Accept类对应一个ServerSocketChannel
*
* @author CringKong
*
*/
private class Acceptor implements Runnable{
@Override
public void run() {
try {
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) {
// 创建一个新的处理类
new Handler(socketChannel, selector);
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
****/
}
2.2多路复用+循环分发
/**
* 多路复用,循环分发任务
* @throws IOException
*/
private void dispatchLoop() throws IOException {
while(true) {
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while(iterator.hasNext()) {
// 任务分派器,无差别的分派任务
dispatchTask(iterator.next());
}
selectionKeys.clear();
}
}
可以看到,这个dispatchLoop
和我们之前实现的NIOserver服务器中的selectLoop实现很相似。其实原理都是一致的,只不过在Reactor
模式中,他就是事件处理器会采用多路复用(demultiplexes )方法。
2.3同步任务派发器(dispatch)
/**
* 任务分派器的进阶版,耦合性降低,拓展性增强
* 子类只需要实现Runnable接口,并重写run()方法,就可以实现多种任务的无差别分派
*
* @param selectionKey
*/
private void dispatchTask(SelectionKey taskSelectionKey) {
Runnable runnable = (Runnable)taskSelectionKey.attachment();
if (runnable != null) {
runnable.run();
}
}
/**
* 任务分派器基础版本,根据通道内准备好的操作类型进行任务分派
*
* @param selectionKey
* @param selector
*/
private void taskDispatcherBasicVersion(SelectionKey taskSelectionKey,Selector selector) {
if (taskSelectionKey.isAcceptable()) {
// 执行自己实现的接收方法,建立连接创建SocketChannl并且注册到Selector
}else if (taskSelectionKey.isReadable()) {
// 执行自己实现的读方法,读取通道内容并做进一步处理
}
}
2.4请求处理器(Request Handler)
/**
* 内部类Handler,实际的I/O操作和编码解码都要在这其中是实现
* 可以发现,一个Handler类对应一个Socket连接
*
* @author CringKong
*
*/
private class Handler implements Runnable{
private SocketChannel socketChannel;
private SelectionKey selectionKey;
private ByteBuffer oldBuffer;
public Handler(SocketChannel socketChannel , Selector selector) throws IOException {
// 初始化的oldBuffer为null
oldBuffer = null;
this.socketChannel = socketChannel;
socketChannel.configureBlocking(false);
// 在构造函数里就注册通道到Selector
this.selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);
// attach(this)将自身对象绑定到key上,作用是使dispatch()函数正确使用
selectionKey.attach(this);
// Selector.wakeup()方法会使阻塞中的Selector.select()方法立刻返回
selector.wakeup();
}
@Override
public void run() {
try {
readDate(selectionKey);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 读取数据并进行操作的函数
* @param selectionKey
* @throws IOException
*/
private void readDate(SelectionKey selectionKey) throws IOException {
ByteBuffer newBuffer = ByteBuffer.allocate(64);
int read;
while((read = socketChannel.read(newBuffer))<=0) {
return;
}
newBuffer.flip();
String line = readLine(newBuffer);
if (line != null) {
// 如果这次读到了行结束符,就将原来不含有行结束符的buffer合并位一行
String sendData = readLine(mergeBuffer(oldBuffer, newBuffer));
if (readLineContent(sendData).equalsIgnoreCase("exit")) { // 如果这一行的内容是exit就断开连接
socketChannel.close();
return;
}
// 然后直接发送回到客户端
ByteBuffer sendBuffer = ByteBuffer.wrap(sendData.getBytes("utf-8"));
while (sendBuffer.hasRemaining()) {
socketChannel.write(sendBuffer);
}
oldBuffer = null;
}else {
// 如果这次没读到行结束付,就将这次读的内容和原来的内容合并
oldBuffer = mergeBuffer(oldBuffer, newBuffer);
}
}
/**
* 读取ByteBuffer直到一行的末尾
* 返回这一行的内容,包括换行符
*
* @param buffer
* @return String 读取到行末的内容,包括换行符 ; null 如果没有换行符
* @throws UnsupportedEncodingException
*/
private String readLine(ByteBuffer buffer) throws UnsupportedEncodingException {
// windows中的换行符表示手段 "\r\n"
// 基于windows的软件发送的换行符是会是CR和LF
char CR = '\r';
char LF = '\n';
boolean crFound = false;
int index = 0;
int len = buffer.limit();
buffer.rewind();
while(index < len) {
byte temp = buffer.get();
if (temp == CR) {
crFound = true;
}
if (crFound && temp == LF) {
// Arrays.copyOf(srcArr,length)方法会返回一个 源数组中的长度到length位 的新数组
return new String(Arrays.copyOf(buffer.array(), index+1),"utf-8");
}
index ++;
}
return null;
}
/**
* 获取一行的内容,不包括换行符
* @param buffer
* @return String 行的内容
* @throws UnsupportedEncodingException
*/
private String readLineContent(String line) throws UnsupportedEncodingException {
System.out.print(line);
System.out.print(line.length());
return line.substring(0, line.length() - 2);
}
/**
* 对传入的Buffer进行拼接
* @param oldBuffer
* @param newBuffer
* @return ByteBuffer 拼接后的Buffer
*/
public ByteBuffer mergeBuffer(ByteBuffer oldBuffer,ByteBuffer newBuffer) {
// 如果原来的Buffer是null就直接返回
if (oldBuffer == null) {
return newBuffer;
}
// 如果原来的Buffer的剩余长度可容纳新的buffer则直接拼接
newBuffer.rewind();
if (oldBuffer.remaining() > (newBuffer.limit()-newBuffer.position())) {
return oldBuffer.put(newBuffer);
}
// 如果不是以上两种情况就构建新的Buffer进行拼接
int oldSize = oldBuffer != null?oldBuffer.limit():0;
int newSize = newBuffer != null?newBuffer.limit():0;
ByteBuffer result = ByteBuffer.allocate(oldSize+newSize);
result.put(Arrays.copyOfRange(oldBuffer.array(), 0, oldSize));
result.put(Arrays.copyOfRange(newBuffer.array(), 0, newSize));
return result;
}
}
这里的Handler
是根据我们之前实现的ECHO服务器直接改写的,具体实现请跳转参考。
三、完整代码
package reactor;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Set;
public class NioReactor {
private Selector selector;
private ServerSocketChannel serverSocketChannel;
public NioReactor(int port) throws IOException {
// 初始化Selector和Channel,并完成注册
selector = Selector.open();
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
selectionKey.attach(new Acceptor());
serverSocketChannel.bind(new InetSocketAddress(port));
}
/**
* 多路复用,循环分发任务
* @throws IOException
*/
private void dispatchLoop() throws IOException {
while(true) {
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while(iterator.hasNext()) {
// 任务分派器,无差别的分派任务
dispatchTask(iterator.next());
}
selectionKeys.clear();
}
}
/**
* 任务分派器的进阶版,耦合性降低,拓展性增强
* 子类只需要实现Runnable接口,并重写run()方法,就可以实现多种任务的无差别分派
*
* @param selectionKey
*/
private void dispatchTask(SelectionKey taskSelectionKey) {
Runnable runnable = (Runnable)taskSelectionKey.attachment();
if (runnable != null) {
runnable.run();
}
}
/**
* 任务分派器基础版本,根据通道内准备好的操作类型进行任务分派
*
* @param selectionKey
* @param selector
*/
private void taskDispatcherBasicVersion(SelectionKey taskSelectionKey,Selector selector) {
if (taskSelectionKey.isAcceptable()) {
// 执行自己实现的接收方法,建立连接创建SocketChannl并且注册到Selector
new Acceptor().run();
}else if (taskSelectionKey.isReadable()) {
// 执行自己实现的读方法,读取通道内容并做进一步处理
}
}
/**
* 内部类Accept,实际TCP连接的建立和SocketChannel的获取在这个类中实现
* 根据类的实现,可以发现一个Accept类对应一个ServerSocketChannel
*
* @author CringKong
*
*/
private class Acceptor implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
try {
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) {
// 创建一个新的处理类
new Handler(socketChannel, selector);
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
/**
* 内部类Handler,实际的I/O操作和编码解码都要在这其中是实现
* 可以发现,一个Handler类对应
*
* @author CringKong
*
*/
private class Handler implements Runnable{
private SocketChannel socketChannel;
private SelectionKey selectionKey;
private ByteBuffer oldBuffer;
public Handler(SocketChannel socketChannel , Selector selector) throws IOException {
// 初始化的oldBuffer为null
oldBuffer = null;
this.socketChannel = socketChannel;
socketChannel.configureBlocking(false);
// 在构造函数里就注册通道到Selector
this.selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);
// attach(this)将自身对象绑定到key上,作用是使dispatch()函数正确使用
selectionKey.attach(this);
// Selector.wakeup()方法会使阻塞中的Selector.select()方法立刻返回
selector.wakeup();
}
@Override
public void run() {
try {
readDate(selectionKey);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 读取数据并进行操作的函数
* @param selectionKey
* @throws IOException
*/
private void readDate(SelectionKey selectionKey) throws IOException {
ByteBuffer newBuffer = ByteBuffer.allocate(64);
int read;
while((read = socketChannel.read(newBuffer))<=0) {
return;
}
newBuffer.flip();
String line = readLine(newBuffer);
if (line != null) {
// 如果这次读到了行结束符,就将原来不含有行结束符的buffer合并位一行
String sendData = readLine(mergeBuffer(oldBuffer, newBuffer));
if (readLineContent(sendData).equalsIgnoreCase("exit")) { // 如果这一行的内容是exit就断开连接
socketChannel.close();
return;
}
// 然后直接发送回到客户端
ByteBuffer sendBuffer = ByteBuffer.wrap(sendData.getBytes("utf-8"));
while (sendBuffer.hasRemaining()) {
socketChannel.write(sendBuffer);
}
oldBuffer = null;
}else {
// 如果这次没读到行结束付,就将这次读的内容和原来的内容合并
oldBuffer = mergeBuffer(oldBuffer, newBuffer);
}
}
/**
* 读取ByteBuffer直到一行的末尾
* 返回这一行的内容,包括换行符
*
* @param buffer
* @return String 读取到行末的内容,包括换行符 ; null 如果没有换行符
* @throws UnsupportedEncodingException
*/
private String readLine(ByteBuffer buffer) throws UnsupportedEncodingException {
// windows中的换行符表示手段 "\r\n"
// 基于windows的软件发送的换行符是会是CR和LF
char CR = '\r';
char LF = '\n';
boolean crFound = false;
int index = 0;
int len = buffer.limit();
buffer.rewind();
while(index < len) {
byte temp = buffer.get();
if (temp == CR) {
crFound = true;
}
if (crFound && temp == LF) {
// Arrays.copyOf(srcArr,length)方法会返回一个 源数组中的长度到length位 的新数组
return new String(Arrays.copyOf(buffer.array(), index+1),"utf-8");
}
index ++;
}
return null;
}
/**
* 获取一行的内容,不包括换行符
* @param buffer
* @return String 行的内容
* @throws UnsupportedEncodingException
*/
private String readLineContent(String line) throws UnsupportedEncodingException {
System.out.print(line);
System.out.print(line.length());
return line.substring(0, line.length() - 2);
}
/**
* 对传入的Buffer进行拼接
* @param oldBuffer
* @param newBuffer
* @return ByteBuffer 拼接后的Buffer
*/
public ByteBuffer mergeBuffer(ByteBuffer oldBuffer,ByteBuffer newBuffer) {
// 如果原来的Buffer是null就直接返回
if (oldBuffer == null) {
return newBuffer;
}
// 如果原来的Buffer的剩余长度可容纳新的buffer则直接拼接
newBuffer.rewind();
if (oldBuffer.remaining() > (newBuffer.limit()-newBuffer.position())) {
return oldBuffer.put(newBuffer);
}
// 如果不是以上两种情况就构建新的Buffer进行拼接
int oldSize = oldBuffer != null?oldBuffer.limit():0;
int newSize = newBuffer != null?newBuffer.limit():0;
ByteBuffer result = ByteBuffer.allocate(oldSize+newSize);
result.put(Arrays.copyOfRange(oldBuffer.array(), 0, oldSize));
result.put(Arrays.copyOfRange(newBuffer.array(), 0, newSize));
return result;
}
}
// 测试
public static void main(String[] args) {
NioReactor reactor;
try {
reactor = new NioReactor(12345);
reactor.dispatchLoop();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
四、测试结果
这个服务器和原来实现的ECHO服务器逻辑上没有任何区别,只是实现了Reactor
模式。
参考资料:
- Reactor模式详解
- Scalable IO in Java – Doug Lea大神之作
- 细说Reactor模式
- Java进阶(五)Java I/O模型从BIO到NIO和Reactor模式