回顾

如果忘记是怎么进入一下方法的,请回去看newChild章节

io.netty.channel.nio.NioEventLoop#run

...
// 检查I/O事件
select(wakenUp.getAndSet(false));
...
// 处理上面select查到的I/O事件
processSelectedKeys();
...
// 运行上面处理的事件集
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
...

上一节,简单走了走select方法,知道了select有阻塞和非阻塞两种方案,并且了解到Netty是如何解决jdk空轮询bug的。而这节就继续往下走,看看processSelectedKeys()方法的执行逻辑。

Netty Version:4.1.6


开始追踪

在追踪之前,先将视角拉回到io.netty.channel.nio.NioEventLoop#run(忘记的话看newChild那章),然后进入processSelectedKeys方法:
io.netty.channel.nio.NioEventLoop#processSelectedKeys

    private void processSelectedKeys() {
        if (selectedKeys != null) {
            processSelectedKeysOptimized(selectedKeys.flip());
        } else {
            processSelectedKeysPlain(selector.selectedKeys());
        }
    }
  • 说明一下,我测试了一下,即便没有客户端连接,selectedKeys也不会为Null,它只会是一个size=0的数组。
  • 这里的selectedKeys类型为SelectedSelectionKeySet,在newChild这章讲过。
  • 另外,在之后的Netty版本中,这里的flip方法被弃用了,原因跟GC回收有关,详细看这个issues

由于selectedKeys在没有连接的情况下仍不为Null,所以继续进入processSelectedKeysOptimized方法:
io.netty.channel.nio.NioEventLoop#processSelectedKeysOptimized

    private void processSelectedKeysOptimized(SelectionKey[] selectedKeys) {
        for (int i = 0;; i ++) {
            final SelectionKey k = selectedKeys[i];
            if (k == null) {
                break;
            }
            selectedKeys[i] = null;

            final Object a = k.attachment();

            if (a instanceof AbstractNioChannel) {
                processSelectedKey(k, (AbstractNioChannel) a);
            } else {
                @SuppressWarnings("unchecked")
                NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
                processSelectedKey(k, task);
            }

            if (needsToSelectAgain) {
                for (;;) {
                    i++;
                    if (selectedKeys[i] == null) {
                        break;
                    }
                    selectedKeys[i] = null;
                }

                selectAgain();
                selectedKeys = this.selectedKeys.flip();
                i = -1;
            }
        }
    }
  • 这里的大致逻辑:就是拿我们上一节检测到的I/O事件(封装到selectedKey中),再执行processSelectedKey逐个执行/处理。

进入processSelectedKey方法,由于代码很多,只贴一些关键的:
io.netty.channel.nio.NioEventLoop#processSelectedKey(java.nio.channels.SelectionKey, io.netty.channel.nio.AbstractNioChannel)

        final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
        if (!k.isValid()) {
            final EventLoop eventLoop;
            try {
                eventLoop = ch.eventLoop();
            } catch (Throwable ignored) {
                return;
            }
            if (eventLoop != this || eventLoop == null) {
                return;
            }
            // close the channel if the key is not valid anymore
            unsafe.close(unsafe.voidPromise());
            return;
        }
            int readyOps = k.readyOps();
            if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
                int ops = k.interestOps();
                ops &= ~SelectionKey.OP_CONNECT;
                k.interestOps(ops);

                unsafe.finishConnect();
            }

            if ((readyOps & SelectionKey.OP_WRITE) != 0) {
                ch.unsafe().forceFlush();
            }

            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
                if (!ch.isOpen()) {
                    return;
                }
            }
  • 上面的ifelse大法好其实就是在判断事件的类型,然后执行不同策略。
  • OP_ACCEPT很眼熟,其实是在端口绑定那节里面讲过的。
  • 一般情况下parentGroup(bossGroup)负责accept事件,然后将任务交给childGroup。
  • unsafe.close(unsafe.voidPromise());似乎很关键,后面可能会继续深入。

parentGroup和childGroup就是指ServerBootstrap中group方法绑定的两NioEventLoopGroup。


小结

  • processSelectedKeys方法的执行逻辑很简单,就是通过ifelse大法好判断前面select方法检测出来事件的事件类型,最终调用不同的方法去处理。
  • 而经过实际测试,一般情况下parentGroup是负责accept事件,然后再将任务交给childGroup执行。