Netty 第十篇:Netty 如何处理新连接(OP_ACCEPT)

📅 2026-05-29    ✍️ 原创    🏷️ Java / NIO / Netty / 源码分析 / 连接处理

在上一篇文章中,我们详细分析了 NioEventLoop 的 run() 方法。其中在处理 IO 事件时,如果是 OP_ACCEPT 事件,会调用 unsafe.read() 方法。

这一篇,我们就顺着这条线索往下走,看看 Netty 是如何把一个操作系统层面的 TCP 连接,转换成 Netty 中的 NioSocketChannel,并最终注册到 Worker 线程池中的。

一句话剧透:服务端 Channel 只负责“接客”,接完之后立刻把连接扔给 ChildGroup 去“服务”。

一、从 unsafe.read() 说起

当 NioEventLoop 检测到 NioServerSocketChannel 上有 OP_ACCEPT 事件时,会执行以下调用链:

NioEventLoop.processSelectedKey()
  -> AbstractNioMessageChannel.NioMessageUnsafe.read()

这里要注意,服务端的 Unsafe 是 NioMessageUnsafe。它的核心逻辑是:读取消息(连接),并把这些消息(连接)传递给 Pipeline。

二、doReadMessages:构建 NioSocketChannel

NioMessageUnsafe.read() 会调用一个抽象方法 doReadMessages(readBuf)。

对于 NioServerSocketChannel 来说,实现如下:

@Override
protected int doReadMessages(List<Object> buf) throws Exception {
    // 1. 调用 JDK 原生方法,接受一个新连接
    SocketChannel ch = SocketUtils.accept(javaChannel());
    
    if (ch != null) {
        // 2. 将 JDK 的 SocketChannel 包装成 Netty 的 NioSocketChannel
        buf.add(new NioSocketChannel(this, ch));
        return 1;
    }
    return 0;
}
💡 关键点:这里的 buf 是一个 List。虽然通常一次 accept 只有一个连接,但 Netty 的设计允许批量处理。new NioSocketChannel(this, ch) 这一步非常重要,它将 JDK 原生的 Channel 包装成了 Netty 的对象。

三、触发 channelRead 事件

当 doReadMessages 执行完毕后,NioMessageUnsafe 会拿到一个装满了 NioSocketChannel 的 List。

接着,它会将这些 Channel 作为“消息”,通过 Pipeline 向下传播:

int size = readBuf.size();
for (int i = 0; i < size; i++) {
    readPending = false;
    pipeline.fireChannelRead(readBuf.get(i));
}

注意:这里传播的不是 ByteBuf,而是一个 Channel 对象

四、ServerBootstrapAcceptor:连接的分发器

还记得我们在前面章节讲过的吗?在 ServerBootstrap.init() 方法中,会给服务端的 Channel 添加一个特殊的 Handler —— ServerBootstrapAcceptor。

当 pipeline.fireChannelRead(nioSocketChannel) 传到这个 Handler 时,它的 channelRead 方法会被调用。

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    // 1. 强制类型转换(msg 就是 NioSocketChannel)
    final Channel child = (Channel) msg;

    // 2. 为这个新连接配置 Pipeline(添加用户定义的 Handlers)
    child.pipeline().addLast(childHandler);

    // 3. 设置 Channel 的属性(Options 和 Attributes)
    setChannelOptions(child, childOptions, logger);
    setAttributes(child, childAttrs);

    // 4. 注册到 ChildGroup(Worker 线程池)
    childGroup.register(child).addListener(...);
}
这就是 Reactor 模型的精髓:
Main Reactor(ParentGroup)负责 Accept,然后把 Channel 交给 Sub Reactor(ChildGroup)负责 Read/Write。

五、NioSocketChannel 的注册流程

childGroup.register(child) 的流程,我们在第七篇中已经详细讲过,这里做一个对比:

对比项 NioServerSocketChannel NioSocketChannel
注册目标 ParentGroup (Boss) ChildGroup (Worker)
Unsafe 类型 NioMessageUnsafe NioByteUnsafe
doRegister javaChannel().register(selector, 0, this) javaChannel().register(selector, 0, this)
doBeginRead interestOps(OP_ACCEPT) interestOps(OP_READ)

1. 注册逻辑复用

注册代码是完全复用的。都是调用 AbstractChannel.register() → register0() → doRegister()。

2. 关注事件不同

唯一的区别在于最后的 doBeginRead():

// NioServerSocketChannel
protected void doBeginRead() throws Exception {
    selectionKey.interestOps(OP_ACCEPT);
}

// NioSocketChannel
protected void doBeginRead() throws Exception {
    selectionKey.interestOps(OP_READ);
}

一旦注册完成并设置好 InterestOps,这个连接就正式进入了工作模式,开始监听读事件了。

六、总结

Netty 处理新连接的完整流程如下:

步骤 核心动作
1. 检测事件 NioEventLoop 发现 OP_ACCEPT
2. 接受连接 NioMessageUnsafe.read()
3. 包装 Channel JDK SocketChannel → NioSocketChannel
4. 传播事件 Pipeline.fireChannelRead(NioSocketChannel)
5. 分发连接 ServerBootstrapAcceptor 接住连接
6. 注册 Worker childGroup.register(NioSocketChannel)
7. 监听读事件 interestOps(OP_READ)
💡 下篇预告:连接注册好了,OP_READ 事件也监听了。那么当客户端真的发来数据时,Netty 是如何读取这些数据的?ByteBuf 又是如何工作的?我们下一篇揭晓。

— 原创文章,转载请注明出处 —

posted @ 2026-05-29 15:49  小金虫  阅读(15)  评论(0)    收藏  举报