一文聊透Netty IO事件的编排利器pipeline |详解所有IO事件(五)

发布于 2022-7-18 17:21
浏览
0收藏

4. 向pipeline添加channelHandler


在我们详细介绍了全部的 inbound 类事件和 outbound 类事件的掩码表示以及事件的触发和传播路径后,相信大家现在可以通过 ChannelInboundHandler 和 ChannelOutboundHandler 来根据具体的业务场景选择合适的 ChannelHandler 类型以及监听合适的事件来完成业务需求了。

 

本小节就该介绍一下自定义的 ChannelHandler 是如何添加到 pipeline 中的,netty 在这个过程中帮我们作了哪些工作?      

 final EchoServerHandler serverHandler = new EchoServerHandler();

            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)

             .............

             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();          
                     p.addLast(serverHandler);

                     ......可添加多个channelHandler......
                 }
             });

以上是笔者简化的一个 netty 服务端配置 ServerBootstrap 启动类的一段示例代码。我们可以看到再向 channel 对应的 pipeline 中添加 ChannelHandler 是通过 ChannelPipeline#addLast 方法将指定 ChannelHandler 添加到 pipeline 的末尾处。

public interface ChannelPipeline
        extends ChannelInboundInvoker, ChannelOutboundInvoker, Iterable<Entry<String, ChannelHandler>> {

    //向pipeline的末尾处批量添加多个channelHandler
    ChannelPipeline addLast(ChannelHandler... handlers);

    //指定channelHandler的executor,由指定的executor执行channelHandler中的回调方法
    ChannelPipeline addLast(EventExecutorGroup group, ChannelHandler... handlers);

     //为channelHandler指定名称
    ChannelPipeline addLast(String name, ChannelHandler handler);

    //为channelHandler指定executor和name
    ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler);
}
public class DefaultChannelPipeline implements ChannelPipeline {

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

    @Override
    public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
        ObjectUtil.checkNotNull(handlers, "handlers");

        for (ChannelHandler h: handlers) {
            if (h == null) {
                break;
            }
            addLast(executor, null, h);
        }

        return this;
    }

    @Override
    public final ChannelPipeline addLast(String name, ChannelHandler handler) {
        return addLast(null, name, handler);
    }
}

最终 addLast 的这些重载方法都会调用到 DefaultChannelPipeline#addLast(EventExecutorGroup, String, ChannelHandler) 这个方法从而完成 ChannelHandler 的添加。

 

@Override
    public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            //检查同一个channelHandler实例是否允许被重复添加
            checkMultiplicity(handler);

            //创建channelHandlerContext包裹channelHandler并封装执行传播事件相关的上下文信息
            newCtx = newContext(group, filterName(name, handler), handler);

            //将channelHandelrContext插入到pipeline中的末尾处。双向链表操作
            //此时channelHandler的状态还是ADD_PENDING,只有当channelHandler的handlerAdded方法被回调后,状态才会为ADD_COMPLETE
            addLast0(newCtx);

            //如果当前channel还没有向reactor注册,则将handlerAdded方法的回调添加进pipeline的任务队列中
            if (!registered) {
                //这里主要是用来处理ChannelInitializer的情况
                //设置channelHandler的状态为ADD_PENDING 即等待添加,当状态变为ADD_COMPLETE时 channelHandler中的handlerAdded会被回调
                newCtx.setAddPending();
                //向pipeline中添加PendingHandlerAddedTask任务,在任务中回调handlerAdded
                //当channel注册到reactor后,pipeline中的pendingHandlerCallbackHead任务链表会被挨个执行
                callHandlerCallbackLater(newCtx, true);
                return this;
            }

            //如果当前channel已经向reactor注册成功,那么就直接回调channelHandler中的handlerAddded方法
            EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {
                //这里需要确保channelHandler中handlerAdded方法的回调是在channel指定的executor中
                callHandlerAddedInEventLoop(newCtx, executor);
                return this;
            }
        }
        //回调channelHandler中的handlerAddded方法
        callHandlerAdded0(newCtx);
        return this;
    }

这个方法的逻辑还是比较复杂的,涉及到很多细节,为了清晰地为大家讲述,笔者这里还是采用总分总的结构,先描述该方法的总体逻辑,然后在针对核心细节要点展开细节分析。

 

因为向 pipeline 中添加 channelHandler 的操作可能会在多个线程中进行,所以为了确保添加操作的线程安全性,这里采用一个 synchronized 语句块将整个添加逻辑包裹起来。

 

1.通过 checkMultiplicity 检查被添加的 ChannelHandler 是否是共享的(标注 @Sharable 注解),如果不是共享的那么则不会允许该 ChannelHandler 的同一实例被添加进多个 pipeline 中。如果是共享的,则允许该 ChannelHandler 的同一个实例被多次添加进多个 pipeline 中。
 

 private static void checkMultiplicity(ChannelHandler handler) {
        if (handler instanceof ChannelHandlerAdapter) {
            ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
            //只有标注@Sharable注解的channelHandler,才被允许同一个实例被添加进多个pipeline中
            //注意:标注@Sharable之后,一个channelHandler的实例可以被添加到多个channel对应的pipeline中
            //可能被多线程执行,需要确保线程安全
            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;
        }
    }

这里大家需要注意的是,如果一个 ChannelHandler 被标注了 @Sharable 注解,这就意味着它的一个实例可以被多次添加进多个 pipeline 中(每个 channel 对应一个 pipeline 实例),而这多个不同的 pipeline 可能会被不同的 reactor 线程执行,所以在使用共享 ChannelHandler 的时候需要确保其线程安全性。

 

比如下面的实例代码:

@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
            .............需要确保线程安全.......
}
 final EchoServerHandler serverHandler = new EchoServerHandler();

  ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
               ..................
            .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     p.addLast(serverHandler);
                 }
             });

EchoServerHandler 为我们自定义的 ChannelHandler ,它被 @Sharable 注解标注,全局只有一个实例,被添加进多个 Channel 的 pipeline 中。从而会被多个 reactor 线程执行到。

一文聊透Netty IO事件的编排利器pipeline |详解所有IO事件(五)-开源基础软件社区

 共享channelHandler.png


2.为 ChannelHandler 创建其 ChannelHandlerContext ,用于封装 ChannelHandler 的名称,状态信息,执行上下文信息,以及用于感知 ChannelHandler 在 pipeline 中的位置信息。newContext 方法涉及的细节较多,后面我们单独介绍。

 

3.通过 addLast0 将新创建出来的 ChannelHandlerContext 插入到 pipeline 中末尾处。方法的逻辑很简单其实就是一个普通的双向链表插入操作。

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

但是这里大家需要注意的点是:虽然此时 ChannelHandlerContext 被物理的插入到了 pipeline 中,但是此时 channelHandler 的状态依然为 INIT 状态,从逻辑上来说并未算是真正的插入到 pipeline 中,需要等到 ChannelHandler 的 handlerAdded 方法被回调时,状态才变为 ADD_COMPLETE ,而只有 ADD_COMPLETE 状态的 ChannelHandler 才能响应 pipeline 中传播的事件。

一文聊透Netty IO事件的编排利器pipeline |详解所有IO事件(五)-开源基础软件社区

 channelhandelr的状态.png


在上篇文章《一文搞懂Netty发送数据全流程》中的《3.1.5 触发nextChannelHandler的write方法回调》小节中我们也提过,在每次 write 事件或者 flush 事件传播的时候,都需要通过 invokeHandler 方法来判断 channelHandler 的状态是否为 ADD_COMPLETE  ,否则当前 channelHandler 则不能响应正在 pipeline 中传播的事件。必须要等到对应的 handlerAdded 方法被回调才可以,因为 handlerAdded 方法中可能包含一些 ChannelHandler 初始化的重要逻辑。

private boolean invokeHandler() {
        // 这里是一个优化点,netty 用一个局部变量保存 handlerState
        // 目的是减少 volatile 变量 handlerState 的读取次数
        int handlerState = this.handlerState;
        return handlerState == ADD_COMPLETE || (!ordered && handlerState == ADD_PENDING);
    }

    void invokeWrite(Object msg, ChannelPromise promise) {
        if (invokeHandler()) {
            invokeWrite0(msg, promise);
        } else {
            // 当前channelHandler虽然添加到pipeline中,但是并没有调用handlerAdded
            // 所以不能调用当前channelHandler中的回调方法,只能继续向前传递write事件
            write(msg, promise);
        }
    }

    private void invokeFlush() {
        if (invokeHandler()) {
            invokeFlush0();
        } else {
            //如果该ChannelHandler虽然加入到pipeline中但handlerAdded方法并未被回调,则继续向前传递flush事件
            flush();
        }
    }

事实上不仅仅是 write 事件和 flush 事件在传播的时候需要判断 ChannelHandler 的状态,所有的 inbound 类事件和 outbound 类事件在传播的时候都需要通过 invokeHandler 方法来判断当前 ChannelHandler 的状态是否为 ADD_COMPLETE ,需要确保在 ChannelHandler 响应事件之前,它的 handlerAdded 方法被回调。

 

4.如果向 pipeline 中添加 ChannelHandler 的时候, channel 还没来得及注册到 reactor中,那么需要将当前 ChannelHandler 的状态先设置为 ADD_PENDING ,并将回调该 ChannelHandler 的 handlerAdded 方法封装成 PendingHandlerAddedTask 任务添加进 pipeline 中的任务列表中,等到 channel 向 reactor 注册之后,reactor 线程会挨个执行 pipeline 中任务列表中的任务。

 

这段逻辑主要用来处理 ChannelInitializer 的添加场景,因为目前只有 ChannelInitializer 这个特殊的 channelHandler 会在 channel 没有注册之前被添加进 pipeline 中

         if (!registered) {
                newCtx.setAddPending();
                callHandlerCallbackLater(newCtx, true);
                return this;
         }

向 pipeline 的任务列表 pendingHandlerCallbackHead 中添加 PendingHandlerAddedTask 任务:

public class DefaultChannelPipeline implements ChannelPipeline {

    // pipeline中的任务列表
    private PendingHandlerCallback pendingHandlerCallbackHead;

    // 向任务列表尾部添加PendingHandlerAddedTask
    private void callHandlerCallbackLater(AbstractChannelHandlerContext ctx, boolean added) {
        assert !registered;

        PendingHandlerCallback task = added ? new PendingHandlerAddedTask(ctx) : new PendingHandlerRemovedTask(ctx);
        PendingHandlerCallback pending = pendingHandlerCallbackHead;
        if (pending == null) {
            pendingHandlerCallbackHead = task;
        } else {
            // Find the tail of the linked-list.
            while (pending.next != null) {
                pending = pending.next;
            }
            pending.next = task;
        }
    }
}

PendingHandlerAddedTask 任务负责回调 ChannelHandler 中的 handlerAdded 方法。

private final class PendingHandlerAddedTask extends PendingHandlerCallback {
        ...............

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

       ...............
}

 

 private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
       try {
            ctx.callHandlerAdded();
        } catch (Throwable t) {
           ...............
        }
    }


一文聊透Netty IO事件的编排利器pipeline |详解所有IO事件(五)-开源基础软件社区

pipeline任务列表.png


除了 ChannelInitializer 这个特殊的 ChannelHandler 的添加是在 channel 向 reactor 注册之前外,剩下的这些用户自定义的 ChannelHandler 的添加,均是在 channel 向 reactor 注册之后被添加进 pipeline 的。这种场景下的处理就会变得比较简单,在 ChannelHandler 被插入到 pipeline 中之后,就会立即回调该 ChannelHandler 的 handlerAdded 方法。但是需要确保 handlerAdded 方法的回调在 channel 指定的 executor 中进行。     

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

如果当前执行线程并不是 ChannelHandler 指定的 executor ( !executor.inEventLoop() ),那么就需要确保 handlerAdded 方法的回调在 channel 指定的 executor 中进行。

 private void callHandlerAddedInEventLoop(final AbstractChannelHandlerContext newCtx, EventExecutor executor) {
        newCtx.setAddPending();
        executor.execute(new Runnable() {
            @Override
            public void run() {
                callHandlerAdded0(newCtx);
            }
        });
    }

 

这里需要注意的是需要在回调 handlerAdded 方法之前将 ChannelHandler 的状态提前设置为 ADD_COMPLETE 。 因为用户可能在 ChannelHandler 中的 handerAdded 回调中触发一些事件,而如果此时 ChannelHandler 的状态不是 ADD_COMPLETE 的话,就会停止对事件的响应,从而错过事件的处理。

 

这种属于一种用户极端的使用情况。

    final void callHandlerAdded() throws Exception {
        if (setAddComplete()) {
            handler().handlerAdded(this);
        }
    }

标签
已于2022-7-18 17:21:22修改
收藏
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐