相关文章链接
processSelectedKeys() vs runAllTasks()
NioServerSocketChannel-Unsafe初始化详解
processSelectedKeys() vs runAllTasks()
核心区别
简单来说:
processSelectedKeys():处理 IO 事件(网络读写)runAllTasks():处理 普通任务(业务逻辑)
一、processSelectedKeys() - 处理 IO 事件
1. 什么是 IO 事件?
IO 事件就是网络相关的事件,由 Selector 监听到的。
IO 事件的类型:
├─ OP_ACCEPT (16) // 服务端接受新连接
├─ OP_CONNECT (8) // 客户端连接完成
├─ OP_READ (1) // 有数据可读
└─ OP_WRITE (4) // 可以写数据
2. processSelectedKeys() 做什么?
private void processSelectedKeys() {
// 遍历所有准备好的 Channel
for (int i = 0; i < selectedKeys.size; ++i) {
SelectionKey k = selectedKeys.keys[i];
AbstractNioChannel ch = (AbstractNioChannel) k.attachment();
// 处理这个 Channel 的 IO 事件
processSelectedKey(k, ch);
}
}
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
int readyOps = k.readyOps(); // 获取就绪的操作类型
// 1. 处理连接完成事件
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
unsafe.finishConnect();
}
// 2. 处理写事件
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush(); // 刷新写缓冲区
}
// 3. 处理读事件或接受事件
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0) {
unsafe.read(); // 读取数据或接受新连接
}
}
3. 实际例子
场景:客户端发送数据到服务端
客户端:
channel.writeAndFlush("Hello Server");
数据通过网络发送
服务端 EventLoop:
selector.select() // 检测到 OP_READ 事件
↓
processSelectedKeys() // 处理 IO 事件
↓
找到对应的 Channel
↓
检测到 OP_READ 事件
↓
unsafe.read() // 从 Socket 读取数据
↓
读取到:"Hello Server"
↓
触发 pipeline.fireChannelRead("Hello Server")
↓
数据流经 pipeline 中的 handlers
关键点:processSelectedKeys() 是在处理"网络层"的事件。
二、runAllTasks() - 处理普通任务
1. 什么是普通任务?
普通任务是通过 execute() 方法提交到 taskQueue 中的任务。
普通任务的来源:
├─ 用户代码提交的任务
├─ Netty 内部提交的任务(如 register、bind、connect)
├─ 定时任务
└─ 其他异步操作
2. runAllTasks() 做什么?
protected boolean runAllTasks(long timeoutNanos) {
// 1. 从定时任务队列取出到期的任务
fetchFromScheduledTaskQueue();
// 2. 计算截止时间
final long deadline = nanoTime() + timeoutNanos;
long runTasks = 0;
// 3. 循环执行任务
for (;;) {
Runnable task = pollTask(); // 从 taskQueue 取任务
if (task == null) {
break; // 没有任务了
}
safeExecute(task); // 执行任务
runTasks++;
// 每 64 个任务检查一次时间
if ((runTasks & 0x3F) == 0) {
if (nanoTime() >= deadline) {
break; // 超时了,停止执行
}
}
}
return true;
}
3. 实际例子
场景:注册 Channel
// main 线程
ServerBootstrap b = new ServerBootstrap();
ChannelFuture f = b.bind(port);
// 内部流程
initAndRegister()
↓
eventLoop.register(channel)
↓
eventLoop.execute(new Runnable() { // 提交任务到 taskQueue
public void run() {
register0(promise); // 这是一个普通任务
}
});
// EventLoop 线程
runAllTasks() // 处理普通任务
↓
从 taskQueue 取出 register0 任务
↓
执行 register0
↓
doRegister() // 将 Channel 注册到 Selector
↓
pipeline.fireChannelRegistered()
关键点:runAllTasks() 是在处理"应用层"的任务。
三、两者的区别对比
对比表格
| 特性 | processSelectedKeys() | runAllTasks() |
|---|---|---|
| 处理对象 | IO 事件(网络事件) | 普通任务(业务逻辑) |
| 数据来源 | Selector(JDK NIO) | taskQueue(Netty) |
| 触发方式 | 网络事件触发 | 代码主动提交 |
| 处理内容 | 读、写、连接、接受 | 任意 Runnable |
| 执行时机 | select() 返回后 | 每次循环都执行 |
| 是否阻塞 | 不阻塞(快速处理) | 可能耗时(业务逻辑) |
形象比喻
EventLoop 就像一个餐厅服务员:
processSelectedKeys():
服务客人(处理网络 IO)
- 客人叫服务员(网络事件)
- 服务员给客人上菜(读数据)
- 服务员收客人的盘子(写数据)
- 这是服务员的主要工作
runAllTasks():
做杂活(处理普通任务)
- 打扫卫生(业务逻辑)
- 整理餐具(内部任务)
- 准备食材(定时任务)
- 这是服务员的额外工作
四、完整的执行流程
场景:客户端发送数据,服务端处理并回复
【第 1 步:客户端发送数据】
客户端:
channel.writeAndFlush("Hello");
【第 2 步:服务端 EventLoop 检测到事件】
服务端 EventLoop:
selector.select() // 检测到 OP_READ 事件
返回
【第 3 步:处理 IO 事件】
processSelectedKeys()
↓
找到对应的 Channel
↓
检测到 OP_READ 事件
↓
unsafe.read()
↓
从 Socket 读取数据:"Hello"
↓
触发 pipeline.fireChannelRead("Hello")
↓
数据流经 pipeline:
Decoder → 解码
BusinessHandler → 处理业务逻辑
↓
在 BusinessHandler 中:
ctx.writeAndFlush("World"); // 回复客户端
↓
这会提交一个 write 任务到 taskQueue
【第 4 步:处理普通任务】
runAllTasks()
↓
从 taskQueue 取出 write 任务
↓
执行 write 任务
↓
将 "World" 写入 Socket 缓冲区
↓
如果缓冲区满了,注册 OP_WRITE 事件
↓
下次循环的 processSelectedKeys() 会处理 OP_WRITE 事件
五、ioRatio 的作用
现在我们理解了两种任务的区别,再来看 ioRatio:
final int ioRatio = this.ioRatio; // 默认 50
if (ioRatio == 100) {
// 不限制任务执行时间
try {
processSelectedKeys(); // 处理 IO 事件
} finally {
runAllTasks(); // 处理所有任务
}
} else {
// 限制任务执行时间
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys(); // 处理 IO 事件
} finally {
final long ioTime = System.nanoTime() - ioStartTime;
// 根据 IO 时间计算任务执行时间
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
ioRatio 的含义
ioRatio 控制 IO 事件处理和普通任务处理的时间比例。
ioRatio = 50(默认):
IO 时间 : 任务时间 = 50 : 50 = 1 : 1
如果处理 IO 事件花了 100ms
那么处理任务最多花 100ms
计算:100 * (100 - 50) / 50 = 100ms
ioRatio = 70:
IO 时间 : 任务时间 = 70 : 30
如果处理 IO 事件花了 100ms
那么处理任务最多花 43ms
计算:100 * (100 - 70) / 70 ≈ 43ms
ioRatio = 100:
不限制任务执行时间
处理完所有 IO 事件
然后处理完所有任务
为什么需要 ioRatio?
平衡两种工作:
如果只处理 IO 事件:
✗ 任务队列会堆积
✗ 业务逻辑得不到执行
如果只处理任务:
✗ IO 事件得不到及时处理
✗ 网络数据会丢失或延迟
通过 ioRatio 平衡:
✓ IO 事件及时处理
✓ 任务也能执行
✓ 两者都不会饿死
353

被折叠的 条评论
为什么被折叠?



