ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Netty学习之核心组件ChannelPipeline

2021-07-31 19:32:46  阅读:156  来源: 互联网

标签:Netty pipeline ChannelHandler ChannelPipeline write msg 组件 new


ChannelPipeline 提供了ChannelHandler 链的容器,并定义了用于在该链上传播入站和出站事件流的API。ChannelPipeline不是单独存在,它肯定会和Channel、ChannelHandler、ChannelHandlerContext关联在一起。

一、ChannelHandler

  1、概述

      

  如上图所示ChannelHandler下主要是两个子接口

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

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

                                      常用的: SimpleChannelInboundHandler

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

                                      适配器: ChannelOutboundHandlerAdapter

  Netty 以适配器类的形式提供了大量默认的ChannelHandler 实现,帮我们简化应用程序处理逻辑的开发过程。每一个Handler都一定会处理出站或者入站(可能两者都处理数据),例如对于入站的Handler可能会继承SimpleChannelInboundHandler或者ChannelInboundHandlerAdapter, 而SimpleChannelInboundHandler又是继承于ChannelInboundHandlerAdapter,最大的区别在于SimpleChannelInboundHandler会对没有外界引用的资源进行一定的清理, 并且入站的消息可以通过泛型来规定。

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

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

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

  • channelRegistered: channel注册到一个EventLoop。
  • channelActive: 变为活跃状态(连接到了远程主机),可以接受和发送数据
  • channelInactive: channel处于非活跃状态,没有连接到远程主机
  • 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、常用方法

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

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

  ChannelPipeline pipeline = ch.pipeline(); 
  FirstHandler firstHandler = new FirstHandler(); 
  pipeline.addLast("handler1", firstHandler); 
  pipeline.addFirst("handler2", new SecondHandler()); 
  pipeline.addLast("handler3", new ThirdHandler()); 
  pipeline.remove("“handler3“"); 
  pipeline.remove(firstHandler); 
  pipeline.replace("handler2", "handler4", new FourthHandler());

  3、入站出站Handler执行顺序

  一般的项目中,inboundHandler和outboundHandler有多个,在Pipeline中的执行顺序?InboundHandler顺序执行,OutboundHandler逆序执行。如代码:

            ch.pipeline().addLast(new InboundHandler1());
          ch.pipeline().addLast(new OutboundHandler1());
          ch.pipeline().addLast(new OutboundHandler2());
          ch.pipeline().addLast(new InboundHandler2());
  或者:
          ch.pipeline().addLast(new OutboundHandler1());
          ch.pipeline().addLast(new OutboundHandler2());
          ch.pipeline().addLast(new InboundHandler1());
          ch.pipeline().addLast(new InboundHandler2());     

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

 InboundHandler1--> InboundHandler2 -->OutboundHandler2 -->OutboundHandler1

  4、ChannelPipeline中channelHandler协作规则

  4.1、业务执行后需要 ChannelHandlerContext.fire*() 或者 Channel*Handler.super*(), 否则不会传递到下一个handler,如下图:

     

   4.2、如果outhandler在inhandler之后添加,所有inhandler中的最后一个inhandler需要写个ctx.channel().write, 这样能进入outhandler的write()中执行,如下图:

          

   4.3、在第4.2中,如果不希望使用ctx.channel().write,那么要触发outhandler的write操作需要把outhandler在inhandler之前添加到pipeline(重要),该情况下还使用ctx.channel().write,会触发两次outhandler.write,如下图:

          

  通常来说outhandler都放到前面添加。netty findchannelhandler机制寻找读事件会先找outhanlder的read方法,在inhandler前面添加的outhandler不能在write方法内调用fireChannelRead事件,否则将pipeline会进入死循环,死循环为:outHandler(read)-->inhandler(read)-->outhandler(write)-->inhandler(read) ··· ···最后这次read触发就是因为outhandler的write方法出现了fireChannelRead事件。

  4.4、outhandler使用ctx.write(msg,promise)传递给下一个outhandler,如下图:

          

  4.5、所有的inhandler的最后一个使用ctx.writeAndFlush(msg)触发给outhandler所有的outhandler的出去口,outhandler最后也需要通过ctx.writeAndFlush(msg)才能发送给客户端。如果多个inhandler执行ctx.writeAndFlush(msg) 客户端则会收到多个返回数据,因为这样outhandler会被触发多次。

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

 三、ChannelHandlerContext

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

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

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

/**
  * 可以看到,DefaultChinnelPipeline 内部使用了两个特殊的Hander 来表示Handel链的头和尾。
  */
 public DefaultChannelPipeline(AbstractChannel channel) {
        if (channel == null) {
            throw new NullPointerException("channel");
        }
        this.channel = channel;
 
        TailHandler tailHandler = new TailHandler();
        tail = new DefaultChannelHandlerContext(this, null, generateName(tailHandler), tailHandler);
 
        HeadHandler headHandler = new HeadHandler(channel.unsafe());
        head = new DefaultChannelHandlerContext(this, null, generateName(headHandler), headHandler);
 
        head.next = tail;
        tail.prev = head;
    }

  所以对于DefaultChinnelPipeline,它的Handler头部和尾部的Handler是固定的,我们所添加的Handler是添加在这个头和尾之间的Handler。

         

四、几者关系

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

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

  • 我们可以在同一个 Channel 中执行读和写操作, 然而同一个 Stream 仅仅支持读或写。
  • Channel 可以异步地读写, 而 Stream 是阻塞的同步读写。
  • Channel 总是从 Buffer 中读取数据, 或将数据写入到 Buffer 中。

  几者的关系图如下:

         

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

  每一个ChannelPipeline中可以包含多个ChannelHandler。所有ChannelHandler都会顺序加入到ChannelPipeline中,ChannelHandler实例与ChannelPipeline之间的桥梁是ChannelHandlerContext实例。

 五、传播过程解释

  现在通过源码将上面的整个传播流程大致走一遍。先从Channel的抽象子类AbstractChannel开始,下面是AbstractChannel#write()方法的实现:

public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
    // ...
    @Override
    public Channel write(Object msg) {
        return pipeline.write(msg);
    }
    // ...
}

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

         

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

final class DefaultChannelPipeline implements ChannelPipeline {
    // ...
    @Override
    public ChannelFuture write(Object msg) {
        return tail.write(msg);
    }
    // ...
}

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

          

  接着看DefaultChannelHandlerContext的write()方法:

final class DefaultChannelHandlerContext extends DefaultAttributeMap implements ChannelHandlerContext {
    // ...
    @Override
    public ChannelFuture write(Object msg) {
        return write(msg, newPromise());
    }
 
    @Override
    public ChannelFuture write(final Object msg, final ChannelPromise promise) {
        if (msg == null) {
            throw new NullPointerException("msg");
        }
 
        validatePromise(promise, true);
 
        write(msg, false, promise);
 
        return promise;
    }
 
    private void write(Object msg, boolean flush, ChannelPromise promise) {
        DefaultChannelHandlerContext next = findContextOutbound();
        next.invokeWrite(msg, promise);
        if (flush) {
            next.invokeFlush();
        }
    }
 
    private DefaultChannelHandlerContext findContextOutbound() {
        DefaultChannelHandlerContext ctx = this;
        do {
            ctx = ctx.prev;
        } while (!ctx.outbound);
        return ctx;
    }
 
    private void invokeWrite(Object msg, ChannelPromise promise) {
        try {
            ((ChannelOutboundHandler) handler).write(this, msg, promise);
        } catch (Throwable t) {
            notifyOutboundHandlerException(t, promise);
        }
    }
 
    // ...
}

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

         

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

         

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

public class ChannelOutboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelOutboundHandler {
    // ...
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        ctx.write(msg, promise);
    }
    // ...
}

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

          

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

标签:Netty,pipeline,ChannelHandler,ChannelPipeline,write,msg,组件,new
来源: https://www.cnblogs.com/yb-ken/p/15085000.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有