09-ChannelPipeline实现

ChannelPipeline实现

  • ChannelPipeline用于组织全部的ChannelHandler,上一篇文章介绍了ChannelPipeline接口的一些特性,本文主要看看它的实现类。
  • 在Netty中ChannelPipeline有好几个实现类,最主要的是DefaultChannelPipeline

一、DefaultChannelPipeline

  • 从ChannelPipeline接口我们知道,其内部的ChannelHandler是按照顺序组织起来的, DefaultChannelPipeline

1.1 属性

  • 主要属性
    /**
     * 双向链表Head节点
     */
    final AbstractChannelHandlerContext head;
    /**
     * 双向链表Tail节点
     */
    final AbstractChannelHandlerContext tail;

    /**
     * 所属Channel对象
     */
    private final Channel channel;
        /**
     * 子执行器集合。
     * 为了保证执行任务时使用同一线程
     * 默认情况下,ChannelHandler 使用 Channel 所在的 EventLoop 作为执行器。
     * 但是如果有需要,也可以自定义执行器。详细解析,见 {@link #childExecutor(EventExecutorGroup)} 。
     * 实际情况下,基本不会用到。
     */
    private Map<EventExecutorGroup, EventExecutor> childExecutors;
    
    /**
     * 是否首次注册
     */
    private boolean firstRegistration = true;

    /**
     * 准备添加 ChannelHandler 的回调
     *
     */
    private PendingHandlerCallback pendingHandlerCallbackHead;

    /**
     * Channel 是否已注册
     */
    private boolean registered;
  • ChannelPipeline内部使用一个双向链表来保存所有的ChannelHandler,不过该双向链表的节点并不是ChannelHandler,而是一个ChannelHandlerContext,ChannelHandlerContext内部封装了ChannelHandler,另外还包含其他的信息,通过双向链表来组织ChannelHandler可以很好的实现前面提到的过滤器模式。

1.2 构造方法

  • DefaultChannelPipeline构造方法主要是初始化主要的属性,主要包含三个步骤:保存channel、创建头尾节点,并且构建双向链表,值得注意的是初始化双向链表的头尾指针。
    protected DefaultChannelPipeline(Channel channel) {
        //1.参数校验
        this.channel = ObjectUtil.checkNotNull(channel, "channel");
        //2.succeededFuture的创建
        succeededFuture = new SucceededChannelFuture(channel, null);
        //3.voidPromise 的创建
        voidPromise =  new VoidChannelPromise(channel, true);

        //4.创建Tail及诶点
        tail = new TailContext(this);
        //5.创建 Head 节点
        head = new HeadContext(this);

        //6.相互指向
        head.next = tail;
        tail.prev = head;
    }
  • DefaultChannelPipeline内部会维持一个双向链表,链表节点是ChannelHandlerContext类型,节点内部保存了pipeline,在下面的节点构造方法也能看出来,它将this传进去,this就是DefaultChannelPipeline。初始化的时候只有head和tail两个节点,节点状态如下:

在这里插入图片描述

  • ChannelHandlerContext内部也是持有ChannelPipeline,因此每一个ChannelHandlerContext节点都可以根据内部持有的ChannelPipeline可以得到执行上下文信息
    /**
     * ChannelHandlerContext节点所属的pipeline
     */
    private final DefaultChannelPipeline pipeline;

1.3 ChannelPipeline的初始化时机

  • ChannelPipeline与Channel绑定的,一个Channel通道对应一个ChannelPipeline,Channel初始化时会创建ChannelPipeline实例,在下面的代码中会进入创建Channel的逻辑,在里面会创建ChannelPipeline,在设置channe通道的时候设置了.channel(NioServerSocketChannel.class),因此bind里面回去创建NioServerSocketChannel类型的Channel实例。
ChannelFuture f = b.bind().sync();
  • 创建NioServerSocketChannel的流程是调用构造方法,然后一直调用父类的构造方法,一直走到父类AbstractChannel的构造方法,如下可以看到内部创建ChannelPipeline实例
protected AbstractChannel(Channel parent) {
        this.parent = parent;
        //1.创建ChannelId对象,对应Channel编号
        id = newId();
        //2.创建Unsafe对象,使用在Channel的生命周期
        unsafe = newUnsafe();
        //3.创建ChannelPipeline对象,是子类DefaultChannelPipeline的实例
        pipeline = newChannelPipeline();
    }

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

二、添加ChannelHandler

2.1 addLast添加方法

  • addLast方法向ChannelPipeline中添加一个ChannelHandler
@Override
    @SuppressWarnings("Duplicates")
    public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        //1.同步控制避免多线程并发操作pipeline底层的双向链表
        synchronized (this) {
            //1.检查是否有重复handler,根据@Sharable判断是否允许重复添加
            checkMultiplicity(handler);

            //2.创建节点,Pipeline内部的节点是ChannelHandlerContext类型
            newCtx = newContext(group, filterName(name, handler), handler);

            //3.添加节点
            addLast0(newCtx);

            //Channel还未注册,添加回调方法。注册完成后会执行回调方法。详细解析,见 {@link #invokeHandlerAddedIfNeeded} 方法。
            // 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) {
                //4.还未注册,则设置AbstractChannelHandlerContext准备添加中
                newCtx.setAddPending();
                //5.此时Channel还没注册的EventLoop中,Netty中事件是在同一个EventLoop执行,所以
                //新增一个任务用于注册后添加 添加PendingHandlerCallback回调
                callHandlerCallbackLater(newCtx, true);
                return this;
            }

            EventExecutor executor = newCtx.executor();
            //6.当前线程不在EventLoop的线程中,提交EventLoop中,执行回调用户方法
            if (!executor.inEventLoop()) {
                //7.设置AbstractChannelHandlerContext准备添加中
                newCtx.setAddPending();
                //8.提交EventLoop中,执行回调ChannelHandler added事件
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        callHandlerAdded0(newCtx);
                    }
                });
                return this;
            }
        }

        //9.回调ChannelHandler added 事件
        callHandlerAdded0(newCtx);
        return this;
    }
  • 由此看到添加ChannelHandler的过程的几点细节,
第一:这个添加过程是同步的;
第二:一个ChannelHandler能否被重复添加是由ChannelHandler的@Sharable注解决定的;
第三:如果Channel尚未注册,那么就会采用回调的模式来执行添加后的逻辑;
第四:如果当前线程不是EventLoop的线程,那么会使用EventLoop里面的线程来执行添加逻辑
  • checkMultiplicity方法校验ChannelHandler能否允许被添加,保证ChannelPipeline中至多只有一个同一类型的非共享Handler:
    /**
     * 检查ChannelHandler是否重复添加,如果ChannelHandler已经被重复添加则检查是否标注了@Sharable,
     * 标注了那么就允许重复添加,反之没有标注则不允许再次添加
     *
     * 是否添加的标记是保存在ChannelHandler的一个added字段内部
     * */
    private static void checkMultiplicity(ChannelHandler handler) {
        if (handler instanceof ChannelHandlerAdapter) {
            ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
            //1.若已经添加且未使用@Sharable注解,则抛出异常
            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.");
            }
            //2.标记已经添加,这个标记是在ChannelHandler中的,也就是说ChannelHandler自己可以记下自己是否
            //被某个ChannelPipline添加过,如果有@Sharable才允许被多次添加,反之则只能被添加一次
            h.added = true;
        }
    }
  • filterName:filterName给ChannelHandler取一个名字,里面的逻辑不复杂,但是代码还不少,如果不传名字就会生产一个名字,如果传了则会校验名字是否重复
    /**
     * 生成一个名字
     * */
    private String filterName(String name, ChannelHandler handler) {
        if (name == null) {
            return generateName(handler);
        }
        //检查名字是否重复,遍历双向链表实现,
        checkDuplicateName(name);
        return name;
    }
    
  • generateName和checkDuplicateName就不贴代码了,本身逻辑不复杂,
  • 另外newContext()本身是构造一个ChannelHandlerContext对象(DefaultChannelHandlerContext),调用了构造方法
  • addLast0方法将构造的DefaultChannelHandlerContext对象添加到双向链表,代码就行修改双向链表的指针,
   private void addLast0(AbstractChannelHandlerContext newCtx) {
        //1.获得tail的前驱
        AbstractChannelHandlerContext prev = tail.prev;
        //2.将新节点插入到原本的tail和tail的前驱之间
        newCtx.prev = prev;
        newCtx.next = tail;
        //3.将原本的tail和原来tail的前驱指针指向新的节点
        prev.next = newCtx;
        tail.prev = newCtx;
    }
  • 这里需要注意的是,双向链表内部最初始的两个节点head和tail对用户不可见,也就是addLast是添加在原本的tail之前,addFirst是添加在原本的head之后,定义的head和tail应该是便于内部链表做操作。

2.2 callHandlerCallbackLater

  • 在Channel注册到EventLoop之前,如果我们addLast的话,此时没有EventExecutor可以执行addLast或者其他添加移除操作,因此在前面的addLast逻辑中会添加一个回调方法,待注册完成之后回调,这里同时注意等待回调的方法可能有很多,在DefaultChannelPipeline内部使用链表存储待回调的任务。
  • 方法的逻辑也很简单,如果已经有回调链表了,就把自己放在链表的最后面,如果没有那么自己就是链表的第一个;
/**
     * 添加回调方法
     * */
    private void callHandlerCallbackLater(AbstractChannelHandlerContext ctx, boolean added) {
        assert !registered;

        //1.创建PendingHandlerCallback对象,参数是true代表添加,false代表remove
        PendingHandlerCallback task = added ? new PendingHandlerAddedTask(ctx) : new PendingHandlerRemovedTask(ctx);
        //2.会调用任务链的头结点
        PendingHandlerCallback pending = pendingHandlerCallbackHead;
        //2.若原pendingHandlerCallbackHead是null,说明还没有任务链表,则赋值给它
        if (pending == null) {
            pendingHandlerCallbackHead = task;
        //3.若原pendingHandlerCallbackHead已存在,说明已经有一个待回调的任务链,那么则最后一个回调指向新创建的回
            //调,也就是把新创建的放在最后面
        } else {
            // Find the tail of the linked-list.
            while (pending.next != null) {
                pending = pending.next;
            }
            pending.next = task;
        }
    }

2.3 PendingHandlerAddedTask任务

  • 在前面添加任务的代码中,根据参数的true和false来决定创建add任务还是remove任务,由此推测应该是有两种任务,我们看看add任务,另一种任务应该差不多。
  • 从注释来看还是比较清晰的,主要核心是通过上下文交给线程池去执行,或者提交给线程池执行,至于做了什么事情,我们还是得看看callHandlerAdded0方法
/**
     * 往ChannelPipeline添加ChannelHandler时,如果常为注册,就添加一个PendingHandlerAddedTask任务
     * 接口本质是一个Runnable实例 -- run
     * 接口又是一个PendingHandlerCallback实例 -- execute
     * */
    private final class PendingHandlerAddedTask extends PendingHandlerCallback {

        PendingHandlerAddedTask(AbstractChannelHandlerContext ctx) {
            super(ctx);
        }

        @Override
        public void run() {
            callHandlerAdded0(ctx);
        }

        @Override
        void execute() {
            //1.从ChannelHandlerContext上下文实例获取到线程池
            EventExecutor executor = ctx.executor();
            //2.在EventLoop的线程中,则回调ChannelHandler added 事件
            if (executor.inEventLoop()) {
                callHandlerAdded0(ctx);
            } else {
                //3.不在,那么则提交EventLoop中执行
                try {
                    executor.execute(this);
                } catch (RejectedExecutionException e) {
                    if (logger.isWarnEnabled()) {
                        logger.warn(
                                "Can't invoke handlerAdded() as the EventExecutor {} rejected it, removing handler {}.",
                                executor, ctx.name(), e);
                    }
                    //4.发生异常,进行移除,传进去的是需要移除的节点
                    remove0(ctx);
                    //5.标记AbstractChannelHandlerContext为已移除
                    ctx.setRemoved();
                }
            }
        }
    }

2.4 callHandlerAdded0

  • callHandlerAdded0是节点添加成功后调用的方法,主要逻辑是触发ChannelHandler的回调方法;
    /**
     * 添加ChannelHandler到ChannelPipeline成功后出发回调
     * */
    private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
        try {
            // We must call setAddComplete before calling handlerAdded. Otherwise if the handlerAdded method generates
            // any pipeline events ctx.handler() will miss them because the state will not allow it.
            //1.将AbstractChannelHandlerContext的添加状态设置为已添加
            ctx.setAddComplete();
            //2.回调ChannelHandler的添加完成(added)事件(在添加完成之后,ChannelHandler还可以回调做点事情)
            ctx.handler().handlerAdded(ctx);
        } catch (Throwable t) {
            //3.如果发生异常,移除该节点
            boolean removed = false;
            try {
                //4.移除
                remove0(ctx);
                try {
                    //5.回调ChannelHandler移除完成(removed)事件
                    ctx.handler().handlerRemoved(ctx);
                } finally {
                    //6.标记节点已移除
                    ctx.setRemoved();
                }
                //7.标记移除成功
                removed = true;
            } catch (Throwable t2) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Failed to remove a handler: " + ctx.name(), t2);
                }
            }
            //8.触发异常的传播
            if (removed) {
                fireExceptionCaught(new ChannelPipelineException(
                        ctx.handler().getClass().getName() + ".handlerAdded() has thrown an exception; removed.", t));
            } else {
                fireExceptionCaught(new ChannelPipelineException(
                        ctx.handler().getClass().getName() + ".handlerAdded() has thrown an exception; also failed to remove.", t));
            }
        }
    }

2.5 callHandlerAddedForAllHandlers

  • 在callHandlerAddedForAllHandlers方法中会调用之前添加的回调任务;
  • 在Channel注册到EventLoop后会调用channelRegistered方法,在channelRegistered方法内部会调用callHandlerAddedForAllHandlers。
private void callHandlerAddedForAllHandlers() {
        final PendingHandlerCallback pendingHandlerCallbackHead;
        //1.加锁、获得 pendingHandlerCallbackHead
        synchronized (this) {
            assert !registered;

            // This Channel itself was registered.
            //2.标记已注册
            registered = true; 
            //3.链表头结点
            pendingHandlerCallbackHead = this.pendingHandlerCallbackHead;
            // Null out so it can be GC'ed.
            // 置空,help gc
            this.pendingHandlerCallbackHead = null; 
        }

        //4.顺序遍历任务链表,执行全部PendingHandlerCallback回调任务
        // This must happen outside of the synchronized(...) block as otherwise handlerAdded(...) may be called while
        // holding the lock and so produce a deadlock if handlerAdded(...) will try to add another handler from outside
        // the EventLoop.
        PendingHandlerCallback task = pendingHandlerCallbackHead;
        while (task != null) {
            task.execute();
            task = task.next;
        }
    }

三、父接口方法

  • 父接口方法包括继承自ChannelInboundInvoker和ChannelOutboundInvoker接口的方法。

3.1 fireChannelRegistered

    @Override
    public ChannelHandlerContext fireChannelRegistered() {
        //向后获得第一个ChannelInboundHandler
        invokeChannelRegistered(findContextInbound());
        return this;
    }
  • invokeChannelRegistered
    static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
        //1.获取ChannelHandlerContext的EventExecutor
        EventExecutor executor = next.executor();
        //2.如果EventExecutor线程在EventLoop线程中,就直接调用
        if (executor.inEventLoop()) {
            next.invokeChannelRegistered();
        } else {
            //3.反之则递交给executor执行
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeChannelRegistered();
                }
            });
        }
    }

3.2 bind

  • bind方法继承自ChannelOutboundInvoker接口;
  • bind方法是交给ChannelOutboundHandler出站处理器来执行;(底层还是交给ChannelOutboundHandler来执行)
    @Override
    public ChannelFuture bind(SocketAddress localAddress) {
        return bind(localAddress, newPromise());
    }
    
    /**
     * bind方法的实现主体
     * */
    @Override
    public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
        //1.参数校验
        if (localAddress == null) {
            throw new NullPointerException("localAddress");
        }
        if (isNotValidPromise(promise, false)) {
            return promise;
        }

        //2.获得下一个ChannelOutboundHandler节点,注意的是在双向链表里面下一
        // 个出站节点其实是往前寻找,下一个入站节点才是往后寻找
        //另外需要注意的是获得的是ChannelHandlerContext,内部封装了ChannelHandler
        final AbstractChannelHandlerContext next = findContextOutbound();
        //3.获得ChannelOutboundHandler的执行器
        EventExecutor executor = next.executor();
        //4.调用下一个ChannelOutboundHandler的bind方法
        if (executor.inEventLoop()) {
            //5.还是固定模式,如果当前线程在EventLoop线程中,就直接执行,反之就提交给EventLoop线程执行
            next.invokeBind(localAddress, promise);
        } else {
            safeExecute(executor, new Runnable() {
                @Override
                public void run() {
                    next.invokeBind(localAddress, promise);
                }
            }, promise, null);
        }
        return promise;
    }
  • invokeBind : invokeBind方法内部还是将bind的动作交给ChannelOutboundHandler来执行
    /**
     * 绑定逻辑
     * */
    private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
            //1.判断是否符合的ChannelHandler
        if (invokeHandler()) {
            try {
                //2.调用该ChannelHandler的bind方法
                ((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
            } catch (Throwable t) {
                //3.通知Outbound事件的传播,发生异常
                notifyOutboundHandlerException(t, promise);
            }
        } else {
            //4.跳过,传播Outbound事件给下一个节点
            bind(localAddress, promise);
        }
    }

四、小结

  • ChannelPipeline的主要实现类是DefaultChannelPipeline,提供了ChannelHandler的添加和移除等操作方法,另外还能进行bind、connect等操作。这些方法很多继承自ChannelInboundInvoker和ChannelOutboundInvoker。
  • 在源码中还是注意一个异步的模式,在很多步骤中,都会考虑异步回调的方式。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值