Netty 源码分析1.6 DefaultChannelPipeline类

深入探讨Netty框架中ChannelPipeline的工作原理,包括事件处理流程、线程安全机制及其实现细节。

###################################################################################
后续文章中都会对一些代码逻辑进行说明,但原文的英文注释一般不会直译,进行保留,只会说明那些没有注释的地方
###################################################################################

本文中关联的所有文章的总目录可以参看系列文章目录

1.前言

2.类的初始化

类名属性类型属性名赋值
DefaultChannelPipelineChannelchannelNioServerSocketChannel对象
DefaultChannelPipelineChannelFuturesucceededFutureSucceededChannelFuture对象
DefaultChannelPipelineVoidChannelPromisevoidPromiseVoidChannelPromise对象
DefaultChannelPipelineAbstractChannelHandlerContextheadTailContext对象
DefaultChannelPipelineAbstractChannelHandlerContexttailHeadContext对象

3 类的作用

我们在看一个类前,要先了解这个类是做什么的。而了解一个类,因为代码非常多,要先从其实现的接口说起,所以我们先看下这个类的继承实现关系:

3.1 类的继承关系图

在这里插入图片描述

3.2 接口ChannelPipeline说明

从继承关系中,我们知道这个类其实就是对ChannelPipeline这个接口进行实现,而这个接口的作用我们看下原文翻译的说明:

ChannelHandler的列表,用于处理或拦截Channel的入站事件和出站操作。 ChannelPipeline实现了Intercepting Filter模式的高级形式,以使用户可以完全控制事件的处理方式以及管道中的ChannelHandler如何相互交互。


建立管道
每个通道都有其自己的管道,并且在创建新通道时会自动创建它。


事件如何在管道中流动
下图描述了ChannelPipeline典型地ChannelHandler如何处理I / O事件。 I / O事件由ChannelInboundHandler或ChannelOutboundHandler处理,并通过调用ChannelHandlerContext中定义的事件传播方法(例如ChannelHandlerContext.fireChannelRead(Object)和ChannelHandlerContext.write(Object))转发到其最近的处理程序。
在这里插入图片描述
      入站事件由入站处理程序在自下而上的方向上进行处理,如该图的左侧所示。 入站处理程序通常处理由图底部的I / O线程生成的入站数据。 通常通过实际的输入操作(例如SocketChannel.read(ByteBuffer))从远程对等方读取入站数据。 如果入站事件超出了顶部入站处理程序的范围,则将其静默丢弃,或者在需要引起注意时将其记录下来。
      出站事件由出站处理程序按自上而下的方向进行处理,如该图的右侧所示。 出站处理程序通常会生成或转换出站流量(例如写请求)。如果出站事件超出了底部出站处理程序,则由与通道关联的I / O线程处理。 I / O线程通常执行实际的输出操作,例如SocketChannel.write(ByteBuffer)。
例如,假设我们创建了以下管道:
ChannelPipeline p = …;
p.addLast(“ 1”,new InboundHandlerA());
p.addLast(“ 2”,new InboundHandlerB());
p.addLast(“ 3”,new OutboundHandlerA());
p.addLast(“ 4”,new OutboundHandlerB());
p.addLast(“ 5”,new InboundOutboundHandlerX());


在上面的示例中,名称以Inbound开头的类表示它是一个入站处理程序。名称以Outbound开头的类表示它是一个出站处理程序。
在给定的示例配置中,事件进入时处理程序的评估顺序为1、2、3、4、5。事件离开时,处理程序的评估顺序为5、4、3、2、1。
ChannelPipeline跳过某些处理程序的评估,以缩短堆栈深度:
      •3和4没有实现ChannelInboundHandler,因此inboundevent的实际评估顺序为:1、2和5。
•1和2没有实现ChannelOutboundHandler,因此出站事件的实际评估顺序为:5、4和3。
•如果5同时实现ChannelInboundHandler和ChannelOutboundHandler,则入站和出站事件的评估顺序可能分别为125和543。


将事件转发到下一个处理程序
      如您在图中所示,您可能会注意到,处理程序必须调用ChannelHandlerContext中的事件传播方法,以将事件转发到其下一个处理程序。这些方法包括:•入站事件传播方法:ChannelHandlerContext.fireChannelRegistered()
ChannelHandlerContext.fireChannelActive()
ChannelHandlerContext.fireChannelRead(Object)
ChannelHandlerContext.fireChannelReadComplete()
ChannelHandlerContext.fireExceptionCaught(Throwable)
ChannelHandlerContext.fireUserEventTriggered(Object)
ChannelHandlerContext.fireChannelWritabilityChanged()
ChannelHandlerContext.fireChannelInactive()
ChannelHandlerContext.fireChannelUnregistered()


•出站事件传播方法:◦ChannelHandlerContext.bind(SocketAddress,ChannelPromise)
ChannelHandlerContext.connect(SocketAddress,SocketAddress,ChannelPromise)
ChannelHandlerContext.write(Object,ChannelPromise)
ChannelHandlerContext.flush()
ChannelHandlerContext.read()
ChannelHandlerContext.disconnect(ChannelPromise)
ChannelHandlerContext.close(ChannelPromise)
ChannelHandlerContext.deregister(ChannelPromise)


下面的示例演示了通常如何进行事件传播:
public MyInboundHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx){
System.out.println(“Connected!”);
ctx.fireChannelActive();
}
}


public class MyOutboundHandler extends ChannelOutboundHandlerAdapter {
@Override
public void close(ChannelHandlerContext ctx,ChannelPromise promise){
System.out.println(“ Closing …”);
ctx.close(promise);
}
}


建立管道


      假定用户在管道中具有一个或多个ChannelHandler,以接收I / O事件(例如,读取)并请求I / O操作(例如,写入和关闭)。例如,典型的服务器在每个通道的管道中将具有以下处理程序,但是您的里程可能会因协议和业务逻辑的复杂性和特征而异:
1.Protocol Decoder-将二进制数据(例如ByteBuf)转换为Java对象。
2.Protocol Encoder-将Java对象转换为二进制数据。
3.业务逻辑处理程序-执行实际的业务逻辑(例如数据库访问)。
并可以如下面的示例所示:static final EventExecutorGroup group = new DefaultEventExecutorGroup(16);



ChannelPipeline管道= ch.pipeline()
pipeline.addLast(“ decoder”,new MyProtocolDecoder());
pipeline.addLast(“ encoder”,new MyProtocolEncoder());


//告诉管道运行MyBusinessLogicHandler的事件处理程序方法
//位于与I / O线程不同的线程中,这样I / O线程不会被
//一项耗时的任务。
//如果您的业务逻辑完全异步或很快完成,那么您就不会
//需要指定一个组。
pipeline.addLast(group,“ handler”,新的MyBusinessLogicHandler());


线程安全
     &nbsp由于ChannelPipeline具有线程安全性,因此可以随时添加或删除ChannelHandler。例如,您可以在即将交换敏感信息时插入加密处理程序,并在交换后将其删除。

3.3 类的具体实现

      接口描述中是那样说的,那么这个类是怎么实现那些特性的?

  1. 入站事件由入站处理程序在自下而上的方向上进行处理;出站事件由出站处理程序按自上而下的方向进行处理;
  2. 线程安全性,可以随时添加或删除ChannelHandler;

带着这两个重要的问题,我们来看下该类的具体代码实现;

3.3.1 我们首先看入站和出站的事件实现

      DefaultChannelPipeline 类中有两个AbstractChannelHandlerContext类型的常量 head和tail;
当是入站事件时,它就会从tail这个链路开始执行;而是出站事件时,就从head这个链路开始执行;

      例如我们在Netty 源码分析一NioServerSocketChannel这篇文章中讲解的注册事件,最终调用的就是DefaultChannelPipeline 类的fireChannelRegistered()方法;而该方法的代码如下所示:

@Override
    public final ChannelPipeline fireChannelRegistered() {
        AbstractChannelHandlerContext.invokeChannelRegistered(head);
        return this;
    }

而上面代码调用的逻辑如下所示:

static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
        EventExecutor executor = next.executor(); ========= 第一行
        if (executor.inEventLoop()) {  ========= 第二行
            next.invokeChannelRegistered(); ======= 第三行
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeChannelRegistered();
                }
            });
        }
    }

============================= 先了解head这个对象具体的实例化====================================
对于上面的代码我们来分析下:
next.executor() 方法其实就是调用head的executor方法;而该值是怎么来的了?这个我们需要了解head的创建,head的创建是在DefaultChannelPipeline类实例化时进行创建的。在Netty 源码分析一NioServerSocketChannel这篇文章中的序列图中我们可以了解到;
DefaultChannelPipeline类是在NioServerSocketChannel类实例化时调用,具体调用的代码如下所示:

/**
     * Returns a new {@link DefaultChannelPipeline} instance.
     */
    protected DefaultChannelPipeline newChannelPipeline() {
        return new DefaultChannelPipeline(this);
    }

而DefaultChannelPipeline类初始化时做的事情我们看下它上面调用的构建方法代码:

protected DefaultChannelPipeline(Channel channel) {
        this.channel = ObjectUtil.checkNotNull(channel, "channel");
        succeededFuture = new SucceededChannelFuture(channel, null);
        voidPromise =  new VoidChannelPromise(channel, true);

        tail = new TailContext(this);
        head = new HeadContext(this);

        head.next = tail;
        tail.prev = head;
    }

head 实例化
      我们重点看下head这个属性的实例化做的事情,了解了其内部进行的东西,我们才能加过头看懂 AbstractChannelHandlerContext.invokeChannelRegistered(head)这个方法中代码执行的逻辑;

HeadContext(DefaultChannelPipeline pipeline) {
            super(pipeline, null, HEAD_NAME, HeadContext.class);
            unsafe = pipeline.channel().unsafe();
            setAddComplete(); // 这行代码在后续中会讲解,它的具体作用
        }

而super里面的代码如下所示:

AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor,
                                  String name, Class<? extends ChannelHandler> handlerClass) {
        this.name = ObjectUtil.checkNotNull(name, "name");
        this.pipeline = pipeline;
        this.executor = executor;
        this.executionMask = mask(handlerClass);
        // Its ordered if its driven by the EventLoop or the given Executor is an instanceof OrderedEventExecutor.
        this.ordered = (executor == null) || (executor instanceof OrderedEventExecutor);
    }

所以最后我们通过上面代码可以了解到:
head这个对象中具体的参数的值的情况如下所示:
pipeline: DefaultChannelPipeline 对象
executor: null
unsafe: NioMessageunsafe 对象;(这个值的情况,大家通过我的NioServerSocketChannel这篇文章的讲解可以了解到)

=========== 至此我们才能真正的分析AbstractChannelHandlerContext.invokeChannelRegistered(head) ========

static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
        EventExecutor executor = next.executor(); ========= 第一行
        if (executor.inEventLoop()) {  ========= 第二行
            next.invokeChannelRegistered(); ======= 第三行
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeChannelRegistered();
                }
            });
        }
    }

第一行代码就是执行head的executor方法
那么这个方法我们可以看下:

@Override
    public EventExecutor executor() {
        if (this.executor == null) {
            return channel().eventLoop();
        } else {
            return this.executor;
        }
    }

      在前面我们了解到,head里面设置的executor这个对象为空,所以上面这个方法返回的其实是 channel().eventLoop();返回的值;而channel()调用的是

 @Override
    public Channel channel() {
        return this.pipeline.channel();
    }

      head对象里面的pipeline就是DefaultChannelPipeline 对象,而这个对象我们通过前面列出的实例化的代码了解到,channel就是在创建DefaultChannelPipeline 这个对象时传入的NioServerSocketChannel这个对象;

所以最后executor() 这个方法返回的是 NioServerSocketChannel 对象中的eventLoop对象,也即是NioEventLoop;

第二行代码executor.inEventLoop()
      其实当代码执行到这里时已经是NioEventLoop 这个线程来执行了,所以executor.inEventLoop() 返回的是true

第三行next.invokeChannelRegistered()
      所以代码会执行到这一条语句,而这条语句就是调用head这个对象本身的invokeChannelRegistered 这个方法;而这个方法的代码如下所示:

private void invokeChannelRegistered() {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelRegistered(this);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            fireChannelRegistered();
        }
    }

其中invokeHandler() 这个方法,它内部主要的功能是:
      尽最大努力检查是否还需要调用ChannelHandler.handlerAdded(ChannelHandlerContext)。 如果不是,则返回false;否则返回true。如果此方法返回false,则不会调用ChannelHandler,而只是转发事件。 这是必需的,因为DefaultChannelPipeline可能已将ChannelHandler放在链接列表中,但还未调用ChannelHandler.handlerAdded(ChannelHandlerContext)方法。

代码如下所示:

private boolean invokeHandler() {
        // Store in local variable to reduce volatile reads.
        int handlerState = this.handlerState;
        return (handlerState == ADD_COMPLETE) || (!this.ordered && (handlerState == ADD_PENDING));
    }

      而head这个对象在创建时,我们HeadContext的在构造方法中有这行代码setAddComplete();这个代码前面我们没有讲解,我们先看它的具体内容:

final boolean setAddComplete() {
        for (;;) {
            int oldState = this.handlerState;
            if (oldState == REMOVE_COMPLETE) {
                return false;
            }
            // Ensure we never update when the handlerState is REMOVE_COMPLETE already.
            // oldState is usually ADD_PENDING but can also be REMOVE_COMPLETE when an EventExecutor is used that is not
            // exposing ordering guarantees.
            if (HANDLER_STATE_UPDATER.compareAndSet(this, oldState, ADD_COMPLETE)) {
                return true;
            }
        }
    }

      可以看出,它主要是保持handlerState这个状态值,只要这个handler没有被移出,那么它就会将值设置为ADD_COMPLETE;

      所以正常情况下invokeHandler()方法返回的是true;最后代码会执行 ((ChannelInboundHandler) handler()).channelRegistered(this);这个代码就相当于执行head这个对象的handler()方法,然后去执行这个方法返回对象的channelRegistered(this);而head这个对象的handler()方法代码如下所示:

@Override
        public ChannelHandler handler() {
            return this;
        }

      也就是返回head自己;所以((ChannelInboundHandler) handler()).channelRegistered(this); 最终调用的就是head对象中自己的channelRegistered(this)方法:

@Override
        public void channelRegistered(ChannelHandlerContext ctx) {
            invokeHandlerAddedIfNeeded();
            ctx.fireChannelRegistered();
        }

      invokeHandlerAddedIfNeeded() 这行代码其实对于调用DefaultChannelPipeline 类的fireChannelRegistered()方法时其实已经不用做任何动作了,因为在此执行之前我们分析Netty 源码分析一NioServerSocketChannel 这篇文章中的register方法时,执行过AbstractChannel.this.pipeline.invokeHandlerAddedIfNeeded(); 这个方法内部已经执行过invokeHandlerAddedIfNeeded();方法要做的事情;

ctx.fireChannelRegistered(); 就是调用head对象中的fireChannelRegistered 方法

@Override
    public ChannelHandlerContext fireChannelRegistered() {
        invokeChannelRegistered(findContextInbound(MASK_CHANNEL_REGISTERED));
        return this;
    }

findContextInbound 方法内部就是调用head的next对象,并且这个对象需要是继承ChannelInboundHandler

private AbstractChannelHandlerContext findContextInbound(int mask) {
        AbstractChannelHandlerContext ctx = this;
        do {
            ctx = ctx.next;
        } while ((ctx.executionMask & mask) == 0);
        return ctx;
    }

后面又会重复执行 invokeChannelRegistered(AbstractChannelHandlerContext next)这个对象了

static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeChannelRegistered();
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeChannelRegistered();
                }
            });
        }
    }

      这样就形成了一条链路调用;而对于加入到pipeline中的哪些handler需要执行哪些方法,都是要这个handler的executionMask 值有关系,而这个值具体是怎么设置的,我们后续单独来说;

总结
怎么保证对应的链路的?

  1. pipeline中有head和tail这两个AbstractChannelHandlerContext对象;
  2. 而加入到pipeline的ChannelHandler对象最终都会被包装成AbstractChannelHandlerContext 对象,并且这些对象中都有一个executionMask 这个值,当我们第一次执行pipeline的addLast方法时,那么head.next 就会指向包装这个ChannelHandler的AbstractChannelHandlerContext对象;后面再添加新的ChannelHandler时,第一个添加的元素的next就会指向这个新元素,以此来形成链表结构;
  3. 当在执行某一个事件时(例如fireChannelRegistered),它需要从head开始时,它就会执行head这个对象中对应的事件方法(例如fireChannelRegistered),然后再调用head.next的对象对应的事件方法;只不过再取head.next对象时,需要判断这个对象的executionMask 是否满足当前事件的 MASK(fireChannelRegistered 这个事件的码值是2)这个码值的整数倍(例如executionMask 为2,4,6,8,10…这些的都是可以被fireChannelRegistered 事件调用的),如果是它就需要执行这个事情;

3.3.2 线程安全的保证

      线程安全我们要从两方法来分析,一种就是添加/删除数据时线程安全;一种是获取遍历数据时要保证不会发生并发情况下的异常;

添加元素分析
      这里了我们先不了解具体的DefaultChannelPipeline类的addLast,addFirst等方法的具体实现;我们可以看个大概的内容;具体的这些功能分析我在单独的篇章中进行讲解;

      我们先来看一个addLast 方法,因为在前面文章中Netty 源码分析一ServerBootstrap的bind方法,在讲解ServerBootstrap类中的init(Channel)方法时我们看过类似于这样的代码:

// 这个是在DefaultChannelPipeline 这个通道中加入新的ChannelHander
        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                // 这个ch就是NioServerSocketChannel
                final ChannelPipeline pipeline = ch.pipeline();
                // config是ServerBootstrapConfig这个类,而这个类其实很简单,就是针对ServerBootstrap设置的信息进行封装,在服务设置时的
                // group,channel,option,handler,childHandler都可以通过该config进行访问;
                // 所以config.handler()这个方法返回的就是ServerBootstrap.handler里面设置的 LoggingHandler这个配置(这个是针对EchoServer这个main方法启动来说的)
                ChannelHandler handler = ServerBootstrap.this.config.handler();
                if (handler != null) {
                    pipeline.addLast(handler);
                }

                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(ch, currentChildGroup,
                            currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });

上面代码就是调用DefaultChannelPipeline类的addLast方法

@Override
    public final ChannelPipeline addLast(ChannelHandler... handlers) {
        return addLast(null, handlers);
    }

      该类中的addLast重载的方法很多,但不管是调用上面的那个方法,还是调用其它的重载的方法,最后调用的都是下面这个方法来进行处理;

@Override
    public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            checkMultiplicity(handler);

            newCtx = newContext(group, filterName(name, handler), handler);

            addLast0(newCtx);

            // If the registered is false it means that the channel was not registered on an eventLoop yet.
            // In this case we add the context to the pipeline and add a task that will call
            // ChannelHandler.handlerAdded(...) once the channel is registered.
            if (!registered) {
                newCtx.setAddPending();
                callHandlerCallbackLater(newCtx, true);
                return this;
            }

            EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {
                callHandlerAddedInEventLoop(newCtx, executor);
                return this;
            }
        }
        callHandlerAdded0(newCtx);
        return this;
    }

      在这里具体的代码实现我们不去分析,我们可以看到这个类在添加时最后使用了synchronized (this)同步代码块,这就保证了我们在往这个类中添加ChannelHandler时线程安全

删除元素时分析
      删除数据时,其实跟添加数据一样,都会使用synchronized (this)同步代码块够保证;代码如下所示:

private AbstractChannelHandlerContext remove(final AbstractChannelHandlerContext ctx) {
        assert ctx != head && ctx != tail;

        synchronized (this) {
            atomicRemoveFromHandlerList(ctx);

            // If the registered is false it means that the channel was not registered on an eventloop yet.
            // In this case we remove the context from the pipeline and add a task that will call
            // ChannelHandler.handlerRemoved(...) once the channel is registered.
            if (!registered) {
                callHandlerCallbackLater(ctx, false);
                return ctx;
            }

            EventExecutor executor = ctx.executor();
            if (!executor.inEventLoop()) {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        callHandlerRemoved0(ctx);
                    }
                });
                return ctx;
            }
        }
        callHandlerRemoved0(ctx);
        return ctx;
    }

遍历数据时不发生并发情况下异常

      在学习一些集合时,我们都知道如果不是线程安全的类,你在遍历这个集合中的元素时,如果有其它线程对这个集合进行增,删数据时会报ConcurrentModificationException这个异常(例如,ArrayList,LinkedList); 那DefaultChannelPipeline这个类为什么不会报了?

      前面我们其实大致了解到DefaultChannelPipeline类中添加的元素其实就是以一个类似于双向链表的内部类的方式来存储的;但它本身不是一个集合,只是一个普通的对象,对象中的属性有next,pre来获取它的下一个元素或者前一个元素,所以它在遍历元素时,不会像我们平时的那样foreach,for,while这种方式来遍历数据,它只会从头部head,或者从尾部tail来进行数据链路处理;当处理完一个元素的事情后,然后通过当前元素关联的next或者pre来取出下一个元素,而这样也没有进行数据是否有变更进行校验,所以不会出现ConcurrentModificationException类似于这样的错误;它这样取数据时,就是程序当前运行时next,pre是什么元素?它就会执行这个元素的事件。

      这样就有可能会存在,我们刚好取到这个元素需要执行这个元素的事件,但这个元素刚好在另一个线程中进行remove,但这不影响这个事件的执行,它还是会执行完这个事件;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值