这篇文章讲的是的ChannelHandler、Context、Pipeline、SockeChannel相关的核心组件关系以及以及使用技巧上的验证,有关的代码以及在实操的这一篇文章讲过了,这里聚焦于此部分技术概念的验证。
1、ChannelHandler体系结构描述
Channel相当于是搭起来客户端与服务端之间通信的一个管道,不过在客户端与服务端之间是存在编码解码以及业务相关的处理的,而这些处理我们程序猿是可以干预的,可以干预的这些过程都都是发生在与Channel板顶的Pipeline中。Pipeline可以看做是一个责任链表的实现,每一步具体的操作都是通过Handler实现的,根据数据流动的方向不同又可以分为出站以及站。不同的Handler之间是需要进行相互响应的,每一个Handler都会被一个HandlerContext包裹,Handler之间的交互可以看做是通过HandlerContext操作的。HandlerContext中包含了Handler多有相关的信息也可以见他看做是Handler的一个上下文环境。
2、ChannelHandler中有哪些声明周期
关于ChannelHandler中的声明周期,可以理解为是对于他相关的组件相关事件的一系列相应。对声明周期的介绍也按照一定的层次从大到小,让后再在水平的方向上来进行一个介绍,这样可以帮助我们理解Channel生命周期的API函数有哪些以及他们的作用是什么。
状态 | 描述 |
---|---|
channelUnregistered | Channel已被创建但是还没有注册到EventLoop |
channelRegistrered | Channel已经被注册到EventLoop |
channelInactive | Channel没有连接到远程节点 |
channelActive | Channel处于活动状态(已经连接到它的远程节点)他现在可以接受和发送数据了 |
状态 | 描述 |
---|---|
handlerAdded | 当吧ChannelHandler添加到ChannelPipeline中时被调用 |
handlerRemoved | 当从ChannelPipeline中移除改ChannelHandler时被调用 |
exceptionCaught | 当处理过程如果ChannelPipeline中有错误产生时候被调用 |
状态 | 描述 |
---|---|
channelReadComplete | 当Channel上的一个读操作完成时候被调用 |
channelRead | ❓当从Channel读取数据时候被调用 |
channelWritabilityChanged | |
userEventTriggered |
状态 | 描述 |
---|---|
bind | 当讲请求绑定到本地时候被调用 |
connect | 当请求连接到远程节点时候被调用 |
disconnect | 当请求将Channel从远程节点断开时被调用 |
close | 当请求关闭Channel时被调用 |
deregister | 当请求将Channel从他的EventLoop注销时调用 |
read | 当请求从Channel读取跟多数据时候被调用 |
flush | 当请求通过Channel将对立数据刷到远程节点时候调用 |
write | 当请求通过Channel将数据写到远程节点时候被调用 |
3、通过Context进行链调用的流程以及发生在此流程中的数据传递
通过Context进行调用的过程可以参考小结-6的划分成为两种类型的一种是从头开始还有一种就是通过中间任意一个context开始的,当调用fire方法的时候本质上Context是去寻找下一个符合条件——同一流向的那个Context,并进行操作。Context是对Handler的包裹,context组成的双向量表就是一个数据处理的通道。数据就是顺着这个链表朝下处理的,调用fire类型的操作是传递了我们当前方法的处理后的结果,通过这样的一个形式就形成了数据的一个传递。至于Handler调用先后顺序的演示即可以参考在编解码器那篇文章也可以看Netty实操章节中关于非阻塞模式实现的那部分Demo。
4、ChannelPromise与ChannelFuture区分联系
ChannelPromise是ChannelFuture的一个 子类,其定义了一些可写的方法,如setSuccess()和setFailure(),从而使ChannelFuture不 可变
这一部分的是指还待补充!!!
5、消息释放的场景
消息释放场景背后对应的技术是我们在ByteBuffer与ByteBuf对比文章中说道的引用计数,个人对引用计数的接触最早是在JVM虚拟机中关于垃圾回收算法的。目前的理解是如果对象在堆中对象是可以被正常回收之类的但是我们的Buf的使用有一个用法就是直接内存分配。直接内存是堆外内存个人理解是他的回收不收JVM的控制所以需要我们手工的干预。总体的一个干预原则就是当我们不需要一个ByteBuf的时候我们需要通过手工来将它释放掉,为了简化这一操作Netty中也提供了一种具体的实现——SimpleChannelInboundHandler(这个实现会在消 息被 channelRead0()方法消费之后自动释放消息),当然有要释放的场景那么自然就有要保留引用计数的场景。
当某个 ChannelInboundHandler 的实现重写 channelRead()方法时,它将负责显式地 释放与池化的 ByteBuf 实例相关的内存
public class DiscardHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ReferenceCountUtil.release(msg);
}
}
个人猜测:channelRead方法会被调用多次,如果当前的msg经过自己处理后不再需要往后传那么就应该尽快的将它释放掉,避免对内存的占用。
为了帮助你诊断潜在的(资源泄漏)问题,Netty提供了class ResourceLeakDetector① 级 别 , 它将对你应用程序的缓冲区分配做大约 1%的采样来检测内存泄露。相关的开销是非常小的
泄露检测级别可以通过将下面的 Java 系统属性设置为表中的一个值来定义: java -Dio.netty.leakDetectionLevel=ADVANCED
消息的释放场景既然是没有的消息那么已经出站的数据机遇也是应该被释放的:
public class DiscardOutboundHandler extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx,
Object msg, ChannelPromise promise) {
ReferenceCountUtil.release(msg);
//通知ChannelPromise数据已经被处理
promise.setSuccess();
}
}
6、通过Pipeline调用的走的是全链路通过Context调用的走的是半链路
lPromise数据已经被处理
promise.setSuccess();
}
}
### 6、通过Pipeline调用的走的是全链路通过Context调用的走的是半链路
这一部分内容已经在源码剖析的那一部分说过了,主要就是通过AbstractChannelHandlerContext.invokeChannelRead(head, msg);直接传递进去的就是头部节点也就是双向链表的头部节点;如果要是通过其中的一个context调用的话他则是通过一个do-while的循环体来寻找到下一个符合条件的节点,从而来进行调用。