Netty學(xué)習(xí)之ChannelHandler&ChannelPipeline
前言
ChannelHandler
Channel生命周期
- ChannelUnregistered,Channel被創(chuàng)建,但是還沒有注冊到EventLoop中
- ChannelRegisted,Channel注冊到EventLoop中
- ChannelActive,Channel激活(連接到遠(yuǎn)程端口),能夠發(fā)送以及接受消息
- ChannelInActive,Channel沒有連接到遠(yuǎn)程端口
生命周期流程
ChannelRegisted --> ChannelActive --> ChannelInactive --> ChannelUnregistered
ChannelHandler生命周期
- handlerAdded,當(dāng)ChannelHandler添加到ChannelPipeline時調(diào)用
- handlerRemoved,當(dāng)ChannelHandler從ChannelPipline中移除時調(diào)用
- exceptionCaught,在ChannelPipline處理時發(fā)生異常時調(diào)用
注意上面每個方法都接收一個ChannelHandlerContext作為參數(shù)
ChannelHandler有重要的兩個子接口
- ChannelInboundHandler,處理輸入的數(shù)據(jù)并且處理各種狀態(tài)轉(zhuǎn)變
- ChannelOutboundHandler,處理輸出的數(shù)據(jù)并且攔截各種操作
ChannelInboundHandler生命周期
- channelRegistered,當(dāng)Channel注冊到對應(yīng)的EventLoop并且能夠處理I/O時調(diào)用
- channelUnregistered,當(dāng)channel從EventLoop中移除并且不能處理任何I/O時調(diào)用
- channelActive,當(dāng)channel激活時調(diào)用,此時channel已經(jīng)連接并且準(zhǔn)備好
- channelInactive,當(dāng)通道斷開連接時調(diào)用
- channelReadComplete,讀操作在Channel中已經(jīng)完成時調(diào)用(讀取完成)
- channelRead,數(shù)據(jù)從channel中讀取時調(diào)用(開始讀取)
- channelWritabilityChanged,寫狀態(tài)改變時調(diào)用
- userEventTriggered,當(dāng)因為管線中傳遞對象時ChannelInboundHanlder.fireUserEventTriggered()被調(diào)用時調(diào)用
當(dāng)覆蓋ChannelInboundHandler的channelRead方法時,需要手動釋放對應(yīng)的內(nèi)存,可以通過ReferenceCountUtil.release(msg)方法進(jìn)行釋放操作
更簡單的方式是通過SimpleChannelInboundHandler,然后覆蓋其channelRead0()方法,該方法會自動釋放資源,所以,不能存儲稍后會使用到的數(shù)據(jù),因為這些數(shù)據(jù)都會失效,通過源碼就可以發(fā)現(xiàn),該方法被channelRead()調(diào)用,并且channelRead0()方法被調(diào)用后,會釋放所占用內(nèi)存。
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean release = true;
try {
if (acceptInboundMessage(msg)) {
@SuppressWarnings("unchecked")
I imsg = (I) msg;
// 這里調(diào)用channelRead0
channelRead0(ctx, imsg);
} else {
release = false;
ctx.fireChannelRead(msg);
}
} finally {
if (autoRelease && release) {
// 釋放空間
ReferenceCountUtil.release(msg);
}
}
}
ChannelOutboundHandler
ChannelOutboundHandler的方法由Channel、ChannelPipeline、ChannelHandlerContext調(diào)用
主要的操作方法有write()、flush()
大部分的方法都會帶一個ChannelPromise參數(shù),當(dāng)操作完成時可以收到通知,該接口是ChannelFuture的子接口。
在Netty中,同時還提供了ChannelInboundHandlerAdapter、ChannelOutboundHanlderAdapter類,一般只需要繼承這兩個類,然后覆蓋想要實現(xiàn)邏輯的方法即可。
資源管理
在使用ChannelInboundHanlder#channelRead()或者ChannelOutboundHandler#write()時,需要保證沒有資源泄露,Netty使用引用技術(shù)來處理緩存的ByteBuf,所以使用完ByteBuf之后更改引用是非常重要的。
Netty提供了一個ResouceLeakDetecor工具類用于檢測是否發(fā)生內(nèi)存泄露,如果發(fā)生泄露,則會打印對應(yīng)的日志信息。有四個不同的級別
-
DISABLED,關(guān)閉泄露檢查 -
SIMPLE,默認(rèn)級別,樣本率為1% -
ADVANCED,報告內(nèi)存泄露以及對應(yīng)的信息 -
PARANOID,每個類均采樣,會嚴(yán)重影響性能
如果一個消息已經(jīng)被消費或者被丟棄,并且不再在管道中傳遞給下一個接受者,則需要手動調(diào)用ReferenceCountUtil.release(msg)將其釋放,如果消息到達(dá)傳輸層,當(dāng)消息被寫出或者通道關(guān)閉后,會自動進(jìn)行釋放。
ChannelPipeline
每個新的Channel都會綁定一個新的ChannelPipeline,并且是永久性的,Channel不能綁定新的ChannelPipeline,也不能從當(dāng)前Channel中解綁(開發(fā)者無需關(guān)心綁定以及解綁)
ChannelHandlerContext使得一個ChannelHanlder能夠與ChannelPipeline以及其他的ChannelHandler進(jìn)行通信
由于ChannelPipeline傳播事件,它會檢測在管線中的下一個ChannelHandler是否匹配移動的方法,如果不匹配,則跳過該ChannleHandler,并且處理下一個,直到找到一個匹配的ChannelHandler。
ChannelPipeline中提供了眾多的方法用于操作ChannelHandler,如addFirst、addLast、remove等。同時,還提供了眾多的方法用于調(diào)用下一個ChannelInboundHandler方法的方法,如fireChannelRead()、fireExceptionCaught()等,調(diào)用下一個ChannelOutboundHandler方法的方法,如flush()、write()、writeAndFlush()等。
ChannelHandlerContext
ChannelHandlerContext表示ChannelHandler和ChannelPipeline之間的連接,當(dāng)一個ChannelHandler被添加到ChannelPipeline時會被創(chuàng)建。
ChannelPipeline的主要作用是管理其所關(guān)聯(lián)的ChannelHandler與其他在ChannelPipeline中的ChannelHandler的交互
如果調(diào)用Channel或者ChannelPipeline中的某些方法,對應(yīng)的操作會在整個ChannelPipeline中傳輸,而通過ChannelHandlerContext調(diào)用,只傳輸給下一個能夠處理該事件的ChannelHandler
異常處理
如果是ChannelInboundHandler中出現(xiàn)異常,會逐步進(jìn)行傳遞,所以,一般在最后一個channelHandler中覆蓋caughtException(),實現(xiàn)對應(yīng)的異常處理邏輯
如果是在ChannelOutboundHandler中出現(xiàn)異常,可以有一下機制
- 每一個outbound操作會返回一個ChannelFuture,注冊了的ChannelFutureListener會收到對應(yīng)的消息,即成功或者失敗
- 幾乎所有的channelOutboundHanlder會傳遞一個ChannelPromise實例,該實例是ChannelFuture的子類,ChannelPromise能夠注冊監(jiān)聽器用于異步通知,也可以直接通知操作,
setSuccess()、setFailure()
總結(jié)
本小節(jié)我們主要詳細(xì)學(xué)習(xí)了ChannelHandler以及ChannelPipeline,ChannelHandler是Netty應(yīng)用中邏輯的存放地方,基本上我們主要交互的對象也是ChannelHandler,不同的ChannelHandler之間通過ChannelPipeline連接起來,構(gòu)成一個Handler處理鏈,而ChannelHandler與ChannelPipeline之間則是通過ChannelHandlerContext進(jìn)行連接,不同的對象用途不同,使用的時候需要注意。