自顶向下深入分析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对象实例与之对应。

Channel和ChannelPipeline是一对一的关系,ChannelPipeline会有多个ChannelHandler,而一般情况,ChannelHandlerContext和ChannelHandler也是一一对应,除了Sharable场景。

作者:Hypercube
链接:https://www.jianshu.com/p/7dc5da98694c
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

自顶向下深入分析Netty(七)--ChannelPipeline和ChannelHandler总述的更多相关文章

  1. 自顶向下深入分析Netty(六)--Channel总述

    自顶向下深入分析Netty(六)--Channel总述 自顶向下深入分析Netty(六)--Channel源码实现 6.1 总述 6.1.1 Channel JDK中的Channel是通讯的载体,而N ...

  2. 自顶向下深入分析Netty(三)--Bootstrap

    自顶向下深入分析Netty(一)--预备知识 自顶向下深入分析Netty(二)--线程模型 自顶向下深入分析Netty(三)--Bootstrap 自顶向下深入分析Netty(四)--EventLoo ...

  3. 自顶向下深入分析Netty(五)--Future

    再次回顾这幅图,在上一章中,我们分析了Reactor的完整实现.由于Java NIO事件驱动的模型,要求Netty的事件处理采用异步的方式,异步处理则需要表示异步操作的结果.Future正是用来表示异 ...

  4. Netty 系列四(ChannelHandler 和 ChannelPipeline).

    一.概念 先来整体的介绍一下这篇博文要介绍的几个概念(Channel.ChannelHandler.ChannelPipeline.ChannelHandlerContext.ChannelPromi ...

  5. Netty源码分析之ChannelPipeline(二)—ChannelHandler的添加与删除

    上篇文章中,我们对Netty中ChannelPipeline的构造与初始化进行了分析与总结,本篇文章我们将对ChannelHandler的添加与删除操作进行具体的的代码分析: 一.ChannelHan ...

  6. Netty学习笔记(二) - ChannelPipeline和ChannelHandler

    ChannelPipeline 和 ChannelHandler 是 Netty 重要的组件之一,通过这篇文章,重点了解这些组件是如何驱动数据流动和处理的. 一.ChannelHandler 在上一篇 ...

  7. Netty 源码解析(四): Netty 的 ChannelPipeline

    今天是猿灯塔“365篇原创计划”第四篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty ...

  8. Netty基础招式——ChannelHandler的最佳实践

    本文是Netty系列第7篇 上一篇文章我们深入学习了Netty逻辑架构中的核心组件EventLoop和EventLoopGroup,掌握了Netty的线程模型,并且介绍了Netty4线程模型中的无锁串 ...

  9. 很详细、很移动的Linux makefile教程:介绍,总述,书写规则,书写命令,使用变量,使用条件推断,使用函数,Make 的运行,隐含规则 使用make更新函数库文件 后序

    很详细.很移动的Linux makefile 教程 内容如下: Makefile 介绍 Makefile 总述 书写规则 书写命令 使用变量 使用条件推断 使用函数 make 的运行 隐含规则 使用m ...

随机推荐

  1. 金融finaunce财经

    金融 (经济学术语) 金融是货币资金融通的总称.主要指与货币流通和银行信用相关的各种活动.主要内容包括: 货币的发行.投放.流通和回笼:各种存款的吸收和提取:各项贷款的发放和收回:银行会计.出纳.转账 ...

  2. Java DbUtils简介

    Dbutils,db utils,顾名思义,是一个数据库工具,体积很小,算是一个dao层的小框架. DbUtils是Apache的开源项目,对JDBC进行了轻量级封装,极大地简化了JDBC编程. Db ...

  3. Sql Server设置用户只能查看并访问特定数据库

    现需要限定特定的用户只能查看并访问特定的数据库,防止多个用户对数据库操作时一些误操作. 参考i6first的如何让用户只能访问特定的数据库(MSSQL)博文 1.新建登录用户 以管理员身份登陆数据库( ...

  4. Ubuntu中如何为wine程序创建快捷方式

    1.安装一个gnome组件及其应用 sudo apt-get install gnome-panel 2.运行以下命令弹出创建快捷方式的窗口 sudo gnome-desktop-item-edit ...

  5. linux开发中常用的命令及技巧(连载)

    1.在内核或uboot目录下搜索相关内容/文件名时:grep "USB" * -nR find -name "*USB*" 2.查看系统中设备 cat  /pr ...

  6. Httpd服务入门知识-使用mod_deflate模块压缩页面优化传输速度

    Httpd服务入门知识-使用mod_deflate模块压缩页面优化传输速度 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.mod_deflate模块概述 mod_deflate ...

  7. JQuery-zTree.js使用范例

    JQuery-zTree.js使用范例 实现Tree树的插件很多,比如常见的UI:Layui.ElementUI.iView ... .这里我们介绍一个小巧的构建Tree树的插件 zTree.js z ...

  8. ubuntu坑(持续更新~)

    0.you should not install packages from any repository that represents a different system version to ...

  9. 【BZOJ 4004】 装备购买(高斯消元+贪心)

    装备购买 题目 脸哥最近在玩一款神奇的游戏,这个游戏里有 n 件装备,每件装备有 m 个属性,用向量zi(aj ,.....,am) 表示 (1 <= i <= n; 1 <= j ...

  10. NSKeyedArchiver : NSCoder

    NSKeyedArchiver : NSCoder @interface NSData : NSObject <NSCopying, NSMutableCopying, NSSecureCodi ...