明確關(guān)鍵點:
要搞懂事件在多個ChannelHandler間的傳播順序棋凳,有兩個關(guān)鍵點需要明確
1.pipeline初始化時拦坠,會創(chuàng)建兩個哨兵Handler,即HeadContext剩岳、TailContext贞滨,我們添加的Handler處于這兩個哨兵Handler之間,HeadContext可以是入站事件傳播的起點拍棕,一定是出站事件傳播的終點
TailContext可以是出站事件傳播的起點(為啥分為"可以是"與"一定是"晓铆,下文會有說明)
2.事件的傳播起點、方向绰播、目標(biāo):
入站事件傳播的起點為當(dāng)前Handler或者HeadContext骄噪,方向為next,也就是往下一個蠢箩,目標(biāo)是InboundHandler
出站事件傳播的起點為當(dāng)前Handler或者TailContext链蕊,方向為prev,也就是往回一個忙芒,目標(biāo)是OutboundHandler
Pipeline初始化示弓,組裝Handler鏈:
下面以這兩點為主線進(jìn)行剖析,服務(wù)端的啟動入口大家都知道呵萨,是Bootstrap.connect
connect方法首先會通過反射創(chuàng)建一個Channel奏属,這個過程如下圖:
在Channel的構(gòu)造方法中,會為該Channel初始化一個DefaultChannelPipeline潮峦,這個類的構(gòu)造方法如下:?
HeadContext囱皿、TailContext,就是這個pipeline中初始的兩個Handler忱嘹,也就是上面說的哨兵Handler嘱腥,其中TailContext是InboundHandler,HeadContext既是InboundHandler拘悦,也是OutboundHandler?
后續(xù)我們在調(diào)用pipeline.addLast()方法添加的Handler都會處于HeadContext與TailContext之間齿兔,比如在我們添加了ByteToMessageDecoder(子類)、MessagetobyteEncoder(子類)础米、BizHandler三個Handler之后分苇,Handler鏈就是這樣(注意,Handler之間是雙向連接的屁桑,從左往右是next方向医寿,從右往左是prev方向):?
read事件傳播:
好了,Handler鏈已經(jīng)組裝好了蘑斧,接下來就是事件傳播了
我們以讀寫事件為例靖秩,在netty中须眷,read事件由netty幫我們傳播,write事件由我們自己傳播
先看read事件沟突,我們得找到read事件的源頭花颗,Netty中,channel的所有IO事件由EventLoop處理事扭,所以我們將視線轉(zhuǎn)移到NioEventLoop中捎稚,NioEventLoop的run方法里就干了兩件事情乐横,1是輪詢并處理selector事件求橄,2是處理taskQueue中任務(wù),我們的重點放在1葡公,從run開始看起罐农,直到出現(xiàn)read事件傳播,大致流程如下:
最后調(diào)用pipeline.fireChannelRead進(jìn)行read事件傳播催什,注意了涵亏!在netty中,事件傳播有兩類方法
1.Pipeline.*蒲凶,比如fireChannelRead傳播讀事件气筋、write傳播寫事件(channel.fire*最終也會走到pipeline.fire*)
2.ChannelHandlerContext.*,比如fireChannelRead傳播讀事件旋圆、write傳播寫事件
這兩類方法的唯一區(qū)別是傳播的起點不一樣宠默,前者的起點是HeadContext(入站事件起點)、或者TailContext(出站事件起點)灵巧,后者的起點是當(dāng)前Handler搀矫;
好了,我們接著看pipeline.fireChannelRead方法邏輯刻肄,方法體如下:?
headH壳颉!敏弃!沒錯卦羡,就是HeadContext,pipeline中第一個Handler麦到,通過Pipeline.*方法傳播入站事件時绿饵,就以HeadContext為起點,它會繼續(xù)把讀事件往下傳播
這個do while循環(huán)的意思很明確隅要,一直往next方向找蝴罪,直到第一個InboundHandler,調(diào)用其channelRead方法步清,對于我們上面的Handler鏈要门,就會首先找到ByteToMessageDecoder虏肾;ByteToMessageDecoder的channelRead方法會先調(diào)用子類的decode方法,然后繼續(xù)傳播事件欢搜,代碼如下:為方便理解封豪,我略去了一些內(nèi)容,重點關(guān)注我紅框標(biāo)出的代碼?
fireChannelRead方法:
它調(diào)用的是ChannelHandlerContext.fire*炒瘟,前面說了吹埠,這類方法的起點是當(dāng)前Handler,傳播的方向和上面貼的邏輯一致疮装,我再貼過來:
?
依然是往next方向缘琅,找到第一個InboundHandler,也就是我們的BizHandler
通常情況下廓推,BizHandler的channelRead方法中刷袍,我們不會再繼續(xù)往下傳播read事件了,read事件到此結(jié)束樊展,所以本次read事件的傳播過程就是這樣:
1.NioEventLoop輪詢出就緒的read事件后呻纹,調(diào)用Pipeline.fireChannelRead方法傳播事件
2.Pipeline.fireChannelRead方法會以HeadContext為起點,向next方向找InboundHandler专缠,在本例中雷酪,也就是ByteToMessageDecoder
3.在解碼出Message后,ByteToMessageDecoder會調(diào)用ChannelHandlerContext.fireChannelRead方法傳播事件
4.該方法會以當(dāng)前Handler為起點涝婉,向next方向找InboundHandler哥力,在本例中,也就是BizHandler
5.BizHandler中我們一般不會繼續(xù)傳播讀事件嘁圈,讀事件結(jié)束
write事件傳播:
再來看看write事件省骂,原理與上面的read事件類似,區(qū)別就是方向+目標(biāo)不同最住!
入站事件的方向是next方向钞澳,目標(biāo)是InboundHandler
出站事件的方向與入站事件相反,是prev方向涨缚,也就是往回傳播轧粟,目標(biāo)是OutboundHandler
正常情況下,write事件由我們開發(fā)者觸發(fā)脓魏,分為兩種方式:?
對應(yīng)我們前面說的兰吟,事件傳播的兩類方法:Pipeline.write、ChannelHandlerContext.write
拿ctx.channel().write來說茂翔,流程大致如下:
?
Pipeline.write方法如下:
?tail;彀!珊燎!沒錯惭嚣,就是TailContext遵湖,pipeline的最后一個Handler,通過Pipeline.*方法傳播出站事件時晚吞,就以TailContext為起點延旧,它會繼續(xù)把讀事件往下傳播
?
可以看到,傳播方向與read相反槽地,往回找前一個OutboundHandler迁沫,我把Handler鏈再貼過來?
TailContext先找到BizHandler,發(fā)現(xiàn)不是OutboundHandler捌蚊,再找到MessageToByteEncoder集畅,發(fā)現(xiàn)是OutboundHandler,調(diào)用其write方法:
先調(diào)用子類的encode方法逢勾,再調(diào)用ChannelHandlerContext.write方法牡整,繼續(xù)往prev方向找前一個OutboundHandler藐吮,也就是HeadContext溺拱,HeadContext會調(diào)用底層unsafe的write方法往netty的寫緩沖區(qū)寫入字節(jié)流,對于我們來說谣辞,write事件到此就結(jié)束了迫摔,所以本次write事件的傳播過程大致如下:
1.BizHandler中調(diào)用ctx.channel().write
2.以TailContext為起點,prev為方向泥从,OutboundHandler為目標(biāo)句占,查找下一個符合要求的Handler,也就是MessageToByteEncoder
3.MessageToByteEncoder調(diào)用完子類的encode方法將消息編碼后躯嫉,繼續(xù)調(diào)用ctx.write傳播事件
4.ctx.write方法以當(dāng)前Handler為起點纱烘,prev為方向,OutboundHandler為目標(biāo)祈餐,查找下一個符合要求的Handler擂啥,也就是HeadContext
5.HeadContext調(diào)用底層的unsafe將消息寫入緩沖區(qū),寫事件結(jié)束
如果帆阳,在BizHandler中哺壶,調(diào)用的是ctx.write方法,那就不會走到TailContext了蜒谤,而是以當(dāng)前Handler為起點山宾,往后找到下一個滿足條件的Handler,也就是MessageToByteEncoder鳍徽,對本例來說资锰,效果一樣
如果在往pipeline中添加Handler時,把MessageToByteEncoder放到BizHandler后面阶祭,也就是這樣:?
你想一想绷杜,ctx.write和ctx.channel().write效果還一樣嗎翎猛?