EventLoop類繼承圖
上文提到了,NioServerSocketChannel將OP_Accept事件注冊到bossGroup的EventLoop的selector上,而EventLoop是一個繼承自java.util.concurrent.ScheduledExecutorService類可執行定時任務的Executor類。
其完整的繼承類圖如下:
EventLoop的run方法源碼
EventLoop不僅可以處理IO線程類,同時還可以執行一個定時或非定時的任務。EventLoop的run方法如下:
protected void run() {
//創建之后就會被調用
//之后會不斷輪詢
for (;;) {
try {
//判斷是否有任務,有任務,返回continue,沒任務需要處理,則調用SELECT
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
// fall through
default:
}
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
runAllTasks();
}
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;//IO事件處理的時間
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
// Always handle shutdown even if the loop processing threw an exception.
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Throwable t) {
handleLoopException(t);
}
}
}
ioRadio表示IO事件執行的時間和任務執行的時間的比值,默認為1:1,我們在創建NioEventLoopGroup的時候可以設置這個值。NioEventLoopGroup.setIoRatio()。
通過源碼可以看出,run方法內在不斷的進行輪詢,首先處理是否有已經觸發的io事件,有則進行執行,而在finally塊中進行runAllTasks(),其中一個帶參數的runAllTasks表示,在指定的時間內返回無論任務是否執行完成。而不帶參數的runAllTasks則沒有時間限制。
下面分別對IO事件processSelectedKeys()和任務處理:runAllTasks()方法進行分析:
EventLoop處理IO事件
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;
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) {
// 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;
}
}
}
//進行事件的判斷和執行
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
//....省略部分代碼
try {
int readyOps = k.readyOps();
// 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());
}
}
根據以上代碼可以看到,當讀取到的事件是OP_Accept或者OP_Read事件時,調用unsafe.read()事件進行處理,unsafe是SocketChannel內部專門處理IO事件的類,不同的SocketChannel擁有不同的unsafe實例,在此我們只討論我們用到的socketChannel:NioServerSocketChannel、NioSocketChannel,下面是它們的類圖。
其中AbstractNioChannle中提供了內部NioUnsafe接口,在子類AbstractNioByteChannle和AbstractNioMessageChannle分別進行了實現,所以當我們處理IO事件并調用Unsafe.read()時,NioSocketChannel和NioServerSocketChannel調用的是不同的unsafe進行處理。
- 其中NioServerSocketChannel由于處理的是Accept事件,所以 unsafe.read的處理主要是接收并創建NioSocketChannel然后fireChannelRead事件,而這個事件最終會被ServerBootstrapAcceptor中的ChannelRead所處理,channelRead中,將當前創建的NioSocketChannel注冊到childGroup中。源碼如下:
//服務端處理OP_ACCEPT事件public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
child.pipeline().addLast(childHandler);
setChannelOptions(child, childOptions, logger);
for (Entry<AttributeKey<?>, Object> e: childAttrs) {
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
try {//注冊Channel到childGroup
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處理的事件是Read事件,所以主要讀取緩沖區的內容到Bytebuf,然后fireChannelRead事件,進行在pipeline上傳播。
關于Pipeline上傳播,在下節中我們會討論,NioServerSocketChannel和NioSocketChannel的pipeline的ChannelHandler鏈
EventLoop執行Task
EventLoop除了執行io事件外,本身也會執行一些任務和定時任務,如下面的runAllTasks即為eventloop執行task的源碼:
protected boolean runAllTasks(long timeoutNanos) {
fetchFromScheduledTaskQueue();
Runnable task = pollTask();
if (task == null) {
return false;
}
final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
long runTasks = 0;
long lastExecutionTime;
for (;;) {
try {
task.run();
} catch (Throwable t) {
logger.warn("A task raised an exception.", t);
}
runTasks ++;
// Check timeout every 64 tasks because nanoTime() is relatively expensive.
// XXX: Hard-coded value - will make it configurable if it is really a problem.
//每執行64個任務,就判斷一次事件是否超時
if ((runTasks & 0x3F) == 0) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
if (lastExecutionTime >= deadline) {
break;
}
}
task = pollTask();
if (task == null) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
break;
}
}
this.lastExecutionTime = lastExecutionTime;
return true;
}
根據源碼可以看到,首先EventLoop會將將要執行的定時任務(拿到還未執行的deadlineTime小于當前時間的所有定時任務),加入當前taskQueue中,并對taskQueue進行輪詢處理,其中如果設置了處理的task的時間限制,則每處理64個task就會判斷一下是否超時,如果超時則退出執行。
之前提到EventLoop內的任務分為兩種,一種是可立即執行的taskQueue,另一種是定時執行的scheduledTaskQueue,當我們在handler中提交普通任務和定時任務時會分別加入這兩個隊列。