1. 程式人生 > >自頂向下深入分析Netty(一)--預備知識

自頂向下深入分析Netty(一)--預備知識

netty是基於Java NIO封裝的網路通訊框架,只有充分理解了Java NIO才能理解好netty的底層設計。Java NIO有幾個重要的概念Channel,Buffer,Selector。NIO是基於Channel和Buffer操作的,資料只能通過Buffer寫入到Channel或者從Channel讀出資料到Buffer中。Selector可以監聽多個通道的事件(連線開啟,資料到達),這樣便可以用一個執行緒監聽多個Channel的事件,從而可以用一個執行緒處理多個網路連線。

1.1 Channel

我們首先來看一下Channel的類圖:

我們主要關注三個類:SocketChannel,ServerSocketChannel以及DataGramChannel,其中SocketChannel是一個連線到TCP網路套接字的通道,可由兩種方式建立:
(1).開啟一個SocketChannel並連線到網際網路上的某臺伺服器。
(2).一個新連線到達ServerSocketChannel時,會建立一個SocketChannel(即由serverSocketChannel.accept()方法返回)。
ServerSocketChannel是一個可以監聽新進來的TCP連線的通道, 與標準IO中的ServerSocket類似。而DatagramChannel是一個能收發UDP包的通道。

1.2 Buffer

一個Buffer物件是固定數量的資料的容器。緩衝區的工作與通道緊密聯絡。通道是I/O傳輸發生時通過的入口,而緩衝區是這些資料傳輸的來源或目標。對於離開緩衝區的傳輸,想傳遞出去的資料被置於一個緩衝區,被傳送到通道。對於傳回緩衝區的傳輸,一個通道將資料複製到所提供的緩衝區中。這裡我們主要關注的是ByteBuffer,因為位元組是作業系統與I/O裝置之間或者作業系統與應用程序之間傳遞資料使用的基本資料型別,並且Java NIO中的Channel只接受ByteBuffer作為引數。

1.3 selector

Selector是Java NIO中能夠檢測多個通道,並能夠知曉通道是否為諸如讀寫時間做好準備的元件。當我們將1.1中的一個或多個SelectableChannel註冊到一個Selector物件中時,一個表示通道和選擇器關係的SelectionKey會被返回。SelectionKey將記住我們關心的通道,並且會追蹤對應的通道是否有事件已經就緒。當呼叫一個Selector物件的select()方法時,相關的SelectionKey將會被更新,用來檢查所有被註冊到改選擇器的通道。我們可以獲取一個SelectionKey的集合,從而找到已經就緒的通道。通過遍歷這些SelectionKey,我們可以選擇出每個從上次呼叫select()開始直到現在已經就緒的通道。

1.4 示例

通過之前的描述,相信你已經對Java NIO有了初步的瞭解,但具體的細節還很模糊,沒關係,記住我們的目標是自頂向下分析,也就是先整體後區域性,我們先建立對整體的認識再不斷完善區域性細節。下面我們將通過一個簡單的Java NIO echo Server例項從整體上理解Java NIO建立服務的過程。

    public static void startServer(int port) throws IOException{
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        serverChannel.bind(new InetSocketAddress(port));
 
        Selector selector = Selector.open();    // 開啟selector
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
 
        while (true) {
            int readyChannels = selector.select();  // 阻塞直到有IO事件就緒
            if (readyChannels <= 0) {
                continue;
            }
 
            Set<SelectionKey> SelectorKeySet = selector.selectedKeys(); // 得到就緒的選擇鍵
            Iterator<SelectionKey> iterator = SelectorKeySet.iterator();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            while (iterator.hasNext()) {    // 遍歷選擇鍵
                SelectionKey key = iterator.next(); 
                if (key.isAcceptable()) {   // 處理accept事件
                    SocketChannel socketChannel = serverChannel.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {  // 處理read事件
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    buffer.clear();
                    socketChannel.read(buffer);
                    buffer.flip();
                    socketChannel.write(buffer);
                }
                iterator.remove();
            }
        }
    }

我們分析一下程式碼的具體含義:2-4行表示開啟一個ServerSocketChannel,設定為非阻塞模式,然後繫結一個本地埠,這樣客戶端便可以通過這個埠連線到伺服器。6-7行表示開啟一個Selector,然後將ServerSocketChannel物件註冊到這個Selector例項上,注意這個ServerSocketChannel關心的事件是SelectionKey.OP_ACCEPT,表示只關心接受事件即接受客戶端到伺服器的連線。9-32行輪詢已經就緒的事件並對具體時間進行處理,注意10行的selector.select()方法,該方法會阻塞直到selecor中註冊的某個事件就緒並更新SelectionKey的狀態,15行呼叫selector.selectedKeys()得到就緒的key集合,key中儲存有就緒的事件以及對應的通道,21-23對就緒的Accept事件處理即伺服器接受一個客戶端連線,注意23行我們將接受的客戶端連線也註冊到同一個Selector物件,這樣24行的Read事件可以被觸發,伺服器讀出客戶端通道的資料並寫入改通道,完成echo服務。我們再梳理一下對就緒事件的處理,在最開始Selector物件中只註冊了ServerSocketChannel的Accept事件,所以當第一個客戶端連線到伺服器時,selector.select()方法不再阻塞,並執行21-23行處理客戶端連線將對應通道註冊到之前的Selector例項上,這樣Selector例項關心的事件為:(1).Accept事件(接受新客戶端的連線);(2).Read事件(讀取老客戶端傳送給伺服器的資料)。因此,當有新客戶端連線時執行21-23行,當有新客戶端發來資料時執行25-29行。

原文地址