首页 > 应用开发 > Java > Netty的ChannelPipline传播源码解析
| 运算符 | 描述 |
|---|---|
| & | 与 |
| | | 或 |
| ~ | 非 |
| ^ | 异或 |
| << | 左移 |
| >> | 右移 |
&(与)
| 十进制 | 二进制 |
|---|---|
| 3 | 0 0 1 1 |
| 5 | 0 1 0 1 |
| & 后结果:1 | 0 0 0 1 |
即:对应位都为 1 时,才为 1,否则全为 0。
|(或)
| 十进制 | 二进制 |
|---|---|
| 3 | 0 0 1 1 |
| 5 | 0 1 0 1 |
| | 后结果 :7 | 0 1 1 1 |
即:对应位只要有 1 时,即为 1,否则全为 0。
~(非)
| 十进制 | 二进制 |
|---|---|
| 3 | 0 0 1 1 |
| ~ 后结果:12 | 1 1 0 0 |
即:对应位取反。
异或 ^
| 十进制 | 二进制 |
|---|---|
| 3 | 0 0 1 1 |
| 5 | 0 1 0 1 |
| ^ 后结果:6 | 0 1 1 0 |
即:只要对应为不同即为 1。
我们在以往学习Netty中见到过类似于以下代码:
我们重点关注位运算:interestOps | readInterestOp
该行代码的意思是位运算计算一个数字,该数字包含 | 前后的数字!
io.netty.channel.DefaultChannelPipeline#DefaultChannelPipeline
可以看到,这里初始化管道的时候,管道内部存在两个Handler tail和head节点,两个节点组成双向链表!
上述代码再一个Netty开发中是很常见的一个代码,这里向通道内添加了一个
ChannelInboundHandlerAdapter,我们进入到addLast方法:
这里总共分为两步:
验证Handler是否被重复添加
他是如何判断是否被添加过的呢?
每一个Handler中都存在一个 added属性,当这个属性为true的时候,证明这个Handler已经被添加过了,Netty常规情况下为了考虑线程安全问题,是不允许一个Handler被重复的使用的!
但是我们有时候会有这样一个需求,Handler的功能比较类似,而且我们通过代码手段,避免了线程安全问题,所以又想重复添加Handler,Netty提供了一个注解
@Sharable注解,当存在该注解的时候,证明这个Handler是可以被复用的,可以被重复添加!
所以,checkMultiplicity方法通过判断类是否增加了
@Sharable注解和
added属性是否为空来验证Handle是否违规重复添加了!
当验证通过之后,将added设置为true,证明这个Handler已经被添加过了!
将Handler封装为包装对象
这里比较难理解的就是这个,我们进入到newContext方法里面:
进入到 DefaultChannelHandlerContext类的源码里面:
这里除了会保存一个handler还会调用父类,我们介入到父类里面:
这里会保存一些属性,这些属性都是我们前面讲过的,大家自行分析下,我们重点关注掩码的计算:
先从缓存中取出,如果不存在就调用 mask0(clazz); 方法计算,然后再放进缓存,我们进入到
mask0(clazz);方法:
这会区分两种情况,一种是
ChannelInboundHandler类型的,一种是
ChannelOutboundHandler类型的,二者逻辑相同,我们以ChannelInboundHandler为例:
首先,再ChannelHandlerMask类里面定义了很多的预设掩码值:
我们回到 mask0方法:
一开始,我们会直接将一个handler的掩码计算为拥有全部方法的掩码!
判断该方法是否存在
@Skip注解,如果存在就排除掉这个掩码!
整个逻辑执行完毕后,这个掩码就只会包含handler中没有被@Sikp注解注解的方法掩码!
有同学可能疑问,我在书写handler的时候并没有增加
@Sikp注解呀! 我们都知道,实现一个Handler就必定需要继承
ChannelInboundHandlerAdapter或者
ChannelOutboundHandlerAdapter, 我们随便挑一个类进去看:
可以看到,这些方法其实都是被默认添加了的,只不过我们重写之后没添加!现在我们明白,handler是如何区分你实现了那些方法的了!
这里会将handler包装为HandlerContext对象,类似于tailContext和HeadContext一样,此时上下文对象的结构如下:
将HandlerContext添加进pipeline中:
整个过程如上,无非就是指针指向地址的变换,比较简单,不做深入分析!
寻找处理器Handler的上下文
删除这个处理器
首先我们关注
atomicRemoveFromHandlerList(ctx);:
指针位置重建之后,我们回调handlerRemoved方法
至此我们就完成了pipeline的创建、添加、删除的源码解析!
我们前面见到过很多的事件传播代码,我们以
channelRegistered 方法的事件回调为例:
io.netty.channel.AbstractChannel.AbstractUnsafe#register0
我们进入到改行代码的源码:
我们可以看到,这里使用了
next.invokeChannelRegistered();方法 我们依旧按照同步方法进行分析!
我们现在进入到了headContext,所以我们进入到:
io.netty.channel.DefaultChannelPipeline.HeadContext#channelRegistered:
这一段代码除了执行Head的invokeHandlerAddedIfNeeded方法之外,还又一次传播了channelRegistered事件,我们进入到
ctx.fireChannelRegistered();:
我们如果想要向下传播,我们首先应该找到下一个节点是谁才能传播,Netty这里调用了
findContextInbound(MASK_CHANNEL_REGISTERED)查找下一个节点,我我们先关注以下参数
MASK_CHANNEL_REGISTERED, 他是
channelRegistered方法的掩码, 我们进入到
findContextInbound方法源码:
从当前节点向下寻找,只要 掩码计算包含这个方法,就证明该context包含channelRegistered方法,就直接返回!
寻找到了handler之后,就开始调用了:
具体逻辑就和上面分析的一致了,调用该handler的ChannelRegistered方法!
传播某一个事件,就会使用哪个事件的掩码,从当前节点向下寻找,知道对应的Handler之后,回调对应的方法!
关于管道的传播,你明白了吗?
提一个问题, 观察以下两种传播方式有何不同:
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70000428/viewspace-2782474/ ,如需转载,请注明出处,否则将追究法律责任。