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中的代码还是很类似的,