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

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

6. 从 pipeline 删除 channelHandler


从上个小节的内容中我们可以看到向 pipeline 中添加 ChannelHandler 的逻辑还是比较复杂的,涉及到的细节比较多。

 

那么在了解了向 pipeline 中添加 ChannelHandler 的过程之后,从 pipeline 中删除 ChannelHandler 的逻辑就变得很好理解了。

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

    //从pipeline中删除指定的channelHandler
    ChannelPipeline remove(ChannelHandler handler);
    //从pipeline中删除指定名称的channelHandler
    ChannelHandler remove(String name);
    //从pipeline中删除特定类型的channelHandler
    <T extends ChannelHandler> T remove(Class<T> handlerType);
}

netty 提供了以上三种方式从 pipeline 中删除指定 ChannelHandler ,下面我们以第一种方式为例来介绍 ChannelHandler 的删除过程。

public class DefaultChannelPipeline implements ChannelPipeline {

    @Override
    public final ChannelPipeline remove(ChannelHandler handler) {
        remove(getContextOrDie(handler));
        return this;
    }

}

6.1 getContextOrDie


首先需要通过 getContextOrDie 方法在 pipeline 中查找到指定的 ChannelHandler 对应的 ChannelHandelrContext 。以便确认要删除的 ChannelHandler 确实是存在于 pipeline 中。

 

context 方法是通过遍历 pipeline 中的双向链表来查找要删除的 ChannelHandlerContext 。

 

private AbstractChannelHandlerContext getContextOrDie(ChannelHandler handler) {
        AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(handler);
        if (ctx == null) {
            throw new NoSuchElementException(handler.getClass().getName());
        } else {
            return ctx;
        }
    }

    @Override
    public final ChannelHandlerContext context(ChannelHandler handler) {
        ObjectUtil.checkNotNull(handler, "handler");
        // 获取 pipeline 双向链表结构的头结点
        AbstractChannelHandlerContext ctx = head.next;
        for (;;) {

            if (ctx == null) {
                return null;
            }

            if (ctx.handler() == handler) {
                return ctx;
            }

            ctx = ctx.next;
        }
    }

6.2 remove


remove 方法的整体代码结构和 addLast0 方法的代码结构一样,整体逻辑也是先从 pipeline 中的双向链表结构中将指定的 ChanneHandlerContext 删除,然后在处理被删除的 ChannelHandler 中 handlerRemoved 方法的回调。

 

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

        synchronized (this) {
            //从pipeline的双向列表中删除指定channelHandler对应的context
            atomicRemoveFromHandlerList(ctx);

            if (!registered) {
                //如果此时channel还未向reactor注册,则通过向pipeline中添加PendingHandlerRemovedTask任务
                //在注册之后回调channelHandelr中的handlerRemoved方法
                callHandlerCallbackLater(ctx, false);
                return ctx;
            }

            //channelHandelr从pipeline中删除后,需要回调其handlerRemoved方法
            //需要确保handlerRemoved方法在channelHandelr指定的executor中进行
            EventExecutor executor = ctx.executor();
            if (!executor.inEventLoop()) {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        callHandlerRemoved0(ctx);
                    }
                });
                return ctx;
            }
        }
        callHandlerRemoved0(ctx);
        return ctx;
    }

1.从 pipeline 中删除指定 ChannelHandler 对应的 ChannelHandlerContext 。逻辑比较简单,就是普通双向链表的删除操作。
 

private synchronized void atomicRemoveFromHandlerList(AbstractChannelHandlerContext ctx) {
        AbstractChannelHandlerContext prev = ctx.prev;
        AbstractChannelHandlerContext next = ctx.next;
        prev.next = next;
        next.prev = prev;
    }

2.如果此时 channel 并未向对应的 reactor 进行注册,则需要向 pipeline 的任务列表中添加 PendingHandlerRemovedTask 任务,再该任务中会执行 ChannelHandler 的 handlerRemoved 回调,当 channel 向 reactor 注册成功后,reactor 会执行 pipeline 中任务列表中的任务,从而回调被删除 ChannelHandler 的 handlerRemoved 方法。

一文聊透Netty IO事件的编排利器pipeline |详解所有IO事件(七)-鸿蒙开发者社区 pipeline任务.png

 

    private final class PendingHandlerRemovedTask extends PendingHandlerCallback {

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

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

在执行 ChannelHandler 中 handlerRemoved 回调的时候,需要对 ChannelHandler 的状态进行判断:只有当 handlerState 为 ADD_COMPLETE 的时候才能回调 handlerRemoved 方法。

 

这里表达的语义是只有当 ChannelHanler 的 handlerAdded 方法被回调之后,那么在 ChannelHanler 被从 pipeline 中删除的时候它的 handlerRemoved 方法才可以被回调。


在 ChannelHandler 的 handlerRemove 方法被回调之后,将 ChannelHandler 的状态设置为 REMOVE_COMPLETE 。

 

 private void callHandlerRemoved0(final AbstractChannelHandlerContext ctx) {

        try {
            // 在这里回调 handlerRemoved 方法
            ctx.callHandlerRemoved();
        } catch (Throwable t) {
            fireExceptionCaught(new ChannelPipelineException(
                    ctx.handler().getClass().getName() + ".handlerRemoved() has thrown an exception.", t));
        }
    }

    final void callHandlerRemoved() throws Exception {
        try {
            if (handlerState == ADD_COMPLETE) {
                handler().handlerRemoved(this);
            }
        } finally {
            // Mark the handler as removed in any case.
            setRemoved();
        }
    }

   final void setRemoved() {
        handlerState = REMOVE_COMPLETE;
    }

3.如果 channel 已经在 reactor 中注册成功,那么当 channelHandler 从 pipeline 中删除之后,需要立即回调其 handlerRemoved 方法。但是需要确保 handlerRemoved 方法在 channelHandler 指定的 executor 中进行。


7. pipeline 的初始化


其实关于 pipeline 初始化的相关内容我们在《详细图解 Netty Reactor 启动全流程》中已经简要介绍了 NioServerSocketChannel 中的 pipeline 的初始化时机以及过程。

 

《Netty 如何高效接收网络连接》中笔者也简要介绍了 NioSocketChannel 中 pipeline 的初始化时机以及过程。

 

本小节笔者将结合这两种类型的 Channel 来完整全面的介绍 pipeline 的整个初始化过程。

 

7.1 NioServerSocketChannel 中 pipeline 的初始化


从前边提到的这两篇文章以及本文前边的相关内容我们知道,Netty 提供了一个特殊的 ChannelInboundHandler 叫做 ChannelInitializer ,用户可以利用这个特殊的 ChannelHandler 对 Channel 中的 pipeline 进行自定义的初始化逻辑。

 

如果用户只希望在 pipeline 中添加一个固定的 ChannelHandler 可以通过如下代码直接添加。

            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)//配置主从Reactor
                  ...........
             .handler(new LoggingHandler(LogLevel.INFO))

如果希望添加多个 ChannelHandler ,则可以通过 ChannelInitializer 来自定义添加逻辑。

 

由于使用 ChannelInitializer 初始化 NioServerSocketChannel 中 pipeline 的逻辑会稍微复杂一点,下面我们均以这个复杂的案例来讲述 pipeline 的初始化过程。
         

 ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)//配置主从Reactor
                  ...........
               .handler(new ChannelInitializer<NioServerSocketChannel>() {
                 @Override
                 protected void initChannel(NioServerSocketChannel ch) throws Exception {
                              ....自定义pipeline初始化逻辑....
                               ChannelPipeline p = ch.pipeline();
                               p.addLast(channelHandler1);
                               p.addLast(channelHandler2);
                               p.addLast(channelHandler3);
                                    ........
                 }
             })

以上这些由用户自定义的用于初始化 pipeline 的 ChannelInitializer ,被保存至 ServerBootstrap 启动类中的 handler 字段中。用于后续的初始化调用

public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable
   private volatile ChannelHandler handler;
}

在服务端启动的时候,会伴随着 NioServeSocketChannel 的创建以及初始化,在初始化 NioServerSokcetChannel 的时候会将一个新的 ChannelInitializer 添加进 pipeline 中,在新的 ChannelInitializer 中才会将用户自定义的 ChannelInitializer 添加进 pipeline 中,随后才执行初始化过程。

 

Netty 这里之所以引入一个新的 ChannelInitializer 来初始化 NioServerSocketChannel 中的 pipeline 的原因是需要兼容前边介绍的这两种初始化 pipeline 的方式。

 

 •一种是直接使用一个具体的 ChannelHandler 来初始化 pipeline。

 

 •另一种是使用 ChannelInitializer 来自定义初始化 pipeline 逻辑。

 

忘记 netty 启动过程的同学可以在回看下笔者的《详细图解 Netty Reactor 启动全流程》这篇文章。
 

@Override
    void init(Channel channel) {
       .........

        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                final ChannelPipeline pipeline = ch.pipeline();
                //ServerBootstrap中用户指定的channelHandler
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    pipeline.addLast(handler);
                }

                .........
            }
        });
   }

注意此时 NioServerSocketChannel 并未开始向 Main Reactor 注册,根据本文第四小节《4. 向 pipeline 添加 channelHandler 》中的介绍,此时向 pipeline 中添加这个新的 ChannelInitializer 之后,netty 会向 pipeline 的任务列表中添加 PendingHandlerAddedTask 。当 NioServerSocketChannel 向 Main Reactor 注册成功之后,紧接着 Main Reactor 线程会调用这个 PendingHandlerAddedTask ,在任务中会执行这个新的 ChannelInitializer 的 handlerAdded 回调。在这个回调方法中会执行上边 initChannel 方法里的代码。

一文聊透Netty IO事件的编排利器pipeline |详解所有IO事件(七)-鸿蒙开发者社区

 server channel pipeline 注册前结构.png


当 NioServerSocketChannel 在向 Main Reactor 注册成功之后,就挨个执行 pipeline 中的任务列表中的任务。

       private void register0(ChannelPromise promise) {
                     .........
                boolean firstRegistration = neverRegistered;
                //执行真正的注册操作
                doRegister();
                //修改注册状态
                neverRegistered = false;
                registered = true;
                //调用pipeline中的任务链表,执行PendingHandlerAddedTask
                pipeline.invokeHandlerAddedIfNeeded();
                .........
    final void invokeHandlerAddedIfNeeded() {
        assert channel.eventLoop().inEventLoop();
        if (firstRegistration) {
            firstRegistration = false;
            // 执行 pipeline 任务列表中的 PendingHandlerAddedTask 任务。
            callHandlerAddedForAllHandlers();
        }
    }

执行 pipeline 任务列表中的 PendingHandlerAddedTask 任务:

    private void callHandlerAddedForAllHandlers() {
        // pipeline 任务列表中的头结点
        final PendingHandlerCallback pendingHandlerCallbackHead;
        synchronized (this) {
            assert !registered;
            // This Channel itself was registered.
            registered = true;
            pendingHandlerCallbackHead = this.pendingHandlerCallbackHead;
            // Null out so it can be GC'ed.
            this.pendingHandlerCallbackHead = null;
        }

        PendingHandlerCallback task = pendingHandlerCallbackHead;
        // 挨个执行任务列表中的任务
        while (task != null) {
            //触发 ChannelInitializer 的 handlerAdded 回调
            task.execute();
            task = task.next;
        }
    }

最终在 PendingHandlerAddedTask 中执行 pipeline 中 ChannelInitializer 的 handlerAdded 回调。

 

这个 ChannelInitializer 就是在初始化 NioServerSocketChannel 的 init 方法中向 pipeline 添加的 ChannelInitializer。

@Sharable
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        if (ctx.channel().isRegistered()) {

            if (initChannel(ctx)) {
                //初始化工作完成后,需要将自身从pipeline中移除
                removeState(ctx);
            }
        }
    }

}

在 handelrAdded 回调中执行 ChannelInitializer 匿名类中 initChannel 方法,注意此时执行的 ChannelInitializer 类为在本小节开头 init 方法中由 Netty 框架添加的 ChannelInitializer ,并不是用户自定义的 ChannelInitializer 。

 

 @Override
    void init(Channel channel) {
       .........

        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                final ChannelPipeline pipeline = ch.pipeline();
                //ServerBootstrap中用户指定的ChannelInitializer
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    pipeline.addLast(handler);
                }

                .........
            }
        });
   }

执行完 ChannelInitializer 匿名类中 initChannel 方法后,需将 ChannelInitializer 从 pipeline 中删除。并回调 ChannelInitializer 的 handlerRemoved 方法。删除过程笔者已经在第六小节《6. 从 pipeline 删除 channelHandler》详细介绍过了。

 

private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
        if (initMap.add(ctx)) { // Guard against re-entrance.
            try {
                //执行ChannelInitializer匿名类中的initChannel方法
                initChannel((C) ctx.channel());
            } catch (Throwable cause) {
                exceptionCaught(ctx, cause);
            } finally {
                ChannelPipeline pipeline = ctx.pipeline();
                if (pipeline.context(this) != null) {
                    //初始化完毕后,从pipeline中移除自身
                    pipeline.remove(this);
                }
            }
            return true;
        }
        return false;
    }

当执行完 initChannel 方法后此时 pipeline 的结构如下图所示:

一文聊透Netty IO事件的编排利器pipeline |详解所有IO事件(七)-鸿蒙开发者社区

 serverSocketChannel注册之后pipeline结构.png


当用户的自定义 ChannelInitializer 被添加进 pipeline 之后,根据第四小节所讲的添加逻辑,此时 NioServerSocketChannel 已经向 main reactor 成功注册完毕,不再需要向 pipeine 的任务列表中添加 PendingHandlerAddedTask 任务,而是直接调用自定义 ChannelInitializer 中的 handlerAdded 回调,和上面的逻辑一样。不同的是这里最终回调至用户自定义的初始化逻辑实现 initChannel 方法中。执行完用户自定义的初始化逻辑之后,从 pipeline 删除用户自定义的 ChannelInitializer 。

            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)//配置主从Reactor
                  ...........
               .handler(new ChannelInitializer<NioServerSocketChannel>() {
                 @Override
                 protected void initChannel(NioServerSocketChannel ch) throws Exception {
                              ....自定义pipeline初始化逻辑....
                               ChannelPipeline p = ch.pipeline();
                               p.addLast(channelHandler1);
                               p.addLast(channelHandler2);
                               p.addLast(channelHandler3);
                                    ........
                 }
             })

随后 netty 会以异步任务的形式向 pipeline 的末尾添加 ServerBootstrapAcceptor ,至此 NioServerSocketChannel 中 pipeline 的初始化工作就全部完成了。

 

7.2 NioSocketChannel 中 pipeline 的初始化


在 7.1 小节中笔者举的这个 pipeline 初始化的例子相对来说比较复杂,当我们把这个复杂例子的初始化逻辑搞清楚之后,NioSocketChannel 中 pipeline 的初始化过程就变的很简单了。

         

ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)//配置主从Reactor
                  ...........
               .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 protected void initChannel(SocketChannel ch) throws Exception {
                              ....自定义pipeline初始化逻辑....
                               ChannelPipeline p = ch.pipeline();
                               p.addLast(channelHandler1);
                               p.addLast(channelHandler2);
                               p.addLast(channelHandler3);
                                    ........
                 }
             })
public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {
    //保存用户自定义ChannelInitializer
    private volatile ChannelHandler childHandler;
}

《Netty 如何高效接收网络连接》一文中我们介绍过,当客户端发起连接,完成三次握手之后,NioServerSocketChannel 上的 OP_ACCEPT 事件活跃,随后会在 NioServerSocketChannel 的 pipeline 中触发 channelRead 事件。并最终在 ServerBootstrapAcceptor 中初始化客户端 NioSocketChannel 。一文聊透Netty IO事件的编排利器pipeline |详解所有IO事件(七)-鸿蒙开发者社区

主从Reactor组完整结构.png

private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter {

       @Override
        @SuppressWarnings("unchecked")
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            final Channel child = (Channel) msg;
            child.pipeline().addLast(childHandler);
                    ...........
       }
}

在这里会将用户自定义的 ChannelInitializer 添加进 NioSocketChannel 中的 pipeline 中,由于此时 NioSocketChannel 还没有向 sub reactor 开始注册。所以在向 pipeline 中添加 ChannelInitializer 的同时会伴随着 PendingHandlerAddedTask 被添加进 pipeline 的任务列表中。

一文聊透Netty IO事件的编排利器pipeline |详解所有IO事件(七)-鸿蒙开发者社区

 niosocketchannel中pipeline的初始化.png


后面的流程大家应该很熟悉了,和我们在7.1小节中介绍的一模一样,当 NioSocketChannel 再向 sub reactor 注册成功之后,会执行 pipeline 中的任务列表中的 PendingHandlerAddedTask 任务,在 PendingHandlerAddedTask 任务中会回调用户自定义 ChannelInitializer 的 handelrAdded 方法,在该方法中执行 initChannel 方法,用户自定义的初始化逻辑就封装在这里面。在初始化完 pipeline 后,将 ChannelInitializer 从 pipeline 中删除,并回调其 handlerRemoved 方法。

 

至此客户端 NioSocketChannel 中 pipeline 初始化工作就全部完成了。

标签
已于2022-7-18 17:24:24修改
收藏
回复
举报
回复
    相关推荐