自顶向下深入分析Netty(七)--ChannelPipeline和ChannelHandler总述
自顶向下深入分析Netty(七)--ChannelPipeline和ChannelHandler总述
自顶向下深入分析Netty(七)--ChannelPipeline源码实现
自顶向下深入分析Netty(七)--ChannelHandlerContext源码实现
像以往一样,继续回顾这幅图。目前为止,我们学习了Netty的EventLoop、Channel以及ChannelFuture,还差最后两个部分:ByteBuf和ChannelHandler。ByteBuf作为通道读写数据的缓冲区,Channel底层数据的读写细节正是由ByteBuf完成。ChannelHandler作为处理各种事件的处理器,为用户提供实际的业务逻辑处理功能。在本章中,我们将介绍ChannelHandler以及存储它的容器ChannelPipeline。使用自顶向下的方法,首先介绍整体ChannePipeline,然后介绍ChannelHandler。
7.1 总述
7.1.1 ChannelPipeline
提到pipeline,我们首先想到的是*nix中的管道,可实现将一个程序的输出作为另一个程序的输入。ChannelPipeline也实现类似的功能,不同的是:ChannelPipeline将一个ChannelHandler的处理后的数据作为下一个ChannelHandler处理的数据源。Netty的ChannelPipeline示意图如下:

Xnix的管道中流动的是数据,ChnanelPipeline中流动的是事件(事件中可能附加数据)。Netty定义了两种事件类型:入站(inbound)事件和出站(outbound)事件。ChannelPipeline使用拦截过滤器模式使用户可以掌控ChannelHandler处理事件的流程。注意:事件在ChannelPipeline中不自动流动而需要调用ChannelHandlerContext中诸如fileXXX()或者read()类似的方法将事件从一个ChannelHandler传播到下一个ChannelHandler。
事实上,ChannelHandler不处理具体的事件,处理具体的事件由相应的子类完成:ChannelInboundHandler处理和拦截入站事件,ChannelOutboundHandler处理和拦截出站事件。那么事件是怎么在ChannelPipeline中流动的呢?我们使用代码注释中的例子:
ChannelPipeline p = ...;
p.addLast("1", new InboundHandlerA());
p.addLast("2", new InboundHandlerB());
p.addLast("3", new OutboundHandlerA());
p.addLast("4", new OutboundHandlerB());
p.addLast("5", new InboundOutboundHandlerX());
对于入站事件,处理序列为:1-->2-->5;对于出站事件,处理序列为:5-->4-->3。可见,入站事件与出站事件处理顺序正好相反。事件不会在ChannelPipeline中自动流动,而完全由用户控制,所以ChannelHandler处理的代码可能如下:
public class InboundHandlerA implements ChannelInboundHandler {
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("Connected!"); // 用户自定义处理逻辑
ctx.fireChannelActive(); // 将channelActive事件传播到InboundHandlerB
}
}
public class OutboundHandlerB extends ChannelOutboundHandler{
@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise) {
System.out.println("Closing .."); // 用户自定义处理逻辑
ctx.close(promise); // 将close事件传播到OutboundHandlerA
}
}
入站事件一般由I/O线程触发,以下事件为入站事件:
ChannelRegistered() // Channel注册到EventLoop
ChannelActive() // Channel激活
ChannelRead(Object) // Channel读取到数据
ChannelReadComplete() // Channel读取数据完毕
ExceptionCaught(Throwable) // 捕获到异常
UserEventTriggered(Object) // 用户自定义事件
ChannelWritabilityChanged() // Channnel可写性改变,由写高低水位控制
ChannelInactive() // Channel不再激活
ChannelUnregistered() // Channel从EventLoop中注销
出站事件一般由用户触发,以下事件为出站事件:
bind(SocketAddress, ChannelPromise) // 绑定到本地地址
connect(SocketAddress, SocketAddress, ChannelPromise) // 连接一个远端机器
write(Object, ChannelPromise) // 写数据,实际只加到Netty出站缓冲区
flush() // flush数据,实际执行底层写
read() // 读数据,实际设置关心OP_READ事件,当数据到来时触发ChannelRead入站事件
disconnect(ChannelPromise) // 断开连接,NIO Server和Client不支持,实际调用close
close(ChannelPromise) // 关闭Channel
deregister(ChannelPromise) // 从EventLoop注销Channel
入站事件一般由I/O线程触发,用户程序员也可根据实际情况触发。考虑这样一种情况:一个协议由头部和数据部分组成,其中头部含有数据长度,由于数据量较大,客户端分多次发送该协议的数据,服务端接收到数据后需要收集足够的数据,组装为更有意义的数据传给下一个ChannelInboudHandler。也许你已经知道,这个收集数据的ChannelInboundHandler正是Netty中基本的Encoder,Encoder中会处理多次ChannelRead()事件,只触发一次对下一个ChannelInboundHandler更有意义的ChannelRead()事件。
出站事件一般由用户触发,而I/O线程也可能会触发。比如,当用户已配置ChannelOption.AutoRead选项,则I/O在执行完ChannelReadComplete()事件,会调用read()方法继续关心OP_READ事件,保证数据到达时自动触发ChannelRead()事件。
如果你初次接触Netty,会对下面的方法感到疑惑,所以列出区别:
channelHandlerContext.close() // close事件传播到下一个Handler
channel.close() // ==channelPipeline.close()
channelPipeline.close() // 事件沿整个ChannelPipeline传播,注意in/outboud的传播起点
回忆AbstractChannel的构造方法:
protected AbstractChannel(Channel parent) {
this.parent = parent;
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
protected DefaultChannelPipeline newChannelPipeline() {
return new DefaultChannelPipeline(this);
}
可见,新建一个Channel时会自动新建一个ChannelPipeline,也就是说他们之间是一对一的关系。另外需要注意的是:ChannelPipeline是线程安全的,也就是说,我们可以动态的添加、删除其中的ChannelHandler。考虑这样的场景:服务器需要对用户登录信息进行加密,而其他信息不加密,则可以首先将加密Handler添加到ChannelPipeline,验证完用户信息后,主动从ChnanelPipeline中删除,从而实现该需求。
7.1.2 ChannelHandler
ChannelHandler并没有方法处理事件,而需要由子类处理:ChannelInboundHandler拦截和处理入站事件,ChannelOutboundHandler拦截和处理出站事件。我们已经明白,ChannelPipeline中的事件不会自动流动,而我们一般需求事件自动流动,Netty提供了两个Adapter:ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter来满足这种需求。其中的实现类似如下:
// inboud事件默认处理过程
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelRegistered(); // 事件传播到下一个Handler
} // outboud事件默认处理过程
public void bind(ChannelHandlerContext ctx, SocketAddress localAddress,
ChannelPromise promise) throws Exception {
ctx.bind(localAddress, promise); // 事件传播到下一个Handler
}
在Adapter中,事件默认自动传播到下一个Handler,这样带来的另一个好处是:用户的Handler类可以继承Adapter且覆盖自己感兴趣的事件实现,其他事件使用默认实现,不用再实现ChannelIn/outboudHandler接口中所有方法,提高效率。
我们常常遇到这样的需求:在一个业务逻辑处理器中,需要写数据库、进行网络连接等耗时业务。Netty的原则是不阻塞I/O线程,所以需指定Handler执行的线程池,可使用如下代码:
static final EventExecutorGroup group = new DefaultEventExecutorGroup(16);
...
ChannelPipeline pipeline = ch.pipeline();
// 简单非阻塞业务,可以使用I/O线程执行
pipeline.addLast("decoder", new MyProtocolDecoder());
pipeline.addLast("encoder", new MyProtocolEncoder());
// 复杂耗时业务,使用新的线程池
pipeline.addLast(group, "handler", new MyBusinessLogicHandler());
ChannelHandler中有一个Sharable注解,使用该注解后多个ChannelPipeline中的Handler对象实例只有一个,从而减少Handler对象实例的创建。代码示例如下:
public class DataServerInitializer extends ChannelInitializer<Channel> {
private static final DataServerHandler SHARED = new DataServerHandler();
@Override
public void initChannel(Channel channel) {
channel.pipeline().addLast("handler", SHARED);
}
}
Sharable注解的使用是有限制的,多个ChannelPipeline只有一个实例,所以该Handler要求无状态。上述示例中,DataServerHandler的事件处理方法中,不能使用或改变本身的私有变量,因为ChannelHandler是非线程安全的,使用私有变量会造成线程竞争而产生错误结果。
7.1.3 ChannelHandlerContext
Context指上下文关系,ChannelHandler的Context指的是ChannleHandler之间的关系以及ChannelHandler与ChannelPipeline之间的关系。ChannelPipeline中的事件传播主要依赖于ChannelHandlerContext实现,由于ChannelHandlerContext中有ChannelHandler之间的关系,所以能得到ChannelHandler的后继节点,从而将事件传播到下一个ChannelHandler。
ChannelHandlerContext继承自AttributeMap,所以提供了attr()方法设置和删除一些状态属性值,用户可将业务逻辑中所需使用的状态属性值存入到Context中。此外,Channel也继承自AttributeMap,也有attr()方法,在Netty4.0中,这两个attr()方法并不等效,这会给用户程序员带来困惑并且增加内存开销,所以Netty4.1中将channel.attr()==ctx.attr()。在使用Netty4.0时,建议只使用channel.attr()防止引起不必要的困惑。
一个Channel对应一个ChannelPipeline,一个ChannelHandlerContext对应一个ChannelHandler,但一个ChannelHandler可以对应多个ChannelHandlerContext。当一个ChannelHandler使用Sharable注解修饰且添加同一个实例对象到不用的Channel时,只有一个ChannelHandler实例对象,但每个Channel中都有一个ChannelHandlerContext对象实例与之对应。
作者:Hypercube
链接:https://www.jianshu.com/p/7dc5da98694c
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
自顶向下深入分析Netty(七)--ChannelPipeline和ChannelHandler总述的更多相关文章
- 自顶向下深入分析Netty(六)--Channel总述
自顶向下深入分析Netty(六)--Channel总述 自顶向下深入分析Netty(六)--Channel源码实现 6.1 总述 6.1.1 Channel JDK中的Channel是通讯的载体,而N ...
- 自顶向下深入分析Netty(三)--Bootstrap
自顶向下深入分析Netty(一)--预备知识 自顶向下深入分析Netty(二)--线程模型 自顶向下深入分析Netty(三)--Bootstrap 自顶向下深入分析Netty(四)--EventLoo ...
- 自顶向下深入分析Netty(五)--Future
再次回顾这幅图,在上一章中,我们分析了Reactor的完整实现.由于Java NIO事件驱动的模型,要求Netty的事件处理采用异步的方式,异步处理则需要表示异步操作的结果.Future正是用来表示异 ...
- Netty 系列四(ChannelHandler 和 ChannelPipeline).
一.概念 先来整体的介绍一下这篇博文要介绍的几个概念(Channel.ChannelHandler.ChannelPipeline.ChannelHandlerContext.ChannelPromi ...
- Netty源码分析之ChannelPipeline(二)—ChannelHandler的添加与删除
上篇文章中,我们对Netty中ChannelPipeline的构造与初始化进行了分析与总结,本篇文章我们将对ChannelHandler的添加与删除操作进行具体的的代码分析: 一.ChannelHan ...
- Netty学习笔记(二) - ChannelPipeline和ChannelHandler
ChannelPipeline 和 ChannelHandler 是 Netty 重要的组件之一,通过这篇文章,重点了解这些组件是如何驱动数据流动和处理的. 一.ChannelHandler 在上一篇 ...
- Netty 源码解析(四): Netty 的 ChannelPipeline
今天是猿灯塔“365篇原创计划”第四篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty ...
- Netty基础招式——ChannelHandler的最佳实践
本文是Netty系列第7篇 上一篇文章我们深入学习了Netty逻辑架构中的核心组件EventLoop和EventLoopGroup,掌握了Netty的线程模型,并且介绍了Netty4线程模型中的无锁串 ...
- 很详细、很移动的Linux makefile教程:介绍,总述,书写规则,书写命令,使用变量,使用条件推断,使用函数,Make 的运行,隐含规则 使用make更新函数库文件 后序
很详细.很移动的Linux makefile 教程 内容如下: Makefile 介绍 Makefile 总述 书写规则 书写命令 使用变量 使用条件推断 使用函数 make 的运行 隐含规则 使用m ...
随机推荐
- 【转载】C#中ArrayList集合类使用RemoveAt方法移除指定索引的元素
ArrayList集合是C#中的一个非泛型的集合类,是弱数据类型的集合类,可以使用ArrayList集合变量来存储集合元素信息,任何数据类型的变量都可加入到同一个ArrayList集合中,在Array ...
- hibernate Criteria中多个or和and的用法 and ( or or)
/s筛选去除无效数据 /* detachedCriteria.add( Restrictions.or( Restrictions.like("chanpin", &qu ...
- sendMessage 与 obtainMessage (sendToTarget)比较
我们平时在做到多线程问题的时候可能利用Handler去传递Message,其中,经常使用的就是 1.new Handler().obtainMessage().sendToTarget(); 2.ne ...
- UML类图 入门 (转载)
学习 UML的类图,主要是为了学习设计模式方便,也便于自己看别人代码时,用UML来整理别人的设计.所以基本的学习工具还是必须掌握. 参考: https://blog.csdn.net/qq_35495 ...
- zabbix--远程执行命令
zabbix 远程执行命令 重启应用 服务器 使用远程执行命令可以在某些时候帮我做一些事情,达到轻量级的自动化,比如当 nginx.mysql.php.redis.tomcat.等等应用挂掉时帮我们自 ...
- AWS 存储过程
DELIMITER $$ USE `mysql`$$ DROP PROCEDURE IF EXISTS `rds_rotate_slow_log`$$ CREATE DEFINER=`rdsadmin ...
- 闲谈关于discuz内核缓存机制
Discuz! 缓存 Discuz! X2.5 的 config_global.php 中有这样一行代码 $_config['cache']['type'] = 'sql'; 这就是 Discuz! ...
- JUnit 4.x 知识点
注解 @Test: 测试方法,在这里还可以测试期望异常和超时时间. @Before: 每个测试方法执行之前执行的方法. @BeforeClass: 一个测试类中所有测试方法执行之前执行的方法,只执行一 ...
- PHP 验证Email的函数
<?php function validateEmail($email) { $isValid = true; $atIndex = strrpos($email, " ...
- Python 类的继承__init__() takes exactly 3 arguments (1 given)
类(class),可以继承基类以便形成具有自己独特属性的类,我们在面向对象的编程中,经常用到类及其继承,可以说没有什么不是类的,今天我们就来详细探讨一下在python中,类的继承是如何做的. 我们假设 ...