Netty 學習(四):ChannelHandler 的事件傳播和生命周期
Netty 學習(四):ChannelHandler 的事件傳播和生命周期
作者: Grey
原文地址:
部落格園:Netty 學習(四):ChannelHandler 的事件傳播和生命周期
CSDN:Netty 學習(四):ChannelHandler 的事件傳播和生命周期
ChannelHandler 的事件傳播
在通訊客戶端和服務端,處理的流程大致有如下步驟
輸入---> 解碼 ---> 根據不同的消息指令解析數據包 ---> 編碼 ---> 輸出
在『根據不同的消息指令解析數據包』這個步驟中,經常需要用if-else
來判斷不同的指令類型並進行解析。邏輯一旦複雜,就會讓程式碼變的極為臃腫,難以維護。
Netty 中的 Pipeline 和 ChannelHandler 就是用來解決這個問題,它通過責任鏈設計模式來組織程式碼邏輯,並且能夠支援邏輯的動態添加和刪除。
在 Netty 框架中,一個連接對應一個 Channel,這個 Channel 的所有處理邏輯都在 ChannelPipeline 的對象里,ChannelPipeline 是雙向鏈表結構,它和 Channel 之間是一對一的關係。這個雙向鏈表每個節點都是一個 ChannelHandlerContext 對象,這個對象可以獲得和 Channel 相關的所有上下文資訊。
示例圖如下
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 的添加順序和執行順序相反。 如下圖示例
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 的生命周期如下圖所示
圖例
本文所有圖例見:processon: Netty學習筆記
程式碼
更多內容見:Netty專欄