Reactor模式與NIO網絡模型

在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連接。本人對網絡編程的認識也是剛剛開始,理解的不對的地方歡迎討論,在此提前謝過。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章