Netty核心概念(6)之Handler


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的線程模型進行分析。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM