Netty 第七篇:Channel 注册流程详解
上一篇我们讲了 initAndRegister() 中的 init 阶段——NioServerSocketChannel 的创建、AbstractNioChannel 设置非阻塞、AbstractChannel 创建 ID / Unsafe / Pipeline,以及 ServerBootstrap.init() 添加 ServerBootstrapAcceptor。
这一篇,我们继续跟进 initAndRegister() 中的第二个阶段——register。这个阶段做的事情,简单来说就是:
把刚刚创建并初始化好的 Channel,注册到 childGroup 中某个 NioEventLoop 的 Selector 上,然后触发一系列事件,最终让这个 Channel 开始监听客户端连接。
register 的调用链非常长,涉及多个类的协作。我们一步一步来拆。
一、register 的宏观入口
回顾一下 AbstractBootstrap.doBind() 中的核心代码:
// AbstractBootstrap.java
final ChannelFuture regFuture = group().register(channel);
这里的 group() 返回的是谁?回顾第四篇的内容——在服务端启动时,我们调用了:
serverBootstrap
.group(new NioEventLoopGroup(1), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(...);
group() 返回的是 parentGroup(bossGroup),也就是那个只有 1 个线程的 NioEventLoopGroup。
所以这里实际调用的是:
parentGroup.register(channel)
也就是把 ServerSocketChannel 注册到 parentGroup 中的那个唯一 NioEventLoop 上。
二、从 childGroup 到 SingleThreadEventLoop
NioEventLoopGroup 继承自 MultithreadEventLoopGroup,它的 register 方法定义在父类中:
// MultithreadEventLoopGroup.java
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
next() 方法会按照轮询策略,从 children 数组中选出一个 NioEventLoop。因为我们 parentGroup 只有 1 个线程,所以这里选出来的永远是那一个。
选出来之后,调用 NioEventLoop.register(channel)。NioEventLoop 继承自 SingleThreadEventLoop,所以我们看 SingleThreadEventLoop 的 register 方法:
// SingleThreadEventLoop.java
public ChannelFuture register(Channel channel) {
return register(new DefaultChannelPromise(channel, this));
}
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
promise.channel().unsafe().register(this, promise);
return promise;
}
这里出现了两个关键点:
unsafe():返回的是 Channel 内部的Unsafe对象register(this, promise):把当前 NioEventLoop 和 Promise 传进去
三、AbstractChannel 的 register 方法
channel.unsafe().register() 实际调用的是 AbstractChannel 中的 register 方法:
// AbstractChannel.java
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
if (eventLoop == null) {
throw new NullPointerException("eventLoop");
}
// 1️⃣ 保存 eventLoop 引用
AbstractChannel.this.eventLoop = eventLoop;
// 2️⃣ 判断当前线程是否在 eventLoop 中
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
}
}
这段逻辑非常精妙:
- 保存 eventLoop 引用:从此这个 Channel 就"绑定"到了这个 NioEventLoop 上,后续所有的 IO 事件都由这个线程处理。
- 判断是否在 eventLoop 线程中:如果当前线程就是 eventLoop 的线程,直接调用
register0();否则,提交一个任务到 eventLoop 的任务队列中,等 eventLoop 线程自己来执行。
这就是为什么 Netty 能做到"一个 Channel 永远由一个线程处理"——注册的那一刻就绑定了,后续永远不会变。
四、register0() 核心三步
不管走哪条路径,最终都会执行到 register0() 方法:
// AbstractChannel.java
private void register0(ChannelPromise promise) {
try {
// 1️⃣ 真正注册到 JDK 的 Selector 上
doRegister();
// 2️⃣ 回调 Pipeline 中所有 Handler 的 handlerAdded 方法
pipeline.invokeHandlerAddedIfNeeded();
// 3️⃣ 触发 ChannelRegistered 和 ChannelActive 事件
pipeline.fireChannelRegistered();
if (isActive()) {
pipeline.fireChannelActive();
}
} catch (Throwable t) {
// 异常处理
promise.setFailure(t);
}
}
三步,每一步都至关重要。我们逐一拆解。
五、doRegister():真正注册到 Selector
doRegister() 是一个抽象方法,实际实现在 AbstractNioChannel 中:
// AbstractNioChannel.java
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(
eventLoop().selector, 0, this);
return;
} catch (CancelledKeyException e) {
// 处理取消的 Key,重试
if (!selected) {
eventLoop().selectNow();
selected = true;
continue;
}
throw e;
}
}
}
核心就一行:
selectionKey = javaChannel().register(eventLoop().selector, 0, this);
这行代码做了什么?它调用了 JDK ServerSocketChannel 的 register 方法:
- 第一个参数:
eventLoop().selector——NioEventLoop 中持有的那个 Selector - 第二个参数:
0——暂时不关注任何事件 - 第三个参数:
this——把 Channel 自身作为 attachment 传进去
selectionKey.attachment() 拿回这个 Channel 对象,然后找到对应的 Pipeline 来处理事件。这就是 Netty 事件分发的基石。六、invokeHandlerAddedIfNeeded():回调 HandlerAdded
注册完成后,Netty 会回调 Pipeline 中所有 Handler 的 handlerAdded 方法:
// DefaultChannelPipeline.java
public final ChannelPipeline invokeHandlerAddedIfNeeded() {
// 遍历 Pipeline 中所有 Handler
// 对尚未调用过 handlerAdded 的 Handler,调用其 handlerAdded 方法
}
在我们的服务端代码中,Pipeline 里有什么?回顾第六篇的内容:
HeadContext(默认,位于 Pipeline 头部)ServerBootstrapAcceptor(在ServerBootstrap.init()中添加的)TailContext(默认,位于 Pipeline 尾部)
所以这一步会依次调用:
HeadContext.handlerAdded()ServerBootstrapAcceptor.handlerAdded()TailContext.handlerAdded()
对 ServerBootstrapAcceptor 来说,它的 handlerAdded 方法继承自 ChannelHandlerAdapter,默认是空实现,什么也不做。但它的存在本身就有意义——它已经在 Pipeline 中就位,等待后续的 Accept 事件到来。
七、fireChannelRegistered() 与 fireChannelActive()
回调完 Handler 后,Netty 开始正向传播两个事件:
pipeline.fireChannelRegistered();
if (isActive()) {
pipeline.fireChannelActive();
}
fireChannelRegistered() 从 HeadContext 开始,沿着 Pipeline 依次调用每个 Handler 的 channelRegistered 方法。
fireChannelActive() 同理,从 HeadContext 开始,依次调用每个 Handler 的 channelActive 方法。
八、从 channelActive 到 read:连锁反应
HeadContext 的 channelActive 方法中有这样一段逻辑:
// HeadContext.java
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelActive();
readIfIsAutoRead();
}
readIfIsAutoRead() 会判断 Channel 是否开启了 autoRead(默认是开启的)。如果开启了,就调用 channel.read()。
channel.read会调用pipeline.read方法,这个 read 会从 TailContext 开始,沿着 Pipeline 从尾到头走一遍。最终会到达 HeadContext 自己的 read 方法,在里面调用 unsafe.beginRead()。
unsafe.beginRead() → AbstractChannel.doBeginRead() → AbstractNioChannel.doBeginRead():
// AbstractNioChannel.java
protected void doBeginRead() throws Exception {
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
// 注册感兴趣的事件
selectionKey.interestOps(readInterestOp);
}
还记得第六篇中提到的 readInterestOp 吗?对于 ServerSocketChannel,它的值是 SelectionKey.OP_ACCEPT(值为 16)。
所以这一步的最终效果是:
告诉 JDK Selector:我对这个 ServerSocketChannel 上的 OP_ACCEPT 事件感兴趣。当有客户端连接进来时,Selector 就能感知到。
九、完整注册流程总结
把整个调用链串起来,从 group().register(channel) 到最终注册 OP_ACCEPT 事件,一共经历了以下阶段:
| 序号 | 所在类 | 核心动作 |
|---|---|---|
| 1 | MultithreadEventLoopGroup |
next() 轮询选出一个 NioEventLoop |
| 2 | SingleThreadEventLoop |
调用 channel.unsafe().register(this, promise) |
| 3 | AbstractChannel |
保存 eventLoop 引用,判断线程归属 |
| 4 | AbstractChannel |
调用 register0(promise) |
| 5 | AbstractNioChannel |
doRegister():注册到 JDK Selector,attachment = this |
| 6 | DefaultChannelPipeline |
invokeHandlerAddedIfNeeded():回调所有 handlerAdded |
| 7 | DefaultChannelPipeline |
fireChannelRegistered():正向传播 Registered 事件 |
| 8 | DefaultChannelPipeline |
fireChannelActive():正向传播 Active 事件 |
| 9 | HeadContext |
channelActive() 中调用 readIfIsAutoRead() |
| 10 | AbstractNioChannel |
doBeginRead():interestOps(readInterestOp) |
用一张图来描述就是:
group().register(channel)
→ next() 选 NioEventLoop
→ SingleThreadEventLoop.register()
→ AbstractChannel.register()
→ register0()
→ doRegister() // JDK Selector 注册
→ invokeHandlerAdded() // 回调 handlerAdded
→ fireRegistered() // 传播 Registered
→ fireActive() // 传播 Active
→ readIfIsAutoRead()
→ pipeline.read()
→ unsafe.beginRead()
→ doBeginRead()
→ interestOps(OP_ACCEPT) // 最终注册监听事件
十、总结
这一篇我们完整追踪了 initAndRegister() 中 register 阶段的全部调用链。核心要点如下:
- Channel 与 EventLoop 的绑定:注册的那一刻就确定了"谁管这个 Channel",后续永不改变。
- JDK Selector 的注册:通过
javaChannel().register(selector, 0, this)完成,attachment 是 Channel 自身。 - Pipeline 事件传播:Registered → Active → read,层层推进,最终触发对 OP_ACCEPT 的监听。
- 为什么延迟注册事件:Netty 采用"按需注册"策略,readInterestOp 在第六篇初始化时埋下伏笔,在这里才真正生效。
- 调用链虽长但职责清晰:每个类各司其职,EventLoop 管调度、AbstractChannel 管流程、AbstractNioChannel 管 JDK 交互、Pipeline 管事件传播。
至此,initAndRegister() 的两个阶段(init 和 register)我们都讲完了。接下来还有 doBind0() 没讲——端口绑定的内部逻辑是什么?绑定之后服务端是如何开始监听客户端连接的?这些我们下一篇继续。
— 原创文章,转载请注明出处 —

浙公网安备 33010602011771号