Netty源码解析之pipeline传播事件机制

本文深入解析Netty的Pipeline初始化和事件传播机制。Pipeline采用双向链表结构,事件传播分为从head开始的下行传播和从tail开始的上行传播。通过Context传播事件,每个Context的executionMask在初始化时已计算好,用于确定关心的事件类型。HeadContext处理绑定channel、激活时自动读和写出数据,TailContext则进行兜底操作,确保未处理的事件得到妥善处理。
摘要由CSDN通过智能技术生成

前言

在分析过前两节(服务端启动Reactor线程模型)之后,我们再来介绍一下,pipeline的传播机制。

在前两篇的分析中,不断的出现了pipeline,出镜率极高,其作用在Netty也是非常重要的,加上前两篇文章,这三篇文章算得上介绍了Netty中的“三板斧”(三大组件Channel、EventLoop、Pipeline),所以对于理解好Netty,理解这三大组件是必不可少的。

pipeline就像一条工厂流水线(责任链模式),其中流水头尾都有一个固定的Handler处理把关,流水中间部分Handler由用户自定义,想要产品最终变成什么样被生产出去,由用户自由组装Handler处理决定,在可扩展性方面来说是相当灵活的,因为如果要新的什么功能或是新的什么处理,在流水线中新增一个Handler即可,不需要的时候,就不要这个Handler,对于功能的增删是非常便利的。当然,pipeline还自带动态增删Handler的功能,这样一个灵活的设计思想,值得学习。

Pipeline初始化

在第一篇文章服务端启动中,我们可以知道,每创建一个Channel,都会在其内部创建一个pipeline

protected AbstractChannel(Channel parent) {
   
  this.parent = parent;
  id = newId();
  unsafe = newUnsafe();
  // 在Channel的构造函数中,初始化pipeline
  pipeline = newChannelPipeline();
}

以此为入口,来分析一下pipeline的初始化

protected DefaultChannelPipeline newChannelPipeline() {
   
  return new DefaultChannelPipeline(this);
}

这个方法,实际上传入了一个Channel实例,表示一个Channel跟一个pipeline实例一一对应。可以知道,这里Pipeline是DefaultChannelPipeline这个实现类,进入该类的构造函数

protected DefaultChannelPipeline(Channel channel) {
   
  // 将channel保存起来,这里可以知道,可以通过pipeline拿到channel
  this.channel = ObjectUtil.checkNotNull(channel, "channel");
  succeededFuture = new SucceededChannelFuture(channel, null);
  voidPromise =  new VoidChannelPromise(channel, true);

  // 在头尾创建了两个context守护头尾
  tail = new TailContext(this);
  head = new HeadContext(this);

  // 双向链表的结构
  head.next = tail;
  tail.prev = head;
}

其实在第一篇文章中,已经介绍了头尾两个Conetxt,也介绍了一个Handler其实是对应一个Context的,可以把Context对象看成是Handler的封装类。可以看到,这里conetxt在pipeline中的数据结构是一个双向链表,由此可以知道,我们可以从head从下访问Handler直到tail,也可以从tail往上访问所有Handler直到head

Pipeline数据结构

这里先来介绍一下pipeline的数据结构,从上面的介绍可以知道,首先在pipeline初始化的时候构造了一个头尾Context的双向链表结构,那么我们再来看看,若添加Handler会发生什么

private void addLast0(AbstractChannelHandlerContext newCtx) {
   
  AbstractChannelHandlerContext prev = tail.prev;
  newCtx.prev = prev;
  newCtx.next = tail;
  prev.next = newCtx;
  tail.prev = newCtx;
}

private void addFirst0(AbstractChannelHandlerContext newCtx) {
   
  AbstractChannelHandlerContext nextCtx = head.next;
  newCtx.prev = head;
  newCtx.next = nextCtx;
  head.next = newCtx;
  nextCtx.prev = newCtx;
}

首先addLast顾名思义,其将元素添加到了tail前面,而addFirst将元素添加到了head后面
在这里插入图片描述
大致流程如图所示,到这里,读者应该能明白pipeline的数据结构

传播事件

接下来,我们来介绍事件的传播走向。

首先,传播事件有两种方式

  • 调用pipeline进行传播

    // pipeline.fireChannelRead()
    public final ChannelPipeline fireChannelRead(Object msg) {
         
      AbstractChannelHandlerContext.invokeChannelRead(head, msg);
      return this;
    }
    
  • 调用pipeline中的某个Conetxt进行传播

    // context.fireChannelRead()
    public ChannelHandlerContext fireChannelRead(final Object msg) {
         
      invokeChannelRead(findContextInbound(MASK_CHANNEL_READ), msg);
      return this;
    }
    

那么,这两种传播方式又有什么区别呢?

头尾传播

首先介绍一下pipeline的传播方式。

  • 读传播(fireRead)

可以看到,在fireChannelRead方法中,调用了AbstractChannelHandlerContext的静态方法invokeChannelRead去传播事件,并且值得一提的是,此时参数传入的是headContext。进入该方法看看

static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
   
  final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
  // 从context中拿到EventLoop,目的是检测是否是发动机线程在执行
  EventExecutor executor = next.executor();
  if (executor.inEventLoop()) {
   
    // 执行context的invokeChannelRead方法
    next.invokeChannelRead(m);
  } else {
   
    executor.execute(new Runnable() {
   
      @Override
      public void run() {
   
        next.invokeChannelRead(m);
      }
    });
  }
}

可以看到,pipeline的传播和context的传播区别并不大,pipeline的传播到最后也会调用到context的传播方法,只不过pipeline的读传播事件会从headContext先发起,从上往下传播

  • 写传播

当调用pipeline的写方法时,首先会从tail进行传播

public final ChannelFuture write(Object msg) {
   
  return tail.write(msg);
}

其会找到pipeline的OutBound类型的Handler,从下往上传播

那么我们这里进行总结

  • 由head开始的往下传播的事件
    • fireChannelActive
    • fireChannelInactive
    • fireExceptionCaught
    • fireChannelRead
    • fireChannelReadComplete
    • …等等
  • 由tail开始的往上传播的事件
    • bind
    • connect
    • write
    • flush
    • …等等

Context传播

上面介绍了从pipeline开始传播的传播方式,可见其只不过是从头尾开始传播,最终还是调用了context进行事件的传播,而直接context传播与pipeline的区别只不过是起始的传播点不同而已。这里开始分析context的传播方法

public ChannelHandlerContext fireChannelRead(final Object msg) {
   
  invokeChannelRead
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值