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

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

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

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

我們的業(yè)務邏輯要放在handler里面,讀寫數(shù)據(jù)用的是ByteBuf。其余的Transport、ServerBootstrap、Channel和EventLoop等等都是套路代碼,對于應用程序來說,了解即可,基本上不用管。真正開發(fā)過netty項目也知道,項目中大部分都是handler類,其它組件只是占很少一部分。




Transport組件

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

OIO -阻塞IO(真正開發(fā)阻塞IO項目,其實也沒必要用netty了。。。)

NIO -Java NIO

Epoll -? Linux Epoll(JNI)

Local Transport - IntraVM調用(通訊的雙方在同一個虛擬機之內(nèi)不再走socket)

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

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

使用不同個傳輸協(xié)議,只需要在通道里面設置不同的類型即可:

netty中的channel類型如下:

所以netty設置和改變傳輸協(xié)議都是一件很簡單的事情。





EventLoopGroup和EventLoop組件

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

每個EventLoop對應一個線程(頂層繼承Executor線程池,但是只有一個線程)

所有連接(channel)都將注冊到一個EventLoop,并且只注冊到一個,整個生命周期中都不會變化

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

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

服務端創(chuàng)建ServerBootstrap組件的時候,需要配置兩個EventLoopGroup,parentGroup(也就是boss)負責處理Accept事件,接收請求,childGroup(也就是worker)負責處理讀寫事件??蛻舳说腂ootstrap組件只需要一個EventLoopGroup即可。





ByteBuf組件

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

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

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

Fluent API (鏈式調用)(ServerBootstrap 組件的配置方式就是鏈式調用)

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

通過內(nèi)置的CompositeBuffer來減少數(shù)據(jù)拷貝(Zero copy)

支持內(nèi)存池,減少GC壓力





ByteBuf組件的操作

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

reader index前面的數(shù)據(jù)是已經(jīng)讀過的數(shù)據(jù),這些數(shù)據(jù)可以丟棄

從reader index開始,到writer index之前的數(shù)據(jù)是可讀數(shù)據(jù)

從writer index開始,為可寫區(qū)域

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

writeByte() - 寫一個字節(jié)

writeLong() - 寫八個字節(jié)

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

readByte() - 讀一個字節(jié)

readLong() - 讀八個字節(jié)

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

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

getXXX(index)

setXXX(index, byte)

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

markReaderIndex()

markWriterIndex()

resetReaderIndex()

resetWriterIndex()

writerIndex(index) - 把write index 放置到參數(shù)中的index上面

readerIndex(index) - 把reader index放置到參數(shù)中的index上面

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

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

indexOf

bytesBefore

forEachByte(ByteBufProcessor)

查詢方法有很多應用,比如在信息中有某個字符的存在,就需要做一些操作,比如每碰到一個換行,就發(fā)送一條信息,這種功能在聊天中用的很多。

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

duplicate()

slice()

slice(start, stop)

nmodifiableBuffer(...),

衍生緩沖區(qū)(Derived Buffers)是一種淺拷貝,如果要進行深拷貝怎么用?使用copy或者 copy(int, int) 方法,會返回有獨立數(shù)據(jù)副本的ByteBuf。




ByteBuf組件的類型

根據(jù)內(nèi)存的位置,ByteBuf的類型可以分為HeapByteBuf和DirectByteBuf,這和NIO中的Buffer是一樣的,HeapByteBuf位置在堆上,底層基于數(shù)組-內(nèi)部為一個字節(jié)數(shù)組(byte array),調用hasArray()方法會返回True,調用array()方法返回其內(nèi)部的數(shù)組,可以對數(shù)組進行直接操作。DirectByteBuf的位置在堆外內(nèi)存,可以減少拷貝,具有更好的性能,但是創(chuàng)建和釋放的開銷更大。Java寫網(wǎng)絡程序經(jīng)常分成兩個部分,第一個是IO部分,讀數(shù)據(jù)解碼等,這些部分用DirectByteBuf效率比較高,因為這部分涉及到向網(wǎng)絡發(fā)送數(shù)據(jù)需要拷貝。如果是其它業(yè)務相關的部分,可以使用HeapByteBuf。

根據(jù)是否使用內(nèi)存池,ByteBuf的類型可以分為Pooled和Unpooled兩種ByteBuf,Unpooled就是不用池,每次都去創(chuàng)建,Pooled類型就是會申請一塊內(nèi)存池,每次分配都從池中分配,每次釋放都放回池中。這種主要是針對DirectByteBuf創(chuàng)建和釋放開銷大來制定的策略,提供一個內(nèi)存池可以提高效率,減少內(nèi)存碎片,減少GC壓力,這種在NIO的Buffer中是沒有的。

根據(jù)是否使用Unsafe操作,ByteBuf的類型可以分為Safe和Unsafe兩種,我們知道JDK中有個Unsafe類,很多JDK中并發(fā)操作的源碼中都用到了這個Unsafe類。直接new可以創(chuàng)建一個safe的ByteBuf,如果創(chuàng)建Unsafe類型可以直接用Unsafe類操作,效率上有一點點提升,不過這些都是底層操作大家了解即可,而且也不是所有平臺都支持Unsafe操作。

ByteBuf還有一種復合緩沖區(qū)(CompositeByteBuf),它是由多個ByteBuf組合成的視圖,是一個ByteBuf列表,可動態(tài)的添加和刪除其中的ByteBuf。在其中可能既包含堆緩沖區(qū),也包含直接緩沖區(qū)。

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

來看另外一個接口ByteBufHolder,里面包含了一個ByteBuf,除此之外,還另外存儲一些元數(shù)據(jù)的屬性值。當要拿到里面包含的ByteBuf的時候,就可以拿到這些數(shù)據(jù)。

比如定義一個數(shù)據(jù)包的時候,就可以實現(xiàn)這個接口,真正的內(nèi)容放在ByteBuf里面。






ByteBuf組件的創(chuàng)建

創(chuàng)建的時候,并不需要執(zhí)行new操作,而是通過ByteBufAllocator分配器來創(chuàng)建,分配器有兩個實現(xiàn),分別是UnpooledByteBufAllocator和PooledByteBufAllocator,從名字可以看出,分配方式就是是否使用內(nèi)存池的區(qū)別。

主要的方法如下:

為了簡化非池化創(chuàng)建,netty提供了 Unpooled 的工具類,它提供了靜態(tài)的輔助方法來創(chuàng)建未池化的ByteBuf實例,內(nèi)部也包含了UnpooledByteBufAllocator的使用,我們創(chuàng)建非池化的ByteBuf直接用工具類即可,主要方法如下:

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

而且在客戶端也用到了,

Unpooled.copiedBuffer方法就是說創(chuàng)建一個ByteBuf,然后把創(chuàng)建的字符串拷貝到ByteBuf中去。





ByteBuf組件隨機讀寫的示例程序

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

上面的每行代碼都有注釋,我們看一下結果:

確實指針沒有改變。這里我們注意獲取讀寫指針用的方法,和隨機讀寫操作用的方法,以及如何創(chuàng)建的ByteBuf。





ByteBuf組件順序讀寫的示例程序

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

來看打印結果:

這里除了創(chuàng)建ByteBuf的方式要注意,還要注意順序讀寫的方法。





ByteBuf組件順序讀寫Int數(shù)據(jù)的示例程序

來看一個順序讀寫int類型數(shù)據(jù)的示例:

總長度為20寫入int數(shù)據(jù)只能寫5個,我們看打印讀取的內(nèi)容:

除了int數(shù)據(jù),其它類型大家也可以試試。





ByteBuf組件獲取對應字符位置的示例程序

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





ByteBuf組件slice操作的示例程序

我們看一下打印結果:

注意slice方法兩個參數(shù)的意義和如何把ByteBuf轉換為字符串。






ByteBuf組件copy深拷貝的示例程序

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





ByteBuf組件復合緩沖區(qū)的示例程序

注意復合緩沖區(qū)的創(chuàng)建和操作,來看打印結果:






ByteBuf組件堆上創(chuàng)建和操作的示例程序

來看一下循環(huán)打印的代碼:

連看一下結果:






ByteBuf組件堆外創(chuàng)建和操作的示例程序

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




代碼地址:https://gitee.com/blueses/netty-demo??06

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容