19-NioEventLoop IO事件处理

NioEventLoop IO事件处理

  • 本文结合代码流程来分析 NioEventLoop 的IO事件处理,
  • 前文我们已经引入并分析了,NioEventLoop 通过一个死循环来监听 Channel发生的IO事件,并对其进行处理,其核心就在 NioEventLoop#run 方法,本文我们直接从该方法入手

一、NioEventLoop#run

  • NioEventLoop#run 方法:
    /**
     * NioEventLoop 的主体循环逻辑,一个死循环,不断的处理 Channel事件,类似于NIO中的while(true){ ... } 死循环
     */
    @Override
    protected void run() {
        logger.info("[Add for debug] NioEventLoop run() 方法启动...");
        //1.死循环,处理注册在自己身上的Channel 的事件
        for (; ; ) {
            try {
                //2.有任务就执行selectNow,没有任务就返回-1
                switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                    //3.默认实现下,不存在这个情况。
                    case SelectStrategy.CONTINUE:
                        continue;
                        //4.SELECT值为-1,返回-1表示hasTasks为false,没有任务
                    case SelectStrategy.SELECT:
                        // 重置 wakenUp 标记为 false
                        // 选择( 查询 )任务
                        select(wakenUp.getAndSet(false));

                        // 'wakenUp.compareAndSet(false, true)' is always evaluated
                        // before calling 'selector.wakeup()' to reduce the wake-up
                        // overhead. (Selector.wakeup() is an expensive operation.)
                        //
                        // However, there is a race condition in this approach.
                        // The race condition is triggered when 'wakenUp' is set to
                        // true too early.
                        //
                        // 'wakenUp' is set to true too early if:
                        // 1) Selector is waken up between 'wakenUp.set(false)' and
                        //    'selector.select(...)'. (BAD)
                        // 2) Selector is waken up between 'selector.select(...)' and
                        //    'if (wakenUp.get()) { ... }'. (OK)
                        //
                        // In the first case, 'wakenUp' is set to true and the
                        // following 'selector.select(...)' will wake up immediately.
                        // Until 'wakenUp' is set to false again in the next round,
                        // 'wakenUp.compareAndSet(false, true)' will fail, and therefore
                        // any attempt to wake up the Selector will fail, too, causing
                        // the following 'selector.select(...)' call to block
                        // unnecessarily.
                        //
                        // To fix this problem, we wake up the selector again if wakenUp
                        // is true immediately after selector.select(...).
                        // It is inefficient in that it wakes up the selector for both
                        // the first case (BAD - wake-up required) and the second case
                        // (OK - no wake-up required).

                        // 唤醒。原因,见上面中文注释
                        if (wakenUp.get()) {
                            selector.wakeup();
                        }
                        // fall through
                    default:
                }

                cancelledKeys = 0;
                needsToSelectAgain = false;

                //ioRatio表示期望的线程处理IO事件的时间比例
                final int ioRatio = this.ioRatio;

                //既然期望是100%,那么久一直处理IO任务
                if (ioRatio == 100) {
                    try {
                        //处理 Channel 感兴趣的就绪 IO 事件
                        processSelectedKeys();
                    } finally {
                        // 运行所有普通任务和定时任务,不限制时间
                        // Ensure we always run tasks.
                        runAllTasks();
                    }
                    //如果不是100%,那么就指定 runAllTasks 的运行时间
                } else {
                    final long ioStartTime = System.nanoTime();
                    try {
                        processSelectedKeys();
                    } finally {
                        // 运行所有普通任务和定时任务,限制时间
                        // Ensure we always run tasks.
                        final long ioTime = System.nanoTime() - ioStartTime;
                        runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                    }
                }
            } catch (Throwable t) {
                handleLoopException(t);
            }
            //优雅关闭,如果抛出来异常,就会处理 shutdown
            // Always handle shutdown even if the loop processing threw an exception.
            try {
                if (isShuttingDown()) {
                    closeAll();
                    if (confirmShutdown()) {
                        return;
                    }
                }
            } catch (Throwable t) {
                handleLoopException(t);
            }
        }
    }
  • 我们按照执行流程一步一步分析,依次分析下面方法
方法名称功能
hasTasks判断是否有任务
select(booolean oldWakenUp)select 事件
processSelectedKeys处理SelectedKeys
runAllTasks执行任务,后面再分析
runAllTasks(long timeoutNanos)指定超时时间执行任务,后面再分析
closeAll关闭
isShuttingDown判断是否处于关闭状态

二、功能方法

2.1 hasTasks

  • hasTasks()方法返回一个布尔值作为selectStrategy.calculateStrategy的参数,
    //SingleThreadEventLoop#hasTasks
    @Override
    protected boolean hasTasks() {
        return super.hasTasks() || !tailTasks.isEmpty();
    }
    
    //SingleThreadEventExecutor
    protected boolean hasTasks() {
        // 仅允许在 EventLoop 线程中执行
        assert inEventLoop(); 
        return !taskQueue.isEmpty();
    }
  • 方法的判断逻辑是:返回true表示任务队列不为空(说明有任务),返回false表示任务队列为空(说明没有任务),额外还会有一个判断当前线程必须属于 EventLoop 内部线程的逻辑,在super的 SingleThreadEventExecutor 内部调用;

2.2 select(booolean oldWakenUp)

  • wakenUp 原子变量标记一个select操作是否应该被唤醒
    /**
     * Boolean that controls determines if a blocked Selector.select should
     * break out of its selection process. In our case we use a timeout for
     * the select method and the select method will block for that time unless
     * waken up.
     * Boolean 变量控制一个阻塞的 Selector.select 操作是否应该被唤醒,因为唤醒方法
     * {@link Selector#wakeup()} 开销比较大,通过该标识,减少调用。
     * 我们的处理方式是使用一个超时时间作为select方法的参数表示应该阻塞的时候,除非被
     * 唤醒,那就中断select方法
     *
     * 唤醒标记。
     *
     * @see #wakeup(boolean)
     * @see #run()
     */
    private final AtomicBoolean wakenUp = new AtomicBoolean();

    wakenUp.getAndSet(false)
  • 根据 calculateStrategy 方法的返回值做两种可能性分析,如果没有任务则返回-1,进入第二个case逻辑,看:select(booolean oldWakenUp) 方法,这里相当于select(true),
    //设置 wakenUp 为false,并将旧值返回作为select 的参数
    select(wakenUp.getAndSet(false));
  • select
/**
     * 对应 NIO 的 select 操作
     * */
    private void select(boolean oldWakenUp) throws IOException {
        //1.记录下Selector对象
        Selector selector = this.selector;
        try {
            // select 计数器
            int selectCnt = 0;
            // 记录当前纳秒时间
            long currentTimeNanos = System.nanoTime();
            //delayNanos() 返回的是最近的一个调度任务的到期时间,没有调度任务返回1秒
            //selectDeadLineNanos 指可以进行select操作的截止时间点
            long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);

            for (; ; ) {
                //四舍五入将 select 操作时间转为毫秒
                long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
                //如果超时时长,则结束 select
                //时间不足1ms,不再进行select操作
                if (timeoutMillis <= 0) {
                    // 如果一次select操作没有进行,那就selectNow 一次,不阻塞
                    if (selectCnt == 0) {
                        selector.selectNow();
                        // 重置 select 计数器
                        selectCnt = 1;
                    }
                    break;
                }

                // If a task was submitted when wakenUp value was true, the task didn't get a chance to call
                // Selector#wakeup. So we need to check task queue again before executing select operation.
                // If we don't, the task might be pended until select operation was timed out.
                // It might be pended until idle timeout if IdleStateHandler existed in pipeline.
                // 此时有任务进入队列且唤醒标志为假,那就selectNow 一次,不阻塞,不会耽误任务执行
                if (hasTasks() && wakenUp.compareAndSet(false, true)) {
                    selector.selectNow();
                    // 重置 select 计数器
                    selectCnt = 1;
                    break;
                }

                // 如果不是前面两种情况,那就阻塞 select ,查询 Channel 是否有就绪的 IO 事件,timeoutMillis表示阻塞时间
                int selectedKeys = selector.select(timeoutMillis);
                // select 计数器 ++
                selectCnt++;

                // 结束 select ,如果满足下面任一一个条件,各种条件用英文注释了,说明需要跳出来
                //select 到了事件、被唤醒、有任务来了、有一个定时任务需要被处理,一旦遇到这些情况之一,那么就需要跳出循环
                // 来处理这些情况了
                //注意传进来的 oldWakenUp 参数只在这里用过一次,含义就是如果oldWakenUp旧值为 true,那么select 一次后就
                // 返回,因为旧值为true,前面的wakeUp的CAS操作不会执行
                if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
                    // - Selected something,
                    // - waken up by user, or
                    // - the task queue has a pending task.
                    // - a scheduled task is ready for processing
                    break;
                }
                //到这里说明不是前面四种情况之一,说明没啥事情要做,如果线程被打断。记录异常(一般情况下不会出现,出现基本是bug 或者错误使用)
                //此时会重置selected keys 并且 break,这很可能是客户端的一个bug,因此日志记录
                if (Thread.interrupted()) {
                    // Thread was interrupted so reset selected keys and break so we not run into a busy loop.
                    // As this is most likely a bug in the handler of the user or it's client library we will
                    // also log it.
                    // See https://github.com/netty/netty/issues/2426
                    if (logger.isDebugEnabled()) {
                        logger.debug("Selector.select() returned prematurely because " +
                                "Thread.currentThread().interrupt() was called. Use " +
                                "NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");
                    }
                    selectCnt = 1;
                    break;
                }

                //到这里说明没有异常,基本也没啥事情需要处理
                long time = System.nanoTime();
                //符合 select超时条件,什么事件都没选择到,selectCnt设置为1,这里超时的话for循环下一次就会break
                if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
                    // timeoutMillis elapsed without anything selected.
                    selectCnt = 1;
                    //select 没有超时就检查select循环次数,若次数到达重建 Selector 对象的阈值上限,进行重建
                } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
                    // The selector returned prematurely many times in a row.
                    // Rebuild the selector to work around the problem.
                    logger.warn("Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.", selectCnt, selector);

                    // 重建 Selector 对象, 这里是对JDK BUG的处理,著名的 epoll bug
                    rebuildSelector();
                    // 修改下 Selector 对象,因为进过 rebuildSelector 方法后 this.selector 已经
                    // 是一个新的 selector 了,重新赋值给方法局部变量
                    selector = this.selector;

                    // Select again to populate selectedKeys.
                    // 立即 selectNow 一次,非阻塞, 重建 selector 之后立即selectNow()
                    selector.selectNow();
                    // 重置 selectCnt 为 1
                    selectCnt = 1;
                    // 结束 select
                    break;
                }

                currentTimeNanos = time;
            }
            //如果selectCnt大于3,则打印日志提示,这个过程有可能重建selector(也可能不重建)
            //如果小于3次,该过程是不可能重建selector的,因为重建阈值是>=3的,此时就不需要大于日志
            if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.", selectCnt - 1, selector);
                }
            }
        } catch (CancelledKeyException e) {
            if (logger.isDebugEnabled()) {
                logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?", selector, e);
            }
            // Harmless exception - log anyway
        }
    }

2.3 processSelectedKeys

2.3.1 processSelectedKeys
  • processSelectedKeys 方法处理发生事件的Channel对应的SelectionKey,这两个方法流程差不多,processSelectedKeysOptimized 到封装好的集合中获取SelectionKey,processSelectedKeysPlain会先select 一次再处理 selectedKeys,我们就看 processSelectedKeysOptimized 方法
    /**
     * 处理事件,其实就是处理 SelectionKey
     * */
    private void processSelectedKeys() {
        if (selectedKeys != null) {
            processSelectedKeysOptimized();
        } else {
            processSelectedKeysPlain(selector.selectedKeys());
        }
    }
2.3.2 processSelectedKeysOptimized
  • processSelectedKeysOptimized:处理之前select获取到的事件集合(事件可以认为封装在 SelectedKeys 里面)
   /**
     * 处理 selectedKeys
     * */
    private void processSelectedKeysOptimized() {
        //1.遍历 SelectedSelectionKeySet 集合,本质是NIO的 SelectionKey 集合,Netty做了一下封装
        for (int i = 0; i < selectedKeys.size; ++i) {
            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

            //拿到之后就将集合元素置为null,Channel关闭之后便于GC
            selectedKeys.keys[i] = null;
            //拿到attachment 属性
            final Object a = k.attachment();

            //如果是 NioChannel,就处理就绪的事件
            if (a instanceof AbstractNioChannel) {
                processSelectedKey(k, (AbstractNioChannel) a);
            } else {
                //反之使用 NioTask 处理一个Channel 就绪的 IO 事件
                @SuppressWarnings("unchecked")
                NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
                processSelectedKey(k, task);
            }

            // 是否需要再select 一次,需要就重新select一次
            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;
            }
        }
    }
2.3.3 processSelectedKey(SelectionKey k, AbstractNioChannel ch)
  • processSelectedKey:processSelectedKey是真正处理事件的方法,这里面我们可以看到和NIO中类似的事件处理代码,注释都在代码中,我们可以开电脑真正的处理过程都是通过 AbstractNioChannel 的内部类 AbstractNioChannel.NioUnsafe 来完成的,而我们自己写的 NIO 代码则是自己去操作NIO buffer完成自己的处理逻辑
/**
     * 处理Channel 的 SelectionKey
     * */
    private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
        // 如果 SelectionKey 是不合法的,则关闭 Channel
        final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
        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) {
                return;
            }
            // close the channel if the key is not valid anymore
            unsafe.close(unsafe.voidPromise());
            return;
        }

        try {
            // 获得就绪的 IO 事件的 ops
            int readyOps = k.readyOps();

            // 处理 OP_CONNECT 就绪事件
            // 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) {
                // 移除对 OP_CONNECT 感兴趣
                // 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();
            }

            // 处理 OP_WRITE 写事件
            // Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
            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
                // 向 Channel 写入数据
                ch.unsafe().forceFlush();
            }

            // SelectionKey.OP_READ 或 SelectionKey.OP_ACCEPT 就绪
            // readyOps == 0 是对 JDK Bug 的处理,防止空的死循环
            // Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead to a spin loop
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
            }
        } catch (CancelledKeyException ignored) {
            // 发生异常,关闭 Channel
            unsafe.close(unsafe.voidPromise());
        }
    }

2.4 closeAll

  • 关闭全部的 Channel
    /**
     * 关闭,关闭之前select一次,然后调用全部Channel的close方法
     */
    private void closeAll() {
        selectAgain();
        Set<SelectionKey> keys = selector.keys();
        Collection<AbstractNioChannel> channels = new ArrayList<AbstractNioChannel>(keys.size());
        for (SelectionKey k : keys) {
            Object a = k.attachment();
            if (a instanceof AbstractNioChannel) {
                channels.add((AbstractNioChannel) a);
            } else {
                k.cancel();
                @SuppressWarnings("unchecked")
                NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
                invokeChannelUnregistered(task, k, null);
            }
        }

        for (AbstractNioChannel ch : channels) {
            ch.unsafe().close(ch.unsafe().voidPromise());
        }
    }

2.5 isShuttingDown

  • 根据状态变量判断是否处于关闭中
    /**
     * 根据状态变量判断是否处于关闭中
     * */
    @Override
    public boolean isShuttingDown() {
        return state >= ST_SHUTTING_DOWN;
    }
  • 到这我们知道了 NioEventLoop 启动后run方法主要做的事情,但是什么时候这个run方法会被启动起来呢?我们打个断点启动下就知道了

三、补充

3.1 NioTask

  • NioTask 自定义 NIO 事件处理接口。对于每个 Nio 事件,可以认为是一个任务( Task ) 提交给 NioEventLoop 执行:
/**
 * An arbitrary task that can be executed by {@link NioEventLoop} when a {@link SelectableChannel} becomes ready.
 * NioTask 代表一个任意的可以被 NioEventLoop 执行的任务,(当SelectableChannel 准备好之后)
 *
 * @see NioEventLoop#register(SelectableChannel, int, NioTask)
 */
public interface NioTask<C extends SelectableChannel> {

    /**
     * 当 SelectableChannel 被 select 选中之后执行该方法,通过实现该接口方法可以实现 processSelectedKey 的逻辑。
     * Invoked when the {@link SelectableChannel} has been selected by the {@link Selector}.
     */
    void channelReady(C ch, SelectionKey key) throws Exception;

    /**
     * 当 SelectableChannel 对应的 SelectionKey 被取消之后调用,因此 NioTask 也不会被调用了
     * 可以通过实现该接口方法,关闭 Channel 。
     * Invoked when the {@link SelectionKey} of the specified {@link SelectableChannel} has been cancelled and thus
     * this {@link NioTask} will not be notified anymore.
     *
     * @param cause the cause of the unregistration. {@code null} if a user called {@link SelectionKey#cancel()} or
     *              the event loop has been shut down.
     */
    void channelUnregistered(C ch, Throwable cause) throws Exception;
}
  • 这个接口没有实现类,在 NioEventLoop#processSelectedKeysOptimized 的方法中调用了该方法
        @SuppressWarnings("unchecked")
        NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
        processSelectedKey(k, task);
  • 在 processSelectedKey 里面直接调用接口的方法处理事件
private static void processSelectedKey(SelectionKey k, NioTask<SelectableChannel> task) {
        // 未执行
        int state = 0; 
        try {
            //调用 NioTask 的channelReady方法处理 Channel 就绪事件
            task.channelReady(k.channel(), k);
            // 执行成功
            state = 1; 
        } catch (Exception e) {
            // SelectionKey 取消
            k.cancel();
            //调用 NioTask 的channelReady方法处理 Channel 取消注册
            invokeChannelUnregistered(task, k, e);
            // 执行异常
            state = 2; 
        } finally {
            switch (state) {
                case 0:
                    // SelectionKey 取消
                    k.cancel();
                    // 执行 Channel 取消注册
                    invokeChannelUnregistered(task, k, null);
                    break;
                case 1:
                    // SelectionKey 不合法,则执行 Channel 取消注册
                    if (!k.isValid()) { // Cancelled by channelReady()
                        invokeChannelUnregistered(task, k, null);
                    }
                    break;
            }
        }
    }

3.2 SelectStrategy接口

  • 选择策略; 策略接口,用于控制一次 select 操作的行为
/**
 * Select strategy interface.
 * 选择策略接口,通过策略接口来控制 select loop 的行为,
 * 比如:如果有时间需要立刻处理的话,一个阻塞的select 操作可能被延迟或者完全跳过
 * <p>
 * Provides the ability to control the behavior of the select loop. For example a blocking select
 * operation can be delayed or skipped entirely if there are events to process immediately.
 */
public interface SelectStrategy {

    /**
     * Indicates a blocking select should follow.
     * <p>
     * 表示使用阻塞 select 的策略。
     */
    int SELECT = -1;
    /**
     * Indicates the IO loop should be retried, no blocking select to follow directly.
     * <p>
     * 表示需要进行重试的策略。
     */
    int CONTINUE = -2;

    /**
     * The {@link SelectStrategy} can be used to steer the outcome of a potential select call.
     * SelectStrategy 可以控制潜在的调用结果
     *
     * @param selectSupplier The supplier with the result of a select result.
     * @param hasTasks       true if tasks are waiting to be processed.
     * @return {@link #SELECT} if the next step should be blocking select {@link #CONTINUE} if
     * the next step should be to not select but rather jump back to the IO loop and try
     * again. Any value >= 0 is treated as an indicator that work needs to be done.
     */
    int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception;
}
  • DefaultSelectStrategy: 默认策略实现,
/**
 * Default select strategy.
 *
 * Select选择策略的默认实现类
 */
final class DefaultSelectStrategy implements SelectStrategy {

    /**
     * 单例
     */
    static final SelectStrategy INSTANCE = new DefaultSelectStrategy();

    private DefaultSelectStrategy() {
    }

    /**
     * 根据是否有任务来决定一个select 操作的逻辑
     * 如果有任务:selectSupplier.get(),这个方法不同的 EventLoop 有不同的实现,NioEventLoop 的实现是调用 selectNow(),
     *  selectNow()方法在NIO中介绍过,就是不阻塞的select操作
     * 如果没有任务:返回-1表示使用阻塞的select策略 (SelectStrategy.SELECT = -1)
     * */
    @Override
    public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
        return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
    }
}
  • DefaultSelectStrategy的实现很简单,根据是否有任务来决定 select 操作的行为,如果没有任务就返回-1,如果有任务就执行 selectSupplier.get(),该方法由具体的 EventLoop 子类实现

  • NioEventLoop的IntSupplier实现: 我们看到实现很简单,在NioEventLoop 中,如果有任务则会直接 selectNow(),不阻塞,

    private final IntSupplier selectNowSupplier = new IntSupplier() {
        @Override
        public int get() throws Exception {
            return selectNow();
        }
    };

3.3 rebuildSelector

  • rebuildSelector 方法用于重建Selector 对象,解决JDK 中的空轮询 BUG,
/**
     * 重建EventLoop的 Selector,解决 CPU 100% 的bug
     * <p>
     * Replaces the current {@link Selector} of this event loop with newly created {@link Selector}s to work
     * around the infamous epoll 100% CPU bug.
     */
    public void rebuildSelector() {
        //只允许在 EventLoop 的线程中执行
        if (!inEventLoop()) {
            execute(new Runnable() {
                @Override
                public void run() {
                    rebuildSelector0();
                }
            });
            return;
        }
        rebuildSelector0();
    }

    /**
     * 重建 Selector
     */
    private void rebuildSelector0() {
        final Selector oldSelector = selector;
        if (oldSelector == null) {
            return;
        }

        //1.创建新的 Selector 对象
        final SelectorTuple newSelectorTuple;
        try {
            newSelectorTuple = openSelector();
        } catch (Exception e) {
            logger.warn("Failed to create a new Selector.", e);
            return;
        }

        // Register all channels to the new Selector.
        // 将注册在 NioEventLoop 上的所有 Channel ,注册到新创建 Selector 对象上
        // nChannels 记录重新注册成功的 Channel 数量
        int nChannels = 0;
        for (SelectionKey key : oldSelector.keys()) {
            Object a = key.attachment();
            try {
                //不合法的,或者和新的 Slector 有联系的就不处理
                if (!key.isValid() || key.channel().keyFor(newSelectorTuple.unwrappedSelector) != null) {
                    continue;
                }
                // 兴趣事件
                int interestOps = key.interestOps();
                // 取消老的 SelectionKey
                key.cancel();
                // 将 Channel 注册到新的 Selector 对象上
                SelectionKey newKey = key.channel().register(newSelectorTuple.unwrappedSelector, interestOps, a);
                // 修改 Channel 的 selectionKey 指向新的 SelectionKey 上
                if (a instanceof AbstractNioChannel) {
                    // Update SelectionKey
                    ((AbstractNioChannel) a).selectionKey = newKey;
                }

                // 计数 ++
                nChannels++;
            } catch (Exception e) {
                logger.warn("Failed to re-register a Channel to the new Selector.", e);
                // 关闭发生异常的 Channel
                if (a instanceof AbstractNioChannel) {
                    AbstractNioChannel ch = (AbstractNioChannel) a;
                    ch.unsafe().close(ch.unsafe().voidPromise());
                    // 调用 NioTask 的取消注册事件
                } else {
                    @SuppressWarnings("unchecked")
                    NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
                    invokeChannelUnregistered(task, key, e);
                }
            }
        }

        // 修改 selector 和 unwrappedSelector属性, 指向新的 Selector 对象
        selector = newSelectorTuple.selector;
        unwrappedSelector = newSelectorTuple.unwrappedSelector;

        // 关闭老的 Selector 对象,因为所有的 Channel 都已经注册到新的 Selector 上面了
        try {
            // time to close the old selector as everything else is registered to the new one
            oldSelector.close();
        } catch (Throwable t) {
            if (logger.isWarnEnabled()) {
                logger.warn("Failed to close the old Selector.", t);
            }
        }

        if (logger.isInfoEnabled()) {
            logger.info("Migrated " + nChannels + " channel(s) to the new Selector.");
        }
    }

四、小结

  • 本文小结了 NioEventLoop 处理IO事件的流程,NioEventLoop 在一个死循环中不断地select、然后处理IO事件、处理任务,并根据一些条件来决定select 是否阻塞。在最后事件处理部分的代码和NIO中的代码还是很类似的,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值