• 6.给大动脉来一刀


    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 源码解析

  • 相关阅读:
    报错:Failed to create BuildConfig class
    emulator control无法使用问题
    the import android cannot be resolved
    报错:init: Could not find wglGetExtensionsStringARB!
    Android SDK升级后报错error when loading the sdk 发现了元素 d:skin 开头无效内容
    Eclipse Android环境搭建
    android中导入低版本project可能会遇到的编译问题(转自: Victor@Beijing)
    22.9
    GIT文档
    机器学习的几个问题探讨
  • 原文地址:https://www.cnblogs.com/scikstack/p/13524247.html
Copyright © 2020-2023  润新知