https://juejin.cn/post/6888135277685473287
NioEventLoop
执行过程在NioEventLoop#run
方法里
protected void run() {
int selectCnt = 0;
for (;;) {
try {
int strategy;
try {
strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
switch (strategy) {
//省略其他情况
case SelectStrategy.SELECT:
//检查是否有io事件
long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
if (curDeadlineNanos == -1L) {
curDeadlineNanos = NONE; // nothing on the calendar
}
nextWakeupNanos.set(curDeadlineNanos);
try {
if (!hasTasks()) {
//检测io事件的主要逻辑
strategy = select(curDeadlineNanos);
}
} finally {
nextWakeupNanos.lazySet(AWAKE);
}
default:
}
} catch (IOException e) {
//省略
}
selectCnt++;
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
boolean ranTasks;
//ioRatio控制处理io事件和处理异步任务队列所用的时间比
//默认为50,即处理io事件和处理异步任务的队列所用的时间比为1:1
if (ioRatio == 100) {
try {
if (strategy > 0) {
//处理io事件
processSelectedKeys();
}
} finally {
//处理异步任务队列
ranTasks = runAllTasks();
}
} else if (strategy > 0) {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
//计算处理io事件所用的时间
final long ioTime = System.nanoTime() - ioStartTime;
//根据ioRatio计算出处理异步任务队列所能使用的最大时间
ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
} else {
ranTasks = runAllTasks(0); // This will run the minimum number of tasks
}
if (ranTasks || strategy > 0) {
if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS && logger.isDebugEnabled()) {
logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
selectCnt - 1, selector);
}
selectCnt = 0;
} else if (unexpectedSelectorWakeup(selectCnt)) { // Unexpected wakeup (unusual case)
selectCnt = 0;
}
} catch (CancelledKeyException e) {
//省略
}
//处理关闭的代码,省略
}
}
复制代码
整个执行过程概括如下
- 检查是否有io事件(SelectStrategy.SELECT那个情况下面的代码)
- 处理io事件
- 处理异步任务队列(外部扔进来的任务)
检测io事件
检测io事件的主要逻辑在case SelectStrategy.SELECT:
下如下代码段里
if (!hasTasks()) {
//检测io事件的主要逻辑
strategy = select(curDeadlineNanos);
}
复制代码
hasTasks
检测异步任务队列里是否还有需要执行的任务,若没有则进入检测io事件的逻辑
下面看看select
方法
private int select(long deadlineNanos) throws IOException {
if (deadlineNanos == NONE) {
return selector.select();
}
// Timeout will only be 0 if deadline is within 5 microsecs
//计算当前时间到下一个最近的定时任务的时间间隔
long timeoutMillis = deadlineToDelayNanos(deadlineNanos + 995000L) / 1000000L;
//如果时间间隔小于等于0,那么进行一个非阻塞的select操作后返回
//否则进行一个阻塞式的select操作(并且传入本次可以阻塞的最大时间)
return timeoutMillis <= 0 ? selector.selectNow() : selector.select(timeoutMillis);
}
复制代码
总结一下,主要逻辑是:
- 若有异步任务就不检测io事件
- 若无异步任务
- 若距最近的定时任务的时间间隔小于等于0,则进行一次非阻塞的select操作
- 若距最近的定时任务的时间间隔大于0,则进行一次阻塞式的select操作(最大阻塞时间为现在到最近的定时任务的时间间隔)
处理io事件
接下来看看processSelectedKeys
方法
private void processSelectedKeys() {
if (selectedKeys != null) {
processSelectedKeysOptimized();
} else {
processSelectedKeysPlain(selector.selectedKeys());
}
}
复制代码
这里的netty默认会开启一个优化,用自己实现的selectedKeys
来替换jdk底层SelectorImpl
类里的成员变量selectedKeys
,jdk底层的selectedKeys
为Hashset类型,而netty的selectedKeys
采用数组实现,优化了select操作的复杂度
跟进去看看processSelectedKeysOptimized
方法
private void processSelectedKeysOptimized() {
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
selectedKeys.keys[i] = null;
//之前解析channel初始化的时候说过,netty会把自己的channel
//当做attachment与jdk底层的channel绑定
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) {
//省略
}
}
}
复制代码
继续跟进看看processSelectedKey
方法
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
if (!k.isValid()) {
//省略
}
try {
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.
//处理写事件
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
//处理读事件
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
复制代码
可以看到这里就是在处理连接、读、写事件
处理异步任务队列
接下来看看runAllTasks
方法
protected boolean runAllTasks(long timeoutNanos) {
//从定时任务队列中取出预计开始时间小于或等于当前时间的任务,放到普通任务队列里
fetchFromScheduledTaskQueue();
//从普通任务队列里取出一个任务
Runnable task = pollTask();
if (task == null) {
//在处理完所有任务后,执行一些收尾工作
afterRunningAllTasks();
return false;
}
final long deadline = timeoutNanos > 0 ? ScheduledFutureTask.nanoTime() + timeoutNanos : 0;
long runTasks = 0;
long lastExecutionTime;
for (;;) {
safeExecute(task);
runTasks ++;
//每执行64个任务检测一次是否超过总共允许执行的时间
//因为nanoTime方法也是比较耗时的,所以不会每次执行任务都检测
if ((runTasks & 0x3F) == 0) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
if (lastExecutionTime >= deadline) {
break;
}
}
task = pollTask();
if (task == null) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
break;
}
}
afterRunningAllTasks();
this.lastExecutionTime = lastExecutionTime;
return true;
}
复制代码
这里简单说一下,netty的任务队列分两类
- 普通任务队列,这里面的任务需要马上执行
- 定时任务队列,这些任务按照预计开始时间排序
总结一下,主要做了这几件事
- 从定时任务队列中取出预计开始时间小于或等于当前时间的任务,放到普通任务队列里
- 不断从任务队列里取出任务执行
- 每执行64个任务检测一次是否超过总共允许执行的时间
- 处理一些收尾任务