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. Java重写与重载

    重写的规则: 参数列表必须完全与被重写方法的相同: 返回类型必须完全与被重写方法的返回类型相同: 访问权限不能比父类中被重写的方法的访问权限更低.例如:如果父类的一个方法被声明为public,那么在子 ...

  2. 后端接口迁移(从 webapi 到 openapi)前端经验总结

    此文已由作者张磊授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 前情提要 以前用的是 webapi 现在统一切成 openapi,字段结构统统都变了 接入接口 20+,涉及模 ...

  3. Android学习记录(9)—Android之Matrix的用法

    Matrix ,中文里叫矩阵,高等数学里有介绍,在图像处理方面,主要是用于平面的缩放.平移.旋转等操作. 首先介绍一下矩阵运算.加法和减法就不用说了,对应位相加就好.图像处理,主要用到的是乘法 .下面 ...

  4. python学习笔记十六:读取JSON文件

    读取JSON文件可以用JSON库,示例代码: #coding:utf-8 import json with open("msg.json") as jsonfile: json_d ...

  5. Python程序执行时的不同电脑路径不同问题

    原因:因代码转移时项目路径发生了变化,导致解释器无法找到对应路径,是的程序无法正常执行 需求: 1.我希望代码能在不同的电脑下,不必修改源代码就能正常执行(所需模块已安装的前提下) 2.我希望代码在命 ...

  6. css3实现圆角边框渐变

    <button class="border">112233</button> 创建button .border{ position: relative; b ...

  7. json序列化datetime类型数据

    错误描述: import jsonimport datetime a = datetime.datetime.now()print(a) b = json.dumps(a)print(b) 如上代码, ...

  8. Python全栈 MySQL 数据库 (简述 、安装、基本命令)

    ParisGabriel              每天坚持手写  一天一篇  决定坚持几年 为了梦想为了信仰    开局一张图     一个月的python已经结束了  下面就是数据库了   先说M ...

  9. java案例1,打印hello java

    package anli1; public class hellojava { public static void main(String []args){ System.out.println(& ...

  10. A Neural Algorithm of Artistic Style

    本系列文章由 @yhl_leo 出品,转载请注明出处. 文章链接: http://blog.csdn.net/yhl_leo/article/details/53931536 1. 资源 Paper: ...