ChannelPipeline

ChannelPipeline不是单独存在,它肯定会和Channel、ChannelHandler、ChannelHandlerContext关联在一起,所以有关概念这里一起讲。

一、ChannelHandler

1、概念

先看图

ChannelHandler下主要是两个子接口

ChannelInboundHandler(入站): 处理输入数据和Channel状态类型改变。

适配器: ChannelInboundHandlerAdapter(适配器设计模式)

常用的: SimpleChannelInboundHandler

ChannelOutboundHandler(出站): 处理输出数据

适配器: ChannelOutboundHandlerAdapter

每一个Handler都一定会处理出站或者入站(可能两者都处理数据),例如对于入站的Handler可能会继承SimpleChannelInboundHandler或者ChannelInboundHandlerAdapter,

而SimpleChannelInboundHandler又是继承于ChannelInboundHandlerAdapter,最大的区别在于SimpleChannelInboundHandler会对没有外界引用的资源进行一定的清理,

并且入站的消息可以通过泛型来规定。

这里为什么有设配器模式呢?

我们在写自定义Handel时候,很少会直接实现上面两个接口,因为接口中有很多默认方法需要实现,所以这里就采用了设配器模式,ChannelInboundHandlerAdapter和

ChannelInboundHandlerAdapter就是设配器模式的产物,让它去实现上面接口,实现它所有方法。那么你自己写自定义Handel时,只要继承它,就无须重写上面接口的所有方法了。

2、Channel 生命周期(执行顺序也是从上倒下)

(1)channelRegistered: channel注册到一个EventLoop。

(2)channelActive: 变为活跃状态(连接到了远程主机),可以接受和发送数据

(3)channelInactive: channel处于非活跃状态,没有连接到远程主机

(4)channelUnregistered: channel已经创建,但是未注册到一个EventLoop里面,也就是没有和Selector绑定

3、ChannelHandler 生命周期

handlerAdded: 当 ChannelHandler 添加到 ChannelPipeline 调用

handlerRemoved: 当 ChannelHandler 从 ChannelPipeline 移除时调用

exceptionCaught: 当 ChannelPipeline 执行抛出异常时调用

二、ChannelPipeline

1、概念

先看图

ChannelPipeline类是ChannelHandler实例对象的链表,用于处理或截获通道的接收和发送数据。它提供了一种高级的截取过滤模式(类似serverlet中的filter功能),让用

户可以在ChannelPipeline中完全控制一个事件以及如何处理ChannelHandler与ChannelPipeline的交互。

对于每个新的通道Channel,都会创建一个新的ChannelPipeline,并将器pipeline附加到channel中。

下图描述ChannelHandler与pipeline中的关系,一个io操作可以由一个ChannelInboundHandler或ChannelOutboundHandle进行处理,并通过调用ChannelInboundHandler

处理入站io或通过ChannelOutboundHandler处理出站IO。

2、常用方法

  1. addFirst(...) //添加ChannelHandler在ChannelPipeline的第一个位置
  2. addBefore(...) //在ChannelPipeline中指定的ChannelHandler名称之前添加ChannelHandler
  3. addAfter(...) //在ChannelPipeline中指定的ChannelHandler名称之后添加ChannelHandler
  4. addLast(...) //在ChannelPipeline的末尾添加ChannelHandler
  5. remove(...) //删除ChannelPipeline中指定的ChannelHandler
  6. replace(...) //替换ChannelPipeline中指定的ChannelHandler

ChannelPipeline可以动态添加、删除、替换其中的ChannelHandler,这样的机制可以提高灵活性。示例:

  1. 1. ChannelPipeline pipeline = ch.pipeline();
  2. 2. FirstHandler firstHandler = new FirstHandler();
  3. 3. pipeline.addLast("handler1", firstHandler);
  4. 4. pipeline.addFirst("handler2", new SecondHandler());
  5. 5. pipeline.addLast("handler3", new ThirdHandler());
  6. 6. pipeline.remove("“handler3“");
  7. 7. pipeline.remove(firstHandler);
  8. 8. pipeline.replace("handler2", "handler4", new FourthHandler());<span style="font-family:Microsoft YaHei;font-size:14px;">

3、入站出站Handler执行顺序

一般的项目中,inboundHandler和outboundHandler有多个,在Pipeline中的执行顺序?

重点记住: InboundHandler顺序执行,OutboundHandler逆序执行。

问题: 下面的handel的执行顺序?

  1. ch.pipeline().addLast(new InboundHandler1());
  2. ch.pipeline().addLast(new OutboundHandler1());
  3. ch.pipeline().addLast(new OutboundHandler2());
  4. ch.pipeline().addLast(new InboundHandler2());
  5. 或者:
  6. ch.pipeline().addLast(new OutboundHandler1());
  7. ch.pipeline().addLast(new OutboundHandler2());
  8. ch.pipeline().addLast(new InboundHandler1());
  9. ch.pipeline().addLast(new InboundHandler2());

其实上面的执行顺序都是一样的:

  1. InboundHandler1--> InboundHandler2 -->OutboundHandler2 -->OutboundHandler1

结论

1)InboundHandler顺序执行,OutboundHandler逆序执行

2)InboundHandler之间传递数据,通过ctx.fireChannelRead(msg)

3)InboundHandler通过ctx.write(msg),则会传递到outboundHandler

4)  使用ctx.write(msg)传递消息,Inbound需要放在结尾,在Outbound之后,不然outboundhandler会不执行;

但是使用channel.write(msg)、pipline.write(msg)情况会不一致,都会执行,那是因为channel和pipline会贯穿整个流。

5)  outBound和Inbound谁先执行,针对客户端和服务端而言,客户端是发起请求再接受数据,先outbound再inbound,服务端则相反。

三、ChannelHandlerContext

ChannelPipeline并不是直接管理ChannelHandler,而是通过ChannelHandlerContext来间接管理,这一点通过ChannelPipeline的默认实现DefaultChannelPipeline可以看出来。

DefaultChannelHandlerContext和DefaultChannelPipeline是ChannelHandlerContext和ChannelPipeline的默认实现在DefaultPipeline内部

DefaultChannelHandlerContext组成了一个双向链表。 我们看下DefaultChannelPipeline的构造函数:

  1. /**
  2. * 可以看到,DefaultChinnelPipeline 内部使用了两个特殊的Hander 来表示Handel链的头和尾。
  3. */
  4. public DefaultChannelPipeline(AbstractChannel channel) {
  5. if (channel == null) {
  6. throw new NullPointerException("channel");
  7. }
  8. this.channel = channel;
  9.  
  10. TailHandler tailHandler = new TailHandler();
  11. tail = new DefaultChannelHandlerContext(this, null, generateName(tailHandler), tailHandler);
  12.  
  13. HeadHandler headHandler = new HeadHandler(channel.unsafe());
  14. head = new DefaultChannelHandlerContext(this, null, generateName(headHandler), headHandler);
  15.  
  16. head.next = tail;
  17. tail.prev = head;
  18. } 

所以对于DefaultChinnelPipeline它的Handel头部和尾部的Handel是固定的,我们所添加的Handel是添加在这个头和尾之前的Handel。(下面这个图更加清晰)

四、几者关系

先大致说下什么是Channel

通常来说, 所有的 NIO 的 I/O 操作都是从 Channel 开始的. 一个 channel 类似于一个 stream。在Netty中,Channel是客户端和服务端建立的一个连接通道。

虽然java Stream 和 NIO Channel都是负责I/O操作,但他们还是有许多区别的:

1)我们可以在同一个 Channel 中执行读和写操作, 然而同一个 Stream 仅仅支持读或写。

2)Channel 可以异步地读写, 而 Stream 是阻塞的同步读写。

3)Channel 总是从 Buffer 中读取数据, 或将数据写入到 Buffer 中。

几者的关系图如下:

总结:

一个Channel包含一个ChannelPipeline,创建Channel时会自动创建一个ChannelPipeline,每个Channel都有一个管理它的pipeline,这关联是永久性的。

这点从源码中就可以看出,我之前写的博客里有说到:【Netty】5 源码 Bootstrap。每一个ChannelPipeline中可以包含多个ChannelHandler。所有ChannelHandler

都会顺序加入到ChannelPipeline中,ChannelHandler实例与ChannelPipeline之间的桥梁是ChannelHandlerContext实例。

五、整个传播流程

现在将上面的整个传播流程,通过源码大致走一遍。

为了搞清楚事件如何在Pipeline里传播,让我们从Channel的抽象子类AbstractChannel开始,下面是AbstractChannel#write()方法的实现:

  1. public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
  2. // ...
  3. @Override
  4. public Channel write(Object msg) {
  5. return pipeline.write(msg);
  6. }
  7. // ...
  8. }

AbstractChannel直接调用了Pipeline的write()方法:

再看DefaultChannelPipeline的write()方法实现:

  1. final class DefaultChannelPipeline implements ChannelPipeline {
  2. // ...
  3. @Override
  4. public ChannelFuture write(Object msg) {
  5. return tail.write(msg);
  6. }
  7. // ...
  8. }

因为write是个outbound事件,所以DefaultChannelPipeline直接找到tail部分的context,调用其write()方法:

接着看DefaultChannelHandlerContext的write()方法

  1. final class DefaultChannelHandlerContext extends DefaultAttributeMap implements ChannelHandlerContext {
  2. // ...
  3. @Override
  4. public ChannelFuture write(Object msg) {
  5. return write(msg, newPromise());
  6. }
  7.  
  8. @Override
  9. public ChannelFuture write(final Object msg, final ChannelPromise promise) {
  10. if (msg == null) {
  11. throw new NullPointerException("msg");
  12. }
  13.  
  14. validatePromise(promise, true);
  15.  
  16. write(msg, false, promise);
  17.  
  18. return promise;
  19. }
  20.  
  21.     private void write(Object msg, boolean flush, ChannelPromise promise) {
  22.         DefaultChannelHandlerContext next = findContextOutbound();
  23.         next.invokeWrite(msg, promise);
  24.         if (flush) {
  25.             next.invokeFlush();
  26.         }
  27.     }
  28.  
  29.     private DefaultChannelHandlerContext findContextOutbound() {
  30.         DefaultChannelHandlerContext ctx = this;
  31.         do {
  32.             ctx = ctx.prev;
  33.         } while (!ctx.outbound);
  34.         return ctx;
  35.     }
  36.  
  37.     private void invokeWrite(Object msg, ChannelPromise promise) {
  38.         try {
  39.             ((ChannelOutboundHandler) handler).write(this, msg, promise);
  40.         } catch (Throwable t) {
  41.             notifyOutboundHandlerException(t, promise);
  42.         }
  43.     }
  44.  
  45.  // ...
  46. }

context的write()方法沿着context链往前找,直至找到一个outbound类型的context为止,然后调用其invokeWrite()方法

invokeWrite()接着调用handler的write()方法

最后看看ChannelOutboundHandlerAdapter的write()方法实现:

  1. public class ChannelOutboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelOutboundHandler {
  2. // ...
  3. @Override
  4. public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
  5. ctx.write(msg, promise);
  6. }
  7. // ...
  8. }

默认的实现调用了context的write()方法而不做任何处理,这样write事件就沿着outbound链继续传播:

可见,Pipeline的事件传播,是靠Pipeline,Context和Handler共同协作完成的。

参考

Netty4学习笔记-- ChannelPipeline(非常感谢作者分享,让我对事件传播有了更清晰的认识)

如果一个人充满快乐,正面的思想,那么好的人事物就会和他共鸣,而且被他吸引过来。同样,一个人老带悲伤,倒霉的事情也会跟过来。

——在自己心情低落的时候,告诫自己不要把负能量带给别人。(大校18)

【Netty】(8)---理解ChannelPipeline的更多相关文章

  1. Netty组件理解(转载)

    转载的文章,写的非常好.   一.先纵览一下Netty,看看Netty都有哪些组件?        为了更好的理解和进一步深入Netty,我们先总体认识一下Netty用到的组件及它们在整个Netty架 ...

  2. netty中的channelPipeline在编程中的作用

    在netty编程中我们绝大多数是要是用nio的,nio相比传统的io更加高效,而nio中核心概念离不开channel,buffer,selector三个重要的对象. 那么在netty中有一个chann ...

  3. Netty中的ChannelPipeline源码分析

    ChannelPipeline在Netty中是用来处理请求的责任链,默认实现是DefaultChannelPipeline,其构造方法如下: private final Channel channel ...

  4. Netty源代码学习——ChannelPipeline模型分析

    參考Netty API io.netty.channel.ChannelPipeline A list of ChannelHandlers which handles or intercepts i ...

  5. netty的理解

    netty作为nio应用的典范,在很多设计方面都值得我们在程序开发中学习. 1.事件驱动,三种事件的传播机制.一种是在channel上触发,一种是在pipeline上触发,一种是在context上触发 ...

  6. netty系列之:channelPipeline详解

    目录 简介 ChannelPipeline 事件传递 DefaultChannelPipeline 总结 简介 我们在介绍channel的时候提到过,几乎channel中所有的实现都是通过channe ...

  7. Netty源码解析 -- ChannelPipeline机制与读写过程

    本文继续阅读Netty源码,解析ChannelPipeline事件传播原理,以及Netty读写过程. 源码分析基于Netty 4.1 ChannelPipeline Netty中的ChannelPip ...

  8. Netty 系列四(ChannelHandler 和 ChannelPipeline).

    一.概念 先来整体的介绍一下这篇博文要介绍的几个概念(Channel.ChannelHandler.ChannelPipeline.ChannelHandlerContext.ChannelPromi ...

  9. Netty 中的 handler 和 ChannelPipeline 分析

    上一节我们讲了 Netty 的启动流程,从启动流程入手分析了 Reactor 模型的第一步:channel 如何绑定 Selector.然后讲到了 EventLoop 在启动的时候发挥了什么作用.整个 ...

随机推荐

  1. Python爬虫-pyspider框架的使用

      pyspider 是一个用python实现的功能强大的网络爬虫系统,能在浏览器界面上进行脚本的编写,功能的调度和爬取结果的实时查看,后端使用常用的数据库进行爬取结果的存储,还能定时设置任务与任务优 ...

  2. python中RabbitMQ的使用(安装和简单教程)

    1,简介 RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现的产品,RabbitMQ是一个消息代理,从"生产者"接收消息 ...

  3. javascript进阶

    BOM操作 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <met ...

  4. 他学习一年Python找不到工作,大佬都说你别再学Python了!

    引言: 都说,滴水穿石非一日之功.然而有些人即使奋斗一辈子也比不上别人一年,别人学习一年比不得你学习一个月.其中缘由,有些人看了大半辈子还没看明白. 即使Python这么火,为何你学习一年的Pytho ...

  5. maven国内镜像(国内oschina的maven服务器关了)

    Maven是官方的库在国外,连下载速度很慢.国内oschina的maven服务器很早之前就关了.今天发现阿里云的一个中央仓库,亲测可用. 找到E:\maven\apache-maven-3.5.2\c ...

  6. Asp.Net 中Grid详解两种方法使用LigerUI加载数据库数据填充数据分页

    1.关于LigerUI: LigerUI 是基于jQuery 的UI框架,其核心设计目标是快速开发.使用简单.功能强大.轻量级.易扩展.简单而又强大,致力于快速打造Web前端界面解决方案,可以应用于. ...

  7. Postman----Presets(预先设置)的使用

    使用场景: 当我们在进行接口测试post请求时,headers是必填项,我们将一个A接口的headers编写后测试完成,再次进行B接口的测试,需重新编写headers,为了简单操作,我们就用到了Pre ...

  8. Linux安装任意版本的dotnet环境

    下载地址 https://www.microsoft.com/net/download/dotnet-core/2.1 安装符合服务器CPU架构的二进制包. 如果架构不对,会出现一下错误: -bash ...

  9. 【开源】OSharpNS,轻量级.net core快速开发框架发布

    OSharpNS简介 OSharp Framework with .NetStandard2.0(OSharpNS)是OSharp的以.NetStandard2.0为目标框架,在AspNetCore的 ...

  10. 【重学计算机】操作系统D5章:文件系统

    1. 文件系统 文件系统概述 文件的组织: 逻辑结构:流式.记录式 物理结构:顺序.连接.直接.索引 文件的存取:顺序.直接.索引 文件的控制:逻辑控制.物理控制 文件的使用:打开.关闭.读.写.控制 ...