接口定义
io.netty.channel.EventLoopGroup extends EventExecutorGroup
|
方法
|
说明
|
|
ChannelFuture register(Channel channel)
|
把一个channel注册到一个EventLoop
|
|
ChannelFuture register(Channel channel, ChannelPromise promise);
|
同上
|
io.netty.channel.EventLoop extends OrderedEventExecutor, EventLoopGroup
|
方法
|
说明
|
|
EventLoopGroup parent()
|
得到创建这个eventLoop的EventLoopGroup
|
EventLoopGroup定义的主要方法是register, 这个方法的语义是把channel和eventLoop绑定在一起。一个channel对应一个eventLoop, 一个eventLoop会持有多个channel。
I/O线程EventLoopGroup的抽象实现
io.netty.channel.MultithreadEventLoopGroup extends MultithreadEventExecutorGroup implements EventLoopGroup
io.netty.channel.SingleThreadEventLoop extends SingleThreadEventExecutor implements EventLoop
两个类主功能都是实现了EventLoopGroup定义的register方法
MultithreadEventLoopGroup
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
public ChannelFuture register(Channel channel, ChannelPromise promise) {
return next().register(channel, promise);
}
SingleThreadEventLoop
public ChannelFuture register(Channel channel) {
return register(channel, new DefaultChannelPromise(channel, this));
}
public ChannelFuture register(final Channel channel, final ChannelPromise promise) {
channel.unsafe().register(this, promise);
return promise;
}
register的实现主要是为了调用Channel.Unsafe实例的register方法。
NIO实现
io.netty.channel.nio.NioEventLoopGroup extends MultithreadEventLoopGroup
io.netty.channel.nio.NioEventLoop extends SingleThreadEventLoop
NioEventLoopGroup是在MultithreadEventLoopGroup基础上实现了对JDK NIO Selector的封装, 它实现以下几个功能:
- 创建selector
- 在selector上注册channel感兴趣的NIO事件
- 实现EventExecutor的run方法,定义NIO事件和Executor任务的处理流程。
- 把NIO事件转换成对channel unsafe的调用或NioTask的调用
- 控制线程执行I/O操作和排队任务的用时比例
- 处理epoll selector cpu 100%的bug
下面来具体分析这几个功能的实现。
创建Selector
NioEventLoop#openSelector()实现了创建selector的功能,默认情况下,使用SelectorProvider#openSelector()方法创建一个新个selector:
final Selector unwrappedSelector = provider.openSelector();
如果设置环境变量io.netty.noKeySetOptimization=true, 会创建一个selectedKeySet = new SelectedSelectionKeySet(), 然后使用java的反射机制把selector的selectedKeys和publicSelectedKeys替换成selectedKeySet,具体步骤是:
1.得到selector的真正类型: sun.nio.ch.SelectorImpl
Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
return Class.forName(
"sun.nio.ch.SelectorImpl",
false,
PlatformDependent.getSystemClassLoader());
} catch (Throwable cause) {
return cause;
}
}
});
final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass;
2.替换selector是属性unwrappedSelector
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
selectedKeysField.set(unwrappedSelector, selectedKeySet);
publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
之所以会设计一个这样的优化选项,是因为一般情况下调用完selector的select或selectNow方法后需要调用Selector#selectedKeys()得到触发NIO事件的的SelectableChannel,这样优化之后,可以直接从selectedKeySet中得到已经触发了NIO事件的SelectableChannel。
在selector上注册channel感兴趣的NIO事件
NioEventLoop提供了unwrappedSelector方法,这个方法返回了它创建好的Selector实例。这样任何的外部类都可以把任意的SelectableChannel注册到这selector上。在AbstractNioChannel中, doRegister方法的实现就是使用了这个方法:
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
另外,它还提供了一个register方法:
public void register(final SelectableChannel ch, final int interestOps, final NioTask<?> task)
这个方法会把task当成SelectableChannel的附件注册到selector上:
ch.register(selector, interestOps, task);
实现EventExecutor的run方法,定义NIO事件和Executor任务的处理流程
在NioEventLoop的run方法中实现NIO事件和EventExecutor的任务处理逻辑,这个run方法在io.netty.util.concurrent.SingleThreadEventExecutor中定义。在上一章中,我们看到了DefaultEventExecutor中是如何实现这个run方法的,这里我们将要看到这run方法的另一个实现。和SingleThreadEventExecutor中的run方法相比,NioEventLoop的run方法不仅要及时地执行taskQueue中的任务,还要能及时地处理NIO事件,因此它会同时检查selector中的NIO事件和和taskQueue队列,任何一个中有事件需要处理或有任务需要执行,它不会阻塞线程。同时它也保证了在没有NIO事件和任务的情况下线程不会无谓的空转浪费CUP资源。
run主要实现如下,为了更清晰的说明它的主要功能,我对原来的代码进行了一些删减。
for(;;){
try{
//phase1: 同时检查NIO事件和任务
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
select(wakenUp.getAndSet(false)); //在taskQueue中没有任务的时候执行select
}
//phase2: 进入处理NIO事件,执行executor任务
try{
//处理NIO事件
processSelectedKeys();
}finally{
//处理taskQueu中的任务
runAllTasks();
}
}catch(Throwable t){
handleLoopException(t);
}
}
run方法有两个阶段构成:
phase1: 检查NIO事件或executor任务,如果有任何的NIO事件或executor任务进入phase2。
这样阶段的主要工作在selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())和select中完成。
selectStrategy.calculateStrategy实现
selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())
这行代码的含义是: 如果hasTasks() == true, 调用以下selector#selectNow, 然后进入phase2。 否则调用select。这里使用了strategy模式,默认的strategy实现是io.netty.channe.DefaultSelectStrategy implements SelectStrategy
@Override
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
}
DefaultSelectStrategy实现了SelectStrategy接口,这接口定义了两个常量:
int SELECT = -1;
int CONTINUE = -2;
运行时selectSuppler参数传入的是selectNowSupplier, 它的实现如下:
private final IntSupplier selectNowSupplier = new IntSupplier() {
@Override
public int get() throws Exception {
return selectNow();
}
};
这里的get方法调用了selectNow, selectNow调用的是Selector#selectNew方法,这个方法的返回值是>=0。
hashTasks的传入的参数是hasTask()的返回值: return !taskQueue.isEmpty();
代码读到这里就会发现,使用默认的的SelectStrategy实现,calculateStrategy在hasTasks()==true时返回值>=0, hasTasks() == false时返回值是SelectStrategy.SELECT,不会返回SelectStrategy.CONTINUE。
select实现
select的执行逻辑是:
1. 计算超select方法的结束时间selectDeadLineNanos
long currentTimeNanos = System.nanoTime();
long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
2. 进入循环,检查超时--超时跳出循环。
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
if (timeoutMillis <= 0) {
if (selectCnt == 0) {
selector.selectNow();
selectCnt = 1;
}
break;
}
3. 如果在select执行过程中有executor任务提交或可以当前的wakeUp由false变成true, 跳出循环
if (hasTasks() && wakenUp.compareAndSet(false, true)) {
selector.selectNow();
selectCnt = 1;
break;
}
4. 调用selector#select等待NIO事件。
int selectedKeys = selector.select(timeoutMillis);
selectCnt ++;
5. 如果满足这些条件的任何一个,跳出循环: 有NIO事件、wakeUp的新旧值都是true、taskQueue中有任务、有定时任务到期。
if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
break;
}
6. 如果线程被中断,跳出循环。
if (Thread.interrupted()) {
break;
}
7. 如果selector.select超时,没有检查到任何NIO事件, 会在下次循环开始时跳出循环。 如果每次超时,跳到第2步继续下一次循环。
long time = System.nanoTime();
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
selectCnt = 1;
}
currentTimeNanos = time;
select 最迟会在当前时间>= selectDeadLineNanos时返回,这个时间是最近一个到期的定时任务执行的时间,换言之如果没有任何的NIO事件或executor任务,select会在定时任务到期时返回。如果没有定时任务,delayNanos(currentTimeNanos)返回的值是 TimeUnit.SECONDS.toNanos(1),即1秒。 select会在检查到任何NIO事件或executor任务时返回,为了保证这点,在selector.select(timeoutMillis)前后都会调用hasTasks检查executor任务,为了能在调用executet提交任务时唤醒selector.select,NioEventLoop覆盖了SingleThreadEventExecutor的wake方法:
protected void wakeup(boolean inEventLoop) {
if (!inEventLoop && wakenUp.compareAndSet(false, true)) {
selector.wakeup();
}
}
这个方法会及时的唤醒selector.select, 保证新提交的任务可以得到及时的执行。
phase2: 进入处理NIO事件,执行executor任务
这个阶段是先调用processSelectedKeys()处理NIO事件,然后掉用 runAllTasks()处理所有已经到期的定时任务和已经在排队的任务。这个阶段还实现了NIO事件和executor任务的用时比例管理,这个特性稍后会详细分析。
- netty源码解解析(4.0)-11 Channel NIO实现-概览
结构设计 Channel的NIO实现位于io.netty.channel.nio包和io.netty.channel.socket.nio包中,其中io.netty.channel.nio是抽象实 ...
- netty源码解解析(4.0)-10 ChannelPipleline的默认实现--事件传递及处理
事件触发.传递.处理是DefaultChannelPipleline实现的另一个核心能力.在前面在章节中粗略地讲过了事件的处理流程,本章将会详细地分析其中的所有关键细节.这些关键点包括: 事件触发接口 ...
- netty源码解解析(4.0)-17 ChannelHandler: IdleStateHandler实现
io.netty.handler.timeout.IdleStateHandler功能是监测Channel上read, write或者这两者的空闲状态.当Channel超过了指定的空闲时间时,这个Ha ...
- netty源码解解析(4.0)-18 ChannelHandler: codec--编解码框架
编解码框架和一些常用的实现位于io.netty.handler.codec包中. 编解码框架包含两部分:Byte流和特定类型数据之间的编解码,也叫序列化和反序列化.不类型数据之间的转换. 下图是编解码 ...
- netty源码解解析(4.0)-20 ChannelHandler: 自己实现一个自定义协议的服务器和客户端
本章不会直接分析Netty源码,而是通过使用Netty的能力实现一个自定义协议的服务器和客户端.通过这样的实践,可以更深刻地理解Netty的相关代码,同时可以了解,在设计实现自定义协议的过程中需要解决 ...
- netty源码解解析(4.0)-4 线程模型-概览
netty线程体系概览 netty的高并发能力很大程度上由它的线程模型决定的,netty定义了两种类型的线程: I/O线程: EventLoop, EventLoopGroup.一个EventLoop ...
- netty源码解解析(4.0)-15 Channel NIO实现:写数据
写数据是NIO Channel实现的另一个比较复杂的功能.每一个channel都有一个outboundBuffer,这是一个输出缓冲区.当调用channel的write方法写数据时,这个数据被一系列C ...
- netty源码解解析(4.0)-12 Channel NIO实现:channel初始化
创建一个channel实例,并把它register到eventLoopGroup中之后,这个channel然后处于inactive状态,仍然是不可用的.只有在bind或connect方法调用成功之后才 ...
- netty源码解解析(4.0)-5 线程模型-EventExecutorGroup框架
上一章讲了EventExecutorGroup的整体结构和原理,这一章我们来探究一下它的具体实现. EventExecutorGroup和EventExecutor接口 io.netty.util.c ...
- netty源码解解析(4.0)-1 核心架构
netty是java开源社区的一个优秀的网络框架.使用netty,我们可以迅速地开发出稳定,高性能,安全的,扩展性良好的服务器应用程序.netty封装简化了在服务器开发领域的一些有挑战性的问题:jdk ...
随机推荐
- 开始Dev之路
从今天开始,开启Dev的发展之路.
- Python3实战系列之七(获取印度售后数据项目)
问题:续接上一篇.说干咱就干呀,勤勤恳恳写程序呀! 目标:此篇开始进入正题了.为实现我们整个项目功能而开始实现各个子模块功能.首先实现第一篇列出的分步功能模块的第四步: 4.python读取excel ...
- python_day11
一.简介 1.什么是数据库? 数据库(Database)是按照数据结构来组织.存储和管理数据的仓库,每个数据库都有一个或多个不同的API用于创建,访问,管理,搜索和复制所保存的数据. 2.关系型数据库 ...
- 别人的Linux私房菜(4)安装CentOS7
linux磁盘分区参考: 添加磁盘分区(总30G). BIOS boot 2MB 系统自定义文件系统 分区格式为主要分区 /boot 1GB 文件系统为xfs 主要分区 / 10GB 文件系统为x ...
- linux 系统管理学习
Linux系统管理一.进程管理1.进程管理的作用1)判断服务器健康状态2)查看系统中所有进程3)杀死进程2.进程的查看1)查看所有进程ps aux 查看系统中所有进程ps -le 查看系统中所有进程- ...
- Number of subarrays having sum exactly equal to k(explanation for 437. Path Sum III of leetcode)
https://www.geeksforgeeks.org/number-subarrays-sum-exactly-equal-k/ 讲解的详细 看这道题是为了解决https://leetcode. ...
- freeRTOS与裸机程序相比有什么区别??
FreeRTOS命名及变量规则 初学FreeRTOS的用户对其变量和函数的命名比较迷惑, FreeRTOS的核心源代码遵从MISRA编码标准指南,关于MISRA编码标准,可以查看文章https: ...
- js-function作用域
你能猜出先弹出什么吗? <!DOCTYPE html> <html lang="en"><head> <meta charset=&quo ...
- 【WPF】实现类似QQ聊天消息的界面
最近公司有个项目,是要求实现类似 QQ 聊天这种功能的. 如下图 这没啥难的,稍微复杂的也就表情的解析而已. 表情在传输过程中的实现参考了新浪微博,采用半角中括号代表表情的方式.例如:“abc[dog ...
- Oracle服务器和客户端安装在同一台机器的情况
最近重装了系统,所有的开发环境需要重新部署一下,因此重新安装了Oracle,结果原来没有问题,这一次又碰到了几个问题(tns12154和tns03505),让我好一搞啊.不过又重新对Oracle加深了 ...