多執行緒Reactor模式旨在分配多個reactor每一個reactor獨立擁有一個selector,在網路通訊中大體設計為負責連線的主Reactor,其中在主Reactor的run函式中若selector檢測到了連線事件的發生則dispatch該事件。
讓負責管理連線的Handler處理連線,其中在這個負責連線的Handler處理器中建立子Handler用以處理IO請求。這樣一來連線請求與IO請求分開執行提高通道的併發量。同時多個Reactor帶來的好處是多個selector可以提高通道的檢索速度
1.1 主伺服器
package com.crazymakercircle.ReactorModel;
import com.crazymakercircle.NioDemoConfig;
import com.crazymakercircle.util.Logger;
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;
import java.util.concurrent.atomic.AtomicInteger;
class MultiThreadEchoServerReactor {
ServerSocketChannel serverSocket;
AtomicInteger next = new AtomicInteger(0);
Selector bossSelector = null;
Reactor bossReactor = null;
//selectors集合,引入多個selector選擇器
//多個選擇器可以更好的提高通道的併發量
Selector[] workSelectors = new Selector[2];
//引入多個子反應器
//如果CPU是多核的可以開啟多個子Reactor反應器,這樣每一個子Reactor反應器還可以獨立分配一個執行緒。
//每一個執行緒可以單獨繫結一個單獨的Selector選擇器以提高通道併發量
Reactor[] workReactors = null;
MultiThreadEchoServerReactor() throws IOException {
bossSelector = Selector.open();
//初始化多個selector選擇器
workSelectors[0] = Selector.open();
workSelectors[1] = Selector.open();
serverSocket = ServerSocketChannel.open();
InetSocketAddress address =
new InetSocketAddress(NioDemoConfig.SOCKET_SERVER_IP,
NioDemoConfig.SOCKET_SERVER_PORT);
serverSocket.socket().bind(address);
//非阻塞
serverSocket.configureBlocking(false);
//第一個selector,負責監控新連線事件
SelectionKey sk =
serverSocket.register(bossSelector, SelectionKey.OP_ACCEPT);
//附加新連線處理handler處理器到SelectionKey(選擇鍵)
sk.attach(new AcceptorHandler());
//處理新連線的反應器
bossReactor = new Reactor(bossSelector);
//第一個子反應器,一子反應器負責一個選擇器
Reactor subReactor1 = new Reactor(workSelectors[0]);
//第二個子反應器,一子反應器負責一個選擇器
Reactor subReactor2 = new Reactor(workSelectors[1]);
workReactors = new Reactor[]{subReactor1, subReactor2};
}
private void startService() {
new Thread(bossReactor).start();
// 一子反應器對應一條執行緒
new Thread(workReactors[0]).start();
new Thread(workReactors[1]).start();
}
//反應器
class Reactor implements Runnable {
//每條執行緒負責一個選擇器的查詢
final Selector selector;
public Reactor(Selector selector) {
this.selector = selector;
}
public void run() {
try {
while (!Thread.interrupted()) {
//單位為毫秒
//每隔一秒列出選擇器感應列表
selector.select(1000);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
if (null == selectedKeys || selectedKeys.size() == 0) {
//如果列表中的通道註冊事件沒有發生那就繼續執行
continue;
}
Iterator<SelectionKey> it = selectedKeys.iterator();
while (it.hasNext()) {
//Reactor負責dispatch收到的事件
SelectionKey sk = it.next();
dispatch(sk);
}
//清楚掉已經處理過的感應事件,防止重複處理
selectedKeys.clear();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
void dispatch(SelectionKey sk) {
Runnable handler = (Runnable) sk.attachment();
//呼叫之前attach繫結到選擇鍵的handler處理器物件
if (handler != null) {
handler.run();
}
}
}
// Handler:新連線處理器
class AcceptorHandler implements Runnable {
public void run() {
try {
SocketChannel channel = serverSocket.accept();
Logger.info("接收到一個新的連線");
if (channel != null) {
int index = next.get();
Logger.info("選擇器的編號:" + index);
Selector selector = workSelectors[index];
new MultiThreadEchoHandler(selector, channel);
}
} catch (IOException e) {
e.printStackTrace();
}
if (next.incrementAndGet() == workSelectors.length) {
next.set(0);
}
}
}
public static void main(String[] args) throws IOException {
MultiThreadEchoServerReactor server =
new MultiThreadEchoServerReactor();
server.startService();
}
}
按上述的設計思想,在主伺服器中實際上設計了三個Reactor,一個主Reactor專門負責連線請求並配已單獨的selector,但是三個Reactor的執行緒Run函式是做的相同的功能,都是根據每個執行緒內部的selector進行檢索事件列表,若註冊的監聽事件發生了則呼叫dispactch分發到每個Reactor對應的Handler。
這裡需要注意的一開始其實只有負責連線事件的主Reactor在註冊selector的時候給相應的key配了一個AcceptorHandler()。
//第一個selector,負責監控新連線事件
SelectionKey sk =
serverSocket.register(bossSelector, SelectionKey.OP_ACCEPT);
//附加新連線處理handler處理器到SelectionKey(選擇鍵)
sk.attach(new AcceptorHandler());
但是Reactor的run方法裡若相應的selector key發生了便要dispatch到一個Handler。這裡其他兩個子Reactor的Handler在哪裡賦值的呢?其實在處理連線請求的Reactor中便建立了各個子Handler,如下程式碼所示:
主Handler中先是根據伺服器channel建立出客服端channel,在進行子selector與channel的繫結。
int index = next.get();
Logger.info("選擇器的編號:" + index);
Selector selector = workSelectors[index];
new MultiThreadEchoHandler(selector, channel);
2.1 IO請求handler+執行緒池
package com.crazymakercircle.ReactorModel;
import com.crazymakercircle.util.Logger;
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;
class MultiThreadEchoHandler implements Runnable {
final SocketChannel channel;
final SelectionKey sk;
final ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
static final int RECIEVING = 0, SENDING = 1;
int state = RECIEVING;
//引入執行緒池
static ExecutorService pool = Executors.newFixedThreadPool(4);
MultiThreadEchoHandler(Selector selector, SocketChannel c) throws IOException {
channel = c;
channel.configureBlocking(false);
//喚醒選擇,防止register時 boss執行緒被阻塞,netty 處理方式比較優雅,會在同一個執行緒註冊事件,避免阻塞boss
selector.wakeup();
//僅僅取得選擇鍵,後設定感興趣的IO事件
sk = channel.register(selector, 0);
//將本Handler作為sk選擇鍵的附件,方便事件dispatch
sk.attach(this);
//向sk選擇鍵註冊Read就緒事件
sk.interestOps(SelectionKey.OP_READ);
//喚醒選擇,是的OP_READ生效
selector.wakeup();
Logger.info("新的連線 註冊完成");
}
public void run() {
//非同步任務,在獨立的執行緒池中執行
pool.execute(new AsyncTask());
}
//非同步任務,不在Reactor執行緒中執行
public synchronized void asyncRun() {
try {
if (state == SENDING) {
//寫入通道
channel.write(byteBuffer);
//寫完後,準備開始從通道讀,byteBuffer切換成寫模式
byteBuffer.clear();
//寫完後,註冊read就緒事件
sk.interestOps(SelectionKey.OP_READ);
//寫完後,進入接收的狀態
state = RECIEVING;
} else if (state == RECIEVING) {
//從通道讀
int length = 0;
while ((length = channel.read(byteBuffer)) > 0) {
Logger.info(new String(byteBuffer.array(), 0, length));
}
//讀完後,準備開始寫入通道,byteBuffer切換成讀模式
byteBuffer.flip();
//讀完後,註冊write就緒事件
sk.interestOps(SelectionKey.OP_WRITE);
//讀完後,進入傳送的狀態
state = SENDING;
}
//處理結束了, 這裡不能關閉select key,需要重複使用
//sk.cancel();
} catch (IOException ex) {
ex.printStackTrace();
}
}
//非同步任務的內部類
class AsyncTask implements Runnable {
public void run() {
MultiThreadEchoHandler.this.asyncRun();
}
}
}
在處理IO請求的Handler中採用了執行緒池,已達到非同步處理的目的。
3.1 客戶端
package com.crazymakercircle.ReactorModel;
import com.crazymakercircle.NioDemoConfig;
import com.crazymakercircle.util.Dateutil;
import com.crazymakercircle.util.Logger;
import java.io.IOException;
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.Scanner;
import java.util.Set;
/**
* create by 尼恩 @ 瘋狂創客圈
**/
public class EchoClient {
public void start() throws IOException {
InetSocketAddress address =
new InetSocketAddress(NioDemoConfig.SOCKET_SERVER_IP,
NioDemoConfig.SOCKET_SERVER_PORT);
// 1、獲取通道(channel)
SocketChannel socketChannel = SocketChannel.open(address);
Logger.info("客戶端連線成功");
// 2、切換成非阻塞模式
socketChannel.configureBlocking(false);
//不斷的自旋、等待連線完成,或者做一些其他的事情
while (!socketChannel.finishConnect()) {
}
Logger.tcfo("客戶端啟動成功!");
//啟動接受執行緒
Processer processer = new Processer(socketChannel);
new Thread(processer).start();
}
static class Processer implements Runnable {
final Selector selector;
final SocketChannel channel;
Processer(SocketChannel channel) throws IOException {
//Reactor初始化
selector = Selector.open();
this.channel = channel;
channel.register(selector,
SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
public void run() {
try {
while (!Thread.interrupted()) {
selector.select();
Set<SelectionKey> selected = selector.selectedKeys();
Iterator<SelectionKey> it = selected.iterator();
while (it.hasNext()) {
SelectionKey sk = it.next();
if (sk.isWritable()) {
ByteBuffer buffer = ByteBuffer.allocate(NioDemoConfig.SEND_BUFFER_SIZE);
Scanner scanner = new Scanner(System.in);
Logger.tcfo("請輸入傳送內容:");
if (scanner.hasNext()) {
SocketChannel socketChannel = (SocketChannel) sk.channel();
String next = scanner.next();
buffer.put((Dateutil.getNow() + " >>" + next).getBytes());
buffer.flip();
// 操作三:傳送資料
socketChannel.write(buffer);
buffer.clear();
}
}
if (sk.isReadable()) {
// 若選擇鍵的IO事件是“可讀”事件,讀取資料
SocketChannel socketChannel = (SocketChannel) sk.channel();
//讀取資料
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int length = 0;
while ((length = socketChannel.read(byteBuffer)) > 0) {
byteBuffer.flip();
Logger.info("server echo:" + new String(byteBuffer.array(), 0, length));
byteBuffer.clear();
}
}
//處理結束了, 這裡不能關閉select key,需要重複使用
//selectionKey.cancel();
}
selected.clear();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
new EchoClient().start();
}
}