Netty 源码学习——EventLoop


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

NioEventLoopGroup 类层次结构

我们先来看下 NioEventLoopGroup 这个类。

public class NioEventLoopGroup extends MultithreadEventLoopGroup {
}

发现他的父类是 MultithreadEventLoopGroup。我们有必要来看一下继承结构图。

NioEventLoopGroup 实例化流程

我们再来回复习下 NioEventLoopGroup 的实例化流程。

EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup work = new NioEventLoopGroup();

在内部构造器调用到最后会出现在这一行。

public NioEventLoopGroup(int nThreads, Executor executor, SelectorProvider selectorProvider, SelectStrategyFactory selectStrategyFactory) {
super(nThreads, executor, new Object[]{selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject()});
}

上面参数分别是

  1. nThreads 线程数量

  2. exceutor 执行 runnable 的执行器实现了 Executor 接口,内部维护了一个线程工厂,在调用 public void execute(Runnable command); 的时候会新建一个线程。

  3. selectorProvider

    1. SelectorProvider provider 属性: NioEventLoopGroup 构造器中通过 SelectorProvider.provider() 获取一个 SelectorProvider。
    2. Selector selector 属性: NioEventLoop 构造器中通过调用通过 selector = provider.openSelector() 获取一个 selector 对象。
  4. selectStrategyFactory 主要用于选择策略。

    1.  public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
      return hasTasks ? selectSupplier.get() : -1;
      }
    2. 判断任务队列是否有任务,如果有,那么调用一次 selectNow 方法,并返回 selectNow 的结果,如果没有,返回 -1。

我们接着往下看发现最终会在 io.netty.util.concurrent.MultithreadEventExecutorGroup#MultithreadEventExecutorGroup(int, java.util.concurrent.Executor, io.netty.util.concurrent.EventExecutorChooserFactory, java.lang.Object…) 中完成初始化操作。后面的流程在之前客户端解析那一章节已经解释,再次不在说了。

protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
if (nThreads <= 0) {
throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
} if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
} children = new EventExecutor[nThreads]; for (int i = 0; i < nThreads; i ++) {
try {
children[i] = newChild(executor, args);
} catch (Exception e) {
// 省略不必要的代码只说明初始化操作
} finally {
// 省略不必要的代码只说明初始化操作
}
} chooser = chooserFactory.newChooser(children);
// 省略不必要的代码只说明初始化操作
}

总结

  1. 由此看来 EventLoopGroup 就是在父类中维护了一个 nThreads 长度的 EventExecutor 数组。
  2. 在此方法内部会调用 newChild(executor, args); 来初始化 children 数组。
  3. chooser 根据数组的长度来选择出 EventExecutorChooser。

NioEventLoop

我们还是先来看下继承结构。

public final class NioEventLoop extends SingleThreadEventLoop {
}

我们可以看到几个熟悉的面孔 Executor、ExecutorService、ScheduledExecutorService 等等。那么我们可以认为 NioEventLoop 就是用来做线程调度的。因为他实现了 execute 方法来向任务队列中添加一个 task, 并由 NioEventLoop 进行调度执行。NioEventLoop 构造器的执行链 NioEventLoop -> SingleThreadEventLoop -> SingleThreadEventExecutor -> AbstractScheduledEventExecutor -> AbstractEventExecutor

EventLoop 是在哪里和 Channel 的绑定的

还记得 io.netty.channel.AbstractChannel.AbstractUnsafe#register 这个方法吗?

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
if (eventLoop == null) {
} else if (AbstractChannel.this.isRegistered()) {
} else if (!AbstractChannel.this.isCompatible(eventLoop)) {
} else {
// 绑定关系
AbstractChannel.this.eventLoop = eventLoop;
if (eventLoop.inEventLoop()) {
this.register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
public void run() {
AbstractUnsafe.this.register0(promise);
}
});
} catch (Throwable var4) {
}
} }
}

AbstractChannel.this.eventLoop = eventLoop; 将 eventLoop 维护到 AbstractChannel 的 private volatile EventLoop eventLoop; 成员变量中。

我们在上面代码还能发现 eventLoop.execute(); 的执行,因为执行到这里是 ServerBootStrap 主线程执行的,所以不会进去 if (eventLoop.inEventLoop()); 分支会走 else 的 eventLoop.execute(); 点进去我们看代码发现是 SingleThreadEventExecutor 里的。

public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
} else {
boolean inEventLoop = this.inEventLoop();
this.addTask(task);
if (!inEventLoop) {
// 重点看这行
this.startThread();
if (this.isShutdown()) {
boolean reject = false;
try {
if (this.removeTask(task)) {
reject = true;
}
} catch (UnsupportedOperationException var5) {
}
if (reject) {
reject();
}
}
}
if (!this.addTaskWakesUp && this.wakesUpForTask(task)) {
this.wakeup(inEventLoop);
}
}
}

看到上面代码 this.startThread(); 直接启动一个线程。

private void startThread() {
if (this.state == 1 && STATE_UPDATER.compareAndSet(this, 1, 2)) {
try {
this.doStartThread();
} catch (Throwable var2) {
STATE_UPDATER.set(this, 1);
PlatformDependent.throwException(var2);
}
}
}

我们会看到当 state 等于 1 的时候会去执行里面的代码,在 SingleThreadEventExecutor 初始化也就是 NioEventLoop 初始化的时候已经把 state 赋值成了 1,也就是说只有在第一次调用的 时候才会是 1。

protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor, boolean addTaskWakesUp, int maxPendingTasks, RejectedExecutionHandler rejectedHandler) {
super(parent);
this.threadLock = new Semaphore(0);
this.shutdownHooks = new LinkedHashSet();
// 将状态赋值为 1
this.state = 1;
this.terminationFuture = new DefaultPromise(GlobalEventExecutor.INSTANCE);
this.addTaskWakesUp = addTaskWakesUp;
this.maxPendingTasks = Math.max(16, maxPendingTasks);
this.executor = ThreadExecutorMap.apply(executor, this);
this.taskQueue = this.newTaskQueue(this.maxPendingTasks);
this.rejectedExecutionHandler = (RejectedExecutionHandler)ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
}

我们继续回到 startThread(); 方法中,发现里面调用了 this.doStartThread(); 因为代码只保留了关键代码。

private void doStartThread() {
// 断言
assert this.thread == null;
this.executor.execute(new Runnable() {
public void run() {
// 给成员属性 thread 赋值
SingleThreadEventExecutor.this.thread = Thread.currentThread();
if (SingleThreadEventExecutor.this.interrupted) {
SingleThreadEventExecutor.this.thread.interrupt();
} boolean success = false;
SingleThreadEventExecutor.this.updateLastExecutionTime();
boolean var112 = false; int oldState;
label1907: {
try {
var112 = true;
// 调用 NioEventLoop run();
SingleThreadEventExecutor.this.run();
success = true;
var112 = false;
break label1907;
} catch (Throwable var119) { } finally { }
}
});
}

上面代码主要做了三件事情

  1. 判断 thread 是否为 null
  2. 给 thread 赋值
  3. 调用 run() 方法
protected void run() {
for (;;) {
try {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.BUSY_WAIT:
// fall-through to SELECT since the busy-wait is not supported with NIO
case SelectStrategy.SELECT:
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
// fall through
default:
}
} catch (IOException e) { } 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;
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);
}
}
}

看了上面代码,我们大致可以猜测到在一个无限循环里面 NioEventLoop 事件循环的核心就是这里!

I/O 事件处理

首先我们看到这一行代码 switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks()));

@Override
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
}

主要是查看任务队列里有没有任务,如果有的话直接 selectSupplier.get(); 返回,没有返回 SelectStrategy.SELECT 值为 -1 需要阻塞等待。那么 selectSupplier.get(); 返回的是什么呢,我们发现 NioEventLoop 有一个属性传入到了这个方法中。

private final IntSupplier selectNowSupplier = new IntSupplier() {
@Override
public int get() throws Exception {
return selectNow();
}
}; int selectNow() throws IOException {
try {
return selector.selectNow();
} finally {
// restore wakeup state if needed
if (wakenUp.get()) {
selector.wakeup();
}
}
}

selectNow() 是直接使用了原生 NIO Selector.selectNow() 方法会检查当前是否有就绪的 IO 事件, 如果有, 则返回就绪 IO 事件的个数; 如果没有, 则返回0. `注意, selectNow() 是立即返回的, 不会阻塞当前线程。

需要需要执行 I/O 事件我们就继续 run(); 往下看。

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;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}

我们看到会分别执行 processSelectedKeys(); runAllTasks(); 方法我们先看第一个方法。

private void processSelectedKeys() {
if (selectedKeys != null) {
processSelectedKeysOptimized();
} else {
processSelectedKeysPlain(selector.selectedKeys());
}
}

上面代码首先会判断 selectedKeys 如果不为 null 的话就执行 processSelectedKeysOptimized(); 否则执行 processSelectedKeysPlain(selector.selectedKeys()); 一般情况下是不会为 null 的,因为里面保存了 I/O 事件。所以我们直接跟进 processSelectedKeysOptimized();

private void processSelectedKeysOptimized() {
for (int i = 0; i < selectedKeys.size; ++i) {
final SelectionKey k = selectedKeys.keys[i];
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) {
selectedKeys.reset(i + 1);
selectAgain();
i = -1;
}
}
}

上面代码遍历每一个 SelectionKey 并执行 processSelectedKey(); 方法。其中有一行 final Object a = k.attachment(); 这个 a 是从哪里放进去的呢?就是当服务启动注册 channel 的时候 AbstractNioChannel.doRegister。

@Override
protected void doRegister() throws Exception {
// 传递了 this 这个 this 就是 channel
selectionKey = javaChannel().register(eventLoop().selector, 0, this);
}

那么我们继续看下 processSelectedKey(); 方法。

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) {
return;
}
if (eventLoop != this || eventLoop == null) {
return;
}
unsafe.close(unsafe.voidPromise());
return;
} try {
int readyOps = k.readyOps();
// 是否是连接事件
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
int ops = k.interestOps();
// 将连接事件数据位置清 0
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops); unsafe.finishConnect();
}
// 是否是可写事件
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush();
}
// 是否是可读事件
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}

看了上面代码主要判断是否是建立连接事件、可写事件、可读事件。

  1. OP_CONNECT, 连接建立事件, 即 TCP 连接已经建立, Channel 处于 active 状态.
  2. OP_WRITE, 可写事件, 即上层可以向 Channel 写入数据.
  3. OP_READ, 可读事件, 即 Channel 中收到了新数据可供上层读取.

连接事件

// 是否是连接事件
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
int ops = k.interestOps();
// 将连接事件数据位置清 0
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops); unsafe.finishConnect();
}

在处理连接事件中只做了两件事情

  1. 将连接事件取消掉也就是将连接标识清空为 0。
  2. 调用 unsafe.finishConnect() 通知上层连接已建立。

可写事件

调用 forceFlush,一旦没有什么可写的,它也将清除 OP_WRITE。

 if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush();
}

可读事件

if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}

我们继续跟进 read(); 方法,发现在 AbstractNioByteChannel 中实现。

@Override
public final void read() {
final ChannelConfig config = config();
if (shouldBreakReadReady(config)) {
clearReadPending();
return;
}
final ChannelPipeline pipeline = pipeline();
final ByteBufAllocator allocator = config.getAllocator();
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
allocHandle.reset(config); ByteBuf byteBuf = null;
boolean close = false;
try {
do {
byteBuf = allocHandle.allocate(allocator);
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= 0) {
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
if (close) {
readPending = false;
}
break;
}
allocHandle.incMessagesRead(1);
readPending = false;
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
} while (allocHandle.continueReading()); allocHandle.readComplete();
pipeline.fireChannelReadComplete(); if (close) {
closeOnRead(pipeline);
}
} catch (Throwable t) {
handleReadException(pipeline, byteBuf, t, close, allocHandle);
} finally {
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}

上面 read() 做了如下工作:

  1. byteBuf = allocHandle.allocate(allocator); 分配 ByteBuf
  2. allocHandle.lastBytesRead(doReadBytes(byteBuf)); 从 SocketChannel 中读取数据
  3. 调用 pipeline.fireChannelRead 发送一个 inbound 事件.

ChannelPipeline 已经在前面介绍过了,此章就不继续说了。至此 I/O 事件处理就说完了。

任务处理

现在让我们回到之前的代码。

if (ioRatio == 100) {
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
runAllTasks();
}
}

processSelectedKeys(); 我们已经看完了,现在将看下 runAllTasks();

protected boolean runAllTasks() {
assert inEventLoop();
boolean fetchedAll;
boolean ranAtLeastOne = false; do {
fetchedAll = fetchFromScheduledTaskQueue();
if (runAllTasksFrom(taskQueue)) {
ranAtLeastOne = true;
}
} while (!fetchedAll); if (ranAtLeastOne) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
}
afterRunningAllTasks();
return ranAtLeastOne;
}

上面代码 fetchedAll = fetchFromScheduledTaskQueue(); 将 ScheduledTaskQueue 中可以执行的任务放入到 taskQueue 中。if (runAllTasksFrom(taskQueue)) {} 执行任务队列中的任务。

protected final boolean runAllTasksFrom(Queue<Runnable> taskQueue) {
Runnable task = pollTaskFrom(taskQueue);
if (task == null) {
return false;
}
for (;;) {
safeExecute(task);
task = pollTaskFrom(taskQueue);
if (task == null) {
return true;
}
}
} protected static void safeExecute(Runnable task) {
try {
task.run();
} catch (Throwable t) {
}
} protected static Runnable pollTaskFrom(Queue<Runnable> taskQueue) {
for (;;) {
Runnable task = taskQueue.poll();
if (task == WAKEUP_TASK) {
continue;
}
return task;
}
}

就是从 taskQueue 中不断的 poll 出一个可执行的 task 进行 run 执行。

事件有限至此 EventLoop 匆匆看完,如有不对的地方,请大家指出,感谢观看。

Netty 源码学习——EventLoop的更多相关文章

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

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

  2. 【Netty源码学习】ServerBootStrap

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

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

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

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

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

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

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

  6. 【Netty源码学习】EventLoopGroup

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

  7. (一)Netty源码学习笔记之概念解读

    尊重原创,转载注明出处,原文地址:http://www.cnblogs.com/cishengchongyan/p/6121065.html  博主最近在做网络相关的项目,因此有契机学习netty,先 ...

  8. netty源码学习

    概述 Netty is an asynchronous event-driven network application framework for rapid development of main ...

  9. Netty源码学习(三)NioEventLoop

    0. NioEventLoop简介 NioEventLoop如同它的名字,它是一个无限循环(Loop),在循环中不断处理接收到的事件(Event) 在Reactor模型中,NioEventLoop就是 ...

随机推荐

  1. (18)C++ string和标准模板库

    一.stringl类 1.string构造函数 string a1("abc");//初始化字符串 cout<<a1<<endl;//abc , '#'); ...

  2. spring boot找不到或无法加载主类 io.renren.RenrenApplication

    spring boot找不到或无法加载主类 io.renren.RenrenApplication 出现问题: spring boot 项目以前一直是好好的,用mvn clean package 打包 ...

  3. kali开启禁止或删除ssh 开机启动

    开启禁止或删除ssh 开机启动 # update-rc.d ssh enable #//开机启动 # update-rc.d ssh disable #//禁止开机启动 # update-rc.d - ...

  4. spring data jpa 使用JPQL的方式查询

    用Spring Data JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件,这时就可以使用@Query注解,结合JPQL的语句方式完成查询 @Que ...

  5. 第一节:mybatis入门

    1.新建数据表 本次测试使用mysql数据,数据库名称为mybatis,新建一张表person,建表语句如下: CREATE TABLE `person` ( `id` ) PRIMARY KEY a ...

  6. 牛客练习赛53 C 富豪凯匹配串

    思路: bitset的简单题,不幸的是当时的我并不知道bitset, C++的 bitset 在 bitset 头文件中,它是一种类似数组的结构,它的每一个元素只能是0或1,每个元素仅用1bit空间, ...

  7. python- 属性 静态方法,类方法

    一,面向对象结构与成员 1,1 面向对象结构分析: 那么每个大区域又可以分为多个小部分: class A: company_name = '老男孩教育' # 静态变量(静态字段) __iphone = ...

  8. javascript中var同时声明多个变量时的原理是什么?

    <script> function show(){ var a=b=c=d=5; } show(); alert(a);//弹a时报错(not defined),而b.c.d都能弹出5 & ...

  9. ASE19 团队项目 alpha 阶段 Frontend 组 scrum7 记录

    本次会议于11月11日,11:30 在微软北京西二号楼13158,持续15分钟. 与会人员:Jingyi Xie, , Ziwei Wu, Jiaqi Xu, Jingwei Yi, Hanyue T ...

  10. Cobalt Strike特征修改

    一.Teamserver模块端口信息 vim teamserver修改Cobalt Strike默认服务端50500端口为其他任意端口 二.SSL默认证书信息 Cobalt Strike默认SSL证书 ...