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線程完成。
參考netty學習系列二:NIO Reactor模型 & Netty線程模型