Java Netty 學習(十)
本篇文章來分析ChannelHandler的相關知識
上一章瞭解到,ChannelHandler起著一個過濾器作用,用於處理在ChannelPipeline中流動的位元組流。
下面慢慢來看。
ChannelInboundHandler
ChannelInboundHandler
主要用於處理入站資料以及各種狀態變化,它是ChannelHandler的子類:
public interface ChannelInboundHandler extends ChannelHandler { // 提供註冊Channel註冊到EventLoop的事件,提供了ChannelHandler自己的ChannelHandlerContext void channelRegistered(ChannelHandlerContext ctx) throws Exception; // Channel取消註冊EventLoop的事件 void channelUnregistered(ChannelHandlerContext ctx) throws Exception; // 判斷Channel是否active void channelActive(ChannelHandlerContext ctx) throws Exception; // 判斷Channel是否unactive void channelInactive(ChannelHandlerContext ctx) throws Exception; // 當前的Channel從流中讀取了一條訊息 void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception; // 當前的讀操作的最後一個位元組已經被channelRead消費 void channelReadComplete(ChannelHandlerContext ctx) throws Exception; // 當用戶事件被觸發 void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception; // 當Channel的寫狀態改變時 void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception; // 出錯時 @Override @SuppressWarnings("deprecation") void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception; }
當然Netty
還提供了一個包裝類ChannelInboundHandlerAdapter
,繼承這個類,就不用實現ChannelInboundHandler
從而實現所有方法了。而ChannelInboundHandlerAdapter
給所有方法都提供了預設實現,實質上是呼叫了ChannelHandlerContext
的方法。
ChannelOutboundHandler
ChannelOutboundHandler
則是處理出站資料並且允許攔截所有操作,當用戶呼叫write方法時,就根據out這根鏈一路傳遞到Channel再流轉到網路上:
public interface ChannelOutboundHandler extends ChannelHandler { // 一旦繫結上就呼叫,而當操作完成時候由promise進行提醒 void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception; // 當連線上時候,觸發 void connect( ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception; // 斷開連線是觸發 void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception; // 當關閉操作時被呼叫 void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception; // 當從當前EventLoop取消註冊時呼叫 void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception; // 攔截ChannelHandlerContext的read方法 void read(ChannelHandlerContext ctx) throws Exception; // 當一個write操作被呼叫時呼叫 void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception; // flush操作,清空快取,並把所有快取資料寫入 void flush(ChannelHandlerContext ctx) throws Exception; }
當然,ChannelOutboundHandler
也提供了一個包裝類ChannelOutboundHandlerAdapter
這裡就不細細分析了。
ByteBuf
在Netty
中,用ByteBuf
作為資料容器,ByteBuf
是一個抽象類,裡面包括了char,int,long,boolean,float,double,byte,short
等8種java基本型別的抽象set和get,子類可以根據不同需求去重寫它們。
ByteBuf
裡面的操作為陣列操作,裡面維護著兩個索引readerIndex
和writeIndex
,具體可以參看NIO中Buffer
例子:
Java Netty 學習(二) - NIO基礎知識Buffer
所以,由於使用的記憶體是通過JNI方式申請的記憶體空間,這些空間JVM是不會進行處理的,所以當使用完之後,需要注意手動釋放,否則易造成記憶體洩漏。
當使用
ChannelInboundHandler.channelRead
和ChannelOutboundHandler.write
時候,都需要保證沒有任何的資源洩漏, 所以在完全使用完某個ByteBuf後,調整其引用計數是很重要的。例如如下程式碼:
@Sharable
public class DiscardInboundHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ReferenceCountUtil.release(msg);
}
}
write方法
@Sharable
public class DiscardOutboundHandler
extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx,
Object msg, ChannelPromise promise) {
ReferenceCountUtil.release(msg);
promise.setSuccess(); // 表示訊息已被處理
}
}
看ReferenceCountUtil的release方法:
public static boolean release(Object msg) {
if (msg instanceof ReferenceCounted) {
return ((ReferenceCounted) msg).release();
}
return false;
}
而ReferenceCounted
的release
方法,則是將reference count-1,當count=0時,則呼叫deallocates
將記憶體物件回收
另外, 可以用Netty提供的ResourceLeakDetector,他將對你的應用程式的緩衝區分配做大約1%的取樣來檢測記憶體洩漏,相關開銷較小。
在每次建立ByteBuf時,建立一個虛引用物件W指向該ByteBuf物件,如果正常呼叫release()操作,則將計數器建議,減1;如果ByteBuf物件不再使用(沒有其他引用)但沒有呼叫release()操作,則GC時虛引用物件A被加入ReferenceQueue中,通過判斷佇列是否為空,即可知道是否存在記憶體洩露。
ChannelHandler流轉
那麼ChannelHandler
在ChannelPipeline
中是如何流轉的呢?
先看下圖,ChannelInboundHanler和ChannelOutboundHandler:
訊息從InboundHandlder一端入站,而從OutboundHandler一端出站。
另一方面, ChannelHandler
、ChannelHandlerContext
、Channel
之間的關係如下圖所示
在寫法中,呼叫ChannelPipeline的add*方法,將handler加入,同時不同型別的handler執行的事件則不同。
參考: