1.Pipeline流水線
Netty的業務處理器流水線ChannelPipeline是基於責任鏈設計模式(Chain of Responsibility)來設計的,內部是一個雙向鏈表結構,能夠支持動態地添加和刪除Handler業務處理器。
-
入站操作
-
出站操作
出站流水從後往前
1.1ChanneHandlerContext上下文
- 不論定義那種類型的Handler業務處理器,最終形式都是以雙向鏈表方式保存的
- 在Handler業務處理器被添加到流水線中時,會創建一個通道處理器上下文ChannelHandlerContext,它代表了ChannelHandler通道處理器和ChannelPipeline通道流水線之間的關聯。
- 主要分類
1.獲取上下文所關聯的Netty組件實例,如所關聯的通道、所關聯的流水線、上下文內部Handler業務處理器實例等;
- 是入站和出站處理方法
- Channel、Handler、ChannelHandlerContext三者的關係爲:
Channel通道擁有一條ChannelPipeline通道流水線,每一個流水線節點爲一個ChannelHandlerContext通道處理器上下文對象,每一個上下文中包裹了一個ChannelHandler通道處理器。在ChannelHandler通道處理器的入站/出站處理方法中,Netty都會傳遞一個Context上下文實例作爲實際參數。通過Context實例的實參,在業務處理中,可以獲取ChannelPipeline通道流水線的實例或者Channel通道的實例。
1.2階段流水線的處理方式
- 1.入站在channelRead方法中,不去調用父類的channelRead入站方法
-2.出站處理流程中,只要開始就不能被階段
2.ByteBuf緩衝區
- ByteBuf的優勢:
- Pooling (池化,這點減少了內存複製和GC,提升了效率)
- 複合緩衝區類型,支持零複製
- 不需要調用flip()方法去切換讀/寫模式
- 擴展性好,例如StringBuffer
- 可以自定義緩衝區類型
- 讀取和寫入索引分開
- 方法的鏈式調用
- 可以進行引用計數,方便重複使用
-
ByteBuf是一個字節容器,內部是一個字節數組
2.1ByteBuf的重要屬性
- readerIndex(讀指針):指示讀取的起始位置。每讀取一個字節,readerIndex自動增加1。一旦readerIndex與writerIndex相等,則表示ByteBuf不可讀了。
- writerIndex(寫指針):指示寫入的起始位置。每寫一個字節,writerIndex自動增加1。一旦增加到writerIndex與capacity()容量相等,則表示ByteBuf已經不可寫了。capacity()是一個成員方法,不是一個成員屬性,它表示ByteBuf中可以寫入的容量。注意,它不是最大容量maxCapacity。
- maxCapacity(最大容量):表示ByteBuf可以擴容的最大容量。當向ByteBuf寫數據的時候,如果容量不足,可以進行擴容。擴容的最大限度由maxCapacity的值來設定,超過maxCapacity就會報錯。
2.2ByteBuf的三組方法
- 容量系列:capacity()、maxCapacity()
- 寫入系列: isWritable()、writableBytes() 等
- 讀取系列: isReadable( )、 readableBytes( )等
2.3代碼示例
public class WriteReadTest {
@Test
public void testWriteRead() {
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
print("動作:分配 ByteBuf(9, 100)", buffer);
buffer.writeBytes(new byte[]{1, 2, 3, 4});
print("動作:寫入4個字節 (1,2,3,4)", buffer);
Logger.info("start==========:get==========");
getByteBuf(buffer);
print("動作:取數據 ByteBuf", buffer);
Logger.info("start==========:read==========");
readByteBuf(buffer);
print("動作:讀完 ByteBuf", buffer);
}
//讀取一個字節
private void readByteBuf(ByteBuf buffer) {
while (buffer.isReadable()) {
Logger.info("讀取一個字節:" + buffer.readByte());
}
}
//讀取一個字節,不改變指針
private void getByteBuf(ByteBuf buffer) {
for (int i = 0; i < buffer.readableBytes(); i++) {
Logger.info("讀取一個字節:" + buffer.getByte(i));
}
}
}
日誌打印:
[main|PrintAttribute.print] |> after ===========動作:分配 ByteBuf(9, 100)============
[main|PrintAttribute.print] |> 1.0 isReadable(): false
[main|PrintAttribute.print] |> 1.1 readerIndex(): 0
[main|PrintAttribute.print] |> 1.2 readableBytes(): 0
[main|PrintAttribute.print] |> 2.0 isWritable(): true
[main|PrintAttribute.print] |> 2.1 writerIndex(): 0
[main|PrintAttribute.print] |> 2.2 writableBytes(): 256
[main|PrintAttribute.print] |> 3.0 capacity(): 256
[main|PrintAttribute.print] |> 3.1 maxCapacity(): 2147483647
[main|PrintAttribute.print] |> 3.2 maxWritableBytes(): 2147483647
[main|PrintAttribute.print] |> after ===========動作:寫入4個字節 (1,2,3,4)============
[main|PrintAttribute.print] |> 1.0 isReadable(): true
[main|PrintAttribute.print] |> 1.1 readerIndex(): 0
[main|PrintAttribute.print] |> 1.2 readableBytes(): 4
[main|PrintAttribute.print] |> 2.0 isWritable(): true
[main|PrintAttribute.print] |> 2.1 writerIndex(): 4
[main|PrintAttribute.print] |> 2.2 writableBytes(): 252
[main|PrintAttribute.print] |> 3.0 capacity(): 256
[main|PrintAttribute.print] |> 3.1 maxCapacity(): 2147483647
[main|PrintAttribute.print] |> 3.2 maxWritableBytes(): 2147483643
[main|WriteReadTest.testWriteRead] |> start==========:get==========
[main|WriteReadTest.getByteBuf] |> 讀取一個字節:1
[main|WriteReadTest.getByteBuf] |> 讀取一個字節:2
[main|WriteReadTest.getByteBuf] |> 讀取一個字節:3
[main|WriteReadTest.getByteBuf] |> 讀取一個字節:4
[main|PrintAttribute.print] |> after ===========動作:取數據 ByteBuf============
[main|PrintAttribute.print] |> 1.0 isReadable(): true
[main|PrintAttribute.print] |> 1.1 readerIndex(): 0
[main|PrintAttribute.print] |> 1.2 readableBytes(): 4
[main|PrintAttribute.print] |> 2.0 isWritable(): true
[main|PrintAttribute.print] |> 2.1 writerIndex(): 4
[main|PrintAttribute.print] |> 2.2 writableBytes(): 252
[main|PrintAttribute.print] |> 3.0 capacity(): 256
[main|PrintAttribute.print] |> 3.1 maxCapacity(): 2147483647
[main|PrintAttribute.print] |> 3.2 maxWritableBytes(): 2147483643
[main|WriteReadTest.testWriteRead] |> start==========:read==========
[main|WriteReadTest.readByteBuf] |> 讀取一個字節:1
[main|WriteReadTest.readByteBuf] |> 讀取一個字節:2
[main|WriteReadTest.readByteBuf] |> 讀取一個字節:3
[main|WriteReadTest.readByteBuf] |> 讀取一個字節:4
[main|PrintAttribute.print] |> after ===========動作:讀完 ByteBuf============
[main|PrintAttribute.print] |> 1.0 isReadable(): false
[main|PrintAttribute.print] |> 1.1 readerIndex(): 4
[main|PrintAttribute.print] |> 1.2 readableBytes(): 0
[main|PrintAttribute.print] |> 2.0 isWritable(): true
[main|PrintAttribute.print] |> 2.1 writerIndex(): 4
[main|PrintAttribute.print] |> 2.2 writableBytes(): 252
[main|PrintAttribute.print] |> 3.0 capacity(): 256
[main|PrintAttribute.print] |> 3.1 maxCapacity(): 2147483647
[main|PrintAttribute.print] |> 3.2 maxWritableBytes(): 2147483643
使用get取數據不會影想ByteBuf的指針
2.4ByteBuf的引用計數
- Netty的ByteBuf的內存回收工作是通過引用計數的方式管理的
- Netty採用“計數器”來追蹤ByteBuf的生命週期,一是對PooledByteBuf的支持,二是能夠儘快地“發現”那些可以回收的ByteBuf(非Pooled),以便提升ByteBuf的分配和銷燬的效率
- 什麼是Pooled(池化)的ByteBuf緩衝區呢?
在通信程序的執行過程中,Buffer緩衝區實例會被頻繁創建、使用、釋放。大家都知道,頻繁創建對象、內存分配、釋放內存,系統的開銷大、性能低,如何提升性能、提高Buffer實例的使用率呢?從Netty4版本開始,新增了對象池化的機制。即創建一個Buffer對象池,將沒有被引用的Buffer對象,放入對象緩存池中;當需要時,則重新從對象緩存池中取出,而不需要重新創建
- 引用計數的大致規則如下:
在默認情況下,當創建完一個ByteBuf時,它的引用爲1;每次調用retain()方法,它的引用就加1;每次調用release()方法,就是將引用計數減1;如果引用爲0,再次訪問這個ByteBuf對象,將會拋出異常;如果引用爲0,表示這個ByteBuf沒有哪個進程引用它,它佔用的內存需要回收。
-
在Netty中,引用計數爲0的緩衝區不能再繼續使用
- 爲了確保引用計數不會混亂,在Netty的業務處理器開發過程中,應該堅持一個原則:retain和release方法應該結對使用。
- 當引用計數已經爲0, Netty會進行ByteBuf的回收。分爲兩種情況:
(1)Pooled池化的ByteBuf內存,回收方法是:放入可以重新分配的ByteBuf池子,等待下一次分配。(2)Unpooled未池化的ByteBuf緩衝區,回收分爲兩種情況:如果是堆(Heap)結構緩衝,會被JVM的垃圾回收機制回收;如果是Direct類型,調用本地方法釋放外部內存(unsafe.freeMemory)
2.5 ByteBuf的Allocator分配器
Netty通過ByteBufAllocator分配器來創建緩衝區和分配內存空間。Netty提供了ByteBufAllocator的兩種實現:PoolByteBufAllocator和UnpooledByteBufAllocator。
- PoolByteBufAllocator(池化ByteBuf分配器)將ByteBuf實例放入池中,提高了性能,將內存碎片減少到最小;這個池化分配器採用了jemalloc高效內存分配的策略,該策略被好幾種現代操作系統所採用。
- UnpooledByteBufAllocator是普通的未池化ByteBuf分配器,它沒有把ByteBuf放入池中,每次被調用時,返回一個新的ByteBuf實例;通過Java的垃圾回收機制回收。
- 內存管理的策略可以靈活調整,這是使用Netty所帶來的又一個好處。只需一行簡單的配置,就能獲得到池化緩衝區帶來的好處
public class AllocatorTest {
@Test
public void showAlloc() {
ByteBuf buffer = null;
//方法一:默認分配器,分配初始容量爲9,最大容量100的緩衝
buffer = ByteBufAllocator.DEFAULT.buffer(9, 100);
//方法二:默認分配器,分配初始爲256,最大容量Integer.MAX_VALUE 的緩衝
buffer = ByteBufAllocator.DEFAULT.buffer();
//方法三:非池化分配器,分配基於Java的堆內存緩衝區
buffer = UnpooledByteBufAllocator.DEFAULT.heapBuffer();
//方法四:池化分配器,分配基於操作系統的管理的直接內存緩衝區
buffer = PooledByteBufAllocator.DEFAULT.directBuffer();
//…..其他方法
}
}
-
根據內存的管理方不同,分爲堆緩存區和直接緩存區,也就是Heap ByteBuf和Direct ByteBuf。
2.6 ByteBuf的自動釋放
- TailHandler自動釋放
如果每個InboundHandler入站處理器,把最初的ByteBuf數據包一路往下傳,那麼TailHandler末尾處理器會自動釋放掉入站的ByteBuf實例
(1)手動釋放ByteBuf。具體的方式爲調用byteBuf.release()。
(2)調用父類的入站方法將msg向後傳遞,依賴後面的處理器釋放ByteBuf。具體的方式爲調用基類的入站處理方法super.channelRead(ctx,msg)。
- SimpleChannelInboundHandler自動釋放
· 手動釋放ByteBuf實例。
· 繼承SimpleChannelInboundHandler,利用它的自動釋放功能。