1. 程式人生 > >Netty學習(2):IO模型之NIO初探

Netty學習(2):IO模型之NIO初探

NIO 概述

前面說到 BIO 有著建立執行緒多,阻塞 CPU 等問題,因此為解決 BIO 的問題,NIO 作為同步非阻塞 IO模型,隨 JDK1.4 而出生了。

在前面我們反覆說過4個概念:同步、非同步、阻塞、非阻塞。因此,我們就首先用最簡單的語言說一下他們的區別,這裡,我們心裡暫時有個概念即可,在後面的學習過程中,還會對其進行深入的探討學習。

概念對比

首先,我們先要確立一個概念,就是一個IO操作其實是分為兩步的,

  1. 發起IO請求,即準備資料和區域;
  2. 實際的IO操作。

而區分一個 IO模型是同步還是非同步,就取決於在進行第2個步驟時,即實際進行讀寫過程中,其是否會阻塞執行緒,如果會阻塞,那麼就是同步的,如果不會(這裡就需要作業系統核心來進行操作了),而是完成後,再通知操作執行緒,那麼其就是非同步的(AIO 就是 OS 來完成檔案讀寫操作,在完成後通過訊息機制來通過執行緒讀寫完畢)。

緊接著,區分一個模型是阻塞還是非阻塞,而是看其在資料沒有準備完畢時,即連線建立後,但還沒有資料過來,請求是需要卡在這等待,還是可以去幹其他的事。

作為上文所述的 BIO 來說,首先因為資料是否讀寫完成,需要其一直在迴圈裡判斷,因此其是同步的,並且如果沒有資料的話,其會一直卡在 read 操作上,不能繼續往下執行,因此 BIO 是一種同步阻塞模型。

而 NIO 的資料讀寫完成與否,也需要自己來判斷,因此也是同步的,但其只有當發生真正讀寫時,才會進行操作,如果沒有準備完畢的話,則可以去做其他的事,因此 NIO 是一個同步非阻塞 IO模型。

總結一下,我們看一個模型是同步還是非同步,其實看的是資料準備完畢後的訊息通知機制;看是阻塞還是非阻塞的,則是看執行緒在請求後的執行緒狀態。

下面就讓我們來看一下 NIO 的基本概念。

NIO基本介紹

  1. 從 JDK1.4 後,官方引入的 IO 新特性,稱為 NIO,是一個同步非阻塞模型;
  2. 相關類在 java.nio 包下,有 3大核心元件,Channel,Buffer,Selector;
  3. NIO 是面向快取區程式設計的,或者說可以是面向塊的,而不是面向位元組,因此極大提高了程式設計的靈活性;
  4. NIO 因為其核心元件和麵向快取區程式設計的特性,因此 資料總是從 Channel 讀取到 Buffer 中,或者從Buffer 讀取到 Channel 中,而 Selector 的作用就是監聽多個 Channel,當 Channel 中有請求需要處理,就進行處理;
  5. 。。。。。(官網或者搜一下,有很多,在這裡就不多贅述了)

元件介紹

Buffer

快取區本質是一個可以讀寫程式的記憶體塊,我們可以將其簡單理解為一個容器。在其內部還提供了一些方法,方便程式設計人員對其進行操作。

其所有可用子類如 ByteBuffer 等繼承的父類為 Buffer,其有一些公有屬性需要牢記。

屬性 說明
mark 標記位,在呼叫 mark() 方法後,可以將當前 position 設為標記,後續在呼叫 reset() 方法時,就可以將 position 重置回該位置,如果不設定,則為負數,那麼在呼叫 reset 時,會報錯。(一般不需要設定)
position 快取區內將要被寫或讀的節點下標,該值不能為負數,且永遠小於等於 limit
limit 快取區內第一個不能寫或讀的節點下標,該值不能為負數,且永遠小於等於 capacity
capacity 快取區被建立時指定的大小,該值不能為負數,且無法更改
mark <= position <= limit <= capacity

Buffer 程式碼演示

 IntBuffer intBuffer = IntBuffer.allocate(5);
        for (int i = 1; i <= intBuffer.capacity(); i++) {
            intBuffer.put(i);
        }
        // 翻轉,讀寫切換
        intBuffer.flip();
        while (intBuffer.hasRemaining()) {
            System.out.println(intBuffer.get());
        }

        intBuffer.position(0);
        for (int i = 1; i <= intBuffer.capacity(); i++) {
            intBuffer.put(i * i);
        }
        intBuffer.flip();
        while (intBuffer.hasRemaining()) {
            System.out.println(intBuffer.get());
        }
    }

從上述程式碼中,可以看到 快取區是一個可讀可寫的區域,且像陣列容器一樣可以在裡面通過下標的方式進行移動,從而修改指定地方內容。

Channel

Channel 是流中的一個元件,在使用過程中,通過流來生成。

Channel 是 NIO 中的一個介面,其實現類中,我們使用的比較多的有FileChannel,ServerSocketChannel,SocketChannelDatagramChannel,其中 FileChannel 用於檔案的讀寫,ServerSocketChannel 和 SocketChannel 用於TCP資料讀寫,DatagramChannel 用於 UDP 資料讀寫。

這裡只描述一些 Channel 的一些簡單概念,在後續文章:《檔案操作》中,再用程式碼詳細展示 Channel 的作用。

Selector

selector 解決的是 BIO 的執行緒阻塞和一個請求就需要建立一個執行緒的問題。selector 可以監控多個 Channel,如果對應的 Channel 有 Event 發生,就可以獲取對應 Event,然後根據獲取 Event 的不同去進行不同的處理邏輯,這樣就不必每個請求都建立執行緒,並且只有當真正有讀寫事件發生時,才會進行操作。

也是隻描述 Selector 的一些簡單概念,在後續文章:《網路程式設計》中,再用程式碼詳細展示 Selector 的作用。

總結

在本文中,我們初步介紹了 NIO 的概念,以及其的 3個核心元件:Buffer,Channel 和 Selector。

在後續的文章中,我們將對其分別進行介紹,通過 NIO 來逐步引入 Netty 的實現。

本文中程式碼已上傳到 GitHub 上,地址為 https://github.com/wb1069003157/nettyPre-research ,歡迎大家來討論,探討。

文章在公眾號「iceWang」第一手更新,有興趣的朋友可以關注公眾號,第一時間看到筆者分享的各項知識點,謝謝!筆芯!