天天看點

Netty架構入門-ChannelPipeline 詳細使用

前言

ChannalPipeline

Pipeline 可以翻譯成管道,在Netty中ChannelPipeline的作用也如同一條管道,通過這條管道處理入站與出站的資料、事件;通過ChannelPipeline對ChannelHandler的有效編排和互動組成一個應用程式資料和事件處理邏輯的核心。

Netty會對每一個新建立的Channel配置設定一個與之關聯的新的ChannelPipeline執行個體,這個關聯是永久的并且是一一對應的,一個Channel不能附加另外一個ChannelPipeline執行個體也不能分離目前關聯的ChannelPipeline執行個體。下面通過一個示例來看看:

bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
  @Override
  protected void initChannel(SocketChannel ch) throws Exception {
    //添加MessageChannel至pipeline中
    ch.pipeline().addLast(new MessageChannel());
  }
});
           

ChannelInitializer是一個ChannelHandler類型,作用是有新的連接配接進來後通過ChannelHandler的

handlerAdded

事件完成Channel中的ChannelPipeline的初始編排工作,

handlerAdded

事件會委托給

initChannel

方法執行,也就是上面例子中看到的,在

initChannel

方法中可以通過參數擷取到目前新連接配接并建立關聯的Channel執行個體,通過Channal執行個體的

pipeline()

方法擷取到一一對應的ChannelPipeline執行個體,再通過

addList

方法加入ChannelHadler類型的執行個體變量,在執行完

initChannel

方法後ChannelInitializer會将自己從目前的Channel關聯的ChannelPipeline中移除。

ChannalPipeline可以用于編排ChannelHandler的方法清單:

方法 描述

addFirst

addBefore

addAfter

addLast

将一個ChannelHandler添加到ChannelPipeline中
remove 将一個ChannelHandler從ChannelPipeline中移除
replace

将ChannelPipeline中的一個ChannelHandler進行替換,

替換成另一個ChannelHandler

閱讀過前面一部分文章的同學會知道Netty對ChannelHandler又做了區分,分為入站與出站,如下圖:

Netty架構入門-ChannelPipeline 詳細使用

通常入站與出站處理器在編排到Pipeline中時會是混合着的,這樣的情況Pipeline能正确的處理入站與出站嗎?ChannelPipeline可以通過本身傳播事件方法,如果一個入站事件被觸發,它将從Pipeline的頭部開始一直向後傳遞直到尾端,這裡需要通過ChannelPipeline的相對論來讨論事件傳播的正确性問題;Netty總是将入站口作為頭部,對出站口作為尾部(内部隻有一條,使用連結清單存儲所有處理器)。當我們使用

ChannelPipeline.add*()

的方法将入站與出站處理器混合添加到Pipeline之後,在Pipeline中将會按添加的順序進行排列,就如上圖中每個處理器上的編号一樣。ChannelPipeline在傳播事件時會測試Pipeline中一個ChannelHandler的類型是否和事件運動的方向相比對,如果不比對将跳過該ChannelHandler将繼續向前,直接到尾端(這裡就展現了ChannelInboundHandler與ChannelOutboundHandler的重要性,ChannelPipeline在傳播時判斷下一個能ChannelHandler的功能是通過ChannelHandlerMask類實作,篇幅問題就放到以後的源碼分析時再講解吧)。是以在ChannelPipeline中雖然隻有使用一條連結清單來儲存所有的ChannelHandler,但通過事件傳播方向的檢測将一條邏輯上拆分成兩條進而實作了對于入站與出站兩條鍊路上分别有各自的頭部與尾部。

ChannelPipeline中使用連結清單來存儲ChannelHandler,是怎樣存儲的呢?

ChannelHandlerContext

調用

ChannelPipeline.add*()

方法添加一個ChannelHandler至目前Pipeline中時都會建立一個ChannelHandlerContext執行個體,将添加的ChannelHandler包裝在ChannelHandlerContext執行個體中。而ChannelPipeline連結清單上的節點類型就是ChannelHandlerContext。上面說到ChannilPipeline是可以進行事件傳播的,這是因為其繼承了

ChannelInboundInvoker

ChannelOutboundInvoker

,這兩個Invoker接口定義了入站與出站各方向上的事件所對應的方法(這兩個接口在後續講解Netty的源碼時再深入導論,這裡就暫且放過,隻要知道就好了);ChannelHanderContext也繼承了這兩個Invoker接口類,這也就是說通過ChannelHanderContext實作也可以觸發事件的傳播,下面來看個例子:

public static class MessageChannel extends ChannelInboundHandlerAdapter {
  @Override
  public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    //使用pipeline傳播事件,注意這有可能會導緻死循環
    ctx.pipeline().fireChannelRead(msg);
    //使用ChannelHandlerContext傳播事件
    ctx.fireChannelRead(msg);
  }
}
           

ChannelHandlerContext.pipeline()

擷取到的是目前Channel所關聯的pipeline,上面也提到過通過pipeline傳播事件時會從頭部開始,當你自己嘗試使用上面的示例運作時得到的結果将會是一個死循環;而通過

ChannelHandlerContext.fireChannelRead

傳播事件将會又下一個ChannelHandler開始傳播,當重寫ChannelInboundHandlerAdapter實作業務邏輯時讓事件傳播下去就需要顯示調用

ChannelHandlerContext.fireChannelRead

或者調用

super.fireChannelRead

方法,不然事件在目前ChannelHandler就會結束傳播。

對事件傳播的兩種不同方式特點說明:

  1. ChannelPipeline的事件傳播是通過将事件方法的執行委托給連結清單中的ChannelHandlerContext執行(ChannelPipeline與ChannelHandlerContext都實作了

    ChannelInboundInvoker

    ChannelOutboundInvoker

    )
  2. 調用

    ChannelPipeline.fireChannelRead

    傳播事件将從連結清單的頭部開始觸發(是連結清單的頭部,不是事件傳播方向上的,但傳播過程中會根據事件的類型判斷方向)
  3. ChannelHandlerContext作為Pipeline連結清單的節點類型承擔了事件傳播的責任,Pipeline的責任隻是編排,而傳播與承載的主要角色是ChannelHandlerContext

通過這個說明應該能了解在事件傳播是怎麼進行的了,通過

ctx.pipeline().fireChannelRead

傳播事件和通過

ctx.fireChannelRead

傳播事件的差別與結果也不難了解了。

類關系圖

最後通過一張類關系圖來回顧下上面本篇的知識點吧。

Netty架構入門-ChannelPipeline 詳細使用

ChannelPipeline與ChannelHandlerContext都繼承自ChannelInboundInvoker與ChannelOutbountInvoker以實作事件的傳播功能;ChannelHandlerContext包裝了ChannelHandler執行個體,負責事件傳播過程中調用具體的ChannelHandler以實作應用程式功能,ChannelPipeline通過連結清單存儲ChannelHandlerContext,負責事件的源的調用,将事件執行委托給ChannelHandlerContext實際執行。

執行流程如下:

Netty架構入門-ChannelPipeline 詳細使用

往期推薦:

Netty架構:ChannelPipeline事件傳播的藝術

Netty架構:ChannelInboundHandler與ChannelOutbountHandler使用講解

Netty架構:ByteBuf 堆緩沖區與直接緩沖區使用講解

Netty架構:選擇Netty的理由

Netty架構:用Service與Client示例入門

網絡I/O :  Netty 體系結構概述

![在這裡插入圖檔描述](https://img-blog.csdnimg.cn/64981691e25f482f91080ecdb0c6170f.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3R1Y2hhbmdzaGVuZzgwMTAwNA==,size_16,color_FFFFFF,t_70#pic_center)