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