Netty源码分析---register0方法详解

Netty源码分析系列之服务端Channel注册 Netty中服务端channel是如何注册到多路复用器上的? 阅读详情

register0() 方法详解

本文详细介绍 AbstractChannel 中的 register0() 方法,这是 Channel 注册到 EventLoop 的核心实现。

一、方法概述

1.1 方法签名

// AbstractChannel.AbstractUnsafe
private void register0(ChannelPromise promise) {
    // 实现代码
}

1.2 调用时机

register0() 方法是在 register(EventLoop, ChannelPromise) 方法中被调用的:

@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    // ... 参数校验 ...
    
    AbstractChannel.this.eventLoop = eventLoop;
    
    if (eventLoop.inEventLoop()) {
        // 如果当前线程就是 EventLoop 线程,直接调用
        register0(promise);
    } else {
        // 否则,提交任务到 EventLoop 的任务队列
        eventLoop.execute(new Runnable() {
            @Override
            public void run() {
                register0(promise);
            }
        });
    }
}

关键点:

  • register0() 一定是在 EventLoop 线程中执行的
  • 要么直接执行(当前线程就是 EventLoop 线程)
  • 要么通过 eventLoop.execute() 提交任务后执行

1.3 方法作用

register0() 方法完成以下核心工作:

  1. 将 Channel 注册到 Selector 上(JDK 底层操作)
  2. 触发 pipeline 中 handler 的 handlerAdded() 回调
  3. 触发 channelRegistered 事件
  4. 如果 Channel 已激活,触发 channelActive 事件

二、完整源码分析

2.1 完整代码

private void register0(ChannelPromise promise) {
    try {
        // 1. 检查 Channel 是否仍然打开
        if (!promise.setUncancellable() || !ensureOpen(promise)) {
            return;
        }
        
        // 2. 标记是否是第一次注册
        boolean firstRegistration = neverRegistered;
        
        // 3. 执行实际的注册操作(JDK 底层)
        doRegister();
        
        // 4. 更新注册状态
        neverRegistered = false;
        registered = true;

        // 5. 触发 handlerAdded 事件
        pipeline.invokeHandlerAddedIfNeeded();

        // 6. 设置 promise 为成功
        safeSetSuccess(promise);
        
        // 7. 触发 channelRegistered 事件
        pipeline.fireChannelRegistered();
        
        // 8. 如果 Channel 已激活,触发 channelActive 事件
        if (isActive()) {
            if (firstRegistration) {
                pipeline.fireChannelActive();
            } else if (config().isAutoRead()) {
                beginRead();
            }
        }
    } catch (Throwable t) {
        // 9. 异常处理
        closeForcibly();
        closeFuture.setClosed();
        safeSetFailure(promise, t);
    }
}

2.2 执行流程图

register0() 开始
    ↓
检查 Channel 是否打开
    ↓
记录是否首次注册(firstRegistration)
    ↓
doRegister() - 将 Channel 注册到 Selector
    ↓
更新状态标记(neverRegistered = false, registered = true)
    ↓
pipeline.invokeHandlerAddedIfNeeded() - 触发 handlerAdded
    ↓
safeSetSuccess(promise) - 设置 promise 成功
    ↓
pipeline.fireChannelRegistered() - 触发 channelRegistered 事件
    ↓
判断 Channel 是否激活?
    ├─ 是 → 判断是否首次注册?
    │        ├─ 是 → fireChannelActive()
    │        └─ 否 → 判断是否 autoRead?
    │                 └─ 是 → beginRead()
    └─ 否 → 结束

三、逐步详解

步骤 1:检查 Channel 是否仍然打开

if (!promise.setUncancellable() || !ensureOpen(promise)) {
    return;
}
promise.setUncancellable()
// DefaultPromise
@Override
public boolean setUncancellable() {
    if (RESULT_UPDATER.compareAndSet(this, null, UNCANCELLABLE)) {
        return true;
    }
    Object result = this.result;
    return !isDone0(result) || !isCancelled0(result);
}

作用:

  • 将 promise 标记为不可取消状态
  • 防止在注册过程中被取消
  • 使用 CAS 操作保证线程安全

返回值:

  • true:设置成功,或者 promise 已经完成但不是被取消的
  • false:promise 已经被取消
ensureOpen(promise)
protected final boolean ensureOpen(ChannelPromise promise) {
    if (isOpen()) {
        return true;
    }
    safeSetFailure(promise, newClosedChannelException(initialCloseCause));
    return false;
}

作用:

  • 检查 Channel 是否仍然打开
  • 如果已关闭,设置 promise 失败并返回 false

为什么需要这个检查?

  • register 操作可能是异步的(通过 eventLoop.execute 提交)
  • 在任务排队等待执行期间,Channel 可能已经被关闭
  • 需要在真正执行注册前再次确认 Channel 状态

步骤 2:标记是否是第一次注册

boolean firstRegistration = neverRegistered;

neverRegistered 字段:

// AbstractChannel
private volatile boolean neverRegistered = true;

作用:

  • 记录这个 Channel 是否从未注册过
  • 用于后续判断是否需要触发 channelActive 事件
  • Netty 支持 Channel 的 deregister 和 re-register,所以需要这个标记

使用场景:

  • 第一次注册:neverRegistered = true,需要触发完整的初始化流程
  • 重新注册:neverRegistered = false,只需要重新开始读取数据

步骤 3:执行实际的注册操作(doRegister)

doRegister();

这是最核心的一步,将 Channel 注册到 Selector 上。

NioSocketChannel 的 doRegister 实现
// AbstractNioChannel
@Override
protected void doRegister() throws Exception {
    boolean selected = false;
    for (;;) {
        try {
            // 调用 JDK 的 Channel.register() 方法
            // 参数说明:
            // - eventLoop().unwrappedSelector():获取 Selector
            // - 0:初始不监听任何事件
            // - this:将当前 NioSocketChannel 作为 attachment 附加到 SelectionKey 上
            selectionKey = javaChannel().register(
                eventLoop().unwrappedSelector(), 0, this);
            return;
        } catch (CancelledKeyException e) {
            if (!selected) {
                // 如果注册失败,强制 selectNow() 清理已取消的 key
                eventLoop().selectNow();
                selected = true;
            } else {
                // 如果再次失败,抛出异常
                throw e;
            }
        }
    }
}

关键点详解:

1. javaChannel() 是什么?
// AbstractNioChannel
protected SelectableChannel javaChannel() {
    return ch;
}

// 在 NioSocketChannel 构造时创建
public NioSocketChannel() {
    this(DEFAULT_SELECTOR_PROVIDER);
}

public NioSocketChannel(SelectorProvider provider) {
    this(newSocket(provider));  // 创建 JDK 的 SocketChannel
}

private static SocketChannel newSocket(SelectorProvider provider) {
    return provider.openSocketChannel();
}
  • javaChannel() 返回的是 JDK NIO 的 SocketChannel 实例
  • 这是在 NioSocketChannel 构造时创建的
  • 对于服务端,返回的是 ServerSocketChannel
2. 为什么 interestOps 设置为 0?
selectionKey = javaChannel().register(selector, 0, this);

原因:

  • 此时只是完成注册,还不需要监听任何事件
  • 后续会在合适的时机修改 interestOps
  • 对于客户端:在 connect 完成后,会设置为 OP_READ
  • 对于服务端:在 bind 完成后,会设置为 OP_ACCEPT

何时修改 interestOps?

// 在 channelActive 事件中
pipeline.fireChannelActive()
    ↓
HeadContext.channelActive()
    ↓
readIfIsAutoRead()
    ↓
channel.read()
    ↓
AbstractChannel.read()
    ↓
pipeline.read()
    ↓
HeadContext.read()
    ↓
unsafe.beginRead()
    ↓
AbstractNioChannel.doBeginRead()
    ↓
selectionKey.interestOps(interestOps | readInterestOp);
3. 为什么将 this 作为 attachment?
selectionKey = javaChannel().register(selector, 0, this);

作用:

  • 将 Netty 的 Channel(NioSocketChannel)附加到 SelectionKey 上
  • 当 Selector 检测到事件时,可以通过 key.attachment() 获取到对应的 Channel
  • 这样就建立了 JDK Channel 和 Netty Channel 的关联

使用场景:

// 在 NioEventLoop 处理 IO 事件时
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    // ch 就是从 attachment 中获取的
    final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
    
    int readyOps = k.readyOps();
    if ((readyOps & SelectionKey.OP_READ) != 0) {
        unsafe.read();  // 处理读事件
    }
}
4. 为什么要循环重试?
for (;;) {
    try {
        selectionKey = javaChannel().register(selector, 0, this);
        return;
    } catch (CancelledKeyException e) {
        if (!selected) {
            eventLoop().selectNow();
            selected = true;
        } else {
            throw e;
        }
    }
}

原因:

  • 如果 SelectionKey 已经被取消,register 会抛出 CancelledKeyException
  • 调用 selectNow() 可以清理已取消的 key
  • 然后重试注册
  • 如果重试还失败,说明有其他问题,抛出异常

步骤 4:更新注册状态

neverRegistered = false;
registered = true;
neverRegistered
private volatile boolean neverRegistered = true;
  • 初始值为 true,表示从未注册过
  • 注册成功后设置为 false
  • 即使后续 deregister,这个值也不会再变回 true
  • 用于区分"第一次注册"和"重新注册"
registered
private volatile boolean registered;
  • 表示当前是否已注册
  • 注册成功后设置为 true
  • deregister 后会设置为 false
  • 用于判断当前 Channel 的注册状态

两者的区别:

第一次注册:
    neverRegistered: true -> false
    registered: false -> true

deregister:
    neverRegistered: false(不变)
    registered: true -> false

重新注册:
    neverRegistered: false(不变)
    registered: false -> true

步骤 5:触发 handlerAdded 事件

pipeline.invokeHandlerAddedIfNeeded();

这是一个非常重要的步骤,会触发 pipeline 中所有 handler 的 handlerAdded() 回调。

为什么需要这个方法?

在 Channel 创建时,我们会往 pipeline 中添加 handler:

bootstrap.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) {
        ch.pipeline().addLast(new LoggingHandler());
        ch.pipeline().addLast(new MyHandler());
    }
});

但是,ChannelInitializer 的 initChannel() 方法什么时候被调用呢?就是在这里!

调用链路
pipeline.invokeHandlerAddedIfNeeded()
    ↓
DefaultChannelPipeline.invokeHandlerAddedIfNeeded()
    ↓
callHandlerAddedForAllHandlers()
    ↓
遍历 pipeline 中的所有 handler
    ↓
调用每个 handler 的 handlerAdded() 方法
    ↓
ChannelInitializer.handlerAdded()
    ↓
initChannel(channel)  // 这里会执行我们自定义的初始化逻辑
    ↓
将 ChannelInitializer 从 pipeline 中移除
ChannelInitializer 的实现
public abstract class ChannelInitializer<C extends Channel> 
        extends ChannelInboundHandlerAdapter {
    
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        if (ctx.channel().isRegistered()) {
            // 如果 Channel 已注册,立即执行初始化
            if (initChannel(ctx)) {
                // 初始化完成后,移除自己
                removeState(ctx);
            }
        }
    }
    
    private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
        if (initMap.add(ctx)) {
            try {
                // 调用用户实现的 initChannel 方法
                initChannel((C) ctx.channel());
            } catch (Throwable cause) {
                exceptionCaught(ctx, cause);
            } finally {
                // 从 pipeline 中移除 ChannelInitializer
                ChannelPipeline pipeline = ctx.pipeline();
                if (pipeline.context(this) != null) {
                    pipeline.remove(this);
                }
            }
            return true;
        }
        return false;
    }
    
    // 用户需要实现这个方法
    protected abstract void initChannel(C ch) throws Exception;
}

执行效果:

注册前的 pipeline:
head -> ChannelInitializer -> tail

执行 invokeHandlerAddedIfNeeded() 后:
head -> LoggingHandler -> MyHandler -> tail

ChannelInitializer 已被移除

步骤 6:设置 promise 为成功

safeSetSuccess(promise);
safeSetSuccess 的实现
protected final void safeSetSuccess(ChannelPromise promise) {
    if (!(promise instanceof VoidChannelPromise) && !promise.trySuccess()) {
        logger.warn("Failed to mark a promise as success because it is done already: {}", promise);
    }
}

作用:

  • 将 promise 标记为成功状态
  • 通知等待这个 promise 的线程
  • 触发 promise 上注册的 listener
为什么叫 “safe”?
  • 使用 trySuccess() 而不是 setSuccess()
  • trySuccess() 如果 promise 已经完成,会返回 false,不会抛异常
  • setSuccess() 如果 promise 已经完成,会抛出 IllegalStateException
promise 的作用
// 在调用 register 时创建的 promise
ChannelFuture regFuture = config().group().register(channel);

// 可以同步等待注册完成
regFuture.sync();

// 或者添加 listener 异步处理
regFuture.addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture future) {
        if (future.isSuccess()) {
            System.out.println("注册成功");
        } else {
            System.out.println("注册失败: " + future.cause());
        }
    }
});

设置成功后的效果:

  1. regFuture.isSuccess() 返回 true
  2. regFuture.sync() 会立即返回(如果在等待)
  3. 所有注册的 listener 会被调用

步骤 7:触发 channelRegistered 事件

pipeline.fireChannelRegistered();

这会触发 pipeline 中所有 Inbound handler 的 channelRegistered() 方法。

事件传播流程
pipeline.fireChannelRegistered()
    ↓
从 head 开始传播
    ↓
HeadContext.channelRegistered(ctx)
    ↓
ctx.fireChannelRegistered()  // 传播给下一个 handler
    ↓
LoggingHandler.channelRegistered(ctx)
    ↓
ctx.fireChannelRegistered()  // 继续传播
    ↓
MyHandler.channelRegistered(ctx)
    ↓
ctx.fireChannelRegistered()  // 继续传播
    ↓
TailContext.channelRegistered(ctx)
用户可以在 handler 中处理这个事件
public class MyHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Channel 已注册到 EventLoop");
        
        // 继续传播事件
        ctx.fireChannelRegistered();
    }
}

注意:

  • 这个事件表示 Channel 已经注册到 EventLoop
  • 但此时 Channel 可能还没有激活(对于客户端,还没有 connect;对于服务端,还没有 bind)
  • 所以通常不在这个事件中做太多事情

步骤 8:如果 Channel 已激活,触发 channelActive 事件

if (isActive()) {
    if (firstRegistration) {
        pipeline.fireChannelActive();
    } else if (config().isAutoRead()) {
        beginRead();
    }
}

这是一个条件判断,根据不同情况执行不同的逻辑。

isActive() 的判断
// NioSocketChannel
@Override
public boolean isActive() {
    SocketChannel ch = javaChannel();
    return ch.isOpen() && ch.isConnected();
}

// NioServerSocketChannel
@Override
public boolean isActive() {
    return javaChannel().socket().isBound();
}

对于客户端(NioSocketChannel):

  • 需要 Channel 打开 且 已连接
  • 在 register 时,通常还没有 connect,所以 isActive() 返回 false
  • 在 connect 完成后,会再次调用 pipeline.fireChannelActive()

对于服务端(NioServerSocketChannel):

  • 需要 Channel 已绑定端口
  • 在 register 时,如果已经 bind,isActive() 返回 true
  • 会触发 channelActive 事件
第一次注册的处理
if (firstRegistration) {
    pipeline.fireChannelActive();
}

触发 channelActive 事件的时机:

  1. 服务端 NioServerSocketChannel:
bind(port)
    ↓
doBind()
    ↓
initAndRegister()
    ↓
register0()
    ↓
此时 isActive() = true(已 bind)
firstRegistration = true
    ↓
触发 fireChannelActive()
  1. 客户端 NioSocketChannel:
connect(host, port)
    ↓
doConnect()
    ↓
initAndRegister()
    ↓
register0()
    ↓
此时 isActive() = false(还未 connect)
不触发 fireChannelActive()
    ↓
connect 完成后
    ↓
finishConnect()
    ↓
触发 fireChannelActive()
channelActive 事件的重要性
// HeadContext.channelActive()
@Override
public void channelActive(ChannelHandlerContext ctx) {
    // 1. 传播事件
    ctx.fireChannelActive();

    // 2. 如果设置了 autoRead,开始读取数据
    readIfIsAutoRead();
}

private void readIfIsAutoRead() {
    if (channel.config().isAutoRead()) {
        channel.read();
    }
}

channel.read() 会做什么?

// AbstractChannel
@Override
public Channel read() {
    pipeline.read();
    return this;
}

// HeadContext.read()
@Override
public void read(ChannelHandlerContext ctx) {
    unsafe.beginRead();
}

// AbstractNioChannel.doBeginRead()
@Override
protected void doBeginRead() throws Exception {
    final SelectionKey selectionKey = this.selectionKey;
    if (!selectionKey.isValid()) {
        return;
    }

    readPending = true;

    final int interestOps = selectionKey.interestOps();
    // 设置感兴趣的事件
    // 对于客户端:OP_READ
    // 对于服务端:OP_ACCEPT
    if ((interestOps & readInterestOp) == 0) {
        selectionKey.interestOps(interestOps | readInterestOp);
    }
}

关键点:

  • 在 channelActive 事件中,会设置 SelectionKey 的 interestOps
  • 对于客户端:设置为 OP_READ,开始监听读事件
  • 对于服务端:设置为 OP_ACCEPT,开始监听连接事件
  • 这就是为什么在 doRegister() 时 interestOps 设置为 0 的原因
重新注册的处理
else if (config().isAutoRead()) {
    beginRead();
}

场景:

  • Channel 之前已经注册过(firstRegistration = false)
  • 然后被 deregister
  • 现在重新 register

处理:

  • 如果设置了 autoRead,直接调用 beginRead()
  • 重新开始监听 IO 事件
  • 不需要再触发 channelActive 事件(因为不是第一次激活)

步骤 9:异常处理

catch (Throwable t) {
    // Close the channel directly to avoid FD leak.
    closeForcibly();
    closeFuture.setClosed();
    safeSetFailure(promise, t);
}

如果注册过程中发生任何异常,需要进行清理工作。

closeForcibly()
// AbstractChannel.AbstractUnsafe
@Override
public final void closeForcibly() {
    assertEventLoop();

    try {
        doClose();
    } catch (Exception e) {
        logger.warn("Failed to close a channel.", e);
    }
}

作用:

  • 强制关闭 Channel
  • 不触发任何事件
  • 直接调用底层的 close 操作
  • 避免文件描述符泄漏
closeFuture.setClosed()
closeFuture.setClosed();

作用:

  • 设置 closeFuture 为已关闭状态
  • 通知等待 Channel 关闭的线程
safeSetFailure(promise, t)
protected final void safeSetFailure(ChannelPromise promise, Throwable cause) {
    if (!(promise instanceof VoidChannelPromise) && !promise.tryFailure(cause)) {
        logger.warn("Failed to mark a promise as failure because it's done already: {}", promise, cause);
    }
}

作用:

  • 将 promise 标记为失败状态
  • 设置失败原因
  • 通知等待的线程和 listener

效果:

ChannelFuture regFuture = bootstrap.register();

// 如果注册失败
regFuture.isSuccess();  // 返回 false
regFuture.cause();      // 返回异常对象

// 或者在 listener 中处理
regFuture.addListener(future -> {
    if (!future.isSuccess()) {
        System.err.println("注册失败: " + future.cause());
    }
});

四、完整的注册流程

4.1 客户端的注册流程

Bootstrap.connect(host, port)
    ↓
doResolveAndConnect()
    ↓
initAndRegister()
    ↓
channelFactory.newChannel()  // 创建 NioSocketChannel
    ↓
init(channel)  // 初始化 channel,添加 ChannelInitializer
    ↓
config().group().register(channel)  // 注册到 EventLoopGroup
    ↓
next().register(channel)  // 选择一个 EventLoop
    ↓
register(new DefaultChannelPromise(channel, this))
    ↓
promise.channel().unsafe().register(this, promise)
    ↓
AbstractChannel.this.eventLoop = eventLoop  // 绑定 EventLoop
    ↓
eventLoop.execute(() -> register0(promise))  // 提交任务
    ↓
【EventLoop 线程执行】
    ↓
register0(promise)
    ↓
doRegister()  // 注册到 Selector,interestOps = 0
    ↓
pipeline.invokeHandlerAddedIfNeeded()  // 触发 handlerAdded
    ↓
ChannelInitializer.initChannel()  // 执行用户的初始化逻辑
    ↓
safeSetSuccess(promise)  // 设置 promise 成功
    ↓
pipeline.fireChannelRegistered()  // 触发 channelRegistered 事件
    ↓
isActive() = false(还未 connect)
    ↓
注册完成,返回
    ↓
【回到主线程】
    ↓
doResolveAndConnect0()
    ↓
doConnect()  // 执行连接操作
    ↓
connect 完成后
    ↓
finishConnect()
    ↓
pipeline.fireChannelActive()  // 触发 channelActive 事件
    ↓
设置 interestOps = OP_READ  // 开始监听读事件

4.2 服务端的注册流程

ServerBootstrap.bind(port)
    ↓
doBind()
    ↓
initAndRegister()
    ↓
channelFactory.newChannel()  // 创建 NioServerSocketChannel
    ↓
init(channel)  // 初始化 channel
    ↓
config().group().register(channel)  // 注册到 bossGroup
    ↓
【与客户端类似的注册流程】
    ↓
register0(promise)
    ↓
doRegister()  // 注册到 Selector
    ↓
pipeline.invokeHandlerAddedIfNeeded()
    ↓
safeSetSuccess(promise)
    ↓
pipeline.fireChannelRegistered()
    ↓
isActive() = false(还未 bind)
    ↓
注册完成,返回
    ↓
【回到主线程】
    ↓
doBind0()
    ↓
channel.bind(localAddress, promise)
    ↓
doBind()  // 执行绑定操作
    ↓
bind 完成后
    ↓
pipeline.fireChannelActive()  // 触发 channelActive 事件
    ↓
设置 interestOps = OP_ACCEPT  // 开始监听连接事件

五、关键时序图

5.1 register0 方法的执行时序

调用线程                EventLoop 线程              Selector
    |                        |                        |
    | register(eventLoop)    |                        |
    |----------------------->|                        |
    |                        |                        |
    | eventLoop.execute()    |                        |
    |----------------------->|                        |
    |                        |                        |
    | 返回 ChannelFuture     |                        |
    |<-----------------------|                        |
    |                        |                        |
    |                        | 从 taskQueue 取任务    |
    |                        |                        |
    |                        | register0()            |
    |                        |                        |
    |                        | doRegister()           |
    |                        |----------------------->|
    |                        |                        | javaChannel.register()
    |                        |<-----------------------|
    |                        |                        |
    |                        | invokeHandlerAdded()   |
    |                        | -> ChannelInitializer  |
    |                        |                        |
    |                        | safeSetSuccess()       |
    |                        |                        |
    | promise 完成           |                        |
    |<-----------------------|                        |
    |                        |                        |
    | listener 回调          |                        |
    |<-----------------------|                        |
    |                        |                        |
    |                        | fireChannelRegistered()|
    |                        |                        |
    |                        | fireChannelActive()    |
    |                        | (如果已激活)           |
    |                        |                        |

5.2 ChannelInitializer 的执行时序

register0()
    ↓
pipeline.invokeHandlerAddedIfNeeded()
    ↓
遍历 pipeline 中的 handler
    ↓
找到 ChannelInitializer
    ↓
ChannelInitializer.handlerAdded(ctx)
    ↓
initChannel(ctx)
    ↓
执行用户的 initChannel(channel) 方法
    ↓
用户代码:
    pipeline.addLast(new LoggingHandler());
    pipeline.addLast(new MyHandler());
    ↓
从 pipeline 中移除 ChannelInitializer
    ↓
pipeline 结构变化:
    之前:head -> ChannelInitializer -> tail
    之后:head -> LoggingHandler -> MyHandler -> tail

六、重要概念辨析

6.1 register 和 active 的区别

register(注册)
  • 表示 Channel 已经注册到 EventLoop 的 Selector 上
  • 此时 Channel 可能还没有激活
  • 触发 channelRegistered 事件
active(激活)
  • 对于客户端:表示连接已建立(connected)
  • 对于服务端:表示端口已绑定(bound)
  • 触发 channelActive 事件
状态转换

客户端:

创建 -> 注册 -> 连接 -> 激活
        ↓       ↓       ↓
        registered  connected  active

服务端:

创建 -> 注册 -> 绑定 -> 激活
        ↓       ↓       ↓
        registered  bound  active

6.2 firstRegistration 的作用

boolean firstRegistration = neverRegistered;
为什么需要这个标记?

Netty 支持 Channel 的 deregister 和 re-register:

// 取消注册
channel.deregister().sync();

// 重新注册到另一个 EventLoop
anotherEventLoop.register(channel).sync();
不同场景的处理

第一次注册(firstRegistration = true):

  • 需要触发完整的初始化流程
  • 触发 channelActive 事件
  • 设置 interestOps

重新注册(firstRegistration = false):

  • 不触发 channelActive 事件(因为已经激活过了)
  • 如果设置了 autoRead,直接调用 beginRead()
  • 重新开始监听 IO 事件

6.3 interestOps 的设置时机

为什么 doRegister 时设置为 0?
selectionKey = javaChannel().register(selector, 0, this);

原因:

  1. 此时只是完成注册,还不需要监听事件
  2. 避免在初始化完成前就收到事件
  3. 等待 pipeline 初始化完成
什么时候设置真正的 interestOps?
pipeline.fireChannelActive()
    ↓
HeadContext.channelActive()
    ↓
readIfIsAutoRead()
    ↓
channel.read()
    ↓
unsafe.beginRead()
    ↓
doBeginRead()
    ↓
selectionKey.interestOps(interestOps | readInterestOp);

时机:

  • 在 channelActive 事件中
  • 此时 pipeline 已经初始化完成
  • 可以安全地开始监听 IO 事件

设置的值:

  • 客户端 NioSocketChannel:OP_READ
  • 服务端 NioServerSocketChannel:OP_ACCEPT

七、常见问题

Q1: register0 为什么一定在 EventLoop 线程中执行?

答案:

  1. 线程安全:避免多线程并发修改 Channel 状态
  2. 顺序性:保证注册操作和后续操作的顺序
  3. 一致性:Channel 的所有操作都在同一个线程中,简化编程模型

Q2: 为什么需要 promise?

答案:

  1. 异步通知:register 是异步操作,通过 promise 通知调用者结果
  2. 错误处理:可以通过 promise 获取失败原因
  3. 回调机制:可以添加 listener 进行异步回调
ChannelFuture future = bootstrap.register();

// 方式1:同步等待
future.sync();

// 方式2:异步回调
future.addListener(f -> {
    if (f.isSuccess()) {
        System.out.println("注册成功");
    } else {
        System.err.println("注册失败: " + f.cause());
    }
});

Q3: ChannelInitializer 什么时候被移除?

答案:

  • 在 invokeHandlerAddedIfNeeded() 中被移除
  • 具体时机是在 ChannelInitializer.handlerAdded() 方法中
  • 执行完 initChannel() 后,立即从 pipeline 中移除自己

为什么要移除?

  • ChannelInitializer 只是一个辅助类,用于初始化 pipeline
  • 初始化完成后就没有用了,应该移除
  • 避免占用内存和影响性能

Q4: 如果 register 失败会怎样?

答案:

  1. 调用 closeForcibly() 强制关闭 Channel
  2. 设置 closeFuture 为已关闭
  3. 设置 promise 为失败状态
  4. 调用者可以通过 promise 获取失败原因
ChannelFuture future = bootstrap.register();
if (!future.isSuccess()) {
    Throwable cause = future.cause();
    // 处理失败情况
}

Q5: register 和 bind/connect 的关系?

答案:

对于服务端:

register -> bind -> channelActive
  • 先注册到 EventLoop
  • 再绑定端口
  • 绑定成功后触发 channelActive

对于客户端:

register -> connect -> channelActive
  • 先注册到 EventLoop
  • 再连接服务器
  • 连接成功后触发 channelActive

关键点:

  • register 必须在 bind/connect 之前
  • 因为 bind/connect 操作需要在 EventLoop 中执行
  • 而 EventLoop 是在 register 时绑定的

Q6: 为什么 doRegister 要循环重试?

答案:

for (;;) {
    try {
        selectionKey = javaChannel().register(selector, 0, this);
        return;
    } catch (CancelledKeyException e) {
        if (!selected) {
            eventLoop().selectNow();
            selected = true;
        } else {
            throw e;
        }
    }
}

原因:

  • 如果 SelectionKey 已经被取消,register 会抛出 CancelledKeyException
  • 调用 selectNow() 可以清理已取消的 key
  • 然后重试注册
  • 如果重试还失败,说明有其他问题,抛出异常

Q7: autoRead 是什么?

答案:

  • autoRead 是 Channel 的一个配置选项
  • 默认值为 true
  • 表示在 Channel 激活后,自动开始读取数据

设置方式:

bootstrap.option(ChannelOption.AUTO_READ, true);

作用:

  • 如果为 true,在 channelActive 时自动调用 channel.read()
  • 如果为 false,需要手动调用 channel.read() 才会开始读取

使用场景:

  • 通常保持默认值 true
  • 如果需要流量控制,可以设置为 false,手动控制读取时机

八、总结

8.1 register0 的核心职责

  1. 注册到 Selector:调用 JDK 的 channel.register() 方法
  2. 初始化 pipeline:触发 handlerAdded 事件,执行 ChannelInitializer
  3. 通知调用者:设置 promise 为成功或失败
  4. 触发事件:触发 channelRegistered 和 channelActive 事件
  5. 开始监听:设置 interestOps,开始监听 IO 事件

8.2 关键步骤回顾

1. 检查 Channel 是否打开
    ↓
2. 记录是否首次注册
    ↓
3. doRegister() - 注册到 Selector(interestOps = 0)
    ↓
4. 更新状态标记
    ↓
5. invokeHandlerAddedIfNeeded() - 初始化 pipeline
    ↓
6. safeSetSuccess(promise) - 通知调用者
    ↓
7. fireChannelRegistered() - 触发注册事件
    ↓
8. 如果已激活,触发 channelActive 事件
    ↓
9. 在 channelActive 中设置 interestOps

8.3 重要概念

  • register:Channel 注册到 EventLoop 的 Selector
  • active:Channel 已激活(客户端已连接,服务端已绑定)
  • firstRegistration:区分首次注册和重新注册
  • interestOps:先设置为 0,在 channelActive 时设置真正的值
  • ChannelInitializer:在 handlerAdded 时执行初始化,然后移除

8.4 线程模型

调用线程(如 main 线程)
    ↓
调用 register(eventLoop, promise)
    ↓
绑定 EventLoop
    ↓
提交任务到 EventLoop 的 taskQueue
    ↓
返回 ChannelFuture
    ↓
【切换到 EventLoop 线程】
    ↓
从 taskQueue 取出任务
    ↓
执行 register0(promise)
    ↓
完成注册
    ↓
设置 promise 成功
    ↓
【回到调用线程】
    ↓
promise 完成,触发 listener

8.5 与其他方法的关系

initAndRegister()
    ↓
创建 Channel
    ↓
初始化 Channel(添加 ChannelInitializer)
    ↓
register(channel)
    ↓
选择 EventLoop
    ↓
register(eventLoop, promise)
    ↓
绑定 EventLoop
    ↓
提交 register0 任务
    ↓
【EventLoop 线程】
    ↓
register0(promise)
    ↓
doRegister() - JDK 注册
    ↓
invokeHandlerAddedIfNeeded() - 初始化 pipeline
    ↓
fireChannelRegistered() - 触发事件
    ↓
fireChannelActive() - 触发激活事件(如果已激活)
    ↓
设置 interestOps - 开始监听 IO 事件

附录:相关源码位置

核心类

  • io.netty.channel.AbstractChannel.AbstractUnsafe
  • io.netty.channel.nio.AbstractNioChannel
  • io.netty.channel.DefaultChannelPipeline
  • io.netty.channel.ChannelInitializer

关键方法

  • AbstractChannel.AbstractUnsafe.register(EventLoop, ChannelPromise)
  • AbstractChannel.AbstractUnsafe.register0(ChannelPromise)
  • AbstractNioChannel.doRegister()
  • DefaultChannelPipeline.invokeHandlerAddedIfNeeded()
  • ChannelInitializer.handlerAdded(ChannelHandlerContext)

相关事件

  • channelRegistered:Channel 已注册到 EventLoop
  • channelActive:Channel 已激活(连接或绑定完成)
  • handlerAdded:Handler 已添加到 pipeline
Netty源码分析之Reactor线程模型详解 在分析源码之前,我们先分析,哪些地方用到了EventLoop? NioServerSocketChannel的连接监听注册 NioSocketChannel的IO事件注册 NioServerSocketChannel连接监听 在AbstractBootstrap类的initAndRegister()方法中,当NioServerSocketChannel初始化完成后,会调用case标记位置的代码进行注册。 final ChannelFuture initAndRegister() { C.. 阅读详情

相关推荐

Netty源码分析系列之新连接的接入

当 netty 的服务端启动以后,就可以开始接收客户端的连接了。那么在 netty 中,服务端是如何来进行新连接的创建的呢?

天堂的博客 384

Netty源码分析篇:Channel 的 register 操作

Netty源码分析篇:Channel 的 register 操作

java_rutu的博客 1324

Java写一条吃满屏幕的贪吃蛇(A*自动寻路算法和一些简单的策略) 二

上一篇文章介绍了一条贪吃蛇的简单实现,这一篇介绍贪吃蛇的自动寻路算法 ,说是自动寻路其实也就是一种遍历地图的算法,如果玩过游戏肯定知道游戏里面一般会有自动寻路功能,贪吃蛇的自动寻路算法也就是实现这个功能。下面慢慢介绍这种算法吧。    最开始,我没有在网上找资料的时候,我对于设计寻路算法的看法是遍历整个地图,将障碍物做上标记,然后找一条能够到达目的地的线路就可以了。所以最开始我用了特别暴力的方式来...

Sober_yang的博客 3632

Netty核心源码分析(二),Netty的Server端接收请求过程源码分析

而ServerBootstrapAcceptor是pipeline的最后一个Handler,它的channelRead方法是在ServerBootStrap父类中实现的,我们继续看ServerBootstrapAcceptor的channelRead方法。总体流程:接受连接---->创建一个新的 NioSocketChannel---------->注册到一个 worker EventLoop-------->注册 selecot Read 事件。4)注册成功后,开始监听Read事件。

秃了也弱了 971

拯救你的备份数据!STM32F407 RTC Tamper功能防篡改设计指南

本文详细介绍了STM32F407 RTC Tamper功能在物联网数据安全中的应用,包括硬件触发、数据清除和时间戳记录等核心机制。通过实战案例和代码示例,展示了如何构建多层次的防御体系,确保敏感数据在物理入侵时自动擦除并记录精确时间戳,适用于智能门锁、工业传感器等高安全需求场景。

weixin_28278377的博客 279

UniApp消息推送进阶:不用插件实现动态语音播报(含保活策略)

本文详细介绍了在UniApp中实现动态语音播报的进阶方案,无需依赖第三方插件。核心采用透传消息传递动态数据,结合本地TTS引擎合成语音,并针对不同安卓厂商提供了深度通道配置与差异化保活策略,有效解决了App退至后台或被清理后语音播报失效的难题。

weixin_29224309的博客 490

Netty源码分析篇:Netty Pipeline 初始化流程

Netty源码分析篇:Netty Pipeline 初始化流程

java_rutu的博客 657

FPGA SPI驱动ADC避坑指南:以ADC128S022为例,详解时序设计与代码调试

本文以ADC128S022为例,详细解析FPGA通过SPI驱动ADC的时序设计与代码调试中的常见问题。从时钟分频计算、SPI模式选择到状态机实现,提供避坑指南和实战技巧,帮助工程师解决数据采集系统中的时序匹配难题。特别针对FPGA与ADC通信中的微妙差异,给出精确的Verilog代码示例和ModelSim仿真验证方法。

weixin_42522374的博客 460

Netty(四)_核心源码分析

Netty(四)_核心源码分析 本篇内容门槛还是有的,需要你对我前三篇netty所说的内容不算陌生 为了提高阅读舒适性,我们并不会一行一行代码解析,并删除部分与目的无关的源码。读源码要带着目的性读,最好你已经知道它背后基本原理,然后带着审视的感觉去进行阅读,反客为主。 在整个流程中,以下这张图要常驻脑海中! 我们选择netty提供的example程序,该程序可以在源码包netty.example.echo包 public final class EchoServer { //... s

JunsIr的技术栈 707

尚硅谷Redis 7学习笔记

尚硅谷Redis 7学习笔记

不更了,主打一个陪伴 2万+

最强AI插件Augment,适配idea、vscode,对标cursor!

Augment是一款新兴的AI辅助编码工具,能够无缝集成到IDEA和VS Code等主流IDE中,直接对标专业AI编码工具Cursor。其核心优势包括多IDE适配、无缝集成、代码理解与生成以及对话式编程体验。Augment通过插件形式保留原IDE功能和快捷键习惯,降低学习成本,同时提供深度理解项目上下文和生成高质量代码的能力。此外,Augment还具备审查变更、检查点、代理与代理自动等核心功能,支持从简单配置更改到复杂功能实现的各种开发任务。Augment代表了AI辅助编程工具的新方向,通过增强开发者熟悉的

todoitbo的博客 1万+

【Netty4】Netty服务端启动源码分析(一)

http://ifeve.com/netty-reactor-4/ https://blog.youkuaiyun.com/huanshirenjian/article/details/90228894

m0_45406092的博客 457

沙猫群优化算法(SCSO)详解:从沙漠生存智慧到全局优化引擎

本文详细介绍了沙猫群优化算法(SCSO),这是一种新型元启发式优化算法,灵感来源于沙猫在沙漠环境中的卓越狩猎行为。SCSO通过模拟沙猫的低频探测能力和精确攻击策略,将优化问题转化为搜索和捕获"猎物"的过程。算法核心包括探索阶段(全局搜索)和开发阶段(局部精细搜索),通过灵敏度范围模型和自适应控制参数实现两者的平衡。SCSO具有参数简单、收敛性能好等优点,适用于解决复杂全局优化问题。

qq_42212808的博客 202

Netty源码分析---Netty-Pipeline初始化流程详解

本文深入解析了Netty Pipeline的初始化流程,主要分为两个阶段:准备阶段和执行阶段。在准备阶段,Netty通过initAndRegister()方法创建Channel实例并将ChannelInitializer添加到Pipeline中,但此时不会立即执行初始化逻辑。执行阶段发生在Channel注册到EventLoop后,触发handlerAdded()方法完成真正的初始化。文章详细跟踪了从客户端启动代码到addLast()方法的核心逻辑,揭示了Netty采用"延迟初始化"策略的

java_rutu的博客 421

Lammps实现Ar的沸腾过程(包含in文件)

Lammps实现简单的沸腾过程

Rockyoung 3503

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Netty源码分析-AbstractUnsafe(register注册流程)

Netty当中的Channel,对外提供的API都封装再了AbstractUnsafe对象里面。 我们分析一个新的客户端连接NioSocketChannel是如何完成注册的。 注册h @Override public ChannelFuture register(final ChannelPromise promise) { ObjectUtil...

大新博客 714

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Netty源码分析第3章(客户端接入流程)---->第5节: 监听读事件

Netty源码分析第3章(客户端接入流程)---->第5节: 监听读事件 Netty源码分析第三章: 客户端接入流程 第五节: 监听读事件 我们回到AbstractUnsafe的register0()方法: private void register0(ChannelPromise promise) { ...

weixin_30701521的博客 159

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

netty源码分析 之七 transport(Unsafe)

来看下unsafe的接口定义 同样看你实现类, AbstractUnsafe 接着看起实现,统一的模板类,由子类来实现方法 接着看register 方法,其中有pipeline.fireChannelRegistered方法 private void register0(ChannelPromise promise) {

akfly的专栏 872

Netty 源码分析之 一 客户端代码分析

没有创新,或者拾人牙慧,知识本来就是这样搬运着传播的。 客户端核心代码如下: public static void start(){ ClientChannelHandler channelHandler = new ClientChannelHandler(); EventLoopGroup bossGroup = new NioEventLoopGroup...

Java_Jsp_Ssh的博客 892

Netty源码分析篇:NioEventLoopGroup分析

Netty源码分析篇:NioEventLoopGroup分析

java_rutu的博客 666

Netty源码分析之NioEventLoop

本文分析Netty的NioEventLoop是如工作的。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ENfGVW05-1644982158935)(//upload-images.jianshu.io/upload_images/2184951-2e248d85df2a1a86.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/490/format/webp)] NioEventLoop中维护了一个线程,线程启动时会调用

huapro.vip的程序人生 189

Netty源码分析---NioEventLoopGroup深度解析

本文深入解析了Netty中的NioEventLoopGroup线程池实现。NioEventLoopGroup是为NIO模型专门设计的事件循环线程池,由多个NioEventLoop线程组成,每个线程对应一个Selector和任务队列。文章详细介绍了其继承体系、构造过程、线程选择策略等核心机制。重点分析了默认线程数设置为CPU核心数*2的原因,以及bossGroup和workerGroup的分工设计。此外还讲解了线程池创建流程、任务执行机制和优雅关闭过程,揭示了Netty高性能背后的线程模型设计思想。

java_rutu的博客 383

Netty源码分析之服务端启动流程

上节对Netty的做了简单介绍,这节分析下Netty启动流程,后面的源码分析都以Netty4.0.32版本为例,以下面启动代码为例子 public class TimeServer { public void bind(int port) throws Exception { //...

cherry93925的博客 194

netty 源码分析一

netty 源码

zhuyijian135757的专栏 3270

Netty源码分析 (三)----- 服务端启动源码分析

本文接着前两篇文章来讲,主要讲服务端类剩下的部分,我们还是来先看看服务端的代码 /** * Created by chenhao on 2019/9/4. */ public final class SimpleServer { public static void main(String[] args) throws Exception { ...

weixin_30294709的博客 140

认真的 Netty 源码解析(二)

Channel 的 register 操作经过前面的铺垫,我们已经具备一定的基础了,我们开始来把前面学到的内容揉在一起。这节,我们会介绍 register 操作,这一步其...

JAVA高级架构 619
上一篇: Netty源码分析---SelectionKey与位运算详解
下一篇: Netty源码分析---SingleThreadEventExecutor-execute方法详解
不早睡不改名@
博客等级 码龄5年 536粉丝 306原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值