Netty 學習(四):ChannelHandler 的事件傳播和生命周期

Netty 學習(四):ChannelHandler 的事件傳播和生命周期

作者: Grey

原文地址:

部落格園:Netty 學習(四):ChannelHandler 的事件傳播和生命周期

CSDN:Netty 學習(四):ChannelHandler 的事件傳播和生命周期

ChannelHandler 的事件傳播

在通訊客戶端和服務端,處理的流程大致有如下步驟

輸入---> 解碼 ---> 根據不同的消息指令解析數據包 ---> 編碼 ---> 輸出

在『根據不同的消息指令解析數據包』這個步驟中,經常需要用if-else來判斷不同的指令類型並進行解析。邏輯一旦複雜,就會讓程式碼變的極為臃腫,難以維護。

Netty 中的 Pipeline 和 ChannelHandler 就是用來解決這個問題,它通過責任鏈設計模式來組織程式碼邏輯,並且能夠支援邏輯的動態添加和刪除。

在 Netty 框架中,一個連接對應一個 Channel,這個 Channel 的所有處理邏輯都在 ChannelPipeline 的對象里,ChannelPipeline 是雙向鏈表結構,它和 Channel 之間是一對一的關係。這個雙向鏈表每個節點都是一個 ChannelHandlerContext 對象,這個對象可以獲得和 Channel 相關的所有上下文資訊。

示例圖如下

image

ChannelHandler 包括兩個子介面:ChannelInboundHandler 和 ChannelOutboundHandler,分別用於處理讀數據和寫數據的邏輯。

我們可以寫一個示例來說明 ChannelHandler 的事件傳播順序(包含 ChannelInboundHandler 和 ChannelOutboundHandler)

在服務端配置如下

      ch.pipeline().addLast(new InHandlerA());
      ch.pipeline().addLast(new InHandlerB());
      ch.pipeline().addLast(new InHandlerC());
      ch.pipeline().addLast(new OutHandlerA());
      ch.pipeline().addLast(new OutHandlerB());
      ch.pipeline().addLast(new OutHandlerC());

其中 InHandlerA 程式碼如下(InHandlerB 和 InHandlerC 類似)

package snippet.chat.server.inbound;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * @author <a href="mailto:[email protected]">Grey</a>
 * @date 2022/9/19
 * @since
 */
public class InHandlerA extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("in-A:" + msg);
        super.channelRead(ctx, msg);
    }
}

OutHandlerA 程式碼如下(OutHandlerB 和 OutHandlerC 類似)

package snippet.chat.server.outbound;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;

/**
 * @author <a href="mailto:[email protected]">Grey</a>
 * @date 2022/9/19
 * @since
 */
public class OutHandlerA extends ChannelOutboundHandlerAdapter {
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("out-A:" + msg);
        super.write(ctx, msg, promise);
    }
}

運行服務端和客戶端,使用客戶端向服務端發送一些數據,可以看到如下日誌

in-A:PooledUnsafeDirectByteBuf(ridx: 0, widx: 108, cap: 2048)
in-B:PooledUnsafeDirectByteBuf(ridx: 0, widx: 108, cap: 2048)
in-C:PooledUnsafeDirectByteBuf(ridx: 0, widx: 108, cap: 2048)
......
out-C:PooledUnsafeDirectByteBuf(ridx: 0, widx: 39, cap: 256)
out-B:PooledUnsafeDirectByteBuf(ridx: 0, widx: 39, cap: 256)
out-A:PooledUnsafeDirectByteBuf(ridx: 0, widx: 39, cap: 256)

由此可以知:inboundHandler 的添加順序和執行順序一致,而 outboundHandler 的添加順序和執行順序相反。 如下圖示例

image

ChannelHandler 的生命周期

可以用程式碼來說明 ChannelHandler 的生命周期,我們基於 ChannelInboundHandlerAdapter,定義了一個 LifeCycleTestHandler,完整程式碼如下

package snippet.chat.client;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * @author <a href="mailto:[email protected]">Grey</a>
 * @date 2022/9/19
 * @since
 */
public class LifeCycleTestHandler extends ChannelInboundHandlerAdapter {
    // 這個回調方法表示當前Channel的所有邏輯處理已經和某個NIO執行緒建立了綁定關係,接收新的連接,然後創建一個執行緒來處理這個連接的讀寫,只不過在Netty里使用了執行緒池的方式,
    // 只需要從執行緒池裡去抓一個執行緒綁定在這個Channel上即可。這裡的NIO執行緒通常指NioEventLoop
    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channel 綁定到執行緒(NioEventLoop):channelRegistered()");
        super.channelRegistered(ctx);
    }

    // 這個回調錶明與這個連接對應的NIO執行緒移除了對這個連接的處理。
    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channel 取消執行緒(NioEventLoop)的綁定:channelUnregistered()");
        super.channelUnregistered(ctx);
    }

    // 當Channel的所有業務邏輯鏈準備完畢(即Channel的Pipeline中已經添加完所有的Handler),
// 以及綁定好一個NIO執行緒之後,這個連接才真正被激活,接下來就會回調到此方法。
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channel 準備就緒:channelActive()");
        super.channelActive(ctx);
    }

    // 這個連接在TCP層面已經不再是ESTABLISH狀態了。
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channel 被關閉:channelInactive()");
        super.channelInactive(ctx);
    }

    // 客戶端向服務端發送數據,每次都會回調此方法,表示有數據可讀。
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("channel 有數據可讀:channelRead()");
        super.channelRead(ctx, msg);
    }

    // 服務端每讀完一次完整的數據,都回調該方法,表示數據讀取完畢。
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channel 某次數據讀完:channelReadComplete()");
        super.channelReadComplete(ctx);
    }

    // 表示在當前Channel中,已經成功添加了一個Handler處理器。
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("邏輯處理器被添加:handlerAdded()");
        super.handlerAdded(ctx);
    }

    // 我們給這個連接添加的所有業務邏輯處理器都被移除。
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("邏輯處理器被移除:handlerRemoved()");
        super.handlerRemoved(ctx);
    }
}


我們在服務端添加這個 Handler,然後啟動服務端和客戶端,可以看到服務台首先輸出如下日誌

邏輯處理器被添加:handlerAdded()
channel 綁定到執行緒(NioEventLoop):channelRegistered()
channel 準備就緒:channelActive()
channel 有數據可讀:channelRead()
Mon Sep 19 22:49:49 CST 2022: 收到客戶端登錄請求……
Mon Sep 19 22:49:49 CST 2022: 登錄成功!
channel 某次數據讀完:channelReadComplete()

由日誌可以看到,ChannelHandler 執行順序為:

handlerAdded()->channelRegistered()->channelActive()->channelRead()->channelReadComplete()

關閉客戶端,保持服務端不關閉,在服務端此時觸發了 Channel 的關閉,列印日誌如下

channel 被關閉:channelInactive()
channel 取消執行緒(NioEventLoop)的綁定:channelUnregistered()
邏輯處理器被移除:handlerRemoved()

如上述日誌可知,ChannelHandler 的執行順序是

channelInactive()->channelUnregistered()->handlerRemoved()

整個 ChannelHandler 的生命周期如下圖所示

image

圖例

本文所有圖例見:processon: Netty學習筆記

程式碼

hello-netty

更多內容見:Netty專欄

參考資料

跟閃電俠學 Netty:Netty 即時聊天實戰與底層原理

深度解析Netty源碼

Tags: