自定义ChannelHandler 的添加过程
前面我们已经分析了ChannelInitializer 是如何插入到Pipeline 中的,接下来就来探讨ChannelInitializer 在哪里被调用,ChannelInitializer 的作用以及我们自定义的ChannelHandler 是如何插入到Pipeline 中的。
先简单复习一下Channel 的注册过程:
1、首先在AbstractBootstrap 的initAndRegister()中,通过group().register(channel),调用MultithreadEventLoopGroup 的register()方法。
2、在MultithreadEventLoopGroup 的register()中调用next()获取一个可用的SingleThreadEventLoop,然后调用它的register()方法。
3、在SingleThreadEventLoop 的register()方法中,通过channel.unsafe().register(this, promise)方法获取channel的unsafe()底层IO 操作对象,然后调用它的register()。
4、在AbstractUnsafe 的register()方法中,调用register0()方法注册Channel 对象。
5、在AbstractUnsafe 的register0()方法中,调用AbstractNioChannel 的doRegister()方法。
6、AbstractNioChannel 的doRegister()方法调用javaChannel().register(eventLoop().selector, 0, this)将Channel对应的Java NIO 的SockerChannel 对象注册到一个eventLoop 的Selector 中,并且将当前Channel 作为attachment。
而我们自定义ChannelHandler 的添加过程,发生在AbstractUnsafe 的register0()方法中,在这个方法中调用了pipeline.fireChannelRegistered()方法,其代码实现如下:
public final ChannelPipeline fireChannelRegistered() {AbstractChannelHandlerContext.invokeChannelRegistered(head);return this;
}
再看AbstractChannelHandlerContext 的invokeChannelRegistered()方法:
static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {EventExecutor executor = next.executor();if (executor.inEventLoop()) {next.invokeChannelRegistered();} else {executor.execute(new Runnable() {@Overridepublic void run() {next.invokeChannelRegistered();}});}
}
很显然,这个代码会从head 开始遍历Pipeline 的双向链表,然后找到第一个属性inbound 为true 的ChannelHandlerContext 实例。想起来了没?我们在前面分析ChannelInitializer 时,花了大量的篇幅来分析了inbound和outbound 属性,现在这里就用上了。回想一下,ChannelInitializer 实现了ChannelInboudHandler,因此它所对应的ChannelHandlerContext 的inbound 属性就是true,因此这里返回就是ChannelInitializer 实例所对应的ChannelHandlerContext 对象,如下图所示:
当获取到inbound 的Context 后,就调用它的invokeChannelRegistered()方法:
private void invokeChannelRegistered() {if (invokeHandler()) {try {((ChannelInboundHandler) handler()).channelRegistered(this);} catch (Throwable t) {notifyHandlerException(t);}} else {fireChannelRegistered();}
}
我们已经知道,每个ChannelHandler 都和一个ChannelHandlerContext 关联,我们可以通过ChannelHandlerContext获取到对应的ChannelHandler。因此很明显,这里handler()返回的对象其实就是一开始我们实例化的ChannelInitializer 对象,并接着调用了ChannelInitializer 的channelRegistered()方法。看到这里, 应该会觉得有点眼熟了。ChannelInitializer 的channelRegistered()这个方法我们在一开始的时候已经接触到了,但是我们并没有深入地分析这个方法的调用过程。下面我们来看这个方法中到底有什么玄机,继续看代码:
public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {if (initChannel(ctx)) {ctx.pipeline().fireChannelRegistered();} else {ctx.fireChannelRegistered();}
}
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) { // Guard againsttry {initChannel((C) ctx.channel());} catch (Throwable cause) {exceptionCaught(ctx, cause);} finally {remove(ctx);}return true;}return false;
}
initChannel((C) ctx.channel())这个方法我们也很熟悉,它就是我们在初始化Bootstrap 时,调用handler 方法传入的匿名内部类所实现的方法:
.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new ChatClientHandler(nickName));}
});
因此,当调用这个方法之后, 我们自定义的ChannelHandler 就插入到了Pipeline,此时Pipeline 的状态如下图所示:
当添加完成自定义的ChannelHandler 后,在finally 代码块会删除自定义的ChannelInitializer,也就是remove(ctx)最终调用ctx.pipeline().remove(this),因此最后的Pipeline 的状态如下:
至此,自定义ChannelHandler 的添加过程也分析得差不多了。
自定义ChannelHandler 的添加过程相关推荐
- Spring源码解析:自定义标签的解析过程
2019独角兽企业重金招聘Python工程师标准>>> spring version : 4.3.x Spring 中的标签分为默认标签和自定义标签两类,上一篇我们探究了默认标签的解 ...
- joomla添加html,如何将自定义html代码添加到Joomla 2.5菜单项?
这里是什么的Joomla提供了这样的情况: 在菜单管理 - >菜单项编辑 编辑每个菜单项,您将看到"链接类型选项"在右栏的部分. 见截图: 正如你在截图中看到,这组设置包含如 ...
- html表单输入框添加验证码,织梦Dedecms为自定义表单添加验证码功能
使用织梦Dedecms自定义表单的时候,即使你做了字段的验证,也很有可能被人刷很多垃圾的内容,更加安全的一个方法是为自定义表单添加上验证码功能.今天我就来为大家分享一下怎样给自定义表单添加验证码! 一 ...
- WMS(一):Window的添加过程
作者:刘望舒 链接:https://www.jianshu.com/p/aadfb70f25e2 前言 在此前的系列文章中我们学习了WindowManager体系和Window的属性,这一篇我们接着来 ...
- Android解析WindowManagerService(二)WMS的重要成员和Window的添加过程
前言 在本系列的上一篇文章中,我们学习了WMS的诞生,WMS被创建后,它的重要的成员有哪些?Window添加过程的WMS部分做了什么呢?这篇文章会给你解答. 1.WMS的重要成员 所谓WMS的重要成员 ...
- amazon alexa_亚马逊使向自定义Alexa Skills添加声音变得更加容易
amazon alexa by Terren Peterson 由Terren Peterson 亚马逊使向自定义Alexa Skills添加声音变得更加容易 (Amazon has made it ...
- Zabbix-server安装后的故障处理及Zabbix-agent的配置添加过程
Zabbix-server安装后的故障处理及Zabbix-agent的配置添加过程 故障处理 第一种情况:在其他参数(Iptables.SeLinux等)配置正确的情况下,如果Web界面出现提示信息, ...
- vue-aMap高德地图的应用(添加覆盖物点坐标、自定义图标、添加信息窗体信息等)
vue-aMap高德地图的应用(添加覆盖物点坐标.自定义图标.添加信息窗体信息等) 最近在项目开发中用到了aMap高德地图,简单记录一下,话不多说,直接上代码. 官方文档参考:高德地图aMap官方文档 ...
- Android R(11)为自定义HIDL接口添加DMFCM(六)
为自定义HIDL接口添加DM&&FCM(六) 1.概览 引入HIDL的一个重要原因是Android团队想要将Android Framework和Android vendor之间的代 ...
最新文章
- java的不足_Java不足之我见
- 接口是什么意思_程序员天天用却不懂得冷知识,这两句口诀,让你理解RESTful接口...
- 总结之:CentOS6.5 DNS服务BIND配置、正反向解析、主从及压力测试(3)
- Effective C++ --5 实现
- 百度地图 key_Android百度地图导航的接入(包含驾车、公交、步行)
- Flutter Raw Image Provider
- 放大缩小保证div对齐_GraphPad Prism 绘图教程 | 如何在图表中对齐对象
- ewebeditor遍历路径漏洞
- win10开机密码忘记了的解锁教程
- Java 8 Iterable.forEach()与foreach循环
- SpringBoot +spring security 与CSRF有关的几个 问题
- Apache 模块 mod_cache应用
- (6)Redis的高可用方案
- 汇编程序求助,window.inc报错
- maven打包的时候同时打源码包,并同时将源码包上传私服
- 盘点JavaScript设计模式(常用十五大设计模式)
- 按键精灵文字识别插件_按键精灵课程学习目录
- 2020COSMO时尚盛典即将闪耀启幕
- android统计app使用时间段,GitHub - yaozs/UseTimeStatistic: Android 系统中统计各个app的使用时长以及使用次数...
- 并查集+思维——Destroying Array