channel
通俗理解就是一个socket连接
- 什么是Channel: 客户端和服务端建立的一个连接通道
- 什么是ChannelHandler: 负责Channel的逻辑处理
- 什么是ChannelPipeline: 负责管理ChannelHandler的有序容器
- 他们是什么关系:
- 一个Channel包含一个ChannelPipeline,所有ChannelHandler都会顺序加入到ChannelPipeline中 创建 Channel时会自动创建一个ChannelPipeline,每个Channel都有一个管理它的pipeline,这关联是永久 性的
Channel当状态出现变化,就会触发对应的事件
方法: handlerAdded : 当 ChannelHandler 添加到 ChannelPipeline 调用
- handlerRemoved : 当 ChannelHandler 从 ChannelPipeline 移除时调用
- exceptionCaught : 执行抛出异常时调用
ChannelHandler下主要是两个子接口
- ChannelInboundHandler:(入站) 处理输入数据和Channel状态类型改变, 适配器 ChannelInboundHandlerAdapter(适配器设计模式) 常用的:SimpleChannelInboundHandler (扩展:适配器设计模式)
- ChannelOutboundHandler:(出站) 处理输出数据,适配器 ChannelOutboundHandlerAdapter
补充:netty内存泄漏
造成原因:如果应用程序在使用ByteBuf后,没有调用方法release()(这个方法将其放回对象池中),又没有任何进一步的引用,则会发生泄漏。 在这种情况下,缓冲区最终将被GC(垃圾回收器)清除,但Netty的对象池不会知道这种情况。 然后,对象池将逐渐相信程序正在使用越来越多的永不返回池中的ByteBuf。这可能会产生内存泄漏。这可能会产生内存泄漏,因为ByteBuf被垃圾回收器回收,对象池回收不到它。导致对象池创建越来越多的新的引用计数对象。具体的Netty内部优化,请看https://speakerdeck.com/normanmaurer/netty-internals-optimizations-everywhere?slide=24
解决方案:
1、回显:writeAndFlush
不回显(writeAndFlush)的时候需要主动释放掉bytebuff,不然会造成内存泄漏
2、手动释放:ReferenceCountUtil.release(msg);
3、或者使用:SimpleChannelInboundHandlerChannelPipeline
ChannelPipeline: 好比厂里的流水线一样,可以在上面添加多个ChannelHanler,也可看成是一串 ChannelHandler 实例,拦截穿过 Channel 的输入输出 event, ChannelPipeline 实现了拦截器的一种高级形 式,使得用户可以对事件的处理以及ChannelHanler之间交互获得完全的控制权 。也可以在里面指定handler的处理顺序
ChannelHandlerContext模块讲解
- ChannelHandlerContext是连接ChannelHandler和ChannelPipeline的桥梁,ChannelHandlerContext部分方法
和Channe、ChannelPipeline重合,好比调用write方法
- Channel、ChannelPipeline、ChannelHandlerContext 都可以调用此方法,前两者都会在整个管道流里 传播,而ChannelHandlerContext就只会在后续的Handler里面传播 (看下图前两个相当于从channel出发,后者会从当前ctx出发到后面)
- AbstractChannelHandlerContext类双向链表结构,next/prev分别是后继节点,和前驱节点
- DefaultChannelHandlerContext 是实现类,但是大部分都是父类那边完成,这个只是简单的实现一些方法主 要就是判断Handler的类型
- ChannelInboundHandler之间的传递,主要通过调用ctx里面的FireXXX()方法来实现下个handler的调用
注意这个handler指的是类如下:
三种写数据的方法
//第一种
//Channel channel = ctx.channel();
//channel.writeAndFlush(Unpooled.copiedBuffer("小滴课堂 xdclass.net",CharsetUtil.UTF_8));
//第二种
//ChannelPipeline channelPipeline = ctx.pipeline();
//channelPipeline.writeAndFlush(Unpooled.copiedBuffer("小滴课堂 xdclass.net",CharsetUtil.UTF_8));
//第三种
ctx.writeAndFlush(Unpooled.copiedBuffer("小滴课堂 xdclass.net",CharsetUtil.UTF_8));
handler之间的调用
入站、出站、handler执行顺序
case1 :使用ChannelHandlerContext
case2:使用Channel、ChannelPipeline
结论
- InboundHandler顺序执行,OutboundHandler逆序执行
- InboundHandler之间传递数据,通过ctx.fireChannelRead(msg)
- InboundHandler通过ctx.write(msg),则会传递到outboundHandler
- 使用ctx.write(msg)传递消息,Inbound需要放在结尾,在Outbound之后,不然outboundhandler会不执行;但 是使用channel.write(msg)、pipline.write(msg)情况会不一致,都会执行
- outBound和Inbound谁先执行,针对客户端和服务端而言,客户端是发起请求再接受数据,先outbound再 inbound,服务端则相反
在下面的链接中,做了详细的实验
https://www.cnblogs.com/tianzhiliang/p/11739372.html ————-
因为Pipleline是执行完所有有效的InboundHandler,再返回执行在最后一个InboundHandler之前的OutboundHandler。注意,有效的InboundHandler是指fire事件触达到的InboundHandler,如果某个InboundHandler没有调用fire事件,后面的InboundHandler都是无效的InboundHandler。为了印证这一点,我们继续做一个实验,我们把其中一个OutboundHandler放在最后一个有效的InboundHandler之前,看看这唯一的一个OutboundHandler是否会执行,其他OutboundHandler是否不会执行。
1、有效的InboundHandler是指通过fire事件能触达到的最后一个InboundHander。
2、如果想让所有的OutboundHandler都能被执行到,那么必须把OutboundHandler放在最后一个有效的InboundHandler之前。
3、推荐的做法是通过addFirst加载所有OutboundHandler,再通过addLast加载所有InboundHandler。
注意这个write是出数据所以只会触发outhandler。outhandler通过write串行
补充
write与flush
- write方法将数据写到内存队列中。
- flush方法刷新内存队列,将其中数据写入对端。(还有些差异后文提)