Netty ChannelHandler 生命周期
1. 前言
本節內容,我們主要講解 ChannelHandler 在執行過程中的生命周期是什么樣的?需要執行哪些核心的生命周期方法以及順序?
了解生命周期的核心目的是,可以在合適的生命周期方法擴展自己的業務功能。
2. UML 關系
首先,我們先來了解以下 ChannelHandler 的類依賴關系圖,具體如下所示:
通過上面的類結構圖,我們總結一下規律:
- ChannelHandler 有兩個子接口,分別是
ChannelInboundHandler
和ChannelOutboundHandler
,其實從字面意思就能知道,它們分別是入站和出站的接口類。 - 如果我們自定義的業務 Handler 直接實現 ChannelInboundHandler 或者 ChannelOutboundHandler,那么我們需要實現的接口非常的多,增加了開發的難度。Netty 已經幫我們封裝好了兩個實現類,分別是 ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter,這樣可以大大簡化了開發工作。
3. 核心生命周期方法
方法 | 描述 |
---|---|
handlerAdded | Handler 被加入 Pipeline 時觸發(僅僅觸發一次) |
channelRegistered | channelRegistered 注冊成功時觸發 |
channelActive | channel 連接就緒時觸發 |
channelRead | channel 有數據可讀時觸發 |
channelReadComplete | channel 有數據可讀,并且讀完時觸發 |
channelInactive | channel 斷開時觸發 |
channelUnregistered | channel 取消注冊時觸發 |
handlerRemoved | handler 被從 Pipeline 移除時觸發 |
問題 1:channelRegistered 注冊指的是什么呢?
Channel 在創建時,需要綁定 ChannelPipeline 和 EventLoop 等操作,完成這些操作時會觸發 channelRegistered () 方法。
問題 2:channelRead 和 channelReadComplete 的區別?
當 Channel 有數據可讀時,會觸發 channelRead 事件,eventLoop 被喚醒并且調用 channelRead () 處理數據;eventLoop 喚醒后讀取數據包裝成 msg,然后將 msg 作為參數調用 channelRead (),期間做了個判斷,讀取到 0 字節或者讀取到的字節數小于 buffer 的容量,滿足以上條件就會調用 channelReadComplete ()。
4. 生命周期執行流程
ChannelHandler 的一些特殊回調方法,這些回調方法的執行是有順序的,而這個執行順序可以稱為 ChannelHandler 的生命周期。
4.1 客戶端發送一次請求
public class InboundHandler1 extends ChannelInboundHandlerAdapter {
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("handlerAdded");
super.handlerAdded(ctx);
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelRegistered");
super.channelRegistered(ctx);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelActive");
super.channelActive(ctx);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("channelRead");
super.channelRead(ctx, msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelReadComplete");
super.channelReadComplete(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelInactive");
super.channelInactive(ctx);
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelUnregistered");
super.channelUnregistered(ctx);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
System.out.println("handlerRemoved");
super.handlerRemoved(ctx);
}
}
執行結果:
handlerAdded
channelRegistered
channelActive
channelRead
channelReadComplete
4.2 客戶端發送多次請求
客戶端每隔 5 秒鐘發送一次消息給服務端,查看效果如何?
實例:
final ChannelFuture channelFuture=bootstrap.connect("127.0.0.1",80).sync();
channelFuture.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isDone()){
if(future.isSuccess()){
//1秒鐘之后,每隔5描述發送一次消息
channelFuture.channel().eventLoop().scheduleWithFixedDelay(new Runnable() {
public void run() {
channelFuture.channel().writeAndFlush("hello world");
}
},1,5, TimeUnit.SECONDS);
}else if(future.isCancelled()){
System.out.println("連接被取消");
}else if(future.cause()!=null){
System.out.println("連接出錯:");
}
}
}
});
執行結果:
handlerAdded
channelRegistered
channelActive
channelRead
channelReadComplete
channelRead
channelReadComplete
channelRead
channelReadComplete
通過執行結果我們發現,第一次的時候執行 handlerAdded ()、channelRegistered ()、channelActive (),后面就不會被執行了。
客戶端的每次請求時,都會觸發 channelRead () 和 channelReadComplete () 兩個核心方法。
4.3 手工關閉通道
疑問:channelInactive、channelUnregistered、handlerRemoved 什么時候會被執行呢?
實例:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("channelRead");
//手工關閉通道
ctx.channel().close();
}
執行結果:
handlerAdded
channelRegistered
channelActive
channelRead
channelReadComplete
channelInactive
channelUnregistered
handlerRemoved
總結,人為的關閉通道或者其他因素(比如:網絡故障等),則會觸發 channelInactive、channelUnregistered、handlerRemoved 的執行。
4.4 生命周期總結
我們來逐個總結一下每個回調方法的含義
- handlerAdded () :當檢測到新連接之后,調用
ch.pipeline().addLast(new LifeCycleHandler());
之后的回調,表示在當前的 channel 中,已經成功添加了一個 handler 到雙向鏈表。 - channelRegistered ():這個回調方法,表示當前的 channel 的所有的邏輯處理已經和某個 NIO 線程建立了綁定關系,從線程池里面去抓一個線程綁定在這個 channel 上,這里的 NIO 線程通常指的是 NioEventLoop。
- channelActive ():當 channel 的所有的業務邏輯鏈準備完畢,channel 的 pipeline 中已經添加完所有的 handler 以及綁定好一個 NIO 線程之后,這條連接算是真正激活了,接下來就會回調到此方法。
- channelRead ():客戶端向服務端發來數據,每次都會回調此方法,表示有數據可讀。
- channelReadComplete ():服務端每次讀完一次完整的數據之后,回調該方法,表示數據讀取完畢。
- channelInactive (): 表面這條連接已經被關閉了,這條連接在 TCP 層面已經不再是 ESTABLISH 狀態了。
- channelUnregistered (): 既然連接已經被關閉,那么與這條連接綁定的線程就不需要對這條連接負責了,這個回調就表明與這條連接對應的 NIO 線程移除掉對這條連接的處理。
- handlerRemoved ():給這條連接上添加的所有的業務邏輯處理器都給移除掉。
ChannelHandler 回調方法的執行順序為
- 連接請求,handlerAdded () -> channelRegistered () -> channelActive () -> channelRead () -> channelReadComplete ();
- 數據請求,channelRead () -> channelReadComplete ();
- 通道被關閉,channelInactive () -> channelUnregistered () -> handlerRemoved ()。
5. 小結
本節內容主要講解 ChannelHandler 的生命周期方法的執行順序及觸發機制,目的是了解每個方法的觸發時間點,有助于業務點的擴展。核心掌握以下知識點:
- 核心的生命周期方法有哪些,它們的觸發時間點是什么;
- channelRegistered 需要清楚,這個不容易理解;
- channelRead 和 channelReadComplete 的區別,需要清楚;
- 通過三種 Demo 來說明了不同的生命周期方法的執行次數,有的是只執行一次,有的是每次都會執行。