淺入淺出Netty(一)BIO與NIO

傳統IO模型

在開始瞭解 Netty 是什麼之前,我們先來回顧一下,如果我們需要實現一個客戶端與服務端通信的程序,使用傳統的Socket通信,應該如何來實現?

public class BIOServer {

    public static void main(String[] args) throws Exception {

        //創建Socket服務,監聽8000端口
        ServerSocket server = new ServerSocket(8000);
        System.out.println("服務端啓動!");
        while (true) {
            //獲取一個套接字(阻塞)
            final Socket socket = server.accept();
            System.out.println("出現一個新客戶端!");
            //業務處理
            handle(socket);
        }
    }

    /**
     * 處理數據
     * @param socket
     * @throws IOException
     */
    private static void handle(Socket socket) throws IOException {
        byte[] bytes = new byte[1024];
        InputStream input = socket.getInputStream();

        int read = 0;
        while (read != -1) {
            //讀取數據(阻塞)
            read = input.read(bytes);
            System.out.println(new String(bytes, 0, read));
        }
    }
}

這段代碼上面有兩個阻塞點,一個是server.accept()(等待客戶端連接),一個是input.read(bytes) (等待客戶端發送信息),如果客戶端一直不發數據,那麼線程就一直會阻塞在input.read(bytes)。此時在阻塞過程中,意味着這條線程是被這個Socket一直佔用着的,其它的Socket不能進來

想要服務端處理多個客戶端的信息,就需要爲每一個客戶端分配一個線程。下面我們修改一下服務端:

public class BIOServerV2 {

    public static void main(String[] args) throws Exception {
        //創建一個緩存線程池
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();

        //創建Socket服務,監聽8000端口
        ServerSocket server = new ServerSocket(8001);
        System.out.println("服務端啓動!");
        while (true) {
            //獲取一個套接字(阻塞)
            final Socket socket = server.accept();
            System.out.println("出現一個新客戶端!");
            //在線程池爲新客戶端開一個線程
            newCachedThreadPool.execute(() -> handle(socket));
        }
    }

    /**
     * 處理數據
     *
     * @param socket
     * @throws IOException
     */
    private static void handle(Socket socket) {
        try {
            byte[] bytes = new byte[1024];
            InputStream input = socket.getInputStream();

            int read = 0;
            while (read != -1) {
                //讀取數據(阻塞)
                read = input.read(bytes);
                System.out.println(new String(bytes, 0, read));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

}

可以看到我們創建了一個緩存線程池,當服務端新連接了一個客戶端的時候,就創建一個新的線程爲客戶端進行服務

上面的 demo,從服務端代碼中我們可以看到,在傳統的 IO 模型中,每個連接創建成功之後都需要一個線程來維護。因爲目前我們每個客戶端都爲其分配了一個線程去運行,如果有一萬個客戶端進來,我們就要分配一萬個線程給客戶端使用,這樣的資源消耗是十分巨大的。

NIO模型

於是JDK 在 1.4 之後提出了 NIO,在 NIO 模型中,一條連接來了之後,直接把這條連接註冊到 selector 上,然後,通過檢查這個 selector,就可以批量監測出有數據可讀的連接,進而讀取數據

image

另外IO 讀寫是面向流的,一次性只能從流中讀取一個或者多個字節,並且讀完之後流無法再讀取,你需要自己緩存數據。 而 NIO 的讀寫是面向 Buffer 的,你可以隨意讀取裏面任何一個字節數據,不需要你自己緩存數據,這一切只需要移動讀寫指針即可。

核心代碼

/**
     * 採用輪詢的方式監聽selector是否有需要處理的事件,如果有,則進行處理
     *
     * @throws IOException
     */
    public void listen() throws IOException {
        System.out.println("服務端啓動成功!");
        //輪詢訪問selector
        while (true) {
            //當註冊的事件到達時,方法返回;否則,該方法會一直阻塞
            selector.select();
            //獲得selector中選中的項的迭代器,選中的項爲註冊的事件
            Iterator<SelectionKey> ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey key = ite.next();
                //刪除已選的key,以防重複處理
                ite.remove();
                if (key.isAcceptable()) {//客戶端請求連接事件
                    handlerAccept(key);
                } else if (key.isReadable()) {//獲得了可讀的事件
                    handlerRead(key);
                }
            }
        }
    }

對於傳統IO和NIO,網上有一對圖片表達的非常好:

image
我們的系統就相當於一個餐廳,大門相當於ServerSocket,客人相當於socket客戶端,服務員相當於每個socket客戶端的處理線程。當在多線程的情況下處理客戶端的時候,就相當於餐廳每一個客人都配備了一個專門的服務員,這不管對系統還是餐廳,都是很大的開銷。

而對於NIO:

image

這裏也是將系統比喻爲一個餐廳,大門相當於serverChannel.socket().bind(new InetSocketAddress(10010)),客人相當於SocketChannel客戶端,服務員相當於線程和selector,只需要一個服務員就可以服務所有的客人了,這對於系統或餐廳來說都是一個低開銷的事情。

下表總結了Java IO和NIO之間的主要區別:

IO NIO
面向流 面向緩衝
阻塞IO 非阻塞IO
選擇器
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章