6.给大动脉来一刀 - ChannelPipeLine

5.不完全的 ServerBootstrap 启动过程源码分析 文章中, 发现有好多类没有了解过, 后续几篇文章都会先了解这些类的左右, 然后在开始说处理客户端连接的问题.

ChannelPipeline 是 ChannelHandler 的管理容器, 它内部维护了一个 ChannelHandler 的链表, 可以方便的实现 ChannelHandler 的查找、添加、删除、替换、遍历等.

每个 Channel 中都会包含一个 ChannelPipleline, 就像之前说的 NioServerSocketChannel 中就包含一个 ChannelPipeline.

  • ChannelOutboundInvoker: 用来处理一些出站数据.
  • ChannelInboundInvoker: 用来处理一些入站数据.

先不管这两个接口因为在 ChannelHandlerContext 会说, 主要是看 ChannelPipeLine 接口的默认实现 DefaultChannelPipeline.

ChannelPipeline 将多个 ChannelHandler 链接在一起来让事件在其中传播处理.

下面展示了一个同时具有入站处理器和出站处理器的 ChannelPipeline:

需要注意的是, 对于入站事件, 总是从左往右传播事件, 所以图中第一个处理入站数据的 ChannelHandler 是1, 然后是2, 然后是4. 对于出站事件来说, 总是从右往左传播事件, 所以图中第一个处理出站数据的 ChannelHandler 是5, 然后是3.

ChannelHandler 的存储

public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
    final AbstractChannelHandlerContext newCtx;
    synchronized (this) {
        // 检查是不是添加了多次
        checkMultiplicity(handler);

        /*
         * 1. filterName 方法, 如果名称重复抛出异常, 如果没有名字则会生成 类名#序号 的名称.
         * 2. 创建 DefaultChannelHandlerContext 实例.
         */
        newCtx = newContext(group, filterName(name, handler), handler);

        /*
         * 下面有详细解释
         */
        addLast0(newCtx);

        /*
         * registered: 表示 io.netty.channel.AbstractChannel 还没有注册, 当注册后该值永远为 true.
         * AbstractChannel 注册就表示 Java 中的 ServerSocketChannel 已经注册到 Selector.
         * 
         * 主要用来保存, Channel 没有注册前添加的任务, 当注册完成后会立即执行这些任务.
         */
        if (!registered) {
            // 只有 handlerAdded 和 handlerRemoved 方法没调用前, 才能通过 CAS 操作将 newCtx 中的 INIT 设置为 ADD_PENDING.
            // INIT: 默认值为 0.
            // ADD_PENDING: 值为 1.
            newCtx.setAddPending();
            
            /*
             * 该方法会根据参数二, 创建不同的任务:
             *   true: PendingHandlerAddedTask.
             *   false: PendingHandlerRemovedTask.
             *
             * 第一个任务会保存到 pendingHandlerCallbackHead 变量中, 后续任务不断追加.
             */
            callHandlerCallbackLater(newCtx, true);
            return this;
        }

        // 如果 ChannelHandler 没有绑定, 则会使用与当前 Channel 注册的 EventLoop.
        EventExecutor executor = newCtx.executor();
        // 如果是由另一个线程调用了该方法(addLast), 则会回调 ChannelHandlerContext 中的 callHandlerAdded 方法.
        if (!executor.inEventLoop()) {
            callHandlerAddedInEventLoop(newCtx, executor);
            return this;
        }
    }
    // 使用当前线程回调 ChannelHandlerContext 中的 callHandlerAdded 方法,
    // 该方法最终会调用 ChannelHandler 的 handlerAdded 方法, 表示该 ChannelHandler 要被添加到 pipeline 中.
    // 例如 ChannelInitializer 抽象类就实现了 handlerAdded 方法, 直接执行 initChannel 方法.
    callHandlerAdded0(newCtx);
    return this;
}

在看这个主要方法(addLast0)时, 先看两个主要属性和这两个属性的初始化:

  • head: 双向链表头.
  • tail: 双向链表尾.
protected DefaultChannelPipeline(Channel channel) {
    // ...

    // 这两个类都是简单的内部类, 没啥可说的
    tail = new TailContext(this);
    head = new HeadContext(this);

    // 由于是双向链表, 所以 head 的下一个是 tail,
    // 而 tail 的上一个是 head.
    head.next = tail;
    tail.prev = head;
}

首先已经知道了 ChannelPipeline 中, 不是使用数组来保存 ChannelHandler 的.

但为啥需要创建这两个内部类?
我个人认为只是为了方便实现 addLast0 方法, 还有就是保证传递给 ServerBootstrapAcceptor.

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

下面是以图形的方式说明一下.

上面这张图是刚初始化完成后的 ChannelHandler 的链表.

下面这张图是添加了一个 ChannelHandler 后的结果, 也就是说, 无论你怎么添加或移除, 头和尾的 handler 永远不会改变.

ChannelPipeline 的传播

当服务端监听到客户端发生的事件时, 可以使用一个或多个 ChannelHandler 处理相应的业务逻辑.

第一个 ChannelHandlerchannelRead 方法执行是在 NioEventLoop#processSelectedKey 方法中调用的, 该方法会根据不同的事件类型执行不同的方法.

OP_ACCEPT 事件传播

最终会调用 NioMessageUnsafe 中的 read() 方法.

public void read() {
    private final List<Object> readBuf = new ArrayList<Object>();
    // ...
    try {
        try {
            // 会获取到所有的 java.nio.channels.SocketChannel 添加到 readBuf 集合中.
            do {
                // 使用 java.nio.channels.ServerSocketChannel#accept 方法,
                // 获取到 java.nio.channels.SocketChannel 并封装成 NioSocketChannel.
                int localRead = doReadMessages(readBuf);
                // ...
            } while (allocHandle.continueReading());
        } catch (Throwable t) {
            exception = t;
        }

        int size = readBuf.size();
        for (int i = 0; i < size; i ++) {
            readPending = false;
            // 然后调用 ChannelHandler 中的 channelRead 方法.
            // 注意: 1. 至于会执行哪些 ChannelHandler, 是根据 6.不完全的ServerBootstrap启动过程源码分析
            //          文章中的 handler() 方法所添加的.
            //       2. 该方法的参数是 NioSocketChannel 啊!
            pipeline.fireChannelRead(readBuf.get(i));
        }
        readBuf.clear();
        allocHandle.readComplete();
        // 执行 ChannelHandler 中的 channelReadComplete 方法.
        pipeline.fireChannelReadComplete();

        // 如果有异常则会执行 ChannelHandler 中的 exceptionCaught 方法.
        if (exception != null) {
            closed = closeOnReadError(exception);
            pipeline.fireExceptionCaught(exception);
        }

        // ...
    } finally {
        if (!readPending && !config.isAutoRead()) {
            removeReadOp();
        }
    }
}

注意 channelRead 方法, 在该方法中必须要显示的调用 super.channelRead(ctx, msg);ctx.fireChannelRead(msg); 方法. 保证 ServerBootstrapAcceptor 这个 ChannelHandler 被调用到, 因为只有这样才能注册 NioSocketChannel.

而注册 NioSocketChannel 时, 都会做什么操作, 可以通过 ChannelHandler 文章得到答案.

OP_WRITE 事件传播

最终会调用 NioMessageUnsafe 中的 read() 方法.

        public final void read() {
            final ChannelConfig config = config();
            // ...
            final ChannelPipeline pipeline = pipeline();
            final ByteBufAllocator allocator = config.getAllocator();
            final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
            allocHandle.reset(config);

            ByteBuf byteBuf = null;
            boolean close = false;
            try {
                do {
                    byteBuf = allocHandle.allocate(allocator);
                    allocHandle.lastBytesRead(doReadBytes(byteBuf));
                    if (allocHandle.lastBytesRead() <= 0) {
                        byteBuf.release();
                        byteBuf = null;
                        close = allocHandle.lastBytesRead() < 0;
                        if (close) {
                            readPending = false;
                        }
                        break;
                    }

                    allocHandle.incMessagesRead(1);
                    readPending = false;
                    // 注意这里执行的是 NioSocketChannel 中 pipeline 中的 ChannelHandler 链表的 channelRead 方法.
                    pipeline.fireChannelRead(byteBuf);
                    byteBuf = null;
                } while (allocHandle.continueReading());

                allocHandle.readComplete();
                // 最后还是执行 channelReadComplete 方法.
                pipeline.fireChannelReadComplete();

                if (close) {
                    closeOnRead(pipeline);
                }
            } catch (Throwable t) {
                handleReadException(pipeline, byteBuf, t, close, allocHandle);
            } finally {
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }
    }

该事件与 OP_ACCEPT 事件类似, 只不过 channelRead 方法传递的是数据, 而 OP_ACCEPT 事件传递的是 NioSocketChannel 也就是客户端信息.

参考资料

Netty学习:ChannelOutboundInvoker
ChannelHandlerContext简介
Netty主要类关系
拜托!面试请不要再问我 Netty 底层架构原理!
Netty源码解读(三)Channel与Pipeline
8.10 ChannelPipeline详解
死磕Netty源码之ChannelPipeline源码解析(一)
【Netty】(8)---理解ChannelPipeline
Netty学习笔记之ChannelHandler
接口ChannelPipeline
Netty 之 ChannelPipeline 源码解析

posted @ 2020-08-18 16:25  scikstack  阅读(149)  评论(0编辑  收藏  举报