1. 程式人生 > >Netty快速入門(08)ByteBuf元件介紹

Netty快速入門(08)ByteBuf元件介紹

前面的內容對netty進行了介紹,寫了一個入門例子。作為一個netty的使用者,我們關注更多的還是業務程式碼。也就是netty中這兩種元件:

ChannelHandler和ChannelPipeline---對應於NIO中的客戶邏輯實現handleRead/handleWrite(interceptor pattern)

ByteBuf---- 對應於NIO 中的ByteBuffer

我們的業務邏輯要放在handler裡面,讀寫資料用的是ByteBuf。其餘的Transport、ServerBootstrap、Channel和EventLoop等等都是套路程式碼,對於應用程式來說,瞭解即可,基本上不用管。真正開發過netty專案也知道,專案中大部分都是handler類,其它元件只是佔很少一部分。

Transport元件

netty做的比較有適應性的就是,不僅支援NIO,還支援很多傳輸協議:

OIO -阻塞IO(真正開發阻塞IO專案,其實也沒必要用netty了。。。)

NIO -Java NIO

Epoll - Linux Epoll(JNI)

Local Transport - IntraVM呼叫(通訊的雙方在同一個虛擬機器之內不再走socket)

Embedded Transport - 供測試使用的嵌入傳輸

UDS- Unix套接字的本地傳輸(客戶端和服務端都在同一個伺服器上就可以使用,效率高)

使用不同個傳輸協議,只需要在通道里面設定不同的型別即可:

netty中的channel型別如下:

所以netty設定和改變傳輸協議都是一件很簡單的事情。

EventLoopGroup和EventLoop元件

每個EventLoopGroup由多個EventLoop組成,並且多個EventLoop之間沒有互動,各做各的事。

每個EventLoop對應一個執行緒(頂層繼承Executor執行緒池,但是隻有一個執行緒)

所有連線(channel)都將註冊到一個EventLoop,並且只註冊到一個,整個生命週期中都不會變化

每個EventLoop管理著多個連線(channel)

連線(Channel)上的讀寫事件是由EventLoop來處理的

服務端建立ServerBootstrap元件的時候,需要配置兩個EventLoopGroup,parentGroup(也就是boss)負責處理Accept事件,接收請求,childGroup(也就是worker)負責處理讀寫事件。客戶端的Bootstrap元件只需要一個EventLoopGroup即可。

ByteBuf元件

前面討論NIO的時候,專門介紹過NIO的Buffer,相對於NIO,netty的ByteBuf更加易於使用:

為讀/寫分別維護單獨的指標,不需要通過flip()進行讀/寫模式切換

容量自動伸縮(類似於ArrayList,StringBuilder)

Fluent API (鏈式呼叫)(ServerBootstrap 元件的配置方式就是鏈式呼叫)

除了使用上之外,netty的ByteBuf還擁有更好的效能:

通過內建的CompositeBuffer來減少資料拷貝(Zero copy)

支援記憶體池,減少GC壓力

ByteBuf元件的操作

ByteBuf通過兩個索引(reader index、writer index)劃分為三個區域:

reader index前面的資料是已經讀過的資料,這些資料可以丟棄

從reader index開始,到writer index之前的資料是可讀資料

從writer index開始,為可寫區域

來看下ByteBuf的主要操作,第一種就是順序的讀寫(改變reader/writer index):

writeByte() - 寫一個位元組

writeLong() - 寫八個位元組

writeXXX() - 所有write方法會讓write index 往前走

readByte() - 讀一個位元組

readLong() - 讀八個位元組

readXXX() - 所有read方法會讓read index往前走

第二種就是隨機讀寫(不改變read/write index):

getXXX(index)

setXXX(index, byte)

前面NIO中的Buffer操作用,有mark和reset方法,用來標記操作的狀態,恢復狀態,netty中也有類似的方法:

markReaderIndex()

markWriterIndex()

resetReaderIndex()

resetWriterIndex()

writerIndex(index) - 把write index 放置到引數中的index上面

readerIndex(index) - 把reader index放置到引數中的index上面

reader index前面的部分是已經讀過的是不是浪費掉了?netty提供了discardReadBytes方法,把reader index前面的內容丟棄掉,就是把reader index後面的資料往前拷貝,這樣空間就可以再利用了。這個方法和前面NIO中的compact方法類似。還有一個方法就是clear方法,把所有資料都清零,讀索引和寫索引歸零:

netty的ByteBuf中還提供了查詢方法:

indexOf

bytesBefore

forEachByte(ByteBufProcessor)

查詢方法有很多應用,比如在資訊中有某個字元的存在,就需要做一些操作,比如每碰到一個換行,就傳送一條資訊,這種功能在聊天中用的很多。

netty中還有一種衍生緩衝區,就是Derived Buffers,可以理解為類似資料庫的檢視,是從ByteBuf中衍生出來的,衍生緩衝區與ByteBuf共享底層的儲存空間,但是它們兩個各自具有各自的index和mark,衍生緩衝區(Derived Buffers)主要的方法:

duplicate()

slice()

slice(start, stop)

nmodifiableBuffer(...),

衍生緩衝區(Derived Buffers)是一種淺拷貝,如果要進行深拷貝怎麼用?使用copy或者 copy(int, int) 方法,會返回有獨立資料副本的ByteBuf。

ByteBuf元件的型別

根據記憶體的位置,ByteBuf的型別可以分為HeapByteBuf和DirectByteBuf,這和NIO中的Buffer是一樣的,HeapByteBuf位置在堆上,底層基於陣列-內部為一個位元組陣列(byte array),呼叫hasArray()方法會返回True,呼叫array()方法返回其內部的陣列,可以對陣列進行直接操作。DirectByteBuf的位置在堆外記憶體,可以減少拷貝,具有更好的效能,但是建立和釋放的開銷更大。Java寫網路程式經常分成兩個部分,第一個是IO部分,讀資料解碼等,這些部分用DirectByteBuf效率比較高,因為這部分涉及到向網路傳送資料需要拷貝。如果是其它業務相關的部分,可以使用HeapByteBuf。

根據是否使用記憶體池,ByteBuf的型別可以分為Pooled和Unpooled兩種ByteBuf,Unpooled就是不用池,每次都去建立,Pooled型別就是會申請一塊記憶體池,每次分配都從池中分配,每次釋放都放回池中。這種主要是針對DirectByteBuf建立和釋放開銷大來制定的策略,提供一個記憶體池可以提高效率,減少記憶體碎片,減少GC壓力,這種在NIO的Buffer中是沒有的。

根據是否使用Unsafe操作,ByteBuf的型別可以分為Safe和Unsafe兩種,我們知道JDK中有個Unsafe類,很多JDK中併發操作的原始碼中都用到了這個Unsafe類。直接new可以建立一個safe的ByteBuf,如果建立Unsafe型別可以直接用Unsafe類操作,效率上有一點點提升,不過這些都是底層操作大家瞭解即可,而且也不是所有平臺都支援Unsafe操作。

ByteBuf還有一種複合緩衝區(CompositeByteBuf),它是由多個ByteBuf組合成的檢視,是一個ByteBuf列表,可動態的新增和刪除其中的ByteBuf。在其中可能既包含堆緩衝區,也包含直接緩衝區。

netty把這種結構也解釋為一種零拷貝,雖然不是嚴格意義上的零拷貝,但是確實可以提高效率。

來看另外一個介面ByteBufHolder,裡面包含了一個ByteBuf,除此之外,還另外儲存一些元資料的屬性值。當要拿到裡面包含的ByteBuf的時候,就可以拿到這些資料。

比如定義一個數據包的時候,就可以實現這個介面,真正的內容放在ByteBuf裡面。

ByteBuf元件的建立

建立的時候,並不需要執行new操作,而是通過ByteBufAllocator分配器來建立,分配器有兩個實現,分別是UnpooledByteBufAllocator和PooledByteBufAllocator,從名字可以看出,分配方式就是是否使用記憶體池的區別。

主要的方法如下:

為了簡化非池化建立,netty提供了 Unpooled 的工具類,它提供了靜態的輔助方法來建立未池化的ByteBuf例項,內部也包含了UnpooledByteBufAllocator的使用,我們建立非池化的ByteBuf直接用工具類即可,主要方法如下:

我們前面介紹netty入門例子的時候,服務端的讀取操作完畢的方法中也用到了這個工具類建立一個空ByteBuf:

而且在客戶端也用到了,

Unpooled.copiedBuffer方法就是說建立一個ByteBuf,然後把建立的字串拷貝到ByteBuf中去。

ByteBuf元件隨機讀寫的示例程式

上面介紹了很多特性和操作,下面看一個示例程式,隨機讀寫,不改變讀寫指標的例子:

上面的每行程式碼都有註釋,我們看一下結果:

確實指標沒有改變。這裡我們注意獲取讀寫指標用的方法,和隨機讀寫操作用的方法,以及如何建立的ByteBuf。

ByteBuf元件順序讀寫的示例程式

我們再來看一個順序讀寫的例子:

來看列印結果:

這裡除了建立ByteBuf的方式要注意,還要注意順序讀寫的方法。

ByteBuf元件順序讀寫Int資料的示例程式

來看一個順序讀寫int型別資料的示例:

總長度為20寫入int資料只能寫5個,我們看列印讀取的內容:

除了int資料,其它型別大家也可以試試。

ByteBuf元件獲取對應字元位置的示例程式

ByteProcessor類中定義了很多特殊字元,有興趣可以看看。來看一下列印效果:

ByteBuf元件slice操作的示例程式

我們看一下列印結果:

注意slice方法兩個引數的意義和如何把ByteBuf轉換為字串。

ByteBuf元件copy深拷貝的示例程式

注意copy方法的用法,來看列印結果:

ByteBuf元件複合緩衝區的示例程式

注意複合緩衝區的建立和操作,來看列印結果:

ByteBuf元件堆上建立和操作的示例程式

來看一下迴圈列印的程式碼:

連看一下結果:

ByteBuf元件堆外建立和操作的示例程式

邏輯上和堆上的方法一樣,來看一下列印結果:

程式碼地址:https://gitee.com/blueses/netty-demo 06

本文由部落格一文多發平臺 OpenWrite 釋出!