doRegister() 方法详解
方法概述
doRegister() 方法是 AbstractChannel 中 register0() 调用的核心方法,它负责将 Netty 的 Channel 与 JDK NIO 的底层 Channel 进行关联,并注册到 Selector 上。
源码分析
完整源码
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
// JDK NIO Channel 的 register 方法签名:
// public final SelectionKey register(Selector sel, int ops, Object att)
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
// 强制 Selector 立即执行 select 操作
// 这会清理掉已取消的 SelectionKey
eventLoop().selectNow();
selected = true;
} else {
// 如果已经执行过 selectNow() 还是失败,说明有严重问题,抛出异常
throw e;
}
}
}
}
详细分析
1. 核心注册操作
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
这一行代码包含了几个关键点:
(1) javaChannel()
- 返回 JDK NIO 的原生 Channel 对象
- 对于
NioSocketChannel,返回的是SocketChannel - 对于
NioServerSocketChannel,返回的是ServerSocketChannel
(2) eventLoop().unwrappedSelector()
- 获取当前 EventLoop 中的 Selector 实例
unwrappedSelector()返回的是原始的 JDK Selector,而不是 Netty 优化后的包装版本- 这个 Selector 是在 NioEventLoop 构造时创建的
(3) 第二个参数:0
这是一个非常重要的设计!
- 注册时将
interestOps设置为 0,表示暂时不监听任何 IO 事件 - 为什么不直接设置为
OP_READ或OP_ACCEPT?
原因:
- 此时 pipeline 可能还没有完全构建好
- handler 的
handlerAdded()方法还没有被调用 - 如果立即监听事件,可能会在 pipeline 准备好之前就收到 IO 事件
- 真正的事件监听会在
channelActive事件触发时通过beginRead()方法设置
(4) 第三个参数:this
- 将 Netty 的 Channel 对象作为 attachment 附加到 SelectionKey 上
- 这样在 Selector 检测到事件时,可以通过
SelectionKey.attachment()快速获取到对应的 Netty Channel - 这是 Netty 将 JDK NIO 和自己的抽象层关联起来的关键
2. 返回值:SelectionKey
selectionKey = javaChannel().register(...);
- 注册成功后,JDK 会返回一个
SelectionKey对象 - Netty 将这个 key 保存在 Channel 的成员变量中
- 后续可以通过这个 key 来修改监听的事件类型(interestOps)
3. 异常处理:CancelledKeyException
catch (CancelledKeyException e) {
if (!selected) {
eventLoop().selectNow();
selected = true;
} else {
throw e;
}
}
为什么会抛出 CancelledKeyException?
当一个 SelectionKey 被取消后,如果再次尝试使用它,就会抛出这个异常。可能的场景:
- 之前的注册操作被取消了,但 key 还没有从 Selector 的 key set 中移除
- 在多线程环境下,另一个线程取消了这个 key
处理策略
第一次捕获异常时:
eventLoop().selectNow();
- 调用
selectNow()非阻塞地执行一次 select 操作 - 这会触发 Selector 清理已取消的 SelectionKey
- 清理完成后,重新尝试注册(因为在 for 循环中)
第二次还是失败:
- 如果执行了
selectNow()后重试还是失败,说明有更严重的问题 - 直接抛出异常,让上层处理
4. 无限循环的设计
for (;;) {
try {
selectionKey = javaChannel().register(...);
return; // 成功后立即返回
} catch (CancelledKeyException e) {
// 处理异常,然后继续循环重试
}
}
- 使用无限循环是为了处理
CancelledKeyException后能够重试 - 正常情况下,第一次就会成功并 return
- 只有在极少数情况下才会进入异常处理和重试逻辑
关键时序图
register0()
|
v
doRegister()
|
v
javaChannel().register(selector, 0, this)
|
v
返回 SelectionKey
|
v
保存到 selectionKey 成员变量
|
v
后续通过 selectionKey 修改 interestOps
与后续流程的关联
1. 注册后的状态
注册完成后:
- Channel 已经和 Selector 关联
- 但
interestOps = 0,不监听任何事件 - SelectionKey 已保存,可以随时修改监听的事件
2. 何时开始真正监听事件?
在 register0() 方法的后续流程中:
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive(); // 触发 channelActive 事件
}
}
channelActive 事件会触发 HeadContext 的处理:
// HeadContext.channelActive()
public void channelActive(ChannelHandlerContext ctx) {
ctx.fireChannelActive(); // 继续传播事件
readIfIsAutoRead(); // 如果配置了 autoRead,开始读取
}
private void readIfIsAutoRead() {
if (channel.config().isAutoRead()) {
channel.read(); // 最终会调用到 beginRead()
}
}
beginRead() 方法会修改 interestOps:
protected void doBeginRead() throws Exception {
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
final int interestOps = selectionKey.interestOps();
// readInterestOp 在构造时设置:
// 客户端是 OP_READ,服务端是 OP_ACCEPT
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
设计精妙之处
1. 延迟监听策略
- 注册时不监听:给 pipeline 构建留出时间
- 激活时才监听:确保所有准备工作都已完成
- 避免竞态条件:防止在未准备好时就收到 IO 事件
2. Attachment 机制
register(selector, 0, this) // this 就是 Netty Channel
在 NioEventLoop 处理 IO 事件时:
// NioEventLoop.processSelectedKeys()
SelectionKey key = ...;
NioSocketChannel channel = (NioSocketChannel) key.attachment();
// 直接获取到 Netty Channel,无需额外查找
这种设计避免了维护一个 Map<SelectionKey, Channel> 的开销。
3. 异常恢复机制
通过 selectNow() + 重试的方式,优雅地处理了 SelectionKey 取消的边界情况,提高了系统的健壮性。
总结
doRegister() 方法虽然代码不多,但设计非常精妙:
- 桥接作用:连接 Netty Channel 和 JDK NIO Channel
- 延迟监听:注册时 interestOps=0,避免过早收到事件
- 关联机制:通过 attachment 建立双向关联
- 异常处理:优雅处理 SelectionKey 取消的情况
- 为后续铺路:保存 SelectionKey,供后续修改 interestOps 使用
这个方法是 Netty 启动流程中,从应用层到操作系统层的关键桥梁!
3万+



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



