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