在web開發中, 我們最常用的模式就是MVC,將web程序的不同部分分而治之。在Nio網絡程序開發中,也有一些類似的開發模式,比如Reactor模式,來方便我們對程序進行解耦和控制。
維基百科對Reactor pattern
的解釋:
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模式主要有以下特點:
- 事件驅動
- 請求分發
- 事件處理
我們通過NIO的編程例子去理解這三個部分:
1、事件驅動:
在編寫程序的過程中,最習慣的方式是:在哪種條件下,將執行哪段代碼
if(condition1) {
//event1
}
else if(condition2) {
//event2
}
//...
現在,我們用多態的方式實現這個邏輯
pulic class Condition1 implements Runable {
run() {
//event1
}
}
pulic class Condition2 implements Runable {
run() {
//event2
}
}
Runable r = new Runable() //Condition1、Condition2 這兩個其中一個
r.run()
...
通過這種方式,能夠根據r實際的類型去執行對應的邏輯,對於Reator模式,也是如此,拿Nio網絡編程來舉例,我們會提前在每一個SelectionKey上註冊對應的響應事件程序,等到事件發生時,再進行回調:
public class Reactor implements Runnable {
final Selector selector;
final ServerSocketChannel ssc;
public Reactor(int port) throws IOException {
selector = Selector.open();
ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(port));
ssc.configureBlocking(false);
//註冊事件處理程序Acceptor
ssc.register(selector, SelectionKey.OP_ACCEPT, new Acceptor(selector, ssc));
}
public void run() {
while (!Thread.interrupted()) {
try {
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
dispatch(keyIterator.next());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void dispatch(SelectionKey key) {
//事件發生時 回調事件響應程序
Runnable r = (Runnable) key.attachment();
if(r != null) {
r.run();
}
}
}
public class Handler implements Runnable {
final SelectionKey sk;
final SocketChannel sc;
static final int MAXIN = 10000, MAXOUT = 10000;
ByteBuffer input = ByteBuffer.allocate(MAXIN);
ByteBuffer output = ByteBuffer.allocate(MAXOUT);
static final int READING = 0, SENDING = 1, PROCESSING = 3;
int state = READING;
static ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 10,
0L,TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
public Handler(Selector selector, SocketChannel sc) throws IOException {
this.sc = sc;
sc.configureBlocking(false);
//註冊事件處理程序
sk = sc.register(selector, SelectionKey.OP_READ, this);
selector.wakeup();
}
public void run() {
try {
if (state == READING) {
read();
} else if (state == SENDING) {
send();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//...
}
2、事件分發:
事件分發對應於多路複用的網路事件,即接收就緒、讀就緒、寫就緒和連接就緒。對於不同的事件,交由不同的邏輯處理。在例子中事件分發通過Java的多態隱式的進行了分發。
3、事件處理:
上面的過程幹了兩件事,響應請求和事件轉發,第三件事情就是真正的處理請求,也就是事件處理Handler
public class Handler implements Runnable {
final SelectionKey sk;
final SocketChannel sc;
static final int MAXIN = 10000, MAXOUT = 10000;
ByteBuffer input = ByteBuffer.allocate(MAXIN);
ByteBuffer output = ByteBuffer.allocate(MAXOUT);
static final int READING = 0, SENDING = 1, PROCESSING = 3;
int state = READING;
static ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 10,
0L,TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
public Handler(Selector selector, SocketChannel sc) throws IOException {
this.sc = sc;
sc.configureBlocking(false);
sk = sc.register(selector, SelectionKey.OP_READ, this);
selector.wakeup();
}
public void run() {
try {
if (state == READING) {
read();
} else if (state == SENDING) {
send();
}
} catch (IOException e) {
e.printStackTrace();
}
}
synchronized void read() throws IOException {
sc.read(input);
if (inputIsComplete()) {
state = PROCESSING;
pool.execute(new Processor());
}
}
synchronized void send() throws IOException {
sc.write(output);
if (outputIsComplete()) {
sk.cancel();
}
}
private boolean inputIsComplete() {
return true;
}
private boolean outputIsComplete() {
return true;
}
private void process() { /* ... */ }
synchronized void processAndHandOff() {
process();
state = SENDING; // or rebind attachment
sk.interestOps(SelectionKey.OP_WRITE);
}
public class Processor implements Runnable{
@Override
public void run() {
processAndHandOff();
}
}
}
總結:對於Reactor的理解主要是將網絡請求分爲三個部分:響應請求,轉發請求和處理請求。響應請求是基於註冊響應程序的事件驅動的方式。在學習Reactor的過程中寫了一個簡單的demo:GitHub連接。本人對網絡編程的認識也是剛剛開始,理解的不對的地方歡迎討論,在此提前謝過。