• 高性能NIO通信框架之Netty(3)ChannelPipeline分析


    一.ChannelPipelineChannelHandler的简介

     NettyChannelPipelineChannelHandler机制类似于ServletFilter过滤器,这类拦截器实际上是职责责任链模式的一种变形,主要是为了方便事件的拦截和用户业务逻辑的定制。

      NettyChannel过滤器实现原理与Servlet Filter机制一致,它将Channel的数据管道抽象为ChannelPipeline,消息ChannelPipeline中流动和传递。ChannelPipeline持有I/O事件拦截器ChannelPipeline的链表,由ChannelHandlerI/O事件进行拦截和处理,可以方便的通过新增和删除ChannelHandler来实现不同的业务逻辑定制,不需要对已有的ChannelHandler进行修改,能够实现对修改封闭和对扩展的支持。

     

    二.ChannelPipeline的功能说明

       ChannelPipelineChannelHandler的容器,它负责ChannelHandler的管理和事件拦截与调度。

     

     

    上图展示了一个消息被ChannelPipelineChannelHandler链拦截和处理的全过程,消息的读取和发送处理全流程描述如下:

    (1)底层的SocketChannel read()方法读取ByteBuf,触发ChannelRead事件,由I/O线程NioEventLoop调用ChannelPipelinefireChannelRead(Object msg)方法,将ByteBuf消息传输到ChannelPipeline中。

    (2)消息依次被HeadHandlerChannelHandler1ChannelHandler2 .... ChannelHandler N-1  ChannelHandler N  TailHandler拦截和处理,在这个过程中,任何ChannelHandler都可以中断当前的流程,结束消息的传递。

    (3)调用ChannelHandlerContextwrite方法发送消息,消息从TailHandler开始经过ChannelHandlerN ..... ChannelHandler1HeadHandler,最终被添加到消息发送缓冲区中等待刷新和发送,在此过程中也可以中断消息的传递,例如当编码失败时,就需要中断流程,构造异常的Future返回。

     

       Netty中的事件分为inboundoutbound事件,inbound事件通常由I/O线程触发,例如TCP链路建立事件、链路关闭事件、读事件、异常通知事件等,接下来介绍下inbound事件和outbound事件,主要在ChannelHandlerContext里面:

       触发Inbound事件的方法如下:

    (1)fireChannelRegistered():Channel注册事件

    (2)fireChannelActive():TCP链路建立成功,Channel激活事件

    (3)fireChannelRead(Object):读事件

    (4)fireChannelReadComplete():读操作完成通知事件

    (5)fireExceptionCaught(Throwable):异常事件通知

    (6)fireUserEventTriggered(Object):用户自定义事件

    (7)fireChannelWritabilityChanaged()Channel的可写状态变化通知事件

    (8)fireChannelInactive()TCP链路关闭,链路不可用通知事件

    outBound事件通常是由用户主动发起的网络I/O操作,例如用户发起的连接操作、绑定操作、消息发送等操作

       触发outbound事件的方法如下:

    (1)bind(SocketAddress,ChannelPromise):绑定本地地址事件

    (2)connect(SocketAddress,SocketAddress,ChannelPromise):连接服务端事件

    (3)write(Object,ChannelPromise):发送事件

    (4)flush():刷新事件

    (5)read():读事件

    (6)disconnect(ChannelPromise):断开连接事件

    (7)close(ChannelPromise):关闭当前Channel事件

     

    2.1 自定义拦截器

       ChannelPipeline通过ChannelHandler接口实现事件的拦截和处理,由于ChannelHandler中的事件种类繁多,不同的ChannelHandler可能只需要关心其中的某一个或者几个事件,所以通常ChannelHandler只需要继承ChannelHandlerAdapter类覆盖自己关心的方法即可;例如:拦截ChannelActive事件,打印TCP链路建立成功日志,代码如下:

    public class LogPrintChannelHandler extends ChannelHandlerAdapter {
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            //打印相关的日志
            System.out.println("TCP Connected");
            //然后将事件继续传递给下一个ChannelHandler
            ctx.fireChannelActive();
        }
    }

     

    2.2  ChannelPipeline的重要特性之动态增加和删除ChannelHandler

       ChannelPipeline支持运行状态动态的添加和删除ChannelHandler,在某些场景下这个特性非常实用。例如当业务高峰期需要对系统做拥塞保护时,就可以根据当前的系统时间进行判断,如果处于业务高峰期,则动态地将系统拥塞保护ChannelHandler添加到当前的ChannelPipeline中,当高峰期过去之后,就可以动态删除拥塞保护ChannelHandler了。

    ChannelPipeline是线程安全的,这意味着N个业务线程可以并发地操作ChannelPipeline而不存在多线程并发问题,这意味着尽管ChannelPipeline是线程安全的,但是用户仍然需要自己保证ChannelHandler的线程安全。

     

    三.ChannelPipeline源码分析

    ChannelPipeline的代码相对比较简单,它实际上是一个ChannelHandler的容器,内部维护了一个ChannelHandler的链表和迭代器,可以方便地实现ChannelHandler查找、添加、替换和删除。

    ChannelPipeline的类继承关系如下:

     

     

    3.1 ChannelPipelineChannelHandler的管理

       ChannelPipelineChannelHandler的管理容器,负责ChannelHandler的查询、添加、替换和删除。由于它与Map等容器的实现非常相似。接口代码如下:

     

     

    由于ChannelPipeline支持动态运行期间动态修改,因此存在两种潜在的多线程并发访问场景:

    1)I/O线程和用户业务线程的并发访问

    2)用户多个线程之间的并发访问

    同时为了保证ChannelPipeline的线程安全性,需要通过线程安全容器或者锁来保证并发操作的安全,Netty使用了synchronized关键字,保证同步块内的所有操作的原子性。首先根据baseName获取它对应的DefaultChannelHandlerContextChannelPipeline维护了ChannelHandler名称和ChannelHandlerContext实例的映射关系,代码如下:

     

     public ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler) {
            return this.addBefore((ChannelHandlerInvoker)null, baseName, name, handler);
        }
    
        public ChannelPipeline addBefore(EventExecutorGroup group, String baseName, String name, ChannelHandler handler) {
            synchronized(this) {
                AbstractChannelHandlerContext ctx = this.getContextOrDie(baseName);
                name = this.filterName(name, handler);
                this.addBefore0(name, ctx, new DefaultChannelHandlerContext(this, this.findInvoker(group), name, handler));
                return this;
            }
        }
      public ChannelHandlerContext context(String name) {
            if(name == null) {
                throw new NullPointerException("name");
            } else {
                synchronized(this) {
                    return (ChannelHandlerContext)this.name2ctx.get(name);
                }
            }
        }

     

       对新增的ChannelHandler名进行重复性校验,如果已经有同名的ChannelHandler存在,则不允许覆盖,抛出IllegalArgumentException("Duplicate handler name: " + name);异常。校验通过之后,使用新增的ChannelHandler等参数构造一个新的DefaultChannelHandlerContext实例;

     

       private String filterName(String name, ChannelHandler handler) {
            if(name == null) {
                return this.generateName(handler);
            } else if(!this.name2ctx.containsKey(name)) {
                return name;
            } else {
                throw new IllegalArgumentException("Duplicate handler name: " + name);
            }
        }

     

    将新建的DefaultChannelHandlerContext添加到当前的pipeline中,

     

     private void addBefore0(String name, AbstractChannelHandlerContext ctx, AbstractChannelHandlerContext newCtx) {
            checkMultiplicity(newCtx);
            newCtx.prev = ctx.prev;
            newCtx.next = ctx;
            ctx.prev.next = newCtx;
            ctx.prev = newCtx;
            this.name2ctx.put(name, newCtx);
            this.callHandlerAdded(newCtx);
        }

     

    如上代码,在添加之前需要对ChannelHandlerContext做重复性校验,如果ChannelHandlerContext不是可以在多个ChannelPipeline中共享的,且已经被添加到ChannelPipeline中则抛出ChannelPipelineException异常,具体代码如下:

     

      private static void checkMultiplicity(ChannelHandlerContext ctx) {
            ChannelHandler handler = ctx.handler();
            if(handler instanceof ChannelHandlerAdapter) {
                ChannelHandlerAdapter h = (ChannelHandlerAdapter)handler;
                if(!h.isSharable() && h.added) {
                    throw new ChannelPipelineException(h.getClass().getName() + " is not a @Sharable handler, so can't be added or removed multiple times.");
                }
    
                h.added = true;
            }
    
        }

     

       ChannelPipeline本身并不直接进行I/O操作,在前面对ChannelUnsafe的介绍中我们知道最终都是由UnsafeChannel来实现真正的I/O操作的。Pipeline负责将I/O事件通过TailHandler进行调度和传播,最终调用UnsafeI/O方法进行I/O操作,然后直接调用TailHandler connect方法,最终会调用到HeadHandlerconnect方法,最终由HeadHandler调用Unsafeconnect方法发起真正的连接,pipeline仅仅负责事件的调度。

  • 相关阅读:
    std::get<C++11多线程库~线程间共享数据>(10):使用互斥量保护共享数据(5)
    std::get<C++11多线程库~线程间共享数据>(10):使用互斥量保护共享数据(4)
    C++多线程库的常用函数 std::lock()
    std::get<C++11多线程库~线程间共享数据>(10):使用互斥量保护共享数据(3)
    std::get<C++11多线程库~线程间共享数据>(10):使用互斥量保护共享数据(2)
    std::get<C++11多线程库~线程间共享数据>(10):使用互斥量保护共享数据(1)
    C++多线程库的常用模板类 std::lock_guard
    C++多线程库的常用类 std::mutex
    npm install 时,下载github的包超时解决方法
    SAP 电商云 Spartacus UI 和路由相关的 State 处理
  • 原文地址:https://www.cnblogs.com/lovegrace/p/11184813.html
Copyright © 2020-2023  润新知