一、總體來說,反應器模式類似事件驅動模式。
- 在事件驅動模式中,當有事件觸發時,事件源會將事件dispatch分發到Handler處理器進行事件處理。
- 反應器模式中的反應器角色,類似於事件驅動模式中的dispatch事件分發器角色。
二、反應器模式的重要組件:
- Reactor反應器:負責查詢IO事件,當檢測到一個IO事件,將其發送給響應的Handler處理器。
- Handler處理器:與IO事件(或者選擇鍵)綁定,負責IO事件的處理。完成真正的連接建立、通道讀取、業務處理、寫出結果。
- SelectionKey選擇鍵的幾個重要方法:
- void attach(Object o)---爲SelectionKey添加附件。
- Object attachment()---獲取SelectionKey的附件。
三、單線程反應器實例
順序:
- 創建反應器。(打開選擇器,監聽端口)
- selector註冊serverSocket---ACCEPT(得到selectionKey-事件集)
- selection附件:Accpt處理器。
- 運行反應器,選擇器獲取事件。
- 分發事件。
- 獲取事件集對應的附件,運行Accpt處理器。
- Accpt處理器:接收新連接
- Accpt處理器:爲新連接創建IOHandler
- Accpt處理器:爲新連接創建IOHandler ---- (1)新的SocketChannel傳輸通道註冊,同一個選擇器中,保證是一個線程處理。
- Accpt處理器:爲新連接創建IOHandler ---- (2)將IOhandler自身作爲附件,加入選擇器中。
- Accpt處理器:爲新連接創建IOHandler ---- (3)註冊事件:讀&寫。
class Reactor implements Runnable {
Selector selector;
ServerSocketChannel serverSocket;
EchoServer Reactor() throws IOException {
//....省略:打開選擇器、serverSocket連接監聽通道
//註冊serverSocket的accept事件---註冊後才能被KEY選中---註冊事件會被選擇
SelectionKey sk =serverSocket.register(selector, SelectionKey.OP_ACCEPT);
//將新連接處理器作爲附件,綁定到sk選擇鍵
//爲事件:新建連接-綁定處理器
sk.attach(new AcceptorHandler());
}
public void run() {
//選擇器輪詢
try {
while (! Thread.interrupted()) {
//獲取IO事件
selector.select();
Set selected = selector.selectedKeys();
Iterator it = selected.iterator();
while (it.hasNext()) {
//反應器負責dispatch收到的事件
SelectionKey sk=it.next();
//分發
dispatch(sk);
}
selected.clear();
}
} catch (IOException ex) { ex.printStackTrace(); }
}
//反應器的分發方法
void dispatch(SelectionKey k) {
//獲取處理器
Runnable handler = (Runnable) (k.attachment());
//調用之前綁定到選擇鍵的handler處理器對象
if (handler ! = null) {
handler.run();
}
}
// 新連接處理器
class AcceptorHandler implements Runnable {
public void run() {
//接受新連接
//需要爲新連接,創建一個輸入輸出的handler處理器-IOHandler
}
}
//….
}
//一、將新的SocketChannel傳輸通道,註冊到反應器Reactor的同一個選擇器中,保證是一個線程處理。
//二、將IOhandler自身作爲附件,加入選擇鍵中。
class IOHandler implements Runnable {
final SocketChannel channel;
final SelectionKey sk;
IOHandler (Selector selector, SocketChannel c) throws IOException {
channel = c;
c.configureBlocking(false);
//僅僅取得選擇鍵,稍候設置感興趣的IO事件
sk = channel.register(selector, 0);
//將Handler處理器作爲選擇鍵的附件
sk.attach(this);
//註冊讀寫就緒事件
sk.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE);
}
public void run() {
//...處理輸入和輸出
}
}
單線程反應器的缺點:某個Handler阻塞時,會導致其他Handler得不到執行。
四、多線程Reactor反應器模式
總體思路:
- 將負責輸入輸出處理的Handler的執行,放入獨立線程池。
- 反應器拆分子反應器,每個子反應器負責一個選擇器,引入多個選擇器。
- 本質上-就是引入多個選擇器,分發,處理。
//....反應器
class MultiThreadEchoServerReactor {
ServerSocketChannelserverSocket;
AtomicInteger next = new AtomicInteger(0);
//選擇器集合,引入多個選擇器
Selector[] selectors = new Selector[2];
//引入多個子反應器
SubReactor[] subReactors = null;
MultiThreadEchoServer Reactor() throws IOException {
//初始化多個選擇器
selectors[0] = Selector.open();
selectors[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);
//第一個選擇器,負責監控新連接事件
SelectionKeysk =
serverSocket.register(selectors[0], SelectionKey.OP_ACCEPT);
//綁定Handler:attach新連接監控handler處理器到SelectionKey(選擇鍵)
sk.attach(new AcceptorHandler());
//第一個子反應器,一子反應器負責一個選擇器
SubReactor subReactor1 = new SubReactor(selectors[0]);
//第二個子反應器,一子反應器負責一個選擇器
SubReactor subReactor2 = new SubReactor(selectors[1]);
subReactors = new SubReactor[]{subReactor1, subReactor2};
}
private void startService() {
// 一子反應器對應一個線程
new Thread(subReactors[0]).start();
new Thread(subReactors[1]).start();
}
//子反應器
class SubReactor implements Runnable {
//每個線程負責一個選擇器的查詢和選擇
final Selector selector;
public SubReactor(Selector selector) {
this.selector = selector;
}
public void run() {
try {
while (! Thread.interrupted()) {
selector.select();
Set<SelectionKey>keySet = selector.selectedKeys();
Iterator<SelectionKey> it = keySet.iterator();
while (it.hasNext()) {
//反應器負責dispatch收到的事件
SelectionKeysk = it.next();
dispatch(sk);
}
keySet.clear();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
void dispatch(SelectionKeysk) {
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();
if (channel ! = null)
new MultiThreadEchoHandler(selectors[next.get()], channel);
} catch (IOException e) {
e.printStackTrace();
}
if (next.incrementAndGet() == selectors.length) {
next.set(0);
}
}
}
public static void main(String[] args) throws IOException {
MultiThreadEchoServerReactor server =
new MultiThreadEchoServerReactor();
server.startService();
}
}