之前的文章中我们说过ChannelPipeline作为Netty中的数据管道,负责传递Channel中消息的事件传播,事件的传播分为入站和出站两个方向,分别通知ChannelInboundHandler与ChannelOutboundHandler来触发对应事件。这篇文章我们先对Netty中入站事件的传播,也就是ChannelInboundHandler进行下分析:

1、入站事件传播示例

我们通过一个简单的例子看下ChannelPipeline中入站事件channelRead的传播

public class ServerApp {
public static void main(String[] args) {
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup work = new NioEventLoopGroup(2);
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss, work).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
// p.addLast(new LoggingHandler(LogLevel.INFO));
// 向ChannelPipeline中添加自定义channelHandler
p.addLast(new ServerHandlerA());
p.addLast(new ServerHandlerB());
p.addLast(new ServerHandlerC());
}
});
bootstrap.bind(8050).sync(); } catch (Exception e) {
// TODO: handle exception
} } } public class ServerHandlerA extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object object) {
System.out.println(this.getClass().getName() + "--"+object.toString());
ctx.fireChannelRead(object);
} @Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.channel().pipeline().fireChannelRead("hello word");
} } public class ServerHandlerB extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object object) {
System.out.println(this.getClass().getName() + "--"+object.toString());
ctx.fireChannelRead(object);
}
} public class ServerHandlerC extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object object) {
System.out.println(this.getClass().getName() + "--"+object.toString());
ctx.fireChannelRead(object);
}
}

客户端连接服务后可看到输出结果

io.netty.example.echo.my.ServerHandlerA--hello word
io.netty.example.echo.my.ServerHandlerB--hello word
io.netty.example.echo.my.ServerHandlerC--hello word

通过输出结果我们可以看到,消息会根据向ChannelPipeline中添加自定义channelHandler的顺序传递,并通过实现channelRead接口处理消息接收事件的。在例子中channelRead事件的传递是通过ctx.fireChannelRead(object)方法实现,接下来我们就从这里入手看下ChannelPipeline事件传递的具体实现。

2、channelRead事件的传播

首先这里需要注意的是我们例子中第一个节点的传递与实际应用中入站数据的传递是通过ChannelPipeline的fireChannelRead方法实现的,因为在实际的应用中,入站事件的传递是由NioUnsafe的read接口实现发起的,需要保证消息是从head结点开始传递的,例子中是为了模拟这一过程。

 ctx.channel().pipeline().fireChannelRead("hello word");
    @Override
public final ChannelPipeline fireChannelRead(Object msg) {
AbstractChannelHandlerContext.invokeChannelRead(head, msg);//默认传入head节点
return this;

进入invokeChannelRead方法内部看下具体实现;

    static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
//ObjectUtil.checkNotNull 判断传入的消息数据是否为空
//next.pipeline.touch 对消息类型进行判断
final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
EventExecutor executor = next.executor();//获取ChannelHandlerContext对应的线程
if (executor.inEventLoop()) {//是否为当前线程
next.invokeChannelRead(m);//调用ChannelHandlerContext中invokeChannelRead的回调方法
} else {
executor.execute(new Runnable() {//如果线程不是当前线程
@Override
public void run() {
next.invokeChannelRead(m);
}
});
}
}

其中invokeChannelRead方法会获取该ChannelHandlerContext所封装的handler实现;

    private void invokeChannelRead(Object msg) {
if (invokeHandler()) {
try {
//获取封装的ChannelInboundHandler实现,并调用我们实现的channelRead方法,
((ChannelInboundHandler) handler()).channelRead(this, msg);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRead(msg);
}
}

前面我们知道首先传入的ChannelPipeline中ChannelHandlerContext链表的head头部节点HeadContext,看下其channelRead的方法实现;

        @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ctx.fireChannelRead(msg);
}

调用当前ChannelHandlerContext的fireChannelRead方法,进入ctx.fireChannelRead(object)方法内部看下具体的源码实现;

    @Override
public ChannelHandlerContext fireChannelRead(final Object msg) {
//开始消息传递,findContextInbound方法按顺序获取当前ChannelHandlerContext的next节点
invokeChannelRead(findContextInbound(), msg);
return this;
}

findContextInbound方法获取的是HeadContext的下一个节点,也就是我们例子中向ChannelPipeline中添加自定义ServerHandlerA;

到这里其实就可以看出Pipeline中channelRead事件的传播主要就是通过ctx.fireChannelRead(msg),获取当前ChannelHandlerContext下一个节点中封装的ChannelInboundHandler来实现的,最后一步一步传递到Tail尾部节点。

3、资源的释放及SimpleChannelInboundHandler

Netty中对象的生命周期由它们的引用计数管理的,为保证入站对象资源被释放,我们需要通过ReferenceCountUtil.release方法减少引用计数,确保对象的的最终计数器最后被置为0,从而被回收释放。我们看下Netty在入站事件中默认是如何减少引用计数的。

第一种方法,如果我们跟上面示例一样,在实现的每一个ChannelInboundHandler中都调用了ctx.fireChannelRead(msg),最后消息会被传递到Tail尾节点,我们看下Tail节点中的channelRead方法

   @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
onUnhandledInboundMessage(msg);
} protected void onUnhandledInboundMessage(Object msg) {
try {
logger.debug(
"Discarded inbound message {} that reached at the tail of the pipeline. " +
"Please check your pipeline configuration.", msg);
} finally {
ReferenceCountUtil.release(msg);
}
}

Tail节点的channelRead方法最终会调用ReferenceCountUtil.release方法来减少引用计数的,所以如果你在处理入站消息的过程中没有增加引用并且通过ctx.fireChannelRead(msg)方法把消息传到了Tail节点,你就不需要自己显式调用ReferenceCountUtil.release方法了。

其次如果继承的是SimpleChannelInboundHandler,可以看到SimpleChannelInboundHandler的channelRead方法实现中也已经调用了ReferenceCountUtil.release方法来减少引用计数;

    @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean release = true;
try {
if (acceptInboundMessage(msg)) {
@SuppressWarnings("unchecked")
I imsg = (I) msg;
channelRead0(ctx, imsg);
} else {
release = false;
ctx.fireChannelRead(msg);
}
} finally {
if (autoRelease && release) {
ReferenceCountUtil.release(msg);
}
}
}

所以关于入站消息的资源释放方式总结如下:

  • 1、继承ChannelInboundHandlerAdapter ,在channelRead的方法实现中调用ctx.fireChannelRead(object)方法,把消息一直向下传递,直到传递到Tail尾部节点,由Tail节点执行 ReferenceCountUtil.release来减少计数器,保证资源释放;
  • 2、继承SimpleChannelInboundHandler,SimpleChannelInboundHandler本身的ChannelRead方法中会执行 ReferenceCountUtil.release来减少引用;
  • 3、如果以上两点都没有做到,那就需要手动调用ReferenceCountUtil.release来减少引用来释放资源;

到这里我们基本了解了ChannelPipeline中入站事件是如何传播与相应的的,以及Netty中入站消息的资源释放机制。其中如有不足与不正确的地方还望指出与海涵。

关注微信公众号,查看更多技术文章。

Netty源码分析之ChannelPipeline—入站事件的传播的更多相关文章

  1. Netty源码分析之ChannelPipeline—出站事件的传播

    上篇文章中我们梳理了ChannelPipeline中入站事件的传播,这篇文章中我们看下出站事件的传播,也就是ChannelOutboundHandler接口的实现. 1.出站事件的传播示例 我们对上篇 ...

  2. 【Netty源码分析】ChannelPipeline(二)

    在上一篇博客[Netty源码学习]ChannelPipeline(一)中我们只是大体介绍了ChannelPipeline相关的知识,其实介绍的并不详细,接下来我们详细介绍一下ChannelPipeli ...

  3. Netty源码分析之ChannelPipeline—异常事件的传播

    ChannelHandler中异常的获取与处理是通过继承重写exceptionCaught方法来实现的,本篇文章我们对ChannelPipeline中exceptionCaught异常事件的传播进行梳 ...

  4. [编织消息框架][netty源码分析]6 ChannelPipeline 实现类DefaultChannelPipeline职责与实现

    ChannelPipeline 负责channel数据进出处理,如数据编解码等.采用拦截思想设计,经过A handler处理后接着交给next handler ChannelPipeline 并不是直 ...

  5. Netty源码分析之ChannelPipeline(一)—ChannelPipeline的构造与初始化

    Netty中ChannelPipeline实际上类似与一条数据管道,负责传递Channel中读取的消息,它本质上是基于责任链模式的设计与实现,无论是IO事件的拦截器,还是用户自定义的ChannelHa ...

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

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

  7. netty源码分析系列文章

    netty源码分析系列文章 nettynetty源码阅读netty源码分析  想在年终之际将对netty研究的笔记记录下来,先看netty3,然后有时间了再写netty4的,希望对大家有所帮助,这个是 ...

  8. Netty 源码分析——ChannelPipeline

    Netty 源码分析--ChannelPipeline 通过前面的两章我们分析了客户端和服务端的流程代码,其中在初始化 Channel 的时候一定会看到一个 ChannelPipeline.所以在 N ...

  9. Netty 源码分析系列(二)Netty 架构设计

    前言 上一篇文章,我们对 Netty做了一个基本的概述,知道什么是Netty以及Netty的简单应用. Netty 源码分析系列(一)Netty 概述 本篇文章我们就来说说Netty的架构设计,解密高 ...

随机推荐

  1. Codeforces Round #524 (Div. 2)(前三题题解)

    这场比赛手速场+数学场,像我这样读题都读不大懂的蒟蒻表示呵呵呵. 第四题搞了半天,大概想出来了,但来不及(中途家里网炸了)查错,于是我交了两次丢了100分.幸亏这次没有掉rating. 比赛传送门:h ...

  2. 1047 编程团体赛 (20 分)C语言

    编程团体赛的规则为:每个参赛队由若干队员组成:所有队员独立比赛:参赛队的成绩为所有队员的成绩和:成绩最高的队获胜. 现给定所有队员的比赛成绩,请你编写程序找出冠军队. 输入格式: 输入第一行给出一个正 ...

  3. (二)Django模板的应用

    一.配置项目的基础模板,分三部分 base.html 基础部分,需要包括基本的HTML标签 header部分 body部分 html闭合标签 {% load staticfiles %} <!D ...

  4. OpenJ_Bailian 4103 踩方格(搜索 动态规划 )

    题目传送门OpenJ_Bailian 4103 描述 有一个方格矩阵,矩阵边界在无穷远处.我们做如下假设:a.    每走一步时,只能从当前方格移动一格,走到某个相邻的方格上:b.    走过的格子立 ...

  5. 「newbee-mall新蜂商城开源啦」1000 Star Get !仓库Star数破千!记录一下

    新蜂商城已经开源了 3 个多月左右的时间,在 2019 年的年末,仓库的 Star 数量冲破了 1000,整理本篇文章的时间是 2020 年 1 月 12 日,目前的 Star 数量是 1180 左右 ...

  6. vim添加多行注释的几种方式

    最近需要在阿里云上部署项目,不可避免地会遇到vim这个工具,查了一些资料,总结了一下使用vim多行注释的方法 块操作 多行注释: 首先按esc进入命令行模式下,按下Ctrl + v,进入列(也叫区块) ...

  7. 深入理解 CSS(Cascading Style Sheets)中的层叠(Cascading)

    标题中的 Cascading 亦可以理解为级联. 进入正文,这是一个很有意思的现象.可以直接跳到 总结一下 部分,看完再回过头来阅读本文. 引子 假设我们有如下结构: <p class=&quo ...

  8. 【C&数据结构】---关于链表结构的前序插入和后序插入

    刷LeetCode题目,需要用到链表的知识,忽然发现自己对于链表的插入已经忘得差不多了,以前总觉得理解了记住了,但是发现真的好记性不如烂笔头,每一次得学习没有总结输出,基本等于没有学习.连复盘得机会都 ...

  9. 1z0-062 题库解析6

    You want execution of large database operations to suspend, and then resume, in the event of space a ...

  10. 设置Linux主机SSH访问服务

    检查是否开启22端口访问权限. 检查是否安装openssh-server 开启ssh服务:sudo service sshd start 使用ssh客户端进行访问:ssh userName@IP