網絡編程NIO之Reactor線程模型


上篇文章中寫了一些NIO相關的知識以及簡單的NIO實現示例,但是示例中,客戶端連接以及讀取、寫入、處理客戶端數據時都是在一個線程中,單個線程處理客戶端的數據,性能會很差,而且不能充分利用服務器的性能,這篇文章主要介紹Reactor線程模型,NIO的多路複用知識,用以提供服務端的性能。

單Reactor線程模型

在這裏插入圖片描述
單Reactor線程模型,最終還是使用了一個線程,和上篇文章最後的示例基本上沒啥差別,只是拆分了三個類來進行處理。

基於工作線程的Reactor線程模型

在這裏插入圖片描述
這裏的線程模型,是在客戶端連接後,業務數據的部分抽出來,放到線程池中處理,這樣做的好處是,如果處理業務數據時間很長,也不會影響客戶端的讀寫操作。
在這裏插入圖片描述
上面圖示是簡單的基於工作線程的工作模式,把業務數據處理單獨抽出來,在線程池處理。

多Reactor線程模型

在這裏插入圖片描述
最終的線程模型,是多Reactor線程模型。
客戶端數據的讀寫也是在多個線程中進行處理,充分提高了性能。
在這裏插入圖片描述

多Reactor線程模型示例

MainReactor-Thread和Acceptor代碼:

   /**
    * mainReactor-Thread
    * 接收客戶端連接,然後交給Acceptor處理
    */
    class MainReactor extends Thread {
        //創建一個Selector
        public Selector selector;

        AtomicInteger integer = new AtomicInteger(0);

        public MainReactor() throws IOException {
            selector = Selector.open();
        }

        @Override
        public void run() {
            while (true) {
                try {
                    //啓動Selector
                    selector.select();
                    //獲取事件
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();
                    //遍歷事件
                    while (iterator.hasNext()) {
                        SelectionKey selectionKey = iterator.next();
                        if (selectionKey.isAcceptable()) {
                            //獲取客戶端通道
                            SocketChannel socketChannel = ((ServerSocketChannel)selectionKey.channel()).accept();
                            Acceptor acceptor = new Acceptor();
                            //把客戶端通道交給Acceptor去處理
                            acceptor.register(socketChannel);
                        }
                        //處理完之後要移除
                        iterator.remove();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        public void register(ServerSocketChannel serverSocketChannel) throws ClosedChannelException {
            //註冊OP_ACCEPT事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        }

       /**
        *  處理客戶端的連接
        *  給每個客戶端連接分配一個subReactor-Thread
        */
       class Acceptor {

           public void register(SocketChannel socketChannel) throws IOException {
               //設置爲非阻塞模式
               socketChannel.configureBlocking(false);
               int index = integer.getAndIncrement() % subReactors.length;
               SubReactor subReactor = subReactors[index];
               //給客戶端連接分配一個subReactor線程
               subReactor.register(socketChannel);
               //啓動subReactor線程
               subReactor.start();
               System.out.println("收到新連接:" + socketChannel.getRemoteAddress());

           }
       }
}

SubReactor-Threads代碼:

    /**
     * 一個線程負責多個客戶端連接
     * 從channel中讀數據、寫數據
     */
    class SubReactor extends Thread {

        //創建一個Selector
        public Selector selector;
        //用於判斷SubReactor線程是否已經啓動
        public volatile boolean isRunning = false;

        public SubReactor() throws IOException {
            selector = Selector.open();
        }

        @Override
        public void start() {
            //判斷SubReactor線程是否已經啓動
            //如果沒有啓動,就啓動SubReactor線程
            if (!isRunning) {
                isRunning = true;
                super.start();
            }
        }

        @Override
        public void run() {
           while (isRunning) {
               try {
                   //啓動Selector
                   selector.select();
                   //獲取事件
                   Set<SelectionKey> selectionKeys = selector.selectedKeys();
                   Iterator<SelectionKey> iterator = selectionKeys.iterator();
                   //遍歷事件
                   while (iterator.hasNext()) {
                       SelectionKey selectionKey = iterator.next();
                       if (selectionKey.isReadable()) {
                           try {
                               SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
                               new Handler(socketChannel);
                           } catch (Exception e) {
                               e.printStackTrace();
                               selectionKey.cancel();
                           }
                       }
                       //處理完之後要移除
                       iterator.remove();
                   }
               } catch (IOException e) {
                   e.printStackTrace();
               }
           }
        }

        public void register(SocketChannel socketChannel) throws IOException {
            //註冊OP_READ事件
            socketChannel.register(selector, SelectionKey.OP_READ);
        }

        /** 讀取或者寫入數據 */
        class Handler {
            //用來讀取或者寫入數據
            public Handler(SocketChannel socketChannel) throws IOException {
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                while (socketChannel.isOpen() && socketChannel.read(readBuffer) != -1) {
                    //如果有數據可讀,簡單的判斷一下大於0
                    if (readBuffer.position() > 0) {
                        break;
                    }
                }
                //沒有數據可讀,就直接返回
                if (readBuffer.position() == 0) {
                    return;
                }
                //轉換爲讀取模式
                readBuffer.flip();
                byte[] bytes = new byte[readBuffer.limit()];
                readBuffer.get(bytes);
                System.out.println("獲取到新的數據:" + new String(bytes));
                System.out.println("獲取到新的數據,來自:" + socketChannel.getRemoteAddress());
                //線程池,用來處理業務數據
                threadPool.execute(new Runnable() {
                    @Override
                    public void run() {

                    }
                });

                //向客戶端寫數據
                String response = "HTTP/1.1 200 OK\r\n" +
                        "Content-Length: 11\r\n\r\n" +
                        "hello world";
                ByteBuffer writeBuffer = ByteBuffer.wrap(response.getBytes());
                while (writeBuffer.hasRemaining()) {
                    socketChannel.write(writeBuffer);
                }
            }
        }
 }

初始化代碼:

    /** 服務端通道 */
    public ServerSocketChannel serverSocketChannel;
    /** 用來接收客戶端連接 */
    public MainReactor mainReactor;
    /** 用來處理客戶端連接的讀取、寫入 */
    public SubReactor[] subReactors = new SubReactor[10];
    /** 線程池,用來處理客戶端連接後的業務邏輯 */
    public ExecutorService threadPool = Executors.newCachedThreadPool();

    public static void main(String[] args) throws IOException {
        NioReactor nioReactor = new NioReactor();
        nioReactor.initAndRegister();
        nioReactor.init();
        nioReactor.bind();
    }

    /** 初始化服務端 */
    public void init() throws IOException {
        //創建一個服務端通道
        serverSocketChannel = ServerSocketChannel.open();
        //設置爲非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //註冊到mainReactor-Thread
        mainReactor.register(serverSocketChannel);
        //啓動mainReactor-Thread線程
        mainReactor.start();
    }

    /** 服務端綁定端口 */
    public void bind() throws IOException {
        serverSocketChannel.socket().bind(new InetSocketAddress(8056));
        System.out.println("服務端啓動成功");
    }

    /** 初始化MainReactor和SubReactor */
    public void initAndRegister() throws IOException {
        mainReactor = new MainReactor();

        for (int i=0; i<subReactors.length; i++) {
           subReactors[i] = new SubReactor();
        }
    }

上面代碼mainReactorThread和subReactorThread中有大量的重複代碼,可以提取出來處理:

/** 多路複用,reactor線程模型 */
public class NioReactor2 {
    abstract class ReactorThread extends Thread {
        //創建一個Selector
        public Selector selector;
        //用於判斷線程是否已經啓動
        public volatile boolean isRunning = false;

        /** 有事件發生,就調用這個方法 */
        public abstract void handler(SelectableChannel channel) throws IOException;

        public ReactorThread() throws IOException {
            selector = Selector.open();
        }

        @Override
        public void run() {
            while (isRunning) {
                //啓動Selector
                try {
                    selector.select();
                    //獲取事件
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();
                    //遍歷事件
                    while (iterator.hasNext()) {
                        SelectionKey key = iterator.next();
                        int readyOps = key.readyOps();
                        //只關注是否有OP_ACCEPT和OP_READ事件
                        if ((readyOps & (SelectionKey.OP_ACCEPT | SelectionKey.OP_READ)) != 0 || readyOps == 0) {
                            try {
                                //獲取channel
                                SelectableChannel channel = key.channel();
                                //設置爲非阻塞模式
                                channel.configureBlocking(false);
                                //有事件,就調用handler方法
                                handler(channel);
                                if (!channel.isOpen()) {
                                    //如果channel關閉,就取消key
                                    key.cancel();
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                                //如果有異常,就取消key
                                key.cancel();
                            }
                        }
                        //處理完之後要移除
                        iterator.remove();
                    }
                    selector.selectNow();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        public SelectionKey register(SelectableChannel channel) throws ClosedChannelException {
            //先註冊到Selector,並沒有註冊任何事件
            return channel.register(selector, 0);
        }

        @Override
        public void start() {
            //判斷SubReactor線程是否已經啓動
            //如果沒有啓動,就啓動SubReactor線程
            if (!isRunning) {
                isRunning = true;
                super.start();
            }
        }
    }


    /** 服務端通道 */
    public ServerSocketChannel serverSocketChannel;
    /** 用來接收客戶端連接 */
    public ReactorThread[] mainReactors = new ReactorThread[1];;
    /** 用來處理客戶端連接的讀取、寫入 */
    public ReactorThread[] subReactors = new ReactorThread[10];
    /** 線程池,用來處理客戶端連接後的業務邏輯 */
    public ExecutorService threadPool = Executors.newCachedThreadPool();

    /**
     * 初始化mainReactors和subReactors
     */
    public void initAndRegister() throws IOException {
        //subReactors線程,用來客戶端連接後的讀寫
        for (int i=0; i<subReactors.length; i++) {
            subReactors[i] = new ReactorThread() {
                @Override
                public void handler(SelectableChannel channel) throws IOException {
                    SocketChannel socketChannel = (SocketChannel)channel;
                    ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                    while (socketChannel.isOpen() && socketChannel.read(readBuffer) != -1) {
                        //如果有數據可讀,簡單的判斷一下大於0
                        if (readBuffer.position() > 0) {
                            break;
                        }
                    }
                    //沒有數據可讀,就直接返回
                    if (readBuffer.position() == 0) {
                        return;
                    }
                    //轉換爲讀取模式
                    readBuffer.flip();
                    byte[] bytes = new byte[readBuffer.limit()];
                    readBuffer.get(bytes);
                    System.out.println("獲取到新的數據:" + new String(bytes));
                    System.out.println("獲取到新的數據,來自:" + socketChannel.getRemoteAddress());
                    //線程池,用來處理業務數據
                    threadPool.execute(new Runnable() {
                        @Override
                        public void run() {

                        }
                    });

                    //向客戶端寫數據
                    String response = "HTTP/1.1 200 OK\r\n" +
                            "Content-Length: 11\r\n\r\n" +
                            "hello world";
                    ByteBuffer writeBuffer = ByteBuffer.wrap(response.getBytes());
                    while (writeBuffer.hasRemaining()) {
                        socketChannel.write(writeBuffer);
                    }
                }
            };
        }

        //mainReactors線程,用於客戶端的連接
        for (int i=0; i<mainReactors.length; i++) {
            mainReactors[i] = new ReactorThread() {
                AtomicInteger integer = new AtomicInteger(0);

                @Override
                public void handler(SelectableChannel channel) throws IOException {
                    //獲取客戶端通道
                    SocketChannel socketChannel = ((ServerSocketChannel)channel).accept();
                    //設置爲非阻塞模式
                    socketChannel.configureBlocking(false);
                    int index = integer.getAndIncrement() % subReactors.length;
                    ReactorThread subReactor = subReactors[index];
                    //啓動線程
                    subReactor.start();
                    //註冊事件
                    SelectionKey key = subReactor.register(socketChannel);
                    key.interestOps(SelectionKey.OP_READ);
                    System.out.println("收到新連接:" + socketChannel.getRemoteAddress());
                }
            };
        }
    }

    /** 初始化服務端 */
    public void init() throws IOException {
        //創建一個服務端通道
        serverSocketChannel = ServerSocketChannel.open();
        //設置爲非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //註冊到mainReactor-Thread
        int index = new Random().nextInt(mainReactors.length);
        SelectionKey keys = mainReactors[index].register(serverSocketChannel);
        keys.interestOps(SelectionKey.OP_ACCEPT);
        //啓動mainReactor-Thread線程
        mainReactors[index].start();
    }

    /** 服務端綁定端口 */
    public void bind() throws IOException {
        serverSocketChannel.socket().bind(new InetSocketAddress(8056));
        System.out.println("服務端啓動成功");
    }

    public static void main(String[] args) throws IOException {
        NioReactor2 nioReactor = new NioReactor2();
        nioReactor.initAndRegister();
        nioReactor.init();
        nioReactor.bind();
    }
}

結束語

到此,NIO中的Reactor線程模型就結束了,上面的示例可以拆分幾個類進行處理,還可以根據HTTP協議的部分,解析請求,做一個簡單的tomcat服務器。

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