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:410486047@qq.com">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:410486047@qq.com">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:410486047@qq.com">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专栏
参考资料
Netty 学习(四):ChannelHandler 的事件传播和生命周期的更多相关文章
- jsp学习与提高(一)——JSP生命周期、三大指令及动作
1.jsp定义: 1.1以java语言为脚本语言,运行在服务端的程序: 1.2处理客户请求,生成页面 1.3其本质是个sevlet会生成.java文件编译后再生成.class文件 2.jsp生命周期( ...
- Netty学习四:Channel
1. Channel Channel是Netty的核心概念之一,它是Netty网络通信的主体,由它负责同对端进行网络通信.注册和数据操作等功能. 1.1 工作原理 如上图所示: 一旦用户端连接成功,将 ...
- Android学习笔记(五)——活动的生命周期
//此系列博文是<第一行Android代码>的学习笔记,如有错漏,欢迎指正! 为了能写出流畅连贯的程序,我们需要了解一下活动的生命周期. 一.返回栈 Android 中的活动是可以层叠的. ...
- JavaEE互联网轻量级框架整合开发(书籍)阅读笔记(6):Spring IOC容器学习(概念、作用、Bean生命周期)
一.IOC控制反转概念 控制反转(IOC)是一种通过描述(在Java中可以是XML或者是注解)并通过第三方去生产或获取特定对象的方式. 主动创建模式,责任在于开发者,而在被动模式下,责任归于Ioc容器 ...
- 一起学习vue源码 - Vue2.x的生命周期(初始化阶段)
作者:小土豆biubiubiu 博客园:https://www.cnblogs.com/HouJiao/ 掘金:https://juejin.im/user/58c61b4361ff4b005d9e8 ...
- Android学习总结(二)——Service基本概念和生命周期
好了,前面我们已经学习了Activity的知识,相信大家也有一定的理解,但是还是不能放松,Android四大组件,我们才学习了一个而已,接下我们继续学习Service.计划总结如下内容: 一.Serv ...
- Android学习路线(十二)Activity生命周期——启动一个Activity
DEMO下载地址:http://download.csdn.net/detail/sweetvvck/7728735 不像其他的编程模式那样应用是通过main()函数启动的.Android系统通过调用 ...
- java线程基础巩固---Thread中断Interrupt方法学习&采用优雅的方式结束线程生命周期
Interrupt学习: 在jdk中关于interrupt相关方法有三个,如下: 关于上面的疑问会在稍后进行阐述滴,下面看代码: 编译运行: 应该说是t线程为啥在被打断之后没有退出,还是在运行状态,这 ...
- 【React】学习笔记(二)——组件的生命周期、React脚手架使用
原教程视频:ttps://www.bilibili.com/video/BV1wy4y1D7JT?p=2&spm_id_from=pageDriver 目录 一.组件的生命周期 1.1.生命周 ...
随机推荐
- NC16564 [NOIP2012]借教室
NC16564 [NOIP2012]借教室 题目 题目描述 在大学期间,经常需要租借教室.大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室.教室的大小功能不同,借教室人的身份不同,借 ...
- 临近梯度下降算法(Proximal Gradient Method)的推导以及优势
邻近梯度下降法 对于无约束凸优化问题,当目标函数可微时,可以采用梯度下降法求解:当目标函数不可微时,可以采用次梯度下降法求解:当目标函数中同时包含可微项与不可微项时,常采用邻近梯度下降法求解.上述三种 ...
- ArrayList集合存储基本数据类型
如何存储基本数据类型 ArrayList对象不能存储基本类型,只能存储引用类型的数据.类似 <int> 不能写,但是存储基本数据类型对应的 包装类型是可以的.所以,想要存储基本类型数据, ...
- CF1593D2 Half of Same
题目大意: 给定一个包含 \(n\)(\(n\) 是偶数)个整数的数列 \(a_1,a_2,\ldots,a_n\). 考虑一个可能的正整数 \(k\),在每次操作中,你可以选定一个 \(i\),并将 ...
- Vs 快捷键---探索不一样的编程
前言:现在很多工具都支持各式各样的快捷键,vs作为后起之秀,多功能的快捷键自然是必不可少的, 而且针对单行操作的快捷键是无需选中整行的,只需要光标停留在所操作的代码上面即可. 1.注释:CTRL+K+ ...
- 1. MGR简介 | 深入浅出MGR
GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 1. 为什么是MGR MGR是MySQL Group Replication的缩写,即MySQL组复制. 在以往,我们一般 ...
- 故障案例 | lsof是怎么"影响"MySQL计算打开文件句柄数的
欢迎来到 GreatSQL社区分享的MySQL技术文章,如有疑问或想学习的内容,可以在下方评论区留言,看到后会进行解答 lsof中附加不同参数产生的结果也不同,小心"踩坑". 1. ...
- DS二叉树——二叉树之数组存储
题目描述 二叉树可以采用数组的方法进行存储,把数组中的数据依次自上而下,自左至右存储到二叉树结点中,一般二叉树与完全二叉树对比,比完全二叉树缺少的结点就在数组中用0来表示.,如下图所示 从上图可以看出 ...
- ss 显示unix 域 的socket 缓冲区不正确
一个unix 域socket,平时我们用ss -auxp 来查看是否有数据在内核没有到用户态, [root@localhost unix]# ss -auxp |grep -i server.o u_ ...
- CF1511G Chips on a Board (倍增)
题面 原题题面 转化方便版题意: 有 n n n 堆石子,第 i i i 堆有 c i ∈ [ 1 , m ] c_i\in [1,m] ci∈[1,m] 个石子,有 q q q 次询问,每次询问给 ...