channel

通俗理解就是一个socket连接

  • 什么是Channel: 客户端和服务端建立的一个连接通道
  • 什么是ChannelHandler: 负责Channel的逻辑处理
  • 什么是ChannelPipeline: 负责管理ChannelHandler的有序容器
  • 他们是什么关系:
    • 一个Channel包含一个ChannelPipeline,所有ChannelHandler都会顺序加入到ChannelPipeline中 创建 Channel时会自动创建一个ChannelPipeline,每个Channel都有一个管理它的pipeline,这关联是永久 性的
  • Channel当状态出现变化,就会触发对应的事件

    • 状态:
      • channelRegistered: channel注册到一个EventLoop (1)
      • channelActive: 变为活跃状态(连接到了远程主机),可以接受和发送数据 (2)
      • channelInactive: channel处于非活跃状态,没有连接到远程主机 (3)
      • channelUnregistered: channel已经创建,但是未注册到一个EventLoop里面,也就是没有和 Selector绑定(4)

        生命周期

        image.pngimage.png

        ChannelHandler和ChannelPipeline模块讲解

        channleHandler

  • 方法: handlerAdded : 当 ChannelHandler 添加到 ChannelPipeline 调用

  • handlerRemoved : 当 ChannelHandler 从 ChannelPipeline 移除时调用
  • exceptionCaught : 执行抛出异常时调用

image.png

image.png
ChannelHandler下主要是两个子接口

  • ChannelInboundHandler:(入站) 处理输入数据和Channel状态类型改变, 适配器 ChannelInboundHandlerAdapter(适配器设计模式) 常用的:SimpleChannelInboundHandler (扩展:适配器设计模式)
  • ChannelOutboundHandler:(出站) 处理输出数据,适配器 ChannelOutboundHandlerAdapter
    image.png

    补充: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、或者使用:SimpleChannelInboundHandler
    image.png
    image.png

    ChannelPipeline

ChannelPipeline: 好比厂里的流水线一样,可以在上面添加多个ChannelHanler,也可看成是一串 ChannelHandler 实例,拦截穿过 Channel 的输入输出 event, ChannelPipeline 实现了拦截器的一种高级形 式,使得用户可以对事件的处理以及ChannelHanler之间交互获得完全的控制权 。也可以在里面指定handler的处理顺序

ChannelHandlerContext模块讲解

  • ChannelHandlerContext是连接ChannelHandler和ChannelPipeline的桥梁,ChannelHandlerContext部分方法 和Channe、ChannelPipeline重合,好比调用write方法
    • Channel、ChannelPipeline、ChannelHandlerContext 都可以调用此方法,前两者都会在整个管道流里 传播,而ChannelHandlerContext就只会在后续的Handler里面传播 (看下图前两个相当于从channel出发,后者会从当前ctx出发到后面)

image.png

  • AbstractChannelHandlerContext类双向链表结构,next/prev分别是后继节点,和前驱节点
  • DefaultChannelHandlerContext 是实现类,但是大部分都是父类那边完成,这个只是简单的实现一些方法主 要就是判断Handler的类型
  • ChannelInboundHandler之间的传递,主要通过调用ctx里面的FireXXX()方法来实现下个handler的调用

注意这个handler指的是类如下:
netty04:核心讲解(下) - 图10
三种写数据的方法

  1. //第一种
  2. //Channel channel = ctx.channel();
  3. //channel.writeAndFlush(Unpooled.copiedBuffer("小滴课堂 xdclass.net",CharsetUtil.UTF_8));
  4. //第二种
  5. //ChannelPipeline channelPipeline = ctx.pipeline();
  6. //channelPipeline.writeAndFlush(Unpooled.copiedBuffer("小滴课堂 xdclass.net",CharsetUtil.UTF_8));
  7. //第三种
  8. ctx.writeAndFlush(Unpooled.copiedBuffer("小滴课堂 xdclass.net",CharsetUtil.UTF_8));

image.png

handler之间的调用image.png

image.png
image.png

入站、出站、handler执行顺序

一般开发中都用ChannelHandlerContext

case1 :使用ChannelHandlerContext

运行结果一:
image.png
运行结果二:
image.png

case2:使用Channel、ChannelPipeline

运行结果一:
image.png
运行结果二:
image.png

结论

  • 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。

image.png
注意这个write是出数据所以只会触发outhandler。outhandler通过write串行

补充

write与flush

  1. write方法将数据写到内存队列中。
  2. flush方法刷新内存队列,将其中数据写入对端。(还有些差异后文提)