Netty专题-(2)NIO三大核心
在之前的文章Netty專題-(1)初識Netty中提到了NIO三大核心Selector 、 Channel 和 Buffer,所以在這一章重點會是介紹這三個核心。
1 緩沖區(Buffer)
1.1 基本介紹
(1)緩沖區本質上是一個可以讀寫數據的內存塊,可以理解成是一個容器對象(含數組);
(2)該對象提供了一組方法,可以更輕松地使用內存塊;
(3)緩沖區對象內置了一些機制,能夠跟蹤和記錄緩沖區的狀態變化情況;
(4)Channel 提供從文件、網絡讀取數據的渠道,但是讀取或寫入的數據都必須經由 Buffer,如圖:
1.2 Buffer類及其子類
(1)在 NIO 中,Buffer 是一個頂層父類,它是一個抽象類, 類的層級關系圖:
(2)Buffer 類定義了所有的緩沖區都具有的四個屬性來提供關于其所包含的數據元素的信息:
| mark | 標記 |
| position | 位置,下一個要被讀或者寫的元素的索引,每次讀寫緩存區數據時都會改變值,為下次讀寫做準備 |
| limit | 表示緩沖區的當前終點,不能對緩沖區超過極限的位置進行讀寫操作,且極限是可以修改的 |
| capacity | 容量,即可以容納的最大數據量,在緩沖區創建時被設定并且不能改變 |
(3)Buffer 類相關方法一覽
1.2 ByteBuffer
從前面可以看出對于 Java 中的基本數據類型(boolean 除外),都有一個 Buffer 類型與之相對應,最常用的是 ByteBuffer 類(二進制數據),該類的主要方法如下:
2 通道(Channel)
2.1 基本介紹
(1)NIO 的通道類似于流,但有些區別如下:
- 通道可以同時進行讀寫,而流只能讀或者只能寫
- 通道可以實現異步讀寫數據
- 通道可以從緩沖讀數據,也可以寫數據到緩沖
(2)BIO 中的 stream 是單向的,例如 FileInputStream 對象只能進行讀取數據的操作,而 NIO 中的通道(Channel) 是雙向的,可以讀操作,也可以寫操作
(3)Channel 在 NIO 中是一個接口 public interface Channel extends Closeable{}
(4)常 用 的 Channel 類 有 : FileChannel 、 DatagramChannel 、 ServerSocketChannel 和 SocketChannel 。ServerSocketChanne 類似 ServerSocket , SocketChannel 類似 Socket
(5)FileChannel 用于文件的數據讀寫,DatagramChannel 用于 UDP 的數據讀寫,ServerSocketChannel 和 SocketChannel 用于 TCP 的數據讀寫。
2.2 FileChannel 類
2.2.1 FileChannel 類主要方法
FileChannel 主要用來對本地文件進行 IO 操作,常見的方法有:
- public int read(ByteBuffer dst) ,從通道讀取數據并放到緩沖區中
- public int write(ByteBuffer src) ,把緩沖區的數據寫到通道中
- public long transferFrom(ReadableByteChannel src, long position, long count),從目標通道中復制數據到當前通道
- public long transferTo(long position, long count, WritableByteChannel target),把數據從當前通道復制給目標通道
2.2.2 FileChannel 類應用實例
上面說了那么多可能大家只是停留在概念階段,最終我們是需要落到實踐,所以直接上干活,使用上面的兩個核心類來進行相應的操作。
(1)使用ByteBuffer(緩沖) 和 FileChannel(通道)實現本地文件寫數據,將 “hello,likangmin” 寫入到 file01.txt 文件中,如果文件不存在就創建。代碼里面有相應的注釋,大家應該很容易就可以看懂,我這里就不做另外的補充。大家在看的同時希望大家自己動手實踐一遍加深印象,只看不動手的話很快就會忘記。
代碼如下,很簡單的幾句:
啟動程序以后文件寫入:
(2)使用ByteBuffer(緩沖) 和 FileChannel(通道)實現本地文件讀數據,將剛剛創建的 file01.txt 中的數據讀入到程序,并顯示在控制臺。
代碼如下:
運行之后在控制臺打印:
(3)在上面的例子中,我們需要單獨使用兩個Buffer來進行操作,那么可以使用一個Buffer來進行文件的讀取和寫入呢?當然是可以的,其主要的實現思路圖如下,使用 FileChannel(通道) 和 方法 read , write,完成文件從1.txt到2.txt的拷貝。
代碼如下:
文件1.txt的內容:
在開始之前2.txt不存在,啟動程序以后,2.txt出現,里面的內容與1.txt完全一致。
(4)在例(3)中,使用Buffer完成文件的拷貝,實際上我們呢可以直接使用transferFrom方法完成拷貝,比如我們拷貝以下的這張照片。
代碼如下:
運行完程序以后,出現a2.jpg:
2.2.3 Buffer 和 Channel 的注意事項和細節
(1)ByteBuffer 支持類型化的 put 和 get, put 放入的是什么數據類型,get 就應該使用相應的數據類型來取出,否 則可能有 BufferUnderflowException 異常。如下:
如果沒有按照對應的數據類型進行獲取,則會出現異常。
(2)可以將一個普通 Buffer 轉成只讀 Buffer
運行程序:
(3)NIO 還提供了 MappedByteBuffer, 可以讓文件直接在內存(堆外的內存)中進行修改, 而如何同步到文件 由 NIO 來完成
這里對以下主要的地方做一下說明:
所以上面使用mappedByteBuffer.put(5, (byte) ‘Y’)會報IndexOutOfBoundsException異常:
(4)前面講的讀寫操作,都是通過一個 Buffer 完成的,NIO 還支持 通過多個 Buffer (即 Buffer 數組) 完成讀 寫操作,即 Scattering 和 Gathering
代碼如下,這里我們首次使用了ServerSocketChannel,大家不用考慮太多,在后面也會具體介紹,這里當前了解即可。
//使用 ServerSocketChannel 和 SocketChannel 網絡ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);//綁定端口到socket ,并啟動serverSocketChannel.socket().bind(inetSocketAddress);//創建buffer數組ByteBuffer[] byteBuffers = new ByteBuffer[2];byteBuffers[0] = ByteBuffer.allocate(5);byteBuffers[1] = ByteBuffer.allocate(3);//等客戶端連接(telnet)SocketChannel socketChannel = serverSocketChannel.accept();int messageLength = 8; //假定從客戶端接收8個字節//循環的讀取while (true) {int byteRead = 0;while (byteRead < messageLength ) {long l = socketChannel.read(byteBuffers);byteRead += l; //累計讀取的字節數System.out.println("byteRead=" + byteRead);//使用流打印, 看看當前的這個buffer的position 和 limitArrays.asList(byteBuffers).stream().map(buffer -> "postion=" + buffer.position() + ", limit=" + buffer.limit()).forEach(System.out::println);}//將所有的buffer進行flipArrays.asList(byteBuffers).forEach(buffer -> buffer.flip());//將數據讀出顯示到客戶端long byteWirte = 0;while (byteWirte < messageLength) {long l = socketChannel.write(byteBuffers); //byteWirte += l;}//將所有的buffer 進行clearArrays.asList(byteBuffers).forEach(buffer-> {buffer.clear();});System.out.println("byteRead:=" + byteRead + " byteWrite=" + byteWirte + ", messagelength" + messageLength);}用樣使用telnet進行數據的發送,發現分了三次進行接收:
3 Selector(選擇器)
3.1 基本介紹
(1)Java 的 NIO,用非阻塞的 IO 方式。可以用一個線程,處理多個的客戶端連接,就會使用到 Selector(選擇器)
(2)Selector 能夠檢測多個注冊的通道上是否有事件發生(注意:多個 Channel 以事件的方式可以注冊到同一個 Selector),如果有事件發生,便獲取事件然后針對每個事件進行相應的處理。這樣就可以只用一個單線程去管理多個通道,也就是管理多個連接和請求
(3)只有在連接通道真正有讀寫事件發生時,才會進行讀寫,就大大地減少了系統開銷,并且不必為每個連接都創建一個線程,不用去維護多個線程
(4)避免了多線程之間的上下文切換導致的開銷
3.2 Selector 示意圖和特點說明
(1)Netty 的 IO 線程 NioEventLoop 聚合了 Selector(選擇器,也叫多路復用器),可以同時并發處理成百上千個客 戶端連接
(2)當線程從某客戶端 Socket 通道進行讀寫數據時,若沒有數據可用時,該線程可以進行其他任務
(3)線程通常將非阻塞 IO 的空閑時間用于在其他通道上執行 IO 操作,所以單獨的線程可以管理多個輸入和輸出通道
(4)由于讀寫操作都是非阻塞的,這就可以充分提升 IO 線程的運行效率,避免由于頻繁 I/O 阻塞導致的線程掛起
(5)一個 I/O 線程可以并發處理 N 個客戶端連接和讀寫操作,這從根本上解決了傳統同步阻塞 I/O 一連接一線程模型,架構的性能、彈性伸縮能力和可靠性都得到了極大的提升
3.3 Selector 類相關方法
Selector 類是一個抽象類, 常用方法和說明如下:
3.4 注意事項
(1)NIO 中的 ServerSocketChannel 功能類似 ServerSocket,SocketChannel 功能類似 Socket
(2)selector 相關方法說明
- selector.select()//阻塞
- selector.select(1000);//阻塞 1000 毫秒,在 1000 毫秒后返回
- selector.wakeup();//喚醒 selector
- selector.selectNow();//不阻塞,立馬返還
在這一章中我們介紹了NIO的三大核心類,包括類的定義和概念,并做了相應的示例。下一章我們將利用NIO的三大核心實現NIO的網路編程Netty專題-(3)NIO網絡編程。
猜你感興趣:
Netty專題-(1)初識Netty
Netty專題-(2)NIO三大核心
Netty專題-(3)NIO網絡編程
相關專題持續更新中,敬請期待…
更多文章請點擊:更多…
總結
以上是生活随笔為你收集整理的Netty专题-(2)NIO三大核心的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Netty专题-(1)初识Netty
- 下一篇: Netty专题-(3)NIO网络编程