深入淺出Netty:read

佔小狼發表於2016-09-18

本系列:


boss執行緒主要負責監聽並處理accept事件,將socketChannel註冊到work執行緒的selector,由worker執行緒來監聽並處理read事件,本節主要分析Netty如何處理read事件。

深入淺出Netty:read

accept->read

當work執行緒的selector檢測到OP_READ事件發生時,觸發read操作。

該read方法定義在類NioByteUnsafe中。

1、allocHandle負責自適應調整當前快取分配的大小,以防止快取分配過多或過少,先看看AdaptiveRecvByteBufAllocator內部實現:

SIZE_TABLE:按照從小到大的順序預先儲存可以分配的快取大小。
從16開始,每次累加16,直到496,接著從512開始,每次增大一倍,直到溢位。
DEFAULT_MINIMUM:最小快取(64),在SIZE_TABLE中對應的下標為3。
DEFAULT_MAXIMUM :最大快取(65536),在SIZE_TABLE中對應的下標為38。
DEFAULT_INITIAL :初始化快取大小,第一次分配快取時,由於沒有上一次實際收到的位元組數做參考,需要給一個預設初始值。
INDEX_INCREMENT:上次預估快取偏小,下次index的遞增值。
INDEX_DECREMENT :上次預估快取偏大,下次index的遞減值。

2、allocHandle.allocate(allocator) 申請一塊指定大小的記憶體。

通過ByteBufAllocator的ioBuffer方法申請快取。

根據平臺是否支援unsafe,選擇使用直接實體記憶體還是堆上記憶體。

direct buffer方案:

UnpooledUnsafeDirectByteBuf是如何實現快取管理的?對Nio的ByteBuffer進行了封裝,通過ByteBuffer的allocateDirect方法實現快取的申請。

memoryAddress = PlatformDependent.directBufferAddress(buffer) 獲取buffer的address欄位值,指向快取地址。
capacity = buffer.remaining() 獲取快取容量。

方法toLeakAwareBuffer(buf)對申請的buf又進行了一次包裝:

Netty中使用引用計數機制來管理資源,ByteBuf實現了ReferenceCounted介面,當例項化一個ByteBuf時,引用計數為1, 程式碼中需要保持一個該物件的引用時需要呼叫retain方法將計數增1,物件使用完時呼叫release將計數減1。當引用計數變為0時,物件將釋放所持有的底層資源或將資源返回資源池。

3、方法doReadBytes(byteBuf) 將socketChannel資料寫入快取。

最終底層採用ByteBuffer實現read操作,這裡有一塊邏輯不清楚,為什麼要用tmpNioBuf?

int localReadAmount = doReadBytes(byteBuf);
1、如果返回0,則表示沒有讀取到資料,則退出迴圈。
2、如果返回-1,表示對端已經關閉連線,則退出迴圈。
3、否則,表示讀取到了資料,資料讀入快取後,觸發pipeline的ChannelRead事件,byteBuf作為引數進行後續處理,這時自定義Inbound型別的handler就可以進行業務處理了。

其中引數msg,就是對應的byteBuf,當請求的資料量比較大時,會多次觸發channelRead事件,預設最多觸發16次,可以通過maxMessagesPerRead欄位進行配置。
如果客戶端傳輸的資料過大,可能會分成好幾次傳輸,因為TCP一次傳輸內容大小有上限,所以同一個selectKey會觸發多次read事件,剩餘的資料會在下一輪select操作繼續讀取。

在實際應用中,應該把所有請求資料都快取起來再進行業務處理。
所有資料都處理完,觸發pipeline的ChannelReadComplete事件,並且allocHandle記錄這次read的位元組數,進行下次處理時快取大小的調整。

到此為止,整個NioSocketChannel的read事件已經處理完成。

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

深入淺出Netty:read

相關文章