Reactor模式思想:分而治之+事件驱动
1)分而治之
一个连接里完整的网络处理过程一般分为accept、read、decode、process、encode、send这几步。
Reactor模式将每个步骤映射为一个Task,服务端线程执行的最小逻辑单元不再是一次完整的网络请求,而是Task,且采用非阻塞方式执行。
2)事件驱动
每个Task对应特定网络事件。当Task准备就绪时,Reactor收到对应的网络事件通知,并将Task分发给绑定了对应网络事件的Handler执行。
3)角色
Reactor:负责响应事件,将事件分发给绑定了该事件的Handler处理;
Handler:事件处理器,绑定了某类事件,负责执行对应事件的Task对事件进行处理;
Acceptor:Handler的一种,绑定了connect事件。当客户端发起connect请求时,Reactor会将accept事件分发给Acceptor处理。
单Reactor单线程模型
优点:
- 不需要做并发控制,代码实现简单清晰。
缺点:
- 不能利用多核CPU;
- 一个线程需要执行处理所有的accept、read、decode、process、encode、send事件,处理成百上千的链路时性能上无法支撑;
- 一旦reactor线程意外跑飞或者进入死循环,会导致整个系统通信模块不可用。
程序示例
package com.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.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
/**
* Reactor线程
*
* @author yaosht
*
*/
public class ServerReactorThread implements Runnable {
// 选择器
public final Selector selector;
// ServerSocketChannel
public final ServerSocketChannel serverSocketChannel;
/**
* 构造方法
*
* @param port
* @throws IOException
*/
public ServerReactorThread(int port) throws IOException {
// 打开一个选择器selector
selector = Selector.open();
// 通过ServerSocketChannel 的open()方法创建一个ServerSocketChannel对象
serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(port);
// 端口地址
serverSocketChannel.socket().bind(inetSocketAddress);
// 配置ServerSocketChannel为非阻塞模式
serverSocketChannel.configureBlocking(false);
// 将该channel注册到selector上并设置关注OP_ACCEPT监听事件
SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 利用selectionKey的attache功能绑定Acceptor 如果有事情,触发Acceptor
selectionKey.attach(new Acceptor());
System.out.println("等待客户端的消息……");
}
@Override
public void run() {
try {
while (true) {
// 进行选择操作
while (selector.select() > 0) {
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
System.out.println("有关注的事件发生:" + keys.size());
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
// 派发事件处理
dispatch(selectionKey);
// 删除已选择集合中的键
iterator.remove();
}
} // while
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 进行SelectionKey事件派发<br/>
* 获取attach附加的对象并进行方法调用:Acceptor或SocketHandler<br/>
*
* @param key
*/
void dispatch(SelectionKey key) {
if (key.isValid()) {
if (key.isAcceptable()) {
System.out.println("Acceptor");
Acceptor acceptor = (Acceptor) (key.attachment());
acceptor.handle(key);
} else if (key.isReadable()) {
System.out.println("SocketHandler");
SocketHandler socketHandler = (SocketHandler) (key.attachment());
socketHandler.handle(key);
}
}
}
/**
* 内部类Acceptor
*
* @author yaosht
*
*/
class Acceptor {
public void handle(SelectionKey key) {
try {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
// 接收该channel上的socket连接
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) {
// 将该channel设置为非阻塞模式
socketChannel.configureBlocking(false);
// 将该channel注册到selector上并设置关注OP_READ事件
SelectionKey selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);
// 调用SocketReadHandlerThread来处理channel的相关事件
selectionKey.attach(new SocketHandler());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package com.reactor;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
/**
* socket读取数据处理线程<br/>
* 在这里将读取、处理、发送放到一起了<br/>
*
* @author yaosht
*
*/
public class SocketHandler {
/**
* 处理读取数据
*/
public void handle(SelectionKey key) {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
try {
// byteBuffer.clear();
SocketChannel socketChannel = (SocketChannel) key.channel();
socketChannel.read(byteBuffer);
// 从缓冲区中get数据并进行解析计算
byteBuffer.flip();
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
String formulaStr = new String(bytes);
System.out.println("服务器端线程:" + Thread.currentThread().getName() + "处理客户端("
+ socketChannel.getRemoteAddress().toString() + ")发送的公式:" + formulaStr);
if (formulaStr.equals("end")) {
System.out.println("客户端(" + socketChannel.getRemoteAddress().toString() + ")结束通信");
socketChannel.close();
key.cancel();
} else if (!formulaStr.toString().contains("+")) {
byteBuffer.clear();
byteBuffer.put(("客户端(" + socketChannel.getRemoteAddress().toString() + ")发送不正确的加法公式").getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
} else {
// 进行计算,并将结果放入到缓冲区中
String stra = formulaStr.substring(0, formulaStr.indexOf("+"));
String strb = formulaStr.substring(formulaStr.indexOf("+") + 1, formulaStr.length());
int a = Integer.parseInt(stra);
int b = Integer.parseInt(strb);
int sum = a + b;
byteBuffer.clear();
System.out.println(
"客户端(" + socketChannel.getRemoteAddress().toString() + ")公式计算结果:" + formulaStr + "=" + sum);
byteBuffer.put(String.valueOf(formulaStr + "=" + sum).getBytes());
// 从缓冲区中读取数据到通道发向客户端
byteBuffer.flip();
socketChannel.write(byteBuffer);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.reactor;
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.util.Iterator;
import java.util.Random;
/**
* 客户端线程
*
* @author yaosht
*
*/
public class ClientThread implements Runnable {
InetSocketAddress addr = null;
public ClientThread(InetSocketAddress addr) {
this.addr = addr;
}
@Override
public void run() {
String str;
// 创建一个对象
Random df = new Random();
try {
// 通过SocketChannel的静态方法open()创建一个SocketChannel对象
SocketChannel socketChannel = SocketChannel.open();
// 配置该socket为非阻塞模式
socketChannel.configureBlocking(false);
Selector selector = Selector.open();
// SocketChannel连接服务器端
// 因为前面设置的是非阻塞,所以调用这个方法会立即返回.
// 因此如果返回true就不需要放到select中
if (!socketChannel.connect(addr)) {
// 向选择器selector注册该socketchannel并关注OP_CONNECT事件
socketChannel.register(selector, SelectionKey.OP_CONNECT);
// 进行选择
selector.select();
// 得到关注的事件集合
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
if (selectionKey.isValid() && selectionKey.isConnectable()) {
SocketChannel channel = (SocketChannel) selectionKey.channel();
// 如果连接成功则完成连接
if (channel.isConnectionPending()) {
channel.finishConnect();
}
System.out.println(Thread.currentThread().getName() + "客户端连接成功");
// 设置该socketChannel为非阻塞模式
channel.configureBlocking(false);
// 向选择器selector注册该socketchannel并关注OP_READ事件
channel.register(selector, SelectionKey.OP_READ);
}
}
}
int formulaNum = 2;
for (int j = 1; j <= formulaNum; j++) {
// 产生两个加法随机数
int num1 = df.nextInt(101);
int num2 = df.nextInt(101);
// 创建数据缓存区对象
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
if (j == formulaNum) {
str = "end";
System.out.println(Thread.currentThread().getName() + "发送结束信号:" + str);
// 将从终端读取到的数据放到缓冲区
byteBuffer.clear();
byteBuffer.put(str.getBytes());
// 读取缓冲区中的数据到通道中,发向服务端
byteBuffer.flip();
socketChannel.write(byteBuffer);
} else {
// 拼接两个随机数产生加法公式
str = num1 + "+" + num2;
System.out.println(Thread.currentThread().getName() + "发送加法公式:" + str);
// 将从终端读取到的数据放到缓冲区
byteBuffer.clear();
byteBuffer.put(str.getBytes());
// 读取缓冲区中的数据到通道中,发向服务端
byteBuffer.flip();
socketChannel.write(byteBuffer);
System.out.println("等待服务器端消息……");
// 监听读事件,没有则阻塞
selector.select();
// 清除缓冲区
byteBuffer.clear();
// 读取通道中从服务器端来的数据到缓冲区中
socketChannel.read(byteBuffer);
byteBuffer.flip();
// 通过缓冲区有效元素大小确定接收数组大小
byte[] bytes = new byte[byteBuffer.remaining()];
// 从缓冲区读取数据
byteBuffer.get(bytes);
String stringBuffer = new String(bytes);
System.out.println(Thread.currentThread().getName() + "服务器端计算结果:" + stringBuffer);
}
}
socketChannel.close();
selector.close();
} catch (Exception e) {
System.out.println("异常:" + e);
}
}
}
package com.reactor;
import java.io.IOException;
/**
* 启动服务器端线程
*
* @author yaosht
*
*/
public class StartServerReactor {
public static void main(String[] args) throws IOException {
new Thread(new ServerReactorThread(8080), "ServerReactorThread").start();
}
}
package com.reactor;
import java.io.IOException;
import java.net.InetSocketAddress;
/**
* 客户端多线程启动
*
* @author yaosht
*
*/
public class StartClient {
public static void main(String[] args) throws IOException {
ClientThread clientThread = new ClientThread(new InetSocketAddress("127.0.0.1", 8080));
for (int i = 0; i < 10; i++) {
new Thread(clientThread, "Thread-" + i).start();
}
}
}
单Reactor多线程
特点:
- 有专门一个reactor线程用于监听服务端ServerSocketChannel,接收客户端的TCP连接请求;
- 网络IO的读/写操作等由一个worker reactor线程池负责,由线程池中的NIO线程负责监听SocketChannel事件,进行消息的读取、解码、编码和发送。
- 一个NIO线程可以同时处理N条链路,但是一个链路只注册在一个NIO线程上处理,防止发生并发操作问题。
程序示例
package com.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.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
/**
* Reactor线程
*
* @author yaosht
*
*/
public class ServerReactorThread implements Runnable {
// 选择器
public final Selector selector;
// ServerSocketChannel
public final ServerSocketChannel serverSocketChannel;
/**
* 构造方法
*
* @param port
* @throws IOException
*/
public ServerReactorThread(int port) throws IOException {
// 打开一个选择器selector
selector = Selector.open();
// 通过ServerSocketChannel 的open()方法创建一个ServerSocketChannel对象
serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(port);
// 端口地址
serverSocketChannel.socket().bind(inetSocketAddress);
// 配置ServerSocketChannel为非阻塞模式
serverSocketChannel.configureBlocking(false);
// 将该channel注册到selector上并设置关注OP_ACCEPT监听事件
SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 利用selectionKey的attache功能绑定Acceptor 如果有事情,触发Acceptor
selectionKey.attach(new Acceptor());
System.out.println("等待客户端的消息……");
}
@Override
public void run() {
try {
while (true) {
// 进行选择操作
while (selector.select() > 0) {
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
System.out.println("有关注的事件发生:" + keys.size());
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
// 派发事件处理
dispatch(selectionKey);
}
// 清除该集合中的所有元素
keys.clear();
} // while
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 进行SelectionKey事件派发<br/>
* 获取attach附加的对象并进行方法调用:Acceptor或SocketHandler<br/>
*
* @param key
*/
void dispatch(SelectionKey key) {
if (key.isValid()) {
Runnable runnable = (Runnable) key.attachment();
// System.out.println(runnable.i);
if (runnable != null) {
runnable.run();
}
}
}
/**
* 内部类Acceptor
*
* @author yaosht
*
*/
class Acceptor implements Runnable {
@Override
public void run() {
try {
// 接收该channel上的socket连接
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) {
// 为每一个连接上的socketChannel创建一个SocketHandler处理类
new SocketHandler(socketChannel, selector);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package com.reactor;
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.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* socket读取数据处理线程<br/>
* 在这里将读取、处理、发送放到一起了<br/>
*
* @author yaosht
*
*/
public class SocketHandler implements Runnable {
// SocketChannel
private SocketChannel socketChannel;
// SelectionKey
private SelectionKey selectionKey;
// 这里使用了状态码来防止多线程出现数据不一致等问题
// 该SocketChannel的事件在处理中
static final int PROCESSING = 1;
// 该SocketChannel的事件还在排队中
static final int WAITING = 2;
// volatile状态变量
private volatile int state = WAITING;
// 静态线程池
public static final ExecutorService pool = Executors.newFixedThreadPool(16);
public SocketHandler(SocketChannel socketChannel, Selector selector) throws IOException {
this.socketChannel = socketChannel;
// 将该channel设置为非阻塞模式
socketChannel.configureBlocking(false);
// 将该channel注册到selector上并设置关注OP_READ事件
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() {
if (state == WAITING) {
// 如果此时没有线程在处理该通道的本次读取,就提交申请到线程池进行读写操作
pool.execute(new Process(selectionKey));
} else {
// 如果此时有线程正在进行读写操作,就直接return,选择器会进行下一次选择和任务分派
System.out.println(this.socketChannel.toString() + "已经被处理中");
return;
}
}
/**
* 这是一个同步方法,因为在reactor中的选择器有可能会出现一种状况:
* 当process线程已经要对某通道进行读写的时候,有可能Selector会再次选择该通道
* 因为此时该process线程还并没有真正的进行读写,会导致另一线程重新创建一个process
* 并试图进行读写操作,此时就会出现cpu资源浪费的情况,或者出现异常,因为线程1在读取通道内容的时候
* 线程2就会被阻塞,而等到线程2执行操作的时候,线程1已经对通道完成了读写操做 因此可以通过设置对象状态码来防止出现这些问题
*/
private synchronized void handle(SelectionKey key) {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
try {
byteBuffer.clear();
socketChannel.read(byteBuffer);
// 从缓冲区中get数据并进行解析计算
byteBuffer.flip();
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
String formulaStr = new String(bytes);
System.out.println("服务器端线程:" + Thread.currentThread().getName() + "处理客户端("
+ socketChannel.getRemoteAddress().toString() + ")发送的公式:" + formulaStr);
if (formulaStr.equals("end")) {
System.out.println("客户端(" + socketChannel.getRemoteAddress().toString() + ")结束通信");
socketChannel.close();
key.cancel();
} else if (!formulaStr.toString().contains("+")) {
byteBuffer.clear();
byteBuffer.put(("客户端(" + socketChannel.getRemoteAddress().toString() + ")发送不正确的加法公式").getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
} else {
// 进行计算,并将结果放入到缓冲区中
String stra = formulaStr.substring(0, formulaStr.indexOf("+"));
String strb = formulaStr.substring(formulaStr.indexOf("+") + 1, formulaStr.length());
int a = Integer.parseInt(stra);
int b = Integer.parseInt(strb);
int sum = a + b;
byteBuffer.clear();
System.out.println(
"客户端(" + socketChannel.getRemoteAddress().toString() + ")公式计算结果:" + formulaStr + "=" + sum);
byteBuffer.put(String.valueOf(formulaStr + "=" + sum).getBytes());
// 从缓冲区中读取数据到通道发向客户端
byteBuffer.flip();
socketChannel.write(byteBuffer);
}
state = WAITING;
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Handler中处理从通道读取数据到缓冲区并进行加法计算的内部线程处理类
*
* @author yaosht
*
*/
class Process implements Runnable {
private SelectionKey selectionKey;
public Process(SelectionKey selectionKey) {
this.selectionKey = selectionKey;
state = PROCESSING;
}
@Override
public void run() {
handle(selectionKey);
}
}
}
package com.reactor;
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.util.Iterator;
import java.util.Random;
/**
* 客户端线程
*
* @author yaosht
*
*/
public class ClientThread implements Runnable {
InetSocketAddress addr = null;
public ClientThread(InetSocketAddress addr) {
this.addr = addr;
}
@Override
public void run() {
String str;
// 创建一个对象
Random df = new Random();
try {
// 通过SocketChannel的静态方法open()创建一个SocketChannel对象
SocketChannel socketChannel = SocketChannel.open();
// 配置该socket为非阻塞模式
socketChannel.configureBlocking(false);
Selector selector = Selector.open();
// SocketChannel连接服务器端
// 因为前面设置的是非阻塞,所以调用这个方法会立即返回.
// 因此如果返回true就不需要放到select中
if (!socketChannel.connect(addr)) {
// 向选择器selector注册该socketchannel并关注OP_CONNECT事件
socketChannel.register(selector, SelectionKey.OP_CONNECT);
// 进行选择
selector.select();
// 得到关注的事件集合
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
if (selectionKey.isValid() && selectionKey.isConnectable()) {
SocketChannel channel = (SocketChannel) selectionKey.channel();
// 如果连接成功则完成连接
if (channel.isConnectionPending()) {
channel.finishConnect();
}
System.out.println(Thread.currentThread().getName() + "客户端连接成功");
// 设置该socketChannel为非阻塞模式
channel.configureBlocking(false);
// 向选择器selector注册该socketchannel并关注OP_READ事件
channel.register(selector, SelectionKey.OP_READ);
}
}
}
int formulaNum = 2;
for (int j = 1; j <= formulaNum; j++) {
// 产生两个加法随机数
int num1 = df.nextInt(101);
int num2 = df.nextInt(101);
// 创建数据缓存区对象
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
if (j == formulaNum) {
str = "end";
System.out.println(Thread.currentThread().getName() + "发送结束信号:" + str);
// 将从终端读取到的数据放到缓冲区
byteBuffer.clear();
byteBuffer.put(str.getBytes());
// 读取缓冲区中的数据到通道中,发向服务端
byteBuffer.flip();
socketChannel.write(byteBuffer);
} else {
// 拼接两个随机数产生加法公式
str = num1 + "+" + num2;
System.out.println(Thread.currentThread().getName() + "发送加法公式:" + str);
// 将从终端读取到的数据放到缓冲区
byteBuffer.clear();
byteBuffer.put(str.getBytes());
// 读取缓冲区中的数据到通道中,发向服务端
byteBuffer.flip();
socketChannel.write(byteBuffer);
System.out.println("等待服务器端消息……");
// 监听读事件,没有则阻塞
selector.select();
// 清除缓冲区
byteBuffer.clear();
// 读取通道中从服务器端来的数据到缓冲区中
socketChannel.read(byteBuffer);
byteBuffer.flip();
// 通过缓冲区有效元素大小确定接收数组大小
byte[] bytes = new byte[byteBuffer.remaining()];
// 从缓冲区读取数据
byteBuffer.get(bytes);
String stringBuffer = new String(bytes);
System.out.println(Thread.currentThread().getName() + "服务器端计算结果:" + stringBuffer);
}
}
socketChannel.close();
selector.close();
} catch (Exception e) {
System.out.println("异常:" + e);
}
}
}
客户端启动与服务器端启动代码一样,就不贴了
主从Reactor多线程
在绝大多数场景下,Reactor多线程模型都可以满足性能需求;但是在极个别特殊场景中,一个NIO线程负责监听和处理所有的客户端连接可能会存在性能问题。
特点:
- 服务端用于接收客户端连接的不再是个1个单独的reactor线程,而是一个boss reactor线程池;
- 服务端启用多个ServerSocketChannel监听不同端口时,每个ServerSocketChannel的监听工作可以由线程池中的一个NIO线程完成。