《netty入門與實戰》筆記-04:pipeline 與 channelHandler

這一小節,咱們將會學習 Netty 裏面一大核心組件: Pipeline 與 ChannelHandlerjava

Netty 中的 pipeline 和 channelHandler 經過責任鏈設計模式來組織代碼邏輯,而且可以支持邏輯的動態添加和刪除 ,Netty 可以支持各種協議的擴展,好比 HTTP,Websocket,Redis,靠的就是 pipeline 和 channelHandler,下面,咱們就來一塊兒學習一下這部份內容。設計模式

pipeline 與 channelHandler 的構成

不管是從服務端來看,仍是客戶端來看,在 Netty 整個框架裏面,一條鏈接對應着一個 Channel,這條 Channel 全部的處理邏輯都在一個叫作 ChannelPipeline 的對象裏面,ChannelPipeline 是一個雙向鏈表結構,他和 Channel 之間是一對一的關係。api

ChannelPipeline 裏面每一個節點都是一個 ChannelHandlerContext 對象,這個對象可以拿到和 Channel 相關的全部的上下文信息,而後這個對象包着一個重要的對象,那就是邏輯處理器 ChannelHandler。promise

接下來,咱們再來看一下 ChannelHandler 有哪些分類。微信

channelHandler 的分類

能夠看到 ChannelHandler 有兩大子接口:框架

第一個子接口是 ChannelInboundHandler,從字面意思也能夠猜到,他是處理讀數據的邏輯,好比,咱們在一端讀到一段數據,首先要解析這段數據,而後對這些數據作一系列邏輯處理,最終把響應寫到對端, 在開始組裝響應以前的全部的邏輯,均可以放置在 ChannelInboundHandler 裏處理,它的一個最重要的方法就是 channelRead()。讀者能夠將 ChannelInboundHandler 的邏輯處理過程與 TCP 的七層協議的解析聯繫起來,收到的數據一層層從物理層上升到咱們的應用層。socket

第二個子接口 ChannelOutBoundHandler 是處理寫數據的邏輯,它是定義咱們一端在組裝完響應以後,把數據寫到對端的邏輯,好比,咱們封裝好一個 response 對象,接下來咱們有可能對這個 response 作一些其餘的特殊邏輯,而後,再編碼成 ByteBuf,最終寫到對端,它裏面最核心的一個方法就是 write(),讀者能夠將 ChannelOutBoundHandler 的邏輯處理過程與 TCP 的七層協議的封裝過程聯繫起來,咱們在應用層組裝響應以後,經過層層協議的封裝,直到最底層的物理層。tcp

這兩個子接口分別有對應的默認實現,ChannelInboundHandlerAdapter,和 ChanneloutBoundHandlerAdapter,它們分別實現了兩大接口的全部功能,默認狀況下會把讀寫事件傳播到下一個 handleride

說了這麼多的理論,其實仍是比較抽象的,下面咱們就用一個具體的 demo 來學習一下這兩大 handler 的事件傳播機制。學習

ChannelInboundHandler 的事件傳播

關於 ChannelInboundHandler ,咱們拿 channelRead() 爲例子,來體驗一下 inbound 事件的傳播。

咱們在服務端的 pipeline 添加三個 ChannelInboundHandler

NettyServer.java

serverBootstrap
        .childHandler(new ChannelInitializer<NioSocketChannel>() {
            protected void initChannel(NioSocketChannel ch) {
                ch.pipeline().addLast(new InBoundHandlerA());
                ch.pipeline().addLast(new InBoundHandlerB());
                ch.pipeline().addLast(new InBoundHandlerC());
            }
        });

每一個 inBoundHandler 都繼承自 ChannelInboundHandlerAdapter,而後實現了 channelRead() 方法

public class InBoundHandlerA extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("InBoundHandlerA: " + msg);
        super.channelRead(ctx, msg);
    }
}

public class InBoundHandlerB extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("InBoundHandlerB: " + msg);
        super.channelRead(ctx, msg);
    }
}

public class InBoundHandlerC extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("InBoundHandlerC: " + msg);
        super.channelRead(ctx, msg);
    }
}

channelRead() 方法裏面,咱們打印當前 handler 的信息,而後調用父類的 channelRead() 方法,而這裏父類的 channelRead() 方法會自動調用到下一個 inBoundHandlerchannelRead() 方法,而且會把當前 inBoundHandler 裏處理完畢的對象傳遞到下一個 inBoundHandler,咱們例子中傳遞的對象都是同一個 msg。

咱們經過 addLast() 方法來爲 pipeline 添加 inBoundHandler,固然,除了這個方法還有其餘的方法,感興趣的同窗能夠自行瀏覽一下 pipeline 的 api ,這裏咱們添加的順序爲 A -> B -> C,最後,咱們來看一下控制檯的輸出

能夠看到,inBoundHandler 的執行順序與咱們經過 addLast() 方法 添加的順序保持一致,接下來,咱們再來看一下 outBoundHandler 的事件傳播。

ChannelOutboundHandler 的事件傳播

關於 ChanneloutBoundHandler ,咱們拿 write() 爲例子,來體驗一下 outbound 事件的傳播。

咱們繼續在服務端的 pipeline 添加三個 ChanneloutBoundHandler

serverBootstrap
        .childHandler(new ChannelInitializer<NioSocketChannel>() {
            protected void initChannel(NioSocketChannel ch) {
                // inBound,處理讀數據的邏輯鏈
                ch.pipeline().addLast(new InBoundHandlerA());
                ch.pipeline().addLast(new InBoundHandlerB());
                ch.pipeline().addLast(new InBoundHandlerC());
                
                // outBound,處理寫數據的邏輯鏈
                ch.pipeline().addLast(new OutBoundHandlerA());
                ch.pipeline().addLast(new OutBoundHandlerB());
                ch.pipeline().addLast(new OutBoundHandlerC());
            }
        });

每一個 outBoundHandler 都繼承自 ChanneloutBoundHandlerAdapter,而後實現了 write() 方法

public class OutBoundHandlerA extends ChannelOutboundHandlerAdapter {
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("OutBoundHandlerA: " + msg);
        super.write(ctx, msg, promise);
    }
}

public class OutBoundHandlerB extends ChannelOutboundHandlerAdapter {
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("OutBoundHandlerB: " + msg);
        super.write(ctx, msg, promise);
    }
}

public class OutBoundHandlerC extends ChannelOutboundHandlerAdapter {
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("OutBoundHandlerC: " + msg);
        super.write(ctx, msg, promise);
    }
}

在 write() 方法裏面,咱們打印當前 handler 的信息,而後調用父類的 write() 方法,而這裏父類的 write() 方法會自動調用到下一個 outBoundHandlerwrite() 方法,而且會把當前 outBoundHandler 裏處理完畢的對象傳遞到下一個 outBoundHand

咱們經過 addLast() 方法 添加 outBoundHandler 的順序爲 A -> B -> C,最後,咱們來看一下控制檯的輸出

能夠看到,outBoundHandler 的執行順序與咱們添加的順序相反,最後,咱們再來看一下 pipeline 的結構和執行順序。

pipeline 的結構

無論咱們定義的是哪一種類型的 handler, 最終它們都是以雙向鏈表的方式鏈接,這裏實際鏈表的節點是 ChannelHandlerContext,這裏爲了讓結構清晰突出,能夠直接把節點看做 ChannelHandlerContext

pipeline 的執行順序

雖然兩種類型的 handler 在一個雙向鏈表裏,可是這兩類 handler 的分工是不同的,inBoundHandler 的事件一般只會傳播到下一個 inBoundHandleroutBoundHandler 的事件一般只會傳播到下一個 outBoundHandler,二者相互不受干擾。

總結

  1. 經過咱們前面編寫客戶端服務端處理邏輯,引出了 pipeline 和 channelHandler 的概念。
  2. channelHandler 分爲 inBound 和 outBound 兩種類型的接口,分別是處理數據讀與數據寫的邏輯,可與 tcp 協議棧聯繫起來。
  3. 兩種類型的 handler 均有相應的默認實現,默認會把事件傳遞到下一個,這裏的傳遞事件其實說白了就是把本 handler 的處理結果傳遞到下一個 handler 繼續處理。
  4. inBoundHandler 的執行順序與咱們實際的添加順序相同,而 outBoundHandler 則相反。

以上內容來源於掘金小冊《Netty 入門與實戰:仿寫微信 IM 即時通信系統》,若想得到更多,更詳細的內容,請用微信掃碼訂閱:

相關文章
相關標籤/搜索