解读消息处理

210 阅读16分钟

解读消息处理

对服务端 Accept事件解读

由前述,我们已经知道了服务端 NioServerSocketChannel相应的 SelectionKey已经设置了感兴趣事件为 Accept,并且来分析了 NioEventLoop的工作原理,这里来服务端的工作细节进行展开讲解

由前述,我们知道 NioEventLoop处理 IO事件对应的方法是 processSelectedKeys

    // 执行 IO事件入口
    private void processSelectedKeys() {
        if (selectedKeys != null) { // 正常情况下, 走这条逻辑
            processSelectedKeysOptimized();
        } else {
            processSelectedKeysPlain(selector.selectedKeys());
        }
    }

    private void processSelectedKeysOptimized() {
        // 遍历已就绪的 Channel事件集合
        for (int i = 0; i < selectedKeys.size; ++i) {
            // 表示当前迭代的就绪事件 SelectionKey
            final SelectionKey k = selectedKeys.keys[i];
            // null out entry in the array to allow to have it GC'ed once the Channel close
            // See https://github.com/netty/netty/issues/2363
            selectedKeys.keys[i] = null; // help GC -> 有点味道了, 这不就类似于 NIO中迭代器遍历后移除自身吗 ?
            // 获取注册阶段向 Selector上提供的 "附件" - 每个 Channel都可以去绑定一个 attchment
            // 经过前面分析, 这里的附件便是 this(Channel注册时将自己作为附件了), 即 Channel本身
            // 这里的 Channel既有可能是 NioServerSocketChannel | NioSocketChannel
            final Object a = k.attachment();

            // 大部分情况下, 都会来执行这个分支
            if (a instanceof AbstractNioChannel) {
                // 处理 IO事件
                processSelectedKey(k, (AbstractNioChannel) a);
            } else {
                @SuppressWarnings("unchecked")
                NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
                processSelectedKey(k, task);
            }

            if (needsToSelectAgain) {
                // null out entries in the array to allow to have it GC'ed once the Channel close
                // See https://github.com/netty/netty/issues/2363
                selectedKeys.reset(i + 1);

                selectAgain();
                i = -1;
            }
        }
    }

我们关心的是服务端 Accept事件被触发,因此这里的 a,会是 NioServerSocketChannel,接着来执行 processSelectedKey方法

    // 参数一:事件对应 参数二:注册时的 Channel
    private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
        // 1.NioServerSocketChannel -> NioMessageUnsafe
        // 2.NioSocketChanenl -> NioByteUnsafe
        final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
        // 一般不会成立, 因为 SelectionKey是合法的
        if (!k.isValid()) {
            final EventLoop eventLoop;
            try {
                eventLoop = ch.eventLoop();
            } catch (Throwable ignored) {
                // If the channel implementation throws an exception because there is no event loop, we ignore this
                // because we are only trying to determine if ch is registered to this event loop and thus has authority
                // to close ch.
                return;
            }
            // Only close ch if ch is still registered to this EventLoop. ch could have deregistered from the event loop
            // and thus the SelectionKey could be cancelled as part of the deregistration process, but the channel is
            // still healthy and should not be closed.
            // See https://github.com/netty/netty/issues/5125
            if (eventLoop == this) {
                // close the channel if the key is not valid anymore
                unsafe.close(unsafe.voidPromise());
            }
            return;
        }

        try {
            // 获取当前 SelectionKey上的事件集合(对应的是单个 Channel)
            int readyOps = k.readyOps();
            // We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
            // the NIO JDK channel implementation may throw a NotYetConnectedException.
            if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
                // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
                // See https://github.com/netty/netty/issues/924
                int ops = k.interestOps();
                ops &= ~SelectionKey.OP_CONNECT;
                k.interestOps(ops);

                unsafe.finishConnect();
            }

            // Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
            // true - 有可写事件发生
            if ((readyOps & SelectionKey.OP_WRITE) != 0) {
                // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
                ch.unsafe().forceFlush();
            }

            // Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
            // to a spin loop
            // 条件 1:如果当前 SelectionKey上对应有 "可读" | "可连接"事件发生的话
            // 条件 2:readOps == 0 -> 这又是什么情况呢 ? 其实是对 bug的处理, 在不该响应的时候进行了响应
            // 这里会通过 Unsafe.read()在此将当前 Channel的事件列表设置为 监听读
            // true - 对于服务端而言便是, 有可读或可连接事件发生了
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                // 1.server -> 创建客户端 socketChannel
                // 2.socketChannel -> NioByteUnsafe.read() 会去读取缓冲区中的数据, 并且将数据响应到 pipeline中...
                unsafe.read();
            }
        } catch (CancelledKeyException ignored) {
            unsafe.close(unsafe.voidPromise());
        }
    }

由于是 Accept事件被触发,因此来到 unsafe.read()

# NioMessageUnsafe.read()

        @Override
        public void read() {
            assert eventLoop().inEventLoop();
            // 获取服务端 config
            final ChannelConfig config = config();
            // 获取服务端 pipeline
            final ChannelPipeline pipeline = pipeline();
            // RecvByteBufAllocator - 控制读循环, 以及预测下一次创建的 ByteBuf的大小
            final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
            // 重置 config, 待讲
            allocHandle.reset(config);

            boolean closed = false;
            Throwable exception = null;
            try {
                try {
                    // 读取消息的循环
                    do {
                        // 如果 localRead == 1, 表示接收到了新连接, 此时新创建的 NioSocketChannel会被添加到 readBuf中
                        int localRead = doReadMessages(readBuf);
                        if (localRead == 0) {
                            break;
                        }
                        // 条件成立, 说明当前服务端处于关闭状态
                        if (localRead < 0) {
                            closed = true;
                            break;
                        }
                        // 更新已读的消息数量
                        allocHandle.incMessagesRead(localRead);
                    } while (continueReading(allocHandle));
                } catch (Throwable t) {
                    exception = t;
                }

                // 执行到这时, readBuf存储的是客户端 NioSocketChannel对象

                int size = readBuf.size();
                for (int i = 0; i < size; i ++) {
                    readPending = false;
                    // 向服务端 pipeline去传播客户端 channel对象 - 关注于 ServerBootstrapAcceptor
                    pipeline.fireChannelRead(readBuf.get(i));
                }
                readBuf.clear();
                allocHandle.readComplete();
                // 重新来设置下 Selector上 server SelectionKey感兴趣事件为 Accept, 以来保证 selector能够继续帮 server监听是否有可连接事件与否
                pipeline.fireChannelReadComplete(); // readOps == 0下, 会来到这

                if (exception != null) {
                    closed = closeOnReadError(exception);

                    pipeline.fireExceptionCaught(exception);
                }

                if (closed) {
                    inputShutdown = true;
                    if (isOpen()) {
                        close(voidPromise());
                    }
                }
            } finally {
                // Check if there is a readPending which was not processed yet.
                // This could be for two reasons:
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
                //
                // See https://github.com/netty/netty/issues/2254
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }

在处理消息的循环中,doReadMessages()干了什么?

# NioServerSocketChannel.doReadMessages()

    @Override
    protected int doReadMessages(List<Object> buf) throws Exception {
        // 参数:JDK层面的 ServerSocketChannel
        // 内部通过 ServerSocketChannel.accept()来获取到 客户端 SocketChannel
        SocketChannel ch = SocketUtils.accept(javaChannel());

        try {
            if (ch != null) {
                // new NioSocketChannel
                // 参数一:NioServerSocketChannel 参数二:SocketChannel
                // 这里做了一层封装, 将 JDK层面的 SocketChannel封装成了 Netty层面的 NioSocketChannel
                buf.add(new NioSocketChannel(this, ch));
                return 1;
            }
        } catch (Throwable t) {
            logger.warn("Failed to create a new channel from an accepted socket.", t);

            try {
                ch.close();
            } catch (Throwable t2) {
                logger.warn("Failed to close a socket.", t2);
            }
        }

        return 0;
    }

有上述,可知如果有连接的话,便通过 JDK层面的 ServerSocketChannel.accept()来处理连接,并且将获得的客户端连接 SocketChannel封装成了 Netty层面的 NioSocketChannel并且加入到了列表 buf中,由于引用传递,可知最终 readBuf中存储着的便是一个个的客户端连接 NioSocketChannel

后续便是对 readBuf中客户端连接迭代进行处理,由 read()可知,调用了 pipeline.fireChannelRead(readBuf.get(i))

这一步往通道中传播了 ChannelRead()事件,感兴趣的 handler可以来进行处理,由一开始所示的 demo可知,此时的服务端 pipeline:head <-> LoggingHandler <-> ServerBootstrapAcceptor <-> tail

显然,我们关注的便是 ServerBootstrapAcceptor是如何来响应 ChannelRead事件的

# ServerBootstrapAcceptor.channelRead()

        // 专门来处理客户端连接事件的
        // 参数一:包装当前 handler的 ctx
        // 参数二:NioSocketChannel实例
        @Override
        @SuppressWarnings("unchecked")
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            final Channel child = (Channel) msg;
            // 以 demo来讲, 往客户端 pipeline中添加 ChannelInitializer
            // 此时, 客户端 pipeline: Head <-> ChannelInitializer <-> Tail
            child.pipeline().addLast(childHandler);
            // 这里就是为客户端 Channel来做些配置
            setChannelOptions(child, childOptions, logger);
            setAttributes(child, childAttrs);

            try {
                // 在 childGroup.register(child)干了什么 ?
                // 从 workerGroup中选出一个 NioEventLoop, 往其注册 NioSocketChannel, 初始感兴趣事件为 0
                // 并且完成了 ChannelInitializer的解压操作, 之后在 HeadContext.read()方法中便会将客户端 NioSocketChannel感兴趣事件设置为包含 Read
                childGroup.register(child).addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {
                        if (!future.isSuccess()) {
                            forceClose(child, future.cause());
                        }
                    }
                });
            } catch (Throwable t) {
                forceClose(child, t);
            }
        }

由上述可知,对客户端 NioSocketChannel进行了初始化,并往客户端 Channel对应 pipeline中添加了一个 handler,以 demo来讲,这里也是在 childHandler时所配置的一个 ChannelInitializer

然后呢?进行了注册逻辑,childGroup.register(child)

# MultithreadEventLoopGroup.register()

    @Override
    public ChannelFuture register(Channel channel) {
        // 这里 next()的实现实际上就是取决于 Chooser的实现 - 在 NioEventLoop构造时分析过了 - 这里来获取到了 Group中的 EventLoop对象
        // EventLoop其实就是单线程线程池(内包含 Selector), 在 Group构造时涉及到 newChild()方法中对 EventLoop进行了实例化, 类型变为 SingleThreadEventLoop
        // 主要去干两件事:
        //   处理 EventLoop工作队列中的任务
        //   处理 EventLoop内部 selector上注册的 Channel事件
        // 以服务端进行分析, 这里的 channel便是服务端的 NioServerSocketChanel
        // register()里主要干的事:将 channel与 EventLoop绑定在了一块, 将 channel的注册作为一个任务添加到了 EventLoop的任务队列中去, 实现了异步操作
        // 主线程不会去阻塞.
        return next().register(channel);
    }

从 workerGroup中选择出一个 NioEventLoop进行注册,next()实现取决于 chooser实现,chooser实现取决于具体的 NioEventLoop数量,本质上都是进行 Round-Robin

# SingleThreadEventLoop.register()

    // 返回值 ChannelFuture, 可知 register是可以来实现异步操作的
    @Override
    public ChannelFuture register(Channel channel) {
        // 可以看到, 这里对 channel进行了些许封装 - 赋予 Channel可执行异步操作的能力(监听器 - 关联事件完成后, 自动去执行回调)
        return register(new DefaultChannelPromise(channel, this));
    }

    @Override
    public ChannelFuture register(final ChannelPromise promise) {
        ObjectUtil.checkNotNull(promise, "promise");
        // 对于服务端:
        // promise.channel() - NioServerSocketChannel
        // promise.channel().unsafe() - NioMessageUnsafe - 这块调试一下就能知道结果了
        // 参数一:NioEventLoop 参数二:以服务端启动分析, 这块包含的便是 NioServerSocketChannel的 ChannelPromise
        // 对于客户端:
        // promise.channel() - NioSocketChannel
        // promise.channel().unsafe() - NioByteUnsafe
        // 参数一:workerGroup中的 NioEventLoop 参数二:包含着 NioSocketChannel的 ChannelPromise
        promise.channel().unsafe().register(this, promise);
        return promise;
    }

由于是对客户端 NioSocketChannel进行注册,此时获取的 unsafe实际上是 NioByteUnsafe实例

# AbstractUnsafe.register()

        // 参数一:NioEventLoop 参数二:以服务端启动分析, 这块包含的便是 NioServerSocketChannel的 ChannelPromise
        @Override
        public final void  register(EventLoop eventLoop, final ChannelPromise promise) {
            ObjectUtil.checkNotNull(eventLoop, "eventLoop");
            // 判断当前 Channel是否有注册过
            if (isRegistered()) {
                // 通过 promise设置失败结果, 并且会去回调监听者, 执行失败的逻辑
                promise.setFailure(new IllegalStateException("registered to an event loop already"));
                return;
            }

            if (!isCompatible(eventLoop)) {
                promise.setFailure(
                        new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
                return;
            }

            // 因为当前方法是在 Unsafe类中, 因此这里通过 AbstractChannel.this去获取外部对象, 这里我们来考虑是:NioServerSocketChannel的场景
            // 这里就是来绑定下关系 (Channel & NioEventLoop), 后续 Channel上的事件或者任务都会依赖当前 EventLoop
            // 以客户端来讲, 这里同样也是为 NioSocketChannel来绑定一个 NioEventLoop - WorkerGroup.next()
            AbstractChannel.this.eventLoop = eventLoop;

            // 这里来判读当前线程是不是与 EventLoop绑定的线程
            // 这块通常不会成立, 以服务端启动分析, 这里线程便是主线程, 非 EventLoop中所绑定线程
            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else { // 这块设计十分巧妙, 如果当前线程不是与 EventLoop绑定的线程, 那么就将注册的任务转交给 EventLoop去执行, 添加到 EventLoop的任务队列中去
                // 这也就实现了单线程下线程安全, 且也实现了主线程执行的非阻塞
                try {
                    // 须清楚一点, 到这里为止, eventLoop对应的线程尚未被创建, 线程的创建时机是怎样的 ?
                    // 当往 eventLoop中第一次提交任务时, 会去创建线程! - 对于客户端 NioEventLoop中线程创建同样!
                    // 提交了异步任务 ①
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise); // 以服务端来讲, 这里的 promise包含着 NioServerSocketChannel
                        }
                    });
                } catch (Throwable t) {
                    logger.warn(
                            "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                            AbstractChannel.this, t);
                    closeForcibly();
                    closeFuture.setClosed();
                    safeSetFailure(promise, t);
                }
            }
        }

由前述,我们也知道,在第一次往 NioEventLoop中添加任务时创建了其对应的 thread,即这里完成了客户端 NioSocketChannel对应的 NioEventLoop中线程的创建,并去调用了 register0()

# AbstractChannel.register0()

        // 须清楚一点;该方法的执行是由 EventLoop来进行的, 与 Main线程无任何关系
        // 参数 promise表示注册结果的, 外部可以向它注册监听器, 以来完成注册后的逻辑
        // 这块也好理解, 只有注册成功后才能去执行 bind操作嘛. -即退出该方法后会去执行一个回调, 以此来执行 bind0()
        // 现在是来执行第一个任务, 接来下是 init()里添加的另一个异步任务,最后一个才会是 bind0()里提交的任务 ③
        private void register0(ChannelPromise promise) {
            try {
                // check if the channel is still open as it could be closed in the mean time when the register
                // call was outside of the eventLoop
                if (!promise.setUncancellable() || !ensureOpen(promise)) {
                    return;
                }
                // 表示当前  Channel是不是第一次进行注册
                boolean firstRegistration = neverRegistered;

                doRegister(); // 关键, 在此方法中完成了 channel的注册逻辑, 即将 channel注册到 selector上
                // 这里就是来设置下注册相关的状态位
                neverRegistered = false;
                registered = true;

                // Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
                // user may already fire events through the pipeline in the ChannelFutureListener.
                // invokeHandlerAddIfNeeded来完成对 ChannelInitializer的解压缩, 并且将自己移除了出去
                // 这里对应服务端和客户端同样适用!
                pipeline.invokeHandlerAddedIfNeeded();// invokeHandlerAddIfNeeded中实际上会去添加异步任务 ②

                // 这一步会去回调注册相关的 promise上注册的那些 listener, 如 "主线程"在 regFuture上注册的监听者
                safeSetSuccess(promise); // 这里回调到 doBind0()方法时又会去添加了一个异步任务 ③

                // 这里传递了一个入站事件, 从 pipeline头开始传递, 此时关注的 handler可以来做一些事情
                pipeline.fireChannelRegistered();


                // Only fire a channelActive if the channel has never been registered. This prevents firing
                // multiple channel actives if the channel is deregistered and re-registered.
                // 以服务端启动流程来进行分析:
                // 这一步实际上是来判断当前 NioServerSocketChannel是否有完成绑定
                // false - 当前是在 register0中, 只来完成了底层 ServerSocketChannel的注册而已
                // 这块也好理解, 只有注册成功后才能去执行 bind操作嘛. -即退出该方法后会去执行一个回调, 以此来执行 bind0()
                // bind()实际上也是由 eventLoop线程去干的是事
                // 以客户端连接进行考虑, 此时客户端已经完成了连接, 因此会进入该方法里面
                if (isActive()) {
                    // 客户端会进来这里, 服务端不会
                    // this == NioSocketChannel
                    // true
                    if (firstRegistration) {
                        // 往客户端 pipeline中广播 ChannelActive事件, 需要注意此时 ChannelInitializer已经进行了解压
                        // 因此可以说, 这里也是一个扩展的, 感兴趣的 handle可以来进行些 ops
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                        // This channel was registered before and autoRead() is set. This means we need to begin read
                        // again so that we process inbound data.
                        //
                        // See https://github.com/netty/netty/issues/4805
                        beginRead();
                    }
                }
            } catch (Throwable t) {
                // Close the channel directly to avoid FD leak.
                closeForcibly();
                closeFuture.setClosed();
                safeSetFailure(promise, t);
            }
        }

这块已经在服务端启动流程中讲述过了,大致就是在 doRegister()方法中完成客户端 Channel的注册,初始感兴趣事件为 0,因为这是个共有的类嘛,可以理解

接着在 pipeline.invokeHandlerAddedIfNeeded()完成了前面所添加的 ChannelInitializer的解压,之后将自身移除了处理,这里可以来做些业务逻辑

然后设置了下 promise的结果,之后便是往 pipeline中传播了 ChannelRegistered()事件,感兴趣的 handler来做些操作

我们关注点在于客户端 NioSocketChannel感兴趣事件是在什么时候进行设置的?接着往下看,调用了 pipeline.fireChannelActive(),往通道中传播了 ChannelActive()事件

# HeadContext.channelActive

        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            // 往下一个 handler进行传播
            ctx.fireChannelActive();

            readIfIsAutoRead(); // 这个方法比较重要
        }

我们关注于 readIfAutoRead()

        private void readIfIsAutoRead() {
            if (channel.config().isAutoRead()) {
                channel.read(); // 这里区别于 ChannelRead, 对应的是一个出站操作
            }
        }

   		 @Override
   		 public Channel read() {
        		pipeline.read();
        		return this;
    		}

// DefaultChannelPipeline
    @Override
    public final ChannelPipeline read() {
        tail.read();
        return this;
    }

最终,还是会来到 HeadContext.read()*

# HeadContext.read()

        // 调用 tail.read() - tail.read() - 最终还是回到这里
        @Override
        public void read(ChannelHandlerContext ctx) {
            unsafe.beginRead();
        }

此时的 unsafe,便是 NioByteUnsafe实例

# AbstractChannel.beginRead()

        @Override
        public final void beginRead() {
            assertEventLoop();

            if (!isActive()) {
                return;
            }

            try {
                doBeginRead(); // 关键
            } catch (final Exception e) {
                invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.fireExceptionCaught(e);
                    }
                });
                close(voidPromise());
            }
        }

# AbstractNioChannel.doBeginRead()

    @Override
    protected void doBeginRead() throws Exception {
        // Channel.read() or ChannelHandlerContext.read() was called
        final SelectionKey selectionKey = this.selectionKey;
        if (!selectionKey.isValid()) {
            return;
        }

        readPending = true;

        // 在启动流程时, 将其设置为了 0
        final int interestOps = selectionKey.interestOps();
        if ((interestOps & readInterestOp) == 0) {
            // 对于服务端 NioServerSocketChannel:(绑定之后才进行感兴趣事件的设置)
            // 因此, 这一步便是来感兴趣事件设置为 Accept - 对于 NioServerSocketChannel事件集设置完毕
            // 对于客户端 NioSocketChannel:(在客户端 Channel进行注册后就来进行设置了)
            // 这一步便将处理客户端 Channel对应的 SelectionKey感兴趣事件设置为 Read
            selectionKey.interestOps(interestOps | readInterestOp);
        }
    }

这一步便完成了处理客户端的 SelectionKey感兴趣事件包含了 Read事件

其实,类比下以往了 NIO下的连接玩法,我们不也是这么个逻辑吗?区别在于 Netty为我们很好地进行了拆分和封装,其拆分逻辑使得对各事件的处理各司其职,即 Reactor模式的体现,这也是 NIO向 Netty的过渡吧

由上,服务端的 Accept事件处理解读完毕

客户端 Read事件解读

其实也是针对同一个方法进行处理,不同的是此时处理的逻辑不同而已

由上已知,服务端接收到可连接事件后,创建出了对应的客户端 Channel,与 workerGroup中的某一个 NioEventLoop进行了关系的绑定,并将 Channel注册到了 NioEventLoop中的 Selector上,设置了感兴趣事件为 READ

再一次将视角放回到方法 processSelectedKey()方法

...
                // Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
            // to a spin loop
            // 条件 1:如果当前 SelectionKey上对应有 "可读" | "可连接"事件发生的话
            // 条件 2:readOps == 0 -> 这又是什么情况呢 ? 其实是对 bug的处理, 在不该响应的时候进行了响应
            // 这里会通过 Unsafe.read()在此将当前 Channel的事件列表设置为 监听读
            // true - 对于服务端而言便是, 有可读或可连接事件发生了
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                // 1.server -> 创建客户端 socketChannel
                // 2.socketChannel -> NioByteUnsafe.read() 会去读取缓冲区中的数据, 并且将数据响应到 pipeline中...
                unsafe.read();
            }

此时,我们考虑的是对于客户端有消息发送发送过来,对于服务端而言,便是触发了可读事件,即此时的 Channel是 NioSocketChannel,so,我们关注于方法 NioByteUnsafe.read()

        @Override
        public final void read() {
            // 获取客户端 config
            final ChannelConfig config = config();
            if (shouldBreakReadReady(config)) {
                clearReadPending();
                return;
            }
            // 获取客户端 pipeine
            final ChannelPipeline pipeline = pipeline();
            // 获取缓冲区分配器 - 只要平台不是安卓的, 获取的缓冲区分配器就是池化内存管理的缓冲区分配器 PooledByteBufAllocator
            final ByteBufAllocator allocator = config.getAllocator();
            // RecvByteBufAllocator - 控制读循环, 以及预测下一次创建的 ByteBuf的大小
            final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
            // 重置...
            allocHandle.reset(config);
            // 对 JDk层面的 ByteBuffer的增强接口实现
            ByteBuf byteBuf = null;
            boolean close = false;
            try {
                do {
                    // 参数:池化内存管理的缓冲区分配器, 它才是真正去进行内存分配的主
                    // allocHandle 只是来预测下要去分配的内存大小
                    // 这块便是对 byteBuf分配内存
                    byteBuf = allocHandle.allocate(allocator);

                    // doReadBytes(byteBuf) - 读取当前 socket缓冲区数据到 byteBuf中去 - 返回真实从 SocketChannel读取的数据量
                    // lastBytesRead() - 更新缓存区预测分配器的 最后一次读取数据量...
                    allocHandle.lastBytesRead(doReadBytes(byteBuf));

                    // true:
                    // a.channel底层 socket读缓冲区已经读取完毕了, 没有数据可读了, 返回 0
                    // b.channel对端关闭了,会返回 -1
                    if (allocHandle.lastBytesRead() <= 0) {
                        // nothing was read. release the buffer.
                        byteBuf.release();
                        byteBuf = null;
                        close = allocHandle.lastBytesRead() < 0;
                        if (close) {
                            // There is nothing left to read as we received an EOF.
                            readPending = false;
                        }
                        break;
                    }
                    // 更新缓存区预测分配器 读取的消息数量
                    allocHandle.incMessagesRead(1);
                    readPending = false;
                    // 往客户端 Channel的 pipeline中传播 ChannelRead()事件, 感兴趣的 handler可以来做些业务处理
                    pipeline.fireChannelRead(byteBuf); // 由这不难看出, 每处理一次数据读取都会去广播 ChannelRead()事件
                    byteBuf = null;
                } while (allocHandle.continueReading());

                // 来到这, 说明消息已经读取完毕了

                allocHandle.readComplete();
                // 往客户端 Channel的 pipeline中传播 ChannelReadComplete()事件
                // 并且来设置客户端对应 SelectionKey感兴趣事件包含了 Read, 表示对应 selector需要继续帮当前 channel监听可读事件
                pipeline.fireChannelReadComplete();

                if (close) {
                    closeOnRead(pipeline);
                }
            } catch (Throwable t) {
                handleReadException(pipeline, byteBuf, t, close, allocHandle);
            } finally {
                // Check if there is a readPending which was not processed yet.
                // This could be for two reasons:
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
                //
                // See https://github.com/netty/netty/issues/2254
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }

对于 RecvByteBufAllocator,我们下一期进行讲解,我们只需知道,这里获取的对应的实例其实是 AdaptiveRecvByteBufAllocator实例

根据 config的创建时机也能看出来:

    public NioSocketChannel() {
        this(DEFAULT_SELECTOR_PROVIDER);
    }

    public NioSocketChannel(SelectorProvider provider) {
        this(newSocket(provider));
    }

    public NioSocketChannel(SocketChannel socket) {
        this(null, socket);
    }

    // 参数一:NioServerSocketChannel 参数二:SocketChannel
    public NioSocketChannel(Channel parent, SocketChannel socket) {
        super(parent, socket);
        // 这里来创建了 config
        config = new NioSocketChannelConfig(this, socket.socket());
    }

去看看 NioSocketChannelConfig

        private NioSocketChannelConfig(NioSocketChannel channel, Socket javaSocket) {
            super(channel, javaSocket);
            calculateMaxBytesPerGatheringWrite();
        }

    public DefaultSocketChannelConfig(SocketChannel channel, Socket javaSocket) {
        super(channel);
        this.javaSocket = ObjectUtil.checkNotNull(javaSocket, "javaSocket");

        // Enable TCP_NODELAY by default if possible.
        if (PlatformDependent.canEnableTcpNoDelayByDefault()) {
            try {
                setTcpNoDelay(true);
            } catch (Exception e) {
                // Ignore.
            }
        }
    }

    public DefaultChannelConfig(Channel channel) {
        // 参数二:new AdaptiveRecvByteBufAllocator()
        this(channel, new AdaptiveRecvByteBufAllocator());
    }

    protected DefaultChannelConfig(Channel channel, RecvByteBufAllocator allocator) {
        setRecvByteBufAllocator(allocator, channel.metadata());
        this.channel = channel;
    }

    private void setRecvByteBufAllocator(RecvByteBufAllocator allocator, ChannelMetadata metadata) {
        checkNotNull(allocator, "allocator");
        checkNotNull(metadata, "metadata");
        if (allocator instanceof MaxMessagesRecvByteBufAllocator) {
            ((MaxMessagesRecvByteBufAllocator) allocator).maxMessagesPerRead(metadata.defaultMaxMessagesPerRead());
        }
        setRecvByteBufAllocator(allocator);
    }

    @Override
    public ChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator) {
        // 这一步来创建出了 rcvBufAllocator
        rcvBufAllocator = checkNotNull(allocator, "allocator");
        return this;
    }

一路跟踪发现,最终是在 DefaultChannelConfig中对 rcvBufAllocator进行了赋值,对应的便是 AdaptiveRecvByteBufAllocator实例

接着,我们回到主线,我们主要来考虑客户端对应 Channel是如何来进行数据的读取的

对应的便是方法:doReadBytes(byteBuf),将预测出来的 byteBuf作为参数传递了进去

不难猜想,这个方法便是将 Channel中数据读进 byteBuf中,具体逻辑我们稍后再看

继续看主线,我们发现在 do...while循环中,每次在 doReadBytes()之后都会有 pipeline.fireChannelRead(byteBuf),这不就是每一次处理完消息后往 Channel对应 pipeline中去广播 ChannelRead()事件吗?出了循环,我们发现还有 pipeline.fireChannelReadComplete(),这似乎验证了我们之前的想法?

现在来看 doReadBytes(byteBuf)

# NioSocketChannel.doReadBytes()

    @Override
    protected int doReadBytes(ByteBuf byteBuf) throws Exception {
        final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
        allocHandle.attemptedBytesRead(byteBuf.writableBytes());
        // 参数一:JDK层面客户端对应的 SocketChannel
        // 参数二:想要去读取的数据量
        // 返回真实从 SocketChannel读取的数据量
        return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
    }

对应 #AbstractByteBuf.writeBytes()

    @Override
    public int writeBytes(ScatteringByteChannel in, int length) throws IOException {
        // 这一步来确认当前 ByteBuf是否能够存储这么多的数据量
        ensureWritable(length);
        // writtenBytes - 表示真实读取的数据量
        int writtenBytes = setBytes(writerIndex, in, length);

        // 我们需要清楚, Netty层面的读取数据底层还是会去依赖 JDK层的 ByteBuffer、Channel等

        if (writtenBytes > 0) {
            // 这里来更新 Netty层的 "写指针"
            writerIndex += writtenBytes;
        }
        return writtenBytes;
    }

由 Netty池化知,我们创建出来的 ByteBuf实际上是池化的 ByteBuf,对应 PooledByteBuf

    // 参数一:写指针
    // 参数二:SocketChannel
    // 参数三:想要写入的数据量
    @Override
    public final int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
        try {
            // 参数及对应的是 JDK层面的 ByteBuf - 这里涉及的逻辑比较多
            // 这一步便将 Channel中数据写入 ByteBuffer中
            return in.read(internalNioBuffer(index, length));
        } catch (ClosedChannelException ignored) {
            return -1;
        }
    }

有上述,我们便知道了客户端对应 Channel读取数据时大致上干了什么,其实去抛内存那层面不讲,无外乎就是去通过 JDK层面 Channel去读取数据,每一次读取数据后传播 ChannelRead(),数据全部处理完毕后,触发 ChannelComplete()

其实,还有些许细节未讲,即 ByteBuf的创建细节,以及 ByteBuf创建的 Size是如何来进行预测的?即注重于 RecvByteBufAllocator,下一期,进行剖析

至此,Netty的消息处理解读完毕!