模塊類結構:
Netty-Buffer類都集中在io.netty.buffer的package中,主要功能是在數據傳輸時保存傳輸的數據,同時對通信數據進行功能封裝,便於對數據空間進行管理。注:在其他的package中有一些繼承自buffer的類,不在這裏說明。
package如下:
工作原理簡介:
一、從存儲方式上來分,ByteBuf分爲:HeapByteBuf和DirectByteBuf。
HeapByteBuf是用byte數組格式來存儲數據,DirectByteBuf是使用java.nio.ByteBuffer來存儲數據。
DirectByteBuf利用java.nio.ByteBuffer是藉助於JVM調用操作系統的底層通信函數,直接操作直接緩衝區可以減少中間緩衝區的複製操作,進而提供程序性能。
HeapByteBuf是在JVM內部開闢緩衝區,在數據操作前先把數據複製到byte數組再進行處理。因爲中間增加了一層數據複製操作,所以會影響性能。
二、從空間初始化方式上來分,ByteBuf分爲:緩存方式分配和非緩存方式分配。
空間分配工具類:PooledByteBufAllocator和UnpooledByteBufAllocator,其中UnpooledByteBufAllocator在外又封裝了一個類Unpooled。
在緩存方式的分配類中,預申請了8個16K空間(分別是byte數組和java.nio.ByteBuffer)放在緩存中,通過類似工廠的創建函數創建ByteBuf時,先檢查緩存中的空間是否符合要求,如果符合先從緩存中分配空間,否則申請新的空間。
空間申請時判斷爲:大於chunkSize(16K),大於PageSize(8192)小於chunkSize(16K),小於PageSize大於512,小於512幾個檔。
注:這幾個值會根據操作系統的不同有所調整,以上值是在win7x64系統上的值。
三、空間分配和擴展
1、HeapByteBuf
①緩存方式
a、分配
根據構建是傳入參數的初始化空間,從對應的緩存空間中取得byte[],分配給到ByteBuf中memory變量。
b、擴展
在需要擴展的空間時,從緩存中取出一個符合要求的新的空間,即byte數組,把新申請的byte數組替換到ByteBuf中,執行System.arraycopy(src, srcOffset, dst, dstOffset, length);即可
②非緩存方式
a、分配
new一個byte[]賦值到ByteBuf的memory中,長度是傳入的初始化長度參數。
b、擴展
new一個新長度的byte[],替換ByteBuf中舊的memory,然後執行System.arraycopy(src, srcOffset, dst, dstOffset, length);即可
2、DirectByteBuf
①緩存方式
a、分配
根據入口的初始化大小參數從緩存中取得對應的空間區域進行分配。
b、擴展
根據申請空間的要求,從緩存中取得(緩存中無法滿足時new一個java.nio.ByteBuffer)滿足要求的java.nio.ByteBuffer,替換掉原有的java.nio.ByteBuffer,如果系統判斷爲unSafe時,直接複製舊的數據到新的java.nio.ByteBuffer;如果系統判斷不爲unSafe時,說明舊的數據還可能有其他Buffer訪問,所以新舊java.nio.ByteBuffer同時進行自身複製一個新的java.nio.ByteBuffer,然後進行數據複製。
②非緩存方式
a、分配
new一個java.nio.ByteBuffer複製給ByteBuf的memory變量,長度爲入口的初始化大小參數。
b、擴展
new一個新的java.nio.ByteBuffer複製給ByteBuf的memory變量,然後把數據複製到新的java.nio.ByteBuffer中。
四、其他類型ByteBuf:
1、CompositeByteBuf
是一個虛擬的ByteBuf,在內部用List保存多個ByteBuf,然後虛擬爲一個ByteBuf供使用。應用場景未知。
同類型:FixedCompositeByteBuf,List長度爲固定長度
2、ReadOnly類型
只讀ByteBuf。包括:ReadOnlyByteBuf、ReadOnlyByteBufferBuf、ReadOnlyUnsafeDirectByteBuf等
3、EmptyByteBuf
4、DuplicatedByteBuf
不建議直接構造此類ByteBuf。執行ByteBuf內部duplicate()生成一個本身的副本。
5、WrappedByteBuf
未知
SimpleLeakAwareByteBuf
6、SwappedByteBuf
反轉字序的ByteBuf
7、SlicedByteBuf
分片的ByteBuf,通過ByteBuf的slice()方法返回,和調用者共享從position指針到limit指針的這部分內容。
五、通信時的處理
在服務端和客戶端通信時,先把ByteBuf的capacity初始化爲1024,然後在接收數據的過程中,再根據每次接收到的數據長度,猜測(動態計算)下次傳輸可能的數據長度,然後記錄這個長度,當下一次數據開始傳輸時把這個長度當做初始長度生成ByteBuf。然後依次循環使用,直到連接關閉爲止。
重點類介紹:
類繼承關係圖:
1、UnpooledByteBufAllocator
非緩存方式ByteBuf生成工具類,是ByteBufAllocator的一種簡單實現,生成的方式是每次調用都new一個新的ByteBuf。
提供了各種ByteBuf的實現方法。
2、PooledByteBufAllocator
緩存方式的ByteBuf生成工具類。預生成了一個高性能的buffer池,分配策略則是結合了buddy allocation和slab allocation的jemalloc變種。
這是官方推薦的工具類,由於在先前的測試中可能存在內存泄露的現象,所以在連接初始化時使用的UnpooledByteBufAllocator分配ByteBuf。但是在官方的最新公告中表示已經解決了此問題,以後還是推薦使用PooledByteBufAllocator來生成ByteBuf來提供整體性能。
3、PoolArena
ByteBuf空間分配核心類。提供了allocate()方法來初始化ByteBuf空間,以及reallocate()方法來動態擴容空間。
同時在繼承類HeapArena和DirectArena類中提供了memoryCopy()函數的空間擴容的具體實現。
與其他模塊接口:
一、ByteBuf的生成
ByteBuf的生成不建議直接使用new一個具體類的方式來實現,netty提供了生成工具類PooledByteBufAllocator和UnpooledByteBufAllocator供用戶使用。
二、ByteBuf主要使用的方法說明
1、capacity()方法和capacity(newCapacity)
無參數時是返回buf現有的容量;
有參數時是重新設置buf的容量,一般用於寫入時容量不足的情況下進行擴容操作
2、order()方法和order(ByteOrder endianness)方法
無參數時返回buf的字節序
有參數時設置buf的字節序,同時依照新的字節序進行轉換
3、readableBytes()和readBytes(obj);
readableBytes()返回當前可讀的字節數:從readIndex到結尾的字節數。一般用於讀取數據前進行數據完整性的判斷。
readBytes()讀取數據到指定的obj變量中(還有其他參數)。
4、writableBytes()和maxWritableBytes()
writableBytes()是返回當前capacity可寫入的字節數是多少;
maxWritableBytes()是返回ByteBuf創建時的maxCapacity可寫入的字節數是多少
5、markReaderIndex()和resetReaderIndex()
markReaderIndex()把當前的readerIndex賦值到markReaderIndex中。
resetReaderIndex()重設readerIndex,把markIndex賦值到readerIndex。
這兩個方法和readableBytes()、readBytes()結合使用可以完成數據的讀取操作。
6、markWriterIndex()和resetWriterIndex()
markWriterIndex()方法是把當前writeIndex賦值到markWriteIndex中。
resetWriterIndex()是把writeIndex設置爲markWriteIndex的值。
7、writeBytes()
寫數據函數,把數據吸入到ByteBuf中。
一般在寫數據時和markWriterIndex()和resetWriterIndex()結合使用
8、 clear()
清空ByteBuf,同時把readerIndex、markReaderIndex、writeIndex、markWriteIndex等設置爲0
三、netty→ByteBuf和java.nio.ByteBuffer的不同
在java.nio.ByteBuffer有capacity、position、limit、mark四種概念,除了容量不會改變以外,position、limit和mark在讀寫時都會發生改變,並且在讀操作前要調用flip()方法重設position和limit纔可以正確讀出寫入的數據。
而在netty的ByteBuf中,封裝時重新定義了readerIndex和writeIndex,在讀寫時只是操作對應的標誌位,開發者在使用當中讀寫時不用關心position、limit、mark,也不用執行flip()方法就可以很方便的讀寫操作。
java.nio.Bytebuffer讀寫實例:
ByteBuffer buf = ByteBuffer.allocate(10);
buf.put(xxx);//寫完畢轉讀取時
buf.flip();//必須調用,否則讀取的數據不正確
buf.get(xxx);
而使用netty的ByteBuf時不需要關心這些指針,如:
ByteBuf buf = xxx;//生成新的ByteBuf
byte writeData[] = {...};
buf.writeBytes(writeData);//寫入數據
byte readData[] = new byte[11];
buf.readBytes(readData);//不用其他操作,可直接讀取
通過上面的示例可以看出,netty的ByteBuf使用起來更方便
四、HeapByteBuf和DirectByteBuf的選擇(個人理解)
HeapByteBuf由Heap管理,Heap是Java堆的意思,內部實現直接採用byte數組;DirectByteBuf使用是堆外內存,Direct應是採用Direct I/O之意,內部實現使用java.nio.DirectByteBuffer。
注:HeapByteBuf通過
在使用上的選擇:
場景1:網絡間的通信:
A計算機的netty進程A1和B計算機上的netty進程A2進行通信,這個時候涉及到跨網絡通信的數據傳輸,前面說過DirectByteBuf是JVM直接操作操作系統的直接緩衝區能夠提供性能,那麼此時使用DirectByteBuf有利於提高程序性能,減少資源的使用。如果此時使用HeapByteBuf,那麼進程現在JVM內部分配緩衝區,寫完數據後再賦值到操作系統的直接緩衝區,然後進行網絡傳輸,這樣緩衝區的複製會影響性能,同時使用完畢後操作系統和JVM都要回收緩衝區資源,實際上增加了資源的使用,也降低了程序的性能。
場景2:進程內部的通信:
同一臺計算機上的netty進程內進行通信,這個時候只涉及到了進程內的通信、涉及不到網絡間傳輸,這個時候使用HeapByteBuf會有更好的優勢。
整體運行情況:
其他;