06.19 「每日分享」網絡編程-NIO、BIO、AIO詳解

點擊上方"java全棧技術"關注,每天學習一個java知識點

引言

java.nio全稱java non-blocking IO,是指jdk1.4 及以上版本里提供的新api(New IO),為所有的原始類型(boolean類型除外)提供緩存支持的數據容器,使用它可以提供非阻塞式的高伸縮性網絡。

目錄大綱

1.阻塞和非阻塞

2.同步和異步

3.Java IO模型,NIO,BIO,AIO

4.NIO的原理

5.NIO操作流程

6.代碼演示

1.阻塞和非阻塞

阻塞和非阻塞是進程在訪問數據的時候,進程需不需要等待。

1. 阻塞

當數據沒有準備好時,都會一直等待緩衝區中的數據準備就緒之後才會開始處理,否則會一直等待下去。

2. 非阻塞

當進程訪問到數據緩衝區時,如果數據還未準備好,會直接返回不會一直等待,如果數據已經準備好了,也會直接返回。

2.同步和異步

同步和異步都是基於應用程序和操作系統處理IO事件鎖採用的方式。

同步:

1. 同步是應用系統直接參與IO讀寫操作。在處理IO事件的時候必須阻塞在某個方法上面等待IO事件完成(阻塞IO事件或則通過輪詢IO事件的方式)。

2. 阻塞IO實現方式,一般是直接阻塞到read和write方法。都是將讀(寫)方法交給線程來操作,然後阻塞線程的方式來實現,只是這樣對線程開銷較大。

異步:

1. 異步是所有的IO讀寫都交給了操作系統,這個時候就可以去做其它的事情並不需要去完成真正的IO操作,當操作完成IO後,系統將會給的應用程序一個通知的。

3.Java IO模型,NIO,BIO,AIO

NIO:

jdk1.4 linux多路複用技術 (select模式)實現IO事件的輪詢方式同步非阻塞的模式,這種方式目前是主流的網絡通信模式。

目前市面上的這種模式的框架有:Mina, netty,mina2.0,nett5.0--網絡通信框架比直接寫

NIO要容易些,並且代碼可讀性更好。

BIO:

JDK1.4以前使用都是BIO阻塞IO,主要是阻塞到線程來操作的,但對於線程的開銷本來就是性能的浪費。

AIO:

jdk1.7(NIO2)才是實現真正的異步aio,學習 它的思想主要是借鑑了linux epoll模式。

4.NIO的原理

先來看一張圖

「每日分享」網絡編程-NIO、BIO、AIO詳解

網絡通信中,NIO也提供了SocketChannel和ServerSocketChannel兩種不同的套接字通道來實現,這兩個類都實現了Channel接口。它們可以設置阻塞與非阻塞兩種模式,為了實現高負載高併發都採取非阻塞的模式。

NIO採用緩衝區Buffer,實現對數據的讀寫操作,緩衝區是固定大小,並由內部狀態記錄有多少數據被放入或者取出。

與阻塞IO不同,阻塞IO採用阻塞式流(Stream)的方式進行讀寫,流是單向的只能向一個方向讀數據或者寫數據。

而通道是雙向的,可以同時在通道上發送和讀取數據,而且是非阻塞的,在沒有數據可讀可寫時可以去做別的事情。

主要使用了ServerSocketChannel,SocketChannel,Selector,ByteBuffer這麼幾個類。

1.ServerSocketChannel(服務端使用類)

採用api文檔解釋:通過調用此類的 open 方法創建服務器套接字通道。新創建的服務器套接字通道已打開,但尚未綁定。

試圖調用未綁定的服務器套接字通道的 accept 方法會導致拋出 NotYetBoundException。

可通過調用相關服務器套接字的某個 bind 方法來綁定服務器套接字通道。多個併發線程可安全地使用服務器套接字通道。

2.SocketChannel(客戶端使用類)

採用api文檔解釋:通過調用此類的某個 open 方法創建套接字通道。

新創建的套接字通道已打開,但尚未連接。試圖在未連接的通道上調用 I/O 操作將導致拋出 NotYetConnectedException。

可通過調用套接字通道的 connect 方法連接該通道;一旦連接後,關閉套接字通道之前它會一直保持已連接狀態。

可通過調用套接字通道的 isConnected 方法來確定套接字通道是否已連接。

3. Selector(選擇器):

是 SelectableChannle 對象的多路複用器,Selector 可以同時監控多個 SelectableChannel 的 IO 狀況,也就是說,利用 Selector可使一個單獨的線程管理多個 Channel,selector 是非阻塞 IO 的核心。

當通道使用register(Selector sel, int ops)方法將通道註冊選擇器時,選擇器對通道事件進行監聽,通過第二個參數指定監聽的事件類型。

其中可監聽的事件類型包括以下:

讀 : SelectionKey.OP_READ (1)

寫 : SelectionKey.OP_WRITE (4)

連接 : SelectionKey.OP_CONNECT (8)

接收 : SelectionKey.OP_ACCEPT (16)

如果需要監聽多個事件是:

int key = SelectionKey.OP_READ | SelectionKey.OP_WRITE ; //表示同時監聽讀寫操作

4.Bytebuffer

ByteBuffer類是Buffer的子類,Buffer是頂層抽象類,ByteBuffer繼承Buffer,也是抽象類。看看繼承結構。

「每日分享」網絡編程-NIO、BIO、AIO詳解

ByteBuffer是在javaNIO中常使用的一個緩衝區類,使用ByteBuffer可以進行高效的IO操作。通過 ByteBuffer提供很多讀寫的方法put(),get(),並且還包含四個很重要的屬性。

容量(capacity)

capacity指的是緩衝區能夠容納元素的最大數量,這個值在緩衝區創建時被設定,而且不能夠改變,如下,我們創建了一個最大容量為10的字節緩衝區;

ByteBuffer bf = ByteBuffer.allocate(10);

上界(limit)

limit指的是緩衝區中第一個不能讀寫的元素的數組下標索引,也可以認為是緩衝區中實際元素的數量;

位置(position)

position指的是下一個要被讀寫的元素的數組下標索引,該值會隨get()和put()的調用自動更新;

標記(mark)

一個備忘位置,調用mark()方法的話,mark值將存儲當前position的值,等下次調用reset()方法時,會設定position的值為之前的標記值;

四個屬性值之間的關係

根據以上四個屬性的定義,我們可以總結出它們之間的關係如下:

0 <= mark <= position <= limit <= capacity

5.NIO操作流程

1. 服務端對象:ServerSocketChannel

2. 客戶端對象:SocketChannel

3. 選擇器:Selector selector= Selector.open();//這樣就打開了選擇器

4. 獲得選擇器中的事件集合:Set<selectionkey> key= selector.selectedKeys()/<selectionkey>

5. Selectionkey:可以通過它來判斷IO事件是否已經就緒

key. isAccptable:是否可以接受客戶端的連接

key, isConnctionable:是否可以連接服務端

key, isReadableo:緩衝區是否可讀

key, isWriteableo:緩衝區是否可寫

6.如何註冊

channel.regist(selector, Selectionkey.OP_WRITE)//註冊寫事件

channel.regist(Selector, Selectionkey.OP_READ);//註冊讀事件

channel.regist(Selector, Selectionkey.OP_CONNECT);//註冊連接事件

channel.regist(Selector, Selectionkey.OP_ACCEPT);//註冊請求事件

以上註冊成功之後就可以通過SelectionKey來判斷io事件是否就緒,然後可以進行後續操作。

6.代碼演示

現在來演示一下網絡編程中的nio使用,使用網絡編程nio的編程就需要使用到Channel(管道)、Selector(事件選擇器) 、Bytebuffer(緩衝區)。

演示代碼一般會使用服務端代碼和客戶端代碼分別書寫。我會盡量在代碼中把註釋寫詳細一些。

1.服務端代碼

「每日分享」網絡編程-NIO、BIO、AIO詳解

「每日分享」網絡編程-NIO、BIO、AIO詳解

「每日分享」網絡編程-NIO、BIO、AIO詳解

「每日分享」網絡編程-NIO、BIO、AIO詳解

「每日分享」網絡編程-NIO、BIO、AIO詳解

「每日分享」網絡編程-NIO、BIO、AIO詳解

「每日分享」網絡編程-NIO、BIO、AIO詳解

2.客戶端代碼

「每日分享」網絡編程-NIO、BIO、AIO詳解

「每日分享」網絡編程-NIO、BIO、AIO詳解

「每日分享」網絡編程-NIO、BIO、AIO詳解

「每日分享」網絡編程-NIO、BIO、AIO詳解

「每日分享」網絡編程-NIO、BIO、AIO詳解

先運行服務端程序會顯示“服務器已經開啟”的字樣,然後運行客戶端程序會在連接成功時發送"我是客戶端來了"的字樣,服務端就會接收到客戶端的數據並且打印。

然後客戶端輸入“111”,緊接著服務端就會打印“服務端接收客戶端傳過來的數據:客戶端輸入數據為:111”的字樣,然後在客戶端繼續輸入222,服務端就會打印出“服務端接收客戶端傳過來的數據:客戶端輸入數據為:222”

如下:

服務端顯示:

服務器已經開啟...
服務端接收客戶端傳過來的數據:我是客戶端來了
服務端接收客戶端傳過來的數據:客戶端輸入數據為:111
服務端接收客戶端傳過來的數據:客戶端輸入數據為:222

客戶端顯示

我是服務器

111

我是服務器

222

我是服務器


分享到:


相關文章: