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

Netty是基于Java NIO封裝的網(wǎng)絡(luò)通訊框架,只有充分理解了Java NIO才能理解好Netty的底層設(shè)計(jì)。Java NIO有幾個(gè)重要的概念Channel,Buffer,Selector。NIO是基于ChannelBuffer操作的,數(shù)據(jù)只能通過Buffer寫入到Channel或者從Channel讀出數(shù)據(jù)到Buffer中。Selector可以監(jiān)聽多個(gè)通道的事件(連接打開,數(shù)據(jù)到達(dá)),這樣便可以用一個(gè)線程監(jiān)聽多個(gè)Channel的事件,從而可以用一個(gè)線程處理多個(gè)網(wǎng)絡(luò)連接。

1.1 Channel

首先來看Channel的類圖:

Channel類圖

主要關(guān)注三個(gè)類:SocketChannel,ServerSocketChannel以及DataGramChannel,其中SocketChannel是一個(gè)連接到TCP網(wǎng)絡(luò)套接字的通道,可由兩種方式創(chuàng)建:

  1. 打開一個(gè)SocketChannel并連接到互聯(lián)網(wǎng)上的某臺服務(wù)器。
  2. 一個(gè)新連接到達(dá)ServerSocketChannel時(shí),會創(chuàng)建一個(gè)SocketChannel(即由serverSocketChannel.accept()方法返回)。

ServerSocketChannel是一個(gè)可以監(jiān)聽新進(jìn)來的TCP連接的通道, 與標(biāo)準(zhǔn)IO中的ServerSocket類似。而DatagramChannel是一個(gè)能收發(fā)UDP包的通道。

1.2 Buffer

一個(gè)Buffer對象是固定數(shù)量的數(shù)據(jù)的容器。緩沖區(qū)的工作與通道緊密聯(lián)系。通道是I/O傳輸發(fā)生時(shí)通過的入口,而緩沖區(qū)是這些數(shù)據(jù)傳輸?shù)膩碓椿蚰繕?biāo)。對于離開緩沖區(qū)的傳輸,想傳遞出去的數(shù)據(jù)被置于一個(gè)緩沖區(qū),被傳送到通道。對于傳回緩沖區(qū)的傳輸,一個(gè)通道將數(shù)據(jù)復(fù)制到所提供的緩沖區(qū)中。這里我們主要關(guān)注的是ByteBuffer,因?yàn)樽止?jié)是操作系統(tǒng)與I/O設(shè)備之間或者操作系統(tǒng)與應(yīng)用進(jìn)程之間傳遞數(shù)據(jù)使用的基本數(shù)據(jù)類型,并且Java NIO中的Channel只接受ByteBuffer作為參數(shù)。

1.3 selector

Selector是Java NIO中能夠檢測多個(gè)通道,并能夠知曉通道是否為諸如讀寫時(shí)間做好準(zhǔn)備的組件。當(dāng)我們將1.1中的一個(gè)或多個(gè)SelectableChannel注冊到一個(gè)Selector對象中時(shí),一個(gè)表示通道和選擇器關(guān)系的SelectionKey會被返回。SelectionKey將記住我們關(guān)心的通道,并且會追蹤對應(yīng)的通道是否有事件已經(jīng)就緒。當(dāng)調(diào)用一個(gè)Selector.select()方法時(shí),相關(guān)的SelectionKey將會被更新,用來檢查所有被注冊到改選擇器的通道。我們可以獲取一個(gè)SelectionKey的集合,從而找到已經(jīng)就緒的通道。通過遍歷這些SelectionKey,我們可以選擇出每個(gè)從上次調(diào)用select()開始直到現(xiàn)在已經(jīng)就緒的通道。

1.4 示例

通過之前的描述,相信你已經(jīng)對Java NIO有了初步的了解,但具體的細(xì)節(jié)還很模糊,沒關(guān)系,記住我們的目標(biāo)是自頂向下分析,也就是先整體后局部,先建立對整體的認(rèn)識再不斷完善局部細(xì)節(jié)。下面將通過一個(gè)簡單的Java NIO echo Server實(shí)例從整體上理解Java NIO建立服務(wù)的過程。

    public static void startServer(int port) throws IOException{
        // 打開服務(wù)端ServerSocketChannel
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        // 設(shè)置為非阻塞模式
        serverChannel.configureBlocking(false); 
        // 綁定一個(gè)本地端口,這樣客戶端便可以通過這個(gè)端口連接到服務(wù)器
        serverChannel.bind(new InetSocketAddress(port));
        
        // 打開selector
        Selector selector = Selector.open(); 
        // 注意關(guān)心的事件是OP_ACCEPT,表示只關(guān)心接受事件即接受客戶端到服務(wù)器的連接
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            // select()阻塞直到注冊的某個(gè)事件就緒并會更新SelectionKey的狀態(tài)
            int readyChannels = selector.select();
            if (readyChannels <= 0) {
                continue;
            }

            // 得到就緒的key集合,key中保存有就緒的事件以及對應(yīng)的Channel通道
            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);
                    // 注意此處新增關(guān)心事件OP_READ
                    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();
            }
        }
    }

其中內(nèi)層while循環(huán)是事件處理的關(guān)鍵,以一個(gè)客戶端連接到服務(wù)端并發(fā)送一段數(shù)據(jù)為例分析:selector最開始只關(guān)心OP_ACCEPT事件即等待客戶端連接,當(dāng)客戶端連接到服務(wù)端完成后即OP_ACCEPT就緒,程序從selector.select()方法返回并進(jìn)入內(nèi)層循環(huán)的第一個(gè)if分支處理Accept事件。在處理過程中,會將剛接受的Channel注冊到selector并關(guān)心該Channel上的OP_READ事件,完成后會清除key并回阻塞在下一輪循環(huán)的selector.select()方法上。當(dāng)客戶端發(fā)送一段數(shù)據(jù)到服務(wù)端后,OP_READ事件就緒,程序從阻塞處返回并進(jìn)入內(nèi)層while循環(huán)的第二個(gè)if分支,從而對OP_READ事件進(jìn)行處理。本例中,會將客戶端發(fā)來的數(shù)據(jù)寫回原本Channel通道,從而完成echo服務(wù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容