前面的內(nèi)容對netty進(jìn)行了介紹病蛉,寫了一個入門例子店茶。作為一個netty的使用者歧焦,我們關(guān)注更多的還是業(yè)務(wù)代碼缺脉。也就是netty中這兩種組件:
ChannelHandler和ChannelPipeline---對應(yīng)于NIO中的客戶邏輯實(shí)現(xiàn)handleRead/handleWrite(interceptor pattern)
ByteBuf---- 對應(yīng)于NIO 中的ByteBuffer
我們的業(yè)務(wù)邏輯要放在handler里面,讀寫數(shù)據(jù)用的是ByteBuf劳秋。其余的Transport仓手、ServerBootstrap、Channel和EventLoop等等都是套路代碼玻淑,對于應(yīng)用程序來說嗽冒,了解即可,基本上不用管补履。真正開發(fā)過netty項(xiàng)目也知道辛慰,項(xiàng)目中大部分都是handler類,其它組件只是占很少一部分干像。
Transport組件
netty做的比較有適應(yīng)性的就是帅腌,不僅支持NIO驰弄,還支持很多傳輸協(xié)議:
OIO -阻塞IO(真正開發(fā)阻塞IO項(xiàng)目,其實(shí)也沒必要用netty了速客。戚篙。。)
NIO -Java NIO
Epoll -? Linux Epoll(JNI)
Local Transport - IntraVM調(diào)用(通訊的雙方在同一個虛擬機(jī)之內(nèi)不再走socket)
Embedded Transport - 供測試使用的嵌入傳輸
UDS-? Unix套接字的本地傳輸(客戶端和服務(wù)端都在同一個服務(wù)器上就可以使用溺职,效率高)
使用不同個傳輸協(xié)議岔擂,只需要在通道里面設(shè)置不同的類型即可:
netty中的channel類型如下:
所以netty設(shè)置和改變傳輸協(xié)議都是一件很簡單的事情。
EventLoopGroup和EventLoop組件
每個EventLoopGroup由多個EventLoop組成浪耘,并且多個EventLoop之間沒有交互乱灵,各做各的事。
每個EventLoop對應(yīng)一個線程(頂層繼承Executor線程池七冲,但是只有一個線程)
所有連接(channel)都將注冊到一個EventLoop痛倚,并且只注冊到一個,整個生命周期中都不會變化
每個EventLoop管理著多個連接(channel)
連接(Channel)上的讀寫事件是由EventLoop來處理的
服務(wù)端創(chuàng)建ServerBootstrap組件的時候澜躺,需要配置兩個EventLoopGroup蝉稳,parentGroup(也就是boss)負(fù)責(zé)處理Accept事件,接收請求掘鄙,childGroup(也就是worker)負(fù)責(zé)處理讀寫事件耘戚。客戶端的Bootstrap組件只需要一個EventLoopGroup即可操漠。
ByteBuf組件
前面討論NIO的時候收津,專門介紹過NIO的Buffer,相對于NIO浊伙,netty的ByteBuf更加易于使用:
為讀/寫分別維護(hù)單獨(dú)的指針朋截,不需要通過flip()進(jìn)行讀/寫模式切換
容量自動伸縮(類似于ArrayList,StringBuilder)
Fluent API (鏈?zhǔn)秸{(diào)用)(ServerBootstrap 組件的配置方式就是鏈?zhǔn)秸{(diào)用)
除了使用上之外吧黄,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往前走
第二種就是隨機(jī)讀寫(不改變read/write index):
getXXX(index)
setXXX(index, byte)
前面NIO中的Buffer操作用,有mark和reset方法声功,用來標(biāo)記操作的狀態(tài)烦却,恢復(fù)狀態(tài),netty中也有類似的方法:
markReaderIndex()
markWriterIndex()
resetReaderIndex()
resetWriterIndex()
writerIndex(index) - 把write index 放置到參數(shù)中的index上面
readerIndex(index) - 把reader index放置到參數(shù)中的index上面
reader index前面的部分是已經(jīng)讀過的是不是浪費(fèi)掉了先巴?netty提供了discardReadBytes方法其爵,把reader index前面的內(nèi)容丟棄掉冒冬,就是把reader index后面的數(shù)據(jù)往前拷貝,這樣空間就可以再利用了摩渺。這個方法和前面NIO中的compact方法類似简烤。還有一個方法就是clear方法,把所有數(shù)據(jù)都清零摇幻,讀索引和寫索引歸零:
netty的ByteBuf中還提供了查詢方法:
indexOf
bytesBefore
forEachByte(ByteBufProcessor)
查詢方法有很多應(yīng)用横侦,比如在信息中有某個字符的存在,就需要做一些操作绰姻,比如每碰到一個換行枉侧,就發(fā)送一條信息,這種功能在聊天中用的很多狂芋。
netty中還有一種衍生緩沖區(qū)榨馁,就是Derived Buffers,可以理解為類似數(shù)據(jù)庫的視圖银酗,是從ByteBuf中衍生出來的辆影,衍生緩沖區(qū)與ByteBuf共享底層的存儲空間,但是它們兩個各自具有各自的index和mark黍特,衍生緩沖區(qū)(Derived Buffers)主要的方法:
duplicate()
slice()
slice(start, stop)
nmodifiableBuffer(...),
衍生緩沖區(qū)(Derived Buffers)是一種淺拷貝蛙讥,如果要進(jìn)行深拷貝怎么用?使用copy或者 copy(int, int) 方法灭衷,會返回有獨(dú)立數(shù)據(jù)副本的ByteBuf次慢。
ByteBuf組件的類型
根據(jù)內(nèi)存的位置,ByteBuf的類型可以分為HeapByteBuf和DirectByteBuf翔曲,這和NIO中的Buffer是一樣的迫像,HeapByteBuf位置在堆上,底層基于數(shù)組-內(nèi)部為一個字節(jié)數(shù)組(byte array)瞳遍,調(diào)用hasArray()方法會返回True闻妓,調(diào)用array()方法返回其內(nèi)部的數(shù)組,可以對數(shù)組進(jìn)行直接操作掠械。DirectByteBuf的位置在堆外內(nèi)存由缆,可以減少拷貝,具有更好的性能猾蒂,但是創(chuàng)建和釋放的開銷更大均唉。Java寫網(wǎng)絡(luò)程序經(jīng)常分成兩個部分,第一個是IO部分肚菠,讀數(shù)據(jù)解碼等舔箭,這些部分用DirectByteBuf效率比較高,因?yàn)檫@部分涉及到向網(wǎng)絡(luò)發(fā)送數(shù)據(jù)需要拷貝蚊逢。如果是其它業(yè)務(wù)相關(guān)的部分层扶,可以使用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類操作惰许,效率上有一點(diǎn)點(diǎn)提升,不過這些都是底層操作大家了解即可史辙,而且也不是所有平臺都支持Unsafe操作汹买。
ByteBuf還有一種復(fù)合緩沖區(qū)(CompositeByteBuf),它是由多個ByteBuf組合成的視圖聊倔,是一個ByteBuf列表晦毙,可動態(tài)的添加和刪除其中的ByteBuf。在其中可能既包含堆緩沖區(qū)耙蔑,也包含直接緩沖區(qū)见妒。
netty把這種結(jié)構(gòu)也解釋為一種零拷貝,雖然不是嚴(yán)格意義上的零拷貝甸陌,但是確實(shí)可以提高效率须揣。
來看另外一個接口ByteBufHolder,里面包含了一個ByteBuf邀层,除此之外,還另外存儲一些元數(shù)據(jù)的屬性值遂庄。當(dāng)要拿到里面包含的ByteBuf的時候寥院,就可以拿到這些數(shù)據(jù)。
比如定義一個數(shù)據(jù)包的時候涛目,就可以實(shí)現(xiàn)這個接口秸谢,真正的內(nèi)容放在ByteBuf里面凛澎。
ByteBuf組件的創(chuàng)建
創(chuàng)建的時候,并不需要執(zhí)行new操作估蹄,而是通過ByteBufAllocator分配器來創(chuàng)建塑煎,分配器有兩個實(shí)現(xiàn),分別是UnpooledByteBufAllocator和PooledByteBufAllocator臭蚁,從名字可以看出最铁,分配方式就是是否使用內(nèi)存池的區(qū)別。
主要的方法如下:
為了簡化非池化創(chuàng)建垮兑,netty提供了 Unpooled 的工具類冷尉,它提供了靜態(tài)的輔助方法來創(chuàng)建未池化的ByteBuf實(shí)例,內(nèi)部也包含了UnpooledByteBufAllocator的使用系枪,我們創(chuàng)建非池化的ByteBuf直接用工具類即可雀哨,主要方法如下:
我們前面介紹netty入門例子的時候,服務(wù)端的讀取操作完畢的方法中也用到了這個工具類創(chuàng)建一個空ByteBuf:
而且在客戶端也用到了私爷,
Unpooled.copiedBuffer方法就是說創(chuàng)建一個ByteBuf雾棺,然后把創(chuàng)建的字符串拷貝到ByteBuf中去。
ByteBuf組件隨機(jī)讀寫的示例程序
上面介紹了很多特性和操作衬浑,下面看一個示例程序捌浩,隨機(jī)讀寫,不改變讀寫指針的例子:
上面的每行代碼都有注釋嚎卫,我們看一下結(jié)果:
確實(shí)指針沒有改變嘉栓。這里我們注意獲取讀寫指針用的方法,和隨機(jī)讀寫操作用的方法拓诸,以及如何創(chuàng)建的ByteBuf侵佃。
ByteBuf組件順序讀寫的示例程序
我們再來看一個順序讀寫的例子:
來看打印結(jié)果:
這里除了創(chuàng)建ByteBuf的方式要注意,還要注意順序讀寫的方法奠支。
ByteBuf組件順序讀寫Int數(shù)據(jù)的示例程序
來看一個順序讀寫int類型數(shù)據(jù)的示例:
總長度為20寫入int數(shù)據(jù)只能寫5個馋辈,我們看打印讀取的內(nèi)容:
除了int數(shù)據(jù),其它類型大家也可以試試倍谜。
ByteBuf組件獲取對應(yīng)字符位置的示例程序
ByteProcessor類中定義了很多特殊字符迈螟,有興趣可以看看。來看一下打印效果:
ByteBuf組件slice操作的示例程序
我們看一下打印結(jié)果:
注意slice方法兩個參數(shù)的意義和如何把ByteBuf轉(zhuǎn)換為字符串尔崔。
ByteBuf組件copy深拷貝的示例程序
注意copy方法的用法答毫,來看打印結(jié)果:
ByteBuf組件復(fù)合緩沖區(qū)的示例程序
注意復(fù)合緩沖區(qū)的創(chuàng)建和操作,來看打印結(jié)果:
ByteBuf組件堆上創(chuàng)建和操作的示例程序
來看一下循環(huán)打印的代碼:
連看一下結(jié)果:
ByteBuf組件堆外創(chuàng)建和操作的示例程序
邏輯上和堆上的方法一樣季春,來看一下打印結(jié)果:
代碼地址:https://gitee.com/blueses/netty-demo??06