0. NioEventLoop简介

NioEventLoop如同它的名字,它是一个无限循环(Loop),在循环中不断处理接收到的事件(Event)

在Reactor模型中,NioEventLoop就是Worker的角色,关联于多个Channel,监听这些Channel上的read/write事件,一旦有事件发生,就做出相应的处理

1. NioEventLoop类图

继承关系可以说是相当复杂了,我们慢慢分析

2.  NioEventLoop的构造方法

    NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
if (selectorProvider == null) {
throw new NullPointerException("selectorProvider");
}
if (strategy == null) {
throw new NullPointerException("selectStrategy");
}
provider = selectorProvider;
final SelectorTuple selectorTuple = openSelector();
selector = selectorTuple.selector;
unwrappedSelector = selectorTuple.unwrappedSelector;
selectStrategy = strategy;
} private SelectorTuple openSelector() {
final Selector unwrappedSelector;
try {
unwrappedSelector = provider.openSelector();//利用JDK提供的SelectorProvider直接创建一个Selector
} catch (IOException e) {
throw new ChannelException("failed to open a new selector", e);
} if (DISABLE_KEYSET_OPTIMIZATION) {//如果没有开启KEYSET优化,将上面的那个Selector直接返回
return new SelectorTuple(unwrappedSelector);
} final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();//创建一个专门存放SelectionKey的Set对象 Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {//用反射的方式创建一个SelectorImpl对象
return Class.forName(
"sun.nio.ch.SelectorImpl",
false,
PlatformDependent.getSystemClassLoader());
} catch (Throwable cause) {
return cause;
}
}
}); if (!(maybeSelectorImplClass instanceof Class) ||
// ensure the current selector implementation is what we can instrument.
!((Class<?>) maybeSelectorImplClass).isAssignableFrom(unwrappedSelector.getClass())) {
if (maybeSelectorImplClass instanceof Throwable) {
Throwable t = (Throwable) maybeSelectorImplClass;
logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, t);
}
return new SelectorTuple(unwrappedSelector);
} final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass; Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys"); Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField);
if (cause != null) {
return cause;
}
cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField);
if (cause != null) {
return cause;
} selectedKeysField.set(unwrappedSelector, selectedKeySet);//强制将SelectorImpl中的selectedKeys域替换为优化版的SelectedSelectionKeySet对象
publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);//强制将SelectorImpl中的publicSelectedKeys域替换为优化版的SelectedSelectionKeySet对象
return null;
} catch (NoSuchFieldException e) {
return e;
} catch (IllegalAccessException e) {
return e;
}
}
}); if (maybeException instanceof Exception) {
selectedKeys = null;
Exception e = (Exception) maybeException;
logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, e);
return new SelectorTuple(unwrappedSelector);
}
selectedKeys = selectedKeySet;
logger.trace("instrumented a special java.util.Set into: {}", unwrappedSelector);
return new SelectorTuple(unwrappedSelector,
new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
}

构造方法的主要作用是创建了一个SelectorImpl对象,如果没有设置DISABLE_KEYSET_OPTIMIZATION属性,SelectorImpl中类型为Set<SelectionKey>的selectedKeys与publicSelectedKeys域会被替换为一个SelectedSelectionKeySet对象。

为什么搞得这么麻烦呢?因为默认的域是Set类型,插入元素的开销是o(log n),而优化版的SelectedSelectionKeySet继承了AbstractSet,具有Set的功能,但是内部是用数组实现,只具有add功能,而且其开销为o(1)。

3. NioEventLoop.run()

run方法是NioEventLoop的核心,此方法会在无限循环中监听关联的Channel上是否有新事件产生

    @Override
protected void run() {
for (;;) {//无限循环
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {//如果任务队列中有任务,调用selectNow()方法,如果没有,则直接返回SelectStrategy.SELECT
case SelectStrategy.CONTINUE://没搞懂这个分支的目的是什么,全局搜了一下SelectStrategy.CONTINUE,没发现有赋这个值的地方
continue;
case SelectStrategy.SELECT:
select(wakenUp.getAndSet(false));//调用select()方法,尝试从关联的channel里读取IO事件。需要注意的是这个select方法相当复杂,因为它悄悄的解决了老版本的JDK的select方法存在的bug,有兴趣的可以仔细分析一下相关源码
if (wakenUp.get()) {
selector.wakeup();
}
default:
} cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;//ioRatio代表EventLoop会花多少时间在IO事件上
if (ioRatio == 100) {
try {
processSelectedKeys();//处理IO事件
} finally {
// Ensure we always run tasks.
runAllTasks();//处理CPU事件
}
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();//处理IO事件
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;//本次循环处理IO事件的耗时
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);//分给CPU事件的耗时
}
}
} 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);
}
}
} private void processSelectedKeys() {
if (selectedKeys != null) {
processSelectedKeysOptimized();//处理IO事件。由于这里采用的是优化版的SelectorImpl,IO事件已经被写在selectedKeys属性里了,所以无需额外传参
} else {
processSelectedKeysPlain(selector.selectedKeys());//未优化版的SelectorImpl,需要调用selectedKeys()方法才能获取准备好的IO事件
}
} private void processSelectedKeysOptimized() {
for (int i = 0; i < selectedKeys.size; ++i) {//遍历已经准备好的IO事件
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;//手动将数组元素赋为null,以帮助gc(因为在系统压力大的时候,SelectionKey数组靠后的部分会被占用,如果不手动将用过的元素设置为null,那么在系统压力小的时候,这些元素是不会被释放的,也就是内存泄漏了) final Object a = k.attachment();//附件是这个事件所关联的Channel,后续的代码会直接从这个Channel上读取数据 if (a instanceof AbstractNioChannel) {
processSelectedKey(k, (AbstractNioChannel) a);//处理某个IO事件
} 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) {
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 || eventLoop == null) {
return;
}
// close the channel if the key is not valid anymore
unsafe.close(unsafe.voidPromise());
return;
} 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) {//如果是可写事件,则flush缓存
// 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()方法
unsafe.read();//此处的unsafe为NioSocketChannel.NioSocketChannelUnsafe
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}

代码很长,大概逻辑如下:

a. 在无限循环中调用Selector.select方法

b. 使用ioRatio控制IO事件与CPU事件的耗时比例(ioRatio的默认值为50)

c. 如果有IO事件发生,遍历所有IO事件并调用processSelectedKey方法

d. 在processSelectedKey中,如果发现事件是READ/ACCEPT类型,则调用NioSocketChannel.NioSocketChannelUnsafe.read()方法对事件进行处理。其实际实现位于AbstractNioByteChannel中:

        @Override
public final void read() {
final ChannelConfig config = config();
final ChannelPipeline pipeline = pipeline();//获取用户注册的pipeline
final ByteBufAllocator allocator = config.getAllocator();//默认值为PooledByteBufAllocator,也就是申请direct memory
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
allocHandle.reset(config); ByteBuf byteBuf = null;
boolean close = false;
try {
do {
byteBuf = allocHandle.allocate(allocator);//开辟一块内存作为buffer
allocHandle.lastBytesRead(doReadBytes(byteBuf));//doReadBytes方法会从绑定的Channel里读取数据到buffer里。顺便记录一下本次读了多少字节的数据出来
if (allocHandle.lastBytesRead() <= 0) {
// nothing was read. release the buffer.
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
break;
} allocHandle.incMessagesRead(1);//计数
readPending = false;
pipeline.fireChannelRead(byteBuf);//触发pipeline的channelRead事件
byteBuf = null;
} while (allocHandle.continueReading()); allocHandle.readComplete();
pipeline.fireChannelReadComplete();//触发pipeline的channelReadComplete事件 if (close) {
closeOnRead(pipeline);
}
} catch (Throwable t) {
handleReadException(pipeline, byteBuf, t, close, allocHandle);
} finally {
// Check if there is a readPending which was not processed yet.
// This could be for two reasons:
// * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
// * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
//
// See https://github.com/netty/netty/issues/2254
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
}

这段代码主要做了两件事情:

a. 在循环中将Channel可读的数据读到一个临时的ByteBuf中

b. 调用pipeline.fireChannelRead方法,处理读取到数据。(具体的处理逻辑在后续文章中阐述)

现在我们已经基本搞清楚NioEventLoop的工作逻辑了:

在无限循环中监听绑定的Channel上的事件,如果有ACCEPT/READ事件发生,则从Channel里读取数据,并调用关联的pipeline的fireChannelRead方法进行处理。

目前仍不清楚的地方是:NioEventLoop是如何与Channel/Pipeline绑定的?

且听后文分解。

Netty源码学习(三)NioEventLoop的更多相关文章

  1. Netty源码分析之NioEventLoop(三)—NioEventLoop的执行

    前面两篇文章Netty源码分析之NioEventLoop(一)—NioEventLoop的创建与Netty源码分析之NioEventLoop(二)—NioEventLoop的启动中我们对NioEven ...

  2. 【Netty源码学习】DefaultChannelPipeline(三)

    上一篇博客中[Netty源码学习]ChannelPipeline(二)我们介绍了接口ChannelPipeline的提供的方法,接下来我们分析一下其实现类DefaultChannelPipeline具 ...

  3. Netty 源码学习——EventLoop

    Netty 源码学习--EventLoop 在前面 Netty 源码学习--客户端流程分析中我们已经知道了一个 EventLoop 大概的流程,这一章我们来详细的看一看. NioEventLoopGr ...

  4. Netty源码学习系列之4-ServerBootstrap的bind方法

    前言 今天研究ServerBootstrap的bind方法,该方法可以说是netty的重中之重.核心中的核心.前两节的NioEventLoopGroup和ServerBootstrap的初始化就是为b ...

  5. 【Netty源码解析】NioEventLoop

    上一篇博客[Netty源码学习]EventLoopGroup中我们介绍了EventLoopGroup,实际说来EventLoopGroup是EventLoop的一个集合,EventLoop是一个单线程 ...

  6. Netty 源码学习——客户端流程分析

    Netty 源码学习--客户端流程分析 友情提醒: 需要观看者具备一些 NIO 的知识,否则看起来有的地方可能会不明白. 使用版本依赖 <dependency> <groupId&g ...

  7. 【Netty源码学习】ChannelPipeline(一)

    ChannelPipeline类似于一个管道,管道中存放的是一系列对读取数据进行业务操作的ChannelHandler. 1.ChannelPipeline的结构图: 在之前的博客[Netty源码学习 ...

  8. 【Netty源码学习】ServerBootStrap

    上一篇博客[Netty源码学习]BootStrap中我们介绍了客户端使用的启动服务,接下来我们介绍一下服务端使用的启动服务. 总体来说ServerBootStrap有两个主要功能: (1)调用父类Ab ...

  9. 【Netty源码学习】EventLoopGroup

    在上一篇博客[Netty源码解析]入门示例中我们介绍了一个Netty入门的示例代码,接下来的博客我们会分析一下整个demo工程运行过程的运行机制. 无论在Netty应用的客户端还是服务端都首先会初始化 ...

  10. mybatis源码学习(三)-一级缓存二级缓存

    本文主要是个人学习mybatis缓存的学习笔记,主要有以下几个知识点 1.一级缓存配置信息 2.一级缓存源码学习笔记 3.二级缓存配置信息 4.二级缓存源码 5.一级缓存.二级缓存总结 1.一级缓存配 ...

随机推荐

  1. 当Hadoop 启动节点Datanode失败解决

    Hadoop 启动节点Datanode失败解决 [日期:2014-11-01] 来源:Linux社区  作者:shuideyidi [字体:大 中 小] 当我动态添加一个Hadoop从节点的之后,出现 ...

  2. 《Cracking the Coding Interview》——第18章:难题——题目8

    2014-04-29 03:10 题目:给定一个长字符串S和一个词典T,进行多模式匹配,统计S中T单词出现的总个数. 解法:这是要考察面试者能不能写个AC自动机吗?对面试题来说太难了吧?我不会,所以只 ...

  3. 《Cracking the Coding Interview》——第11章:排序和搜索——题目7

    2014-03-21 22:05 题目:给你N个盒子堆成一座塔,要求下面盒子的长和宽都要严格大于上面的.问最多能堆多少个盒子? 解法1:O(n^2)的动态规划解决.其实是最长递增子序列问题,所以也可以 ...

  4. CSS系列(5)-如何使用Firebug查看网页的html和css

    Firebug是火狐浏览器Firefox的一个插件,专门为开发人员开发的.使用Firebug需要先在Firefox中安装这个插件,网上有很多教程,可以对照着安装一下. 不同的火狐浏览器版本中的Fire ...

  5. ADB push 和ADB pull命令

    adb push命令 :从电脑上传送文件到手机: adb pull命令 :从手机传送文件到电脑上      

  6. Python学习-KindEditor-富文本编辑框

    1.进入官网 2.下载 官网下载:http://kindeditor.net/down.php 本地下载:http://files.cnblogs.com/files/wupeiqi/kindedit ...

  7. eclipse importing maven projects 卡顿

    导入一个maven工程后 一直显示 importing maven projects 9% 解决办法: 找到eclipse安装目录下的eclipse.ini 在最后加入 -vm $JAVA_HOME% ...

  8. 孤荷凌寒自学python第十四天python代码的书写规范与条件语句及判断条件式

    孤荷凌寒自学python第十四天python代码的书写规范与条件语句及判断条件式 (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) 在我学习过的所有语言中,对VB系的语言比较喜欢,而对C系和J系 ...

  9. Leetcode 670.最大交换

    最大交换 给定一个非负整数,你至多可以交换一次数字中的任意两位.返回你能得到的最大值. 示例 1 : 输入: 2736 输出: 7236 解释: 交换数字2和数字7. 示例 2 : 输入: 9973 ...

  10. 逆向映射是干嘛的anon_vma, vma, anon_vma_chain

    逆向映射是为了从page得到进程信息,里面有三个比较重要的结构体: mm_area_struct, anon_vma_chain, anon_vma 想象一种复杂的场景 所以其实一个进程对应着很多an ...