1.前言
本節介紹Netty中第三個重要的概念——Handler,這個在前兩節都提到了,尤其是Channel和Handler聯系緊密。handler本身的設計非常簡單,但是所起到的作用卻很大,Netty中對於handler的實現非常多(handler是控制socket io的各個生命周期的業務實現,netty實現了很多種協議,自然有很多handler類)。本節並不關心各種不同功能的handler具體實現,主要講解handler的設計和作用及使用方法。
2.主要概念
2.1 ChannelHandler
這個圖可以看到ChannelHandler的基本結構,其有兩個子類接口,ChannelInboundHandler和ChannelOutboundHandler。這也就是我之前常常提到的handler分成in和out類型,針對不同操作的方法。
handlerAdded():handler被添加到channel的時候執行,這個動作就是由pipeline的添加handler方法完成的。對於服務端,在客戶端連接進來的時候,就通過ServerBootstrapAcceptor的read方法,為每一個channel添加了handler。該方法對於handler而言是第一個觸發的方法。
handlerRemoved():handler被移除channel的時候執行,這個動作在pipeline中也存在,基本上是在channel斷開的時候會進行這一步。
exceptionCaught():捕獲channel生命周期中異常處理的類,該方法被移動到ChannelInboundHandler中了。已經廢棄。
注解Sharable表明該handler可以被多個pipeline重復使用。
2.2 ChannelInboundHandler
該接口主要負責的是業務邏輯,主要接口方法如下:
channelRegistered():在channel注冊到線程池的時候會被觸發。
channelUnregistered():在channel關閉的時候觸發。
channelActive():registered完成之后且channel處理active狀態,首次注冊狀態。主要見AbstractChannel的register0(promise)方法。
channelnactive():unregistered之前執行,主要見AbstractChannel的deregister方法。
channelRead():這個主要見pipeline的fireChannelRead方法,其被channel在獲取到數據的階段進行調用,進而觸發到handler的channelRead方法。
channelReadComplete():這個和上面read方法一樣,fireChannelReadComplete方法,被channel運行過程中read過程完成會進行調用。
userEventTriggered():這個也是由pipeline提供的方法作為入口fireUserEventTriggered,這個就是觸發一個事件了,以IdleStateHandler為例,其一般作為心跳檢測事件,放入線程池執行,判斷空閑就會觸發該方法,傳導到各個handler。
channelWritabilityChanged():這個入口也在pipeline,但是好像沒有怎么用到,channel並沒有調用這個方法,一般也沒怎么用該方法。
exceptionCaught():這個入口同樣在pipeline,被channel的讀取過程拋出異常時觸發,當然不只這一個地方。
上述方法追根溯源都是通過pipeline來觸發整個執行鏈的,后面講到context時會詳細說明一下這個過程。只需要知道入口在這個地方即可。
2.3 ChannelOutboundHandler
比起in類型handler傾向於處理業務邏輯,out類型的handler更傾向於處理連接邏輯。下面是該類型的接口方法定義:
bind():綁定端口的操作
connect():連接遠程地址
disconnect():斷開連接
close():端口綁定的端口
deregister():取消注冊到線程池中
read():設置監聽讀取事件
write():寫入數據的操作
flush():刷新緩沖區,立刻發送。
上述接口都是和連接相關的處理,而且這些都是通過pipeline的相關方法觸發的,最終調用的是tail對象。實際上這個handler比較雞肋,原因channel那節其實已經說明過,大部分實現類都做不了什么,最終都交給headContext對象調用unsafe相關方法由其處理了。大部分handler只對write方法進行了處理,其他都直接交給其他的handler處理了。我印象中之前Netty5好像要取消in和out的區別,outhandler太雞肋了(Netty5已經放棄開發,官方的說法是forkjoin框架開發很復雜,還沒准備好開發)。
2.4 ChannelHandlerContext
handlerContext在之前我們就簡要提到過,這個和pipeline聯合使用,管理handler鏈。handler本身沒有持有順序關系,都是通過handlerContext完成的。handlerContext自身會通過配合handler造成順着構成的鏈式順序調用下去,這里會仔細說明一下這個過程。
接口方法就不一一介紹了,看到的都是一些熟悉的方法,針對業務的起始調用fireXXX(),其它的方法就不進行介紹了。handlerContext和pipeline一樣實現類很少,基本使用的就是DefaultChannelHandlerContext,其繼承自AbstractChannelHandlerContext,大部分方法也都是在抽象父類中。下面具體介紹一下是如何工作的:
還是回到pipeline的構造handler鏈的過程:
protected DefaultChannelPipeline(Channel channel) { this.channel = ObjectUtil.checkNotNull(channel, "channel"); succeededFuture = new SucceededChannelFuture(channel, null); voidPromise = new VoidChannelPromise(channel, true); tail = new TailContext(this); head = new HeadContext(this); head.next = tail; tail.prev = head; }
pipeline初始化的時候構建了tail和head兩個handler,這兩個是特殊的,位置不能替換的。
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) { final AbstractChannelHandlerContext newCtx; synchronized (this) { checkMultiplicity(handler); newCtx = newContext(group, filterName(name, handler), handler); addLast0(newCtx); // If the registered is false it means that the channel was not registered on an eventloop yet. // In this case we add the context to the pipeline and add a task that will call // ChannelHandler.handlerAdded(...) once the channel is registered. if (!registered) { newCtx.setAddPending(); callHandlerCallbackLater(newCtx, true); return this; } EventExecutor executor = newCtx.executor(); if (!executor.inEventLoop()) { newCtx.setAddPending(); executor.execute(new Runnable() { @Override public void run() { callHandlerAdded0(newCtx); } }); return this; } } callHandlerAdded0(newCtx); return this; } private void addLast0(AbstractChannelHandlerContext newCtx) { AbstractChannelHandlerContext prev = tail.prev; newCtx.prev = prev; newCtx.next = tail; prev.next = newCtx; tail.prev = newCtx; }
先判斷這個handler有沒有被其他pipeline加載過,是否是sharable類型,進行相關設置。通過handler創建context。然后插入到tail前面,后面就是添加handler之后觸發的方法,觸發handlerAdd方法。通過addLast0方法,可以看見context中的參數prev和next被初始化了。也就是說通過當前的context能夠找到前一個和后一個context了。下面抽出handler接口的其中一個方法,我們來研究一下handler鏈是怎么執行的。
public ChannelHandlerContext fireChannelActive() { invokeChannelActive(findContextInbound()); return this; } static void invokeChannelActive(final AbstractChannelHandlerContext next) { EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeChannelActive(); } else { executor.execute(new Runnable() { @Override public void run() { next.invokeChannelActive(); } }); } } private void invokeChannelActive() { if (invokeHandler()) { try { ((ChannelInboundHandler) handler()).channelActive(this); } catch (Throwable t) { notifyHandlerException(t); } } else { fireChannelActive(); } } private AbstractChannelHandlerContext findContextInbound() { AbstractChannelHandlerContext ctx = this; do { ctx = ctx.next; } while (!ctx.inbound); return ctx; }
handler的任何一個方法,在context中都是由三個這樣的方法來構成鏈式執行的。static方法都是給pipeline調用的,其入參就是head,handler鏈的頭結點。調用了invokeChannelActive()方法,這里就是我們的handler方法相關內容被觸發了。乍一看好像執行完了就沒了啊,這個地方要接下去就需要handler配合了。隨便找一個handler,比如ChannelInboundHandlerAdapter,其相關方法都調用了ctx.fireChannelActive(); 這里就接上了,ctx的fireChannelActive不就是回到了invokeChannelActive()方法了。其入參就不再是當前的context,而是通過findContextInbound來找到下一個in類型的handler,這也說明這個接口是針對in類型的接口。所以一個基本的循環就是ctx.invokeChannelActive(ctx) ->ctx.invokeChannelActive() -> hander.channelActive()->ctx.fireChannelActive(ctx)->findContextInbound()->ctx.invokeChannelActive(ctx)。這么個循環鏈,起點就是pipeline的invokeChannelActive(head),終點就是handler.channelActive()沒有調用ctx.channel的時候,最后的tailContext就是沒有執行任何操作,所以執行到這鏈路就斷了。
3.后記
本節對handler的一個基本內容進行了說明,主要講解了handlerContext的執行過程,明確了調用鏈的入口在於pipeline,通過pipeline和context的控制,保證鏈的執行。channel的生命周期控制pipeline就能控制整個handler的執行。下圖給個具體說明:
上圖綜合了前幾章的內容,整個核心調用過程就是這些了,但是還沒有設置線程問題,所以啟動類以及線程相關沒有加入到圖中,下一章將對Netty的線程模型進行分析。