Netty 第六篇:initAndRegister 中的 init 阶段详解

 

📅 2026-05-22    ✍️ 原创    🏷️ Java NIO Netty 源码分析 Channel初始化

上一篇我们拆解了 bind(8080) 方法的整体骨架,知道了它内部主要做了两件事:initAndRegisterdoBind0。其中 initAndRegister 又可以分为两大步骤:Channel 的初始化(init)Channel 的注册(register)

这一篇,我们就来深入 init 阶段,看看 Netty 在创建一个 NioServerSocketChannel 时,到底做了哪些事情。你会发现,Netty 正是在这个阶段,悄悄把 NIO 的底层组件"组装"了起来。


一、从 Channel 的创建说起

回顾一下上一篇中 bind() 的调用链,最终会走到 AbstractBootstrap.initAndRegister() 方法。在这个方法里,第一步就是通过 channelFactory.newChannel() 创建一个 Channel 实例。

// AbstractBootstrap.initAndRegister() 节选
Channel channel = channelFactory.newChannel();

这里的 channelFactory 是什么?回顾我们服务端代码中的配置:

// NettyServer.java
serverBootstrap
    .group(new NioEventLoopGroup(1), new NioEventLoopGroup())
    .channel(NioServerSocketChannel.class)  // ← 这里
    .childHandler(...);

.channel(NioServerSocketChannel.class) 内部会通过 ReflectiveChannelFactoryNioServerSocketChannel 的 Class 对象保存下来,后续调用 newChannel() 时,通过反射调用其无参构造函数来创建实例。

也就是说,newChannel() 最终等价于:

// 反射调用 NioServerSocketChannel 的无参构造函数
NioServerSocketChannel channel = new NioServerSocketChannel();

接下来我们就进入这个构造函数,看看里面发生了什么。


二、NioServerSocketChannel 的构造函数

NioServerSocketChannel 的构造函数是整个初始化阶段的"起点",我们直接看源码:

// NioServerSocketChannel.java
public class NioServerSocketChannel extends AbstractNioMessageChannel
        implements ServerSocketChannel {

    private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER =
            SelectorProvider.provider();

    private static ServerSocketChannel newSocket(SelectorProvider provider) {
        try {
            return provider.openServerSocketChannel();
        } catch (IOException e) {
            throw new ChannelException("Failed to open a server socket channel.", e);
        }
    }

    public NioServerSocketChannel() {
        this(newSocket(DEFAULT_SELECTOR_PROVIDER));
    }

    public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        this.config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }
}

构造函数虽然短,但信息量很大。我们一步一步拆解。

2.1 newSocket():打通 Netty 与 NIO 的联系

newSocket(DEFAULT_SELECTOR_PROVIDER) 这个方法,本质上就是调用了 JDK NIO 的 API:

// 等价于 JDK 中的
ServerSocketChannel channel = SelectorProvider.provider().openServerSocketChannel();
// 也就是我们第一篇中写的:
ServerSocketChannel channel = ServerSocketChannel.open();

这一步非常关键——Netty 并没有"发明"一个新的 Channel 实现,而是直接持有了 JDK NIO 的 ServerSocketChannel 实例。Netty 的所有网络操作,最终都是委托给这个 JDK 底层 Channel 来完成的。

Netty 的核心设计思想之一:不直接替换 JDK NIO,而是在其之上做封装和增强。

2.2 readInterestOp:这个 Channel 关注什么事件?

注意构造函数中调用父类构造函数的这一行:

super(null, channel, SelectionKey.OP_ACCEPT);

第二个参数是 JDK 的 ServerSocketChannel 实例,第三个参数 SelectionKey.OP_ACCEPT(值为 16)表示:这个 Channel 关注的是"接受客户端连接"事件

这个参数会被保存到父类 AbstractNioChannel 中的 readInterestOp 字段里,供后续的注册阶段使用。这里我们先不展开注册的具体逻辑,只需要知道:

  • OP_ACCEPT = 16 → 服务端 Socket 关注"有新客户端连接"
  • OP_READ = 1 → 客户端 Socket 关注"有数据可读"
  • OP_WRITE = 4 → 关注"可以写数据"

不同的 Channel 类型,在构造时传入的 readInterestOp 不同。比如 NioSocketChannel 传入的就是 OP_READ


三、AbstractNioChannel 做了什么?

NioServerSocketChannel 的父类是 AbstractNioMessageChannel,再往上追溯是 AbstractNioChannel。我们来看 AbstractNioChannel 的构造函数:

// AbstractNioChannel.java
public abstract class AbstractNioChannel extends AbstractChannel {

    private final SelectableChannel ch;
    protected final int readInterestOp;
    private volatile SelectionKey selectionKey;

    protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;
        try {
            ch.configureBlocking(false);
        } catch (IOException e) {
            throw new ChannelException("Failed to enter non-blocking mode.", e);
        }
    }
}

这里做了两件核心事情:

3.1 保存 JDK Channel 的引用

this.ch = ch; 把 JDK 的 ServerSocketChannel 保存到了 ch 字段中。后续所有对底层 Channel 的操作(比如 bindacceptconfigureBlocking 等),都是通过这个 ch 来完成的。

3.2 设置为非阻塞模式

ch.configureBlocking(false); —— 这一行是不是很眼熟?它正是我们第一篇 NIO 原生 Demo 中写过的那行代码:

// 第一篇 NIO Demo 中的代码
ssc.configureBlocking(false);

没错,Netty 在底层帮你调用了它。所有 NIO Channel 必须是非阻塞的,这是 Selector 多路复用的前提条件。 如果这里是阻塞模式,注册到 Selector 上时会直接抛出 IllegalBlockingModeException

同时,readInterestOp 也被保存了下来,等待后续注册到 Selector 时使用。


四、AbstractChannel 做了什么?

再往上一层,AbstractNioChannel 继承自 AbstractChannel。它的构造函数做了更多"基础设施"级别的初始化:

// AbstractChannel.java
public abstract class AbstractChannel implements Channel {

    private final ChannelId id;
    private final Unsafe unsafe;
    private final DefaultChannelPipeline pipeline;

    protected AbstractChannel(Channel parent) {
        this.id = DefaultChannelId.newInstance();
        this.unsafe = newUnsafe();
        this.pipeline = new DefaultChannelPipeline(this);
    }
}

这里创建了三个非常重要的组件:

4.1 ChannelId:唯一标识

DefaultChannelId.newInstance() 会生成一个全局唯一的 Channel 标识。每个 Channel 实例都有自己的 ID,用于在日志、监控、调试时区分不同的连接。

ID 的生成规则结合了:机器标识(MAC 地址 / 主机名哈希)+ 时间戳 + 随机数 + 序列号,确保在分布式环境下也不会冲突。

4.2 Unsafe:底层操作的封装

unsafe 是 Netty 中一个非常重要的概念。它并不是 JDK 的 Unsafe,而是 Netty 自己定义的一个接口:

// Unsafe interface(简化)
interface Unsafe {
    SocketAddress localAddress();
    SocketAddress remoteAddress();
    void bind(SocketAddress localAddress, ChannelPromise promise);
    void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);
    void read();
    void write(Object msg, ChannelPromise promise);
    void flush();
    void close(ChannelPromise promise);
}

Unsafe 的作用:它是对底层 JDK Socket 操作的统一封装。不管是 NIO 的 ServerSocketChannelSocketChannel,还是 OIO(阻塞 IO)、EPoll 等实现,对上层来说,都通过 Unsafe 这个接口来操作。

举个例子,当 Pipeline 中的 Handler 调用 ctx.writeAndFlush(msg) 时,最终会走到 Unsafe.write(),再由 Unsafe 调用 JDK 的 SocketChannel.write() 把数据写到内核缓冲区。

Unsafe = Netty 的"底层操作门面",屏蔽了不同 IO 模型的差异。

newUnsafe() 是一个抽象方法,由子类实现。NioServerSocketChannel 对应的 Unsafe 实现是 NioMessageUnsafe(关注消息级别的操作,比如 Accept)。

4.3 Pipeline:默认包含 Head 和 Tail

new DefaultChannelPipeline(this) 创建了一个双向链表结构,默认包含两个节点:

节点类型位置作用
Head HeadContext 链表头部 负责将事件从 Pipeline 传递到 Unsafe(底层操作)
Tail TailContext 链表尾部 负责收尾处理,比如释放未处理的消息、记录未捕获的异常

也就是说,一个刚创建好的 Channel,它的 Pipeline 里已经有两个 Handler 了。后续我们通过 childHandler 添加的自定义 Handler,会被插入到 Head 和 Tail 之间。

Pipeline 的结构大致如下:

Head → [用户 Handler 1] → [用户 Handler 2] → ... → Tail

当数据从客户端到达时,事件会沿着 Pipeline 从 Head 向 Tail 方向传播( inbound );当服务端要写数据时,事件从 Tail 向 Head 方向传播( outbound ),最终到达 Head 后交给 Unsafe 执行实际的写操作。


五、NioServerSocketChannelConfig

回到 NioServerSocketChannel 的构造函数,还有最后一行:

this.config = new NioServerSocketChannelConfig(this, javaChannel().socket());

NioServerSocketChannelConfig 是什么?它是 Netty 对 JDK ServerSocket 配置参数的封装。通过它,我们可以设置一些底层的 Socket 参数,比如:

配置项对应 JDK Socket 参数作用
SO_BACKLOG ServerSocket.setBacklog() 设置连接队列大小
SO_REUSEADDR ServerSocket.setReuseAddress() 允许端口复用(服务重启时快速绑定)
SO_TIMEOUT ServerSocket.setSoTimeout() 设置阻塞操作的超时时间

这些配置项可以通过 ServerBootstrap.option() 方法来设置:

serverBootstrap
    .option(ChannelOption.SO_BACKLOG, 1024)
    .option(ChannelOption.SO_REUSEADDR, true)
    .channel(NioServerSocketChannel.class)
    .childHandler(...);

当调用 option() 设置参数时,Netty 会在 Channel 初始化阶段把这些值应用到 JDK 的 ServerSocket 上。


六、ServerBootstrap.init() 方法

到这里,NioServerSocketChannel 的构造函数执行完毕,Channel 已经被创建出来了。但 initAndRegister 中的 init 阶段还没结束——接下来会调用 ServerBootstrap.init(channel) 方法,对 Channel 做进一步的初始化。

我们来看 ServerBootstrap.init() 做了什么:

// ServerBootstrap.java
@Override
void init(Channel channel) throws Exception {
    // 1. 设置 Channel 的选项(Options)
    final Map<ChannelOption<?>, Object> options = options0();
    synchronized (options) {
        for (Map.Entry<ChannelOption<?>, Object> e : options.entrySet()) {
            try {
                channel.config().setOption(e.getKey(), e.getValue());
            } catch (Throwable t) {
                // 记录日志,不中断流程
            }
        }
    }

    // 2. 设置 Channel 的属性(Attrs)
    final Map<AttributeKey<?>, Object> attrs = attrs0();
    synchronized (attrs) {
        for (Map.Entry<AttributeKey<?>, Object> e : attrs.entrySet()) {
            channel.attr(e.getKey()).set(e.getValue());
        }
    }

    // 3. 向 Pipeline 中添加 自定义的handler与ServerBootstrapAcceptor
    ChannelPipeline p = channel.pipeline();
   ChannelHandler handler = ServerBootstrap.this.config.handler();
p.addLast(new ChannelHandler[]{handler}); p.addLast(new ServerBootstrapAcceptor( childGroup, childHandler, childOptions, childAttrs)); }

这里做了三件事:

6.1 设置 Options

把我们之前通过 .option() 设置的参数(如 SO_BACKLOGSO_REUSEADDR)逐个应用到 Channel 的配置中。

6.2 设置 Attrs

把我们之前通过 .attr() 设置的自定义属性绑定到 Channel 上。这些属性可以在后续的 Handler 中通过 channel.attr(key).get() 读取。

6.3 添加自定义的Handler与 ServerBootstrapAcceptor

这是 init 阶段最关键的一步——这一步向 Pipeline 中添加一个特殊的 Handler:ServerBootstrapAcceptor

ServerBootstrapAcceptor 的作用:当服务端 Accept 到一个新的客户端连接时,这个 Acceptor 负责:

  1. 拿到新创建的 SocketChannel
  2. 把它注册到 childGroup(workerGroup)中的某个 NioEventLoop 上
  3. childHandler(我们在服务端代码中写的业务逻辑 Handler)添加到新 Channel 的 Pipeline 中
  4. 设置 childOptionschildAttrs

简单说,ServerBootstrapAcceptor 就是"连接分发器",它架起了 parentGroup 和 childGroup 之间的桥梁。

💡 注意:ServerBootstrapAcceptor 只会被添加到 ServerSocketChannel 的 Pipeline 中,不会出现在客户端的 Pipeline 里。它专门负责"接客"——把新连接交给 worker 线程处理。

七、完整初始化流程总结

到这里,initAndRegister 中的 init 阶段就全部完成了。我们用一张表来总结整个流程:

步骤所在类做了什么
1. 创建 JDK ServerSocketChannel NioServerSocketChannel 调用 SelectorProvider.openServerSocketChannel(),创建底层 NIO Channel
2. 保存 readInterestOp AbstractNioChannel 记录该 Channel 关注的事件类型(OP_ACCEPT = 16)
3. 设置非阻塞模式 AbstractNioChannel 调用 configureBlocking(false),为 Selector 注册做准备
4. 创建 ChannelId AbstractChannel 生成全局唯一的 Channel 标识
5. 创建 Unsafe AbstractChannel 封装底层 JDK Socket 操作(bind / accept / write / close 等)
6. 创建 Pipeline AbstractChannel 初始化双向链表,默认包含 Head 和 Tail 两个节点
7. 创建 ChannelConfig NioServerSocketChannel 封装 Socket 参数(SO_BACKLOG / SO_REUSEADDR 等)
8. 应用 Options 和 Attrs ServerBootstrap 将用户配置的 option 和 attr 应用到 Channel 上
9. 添加 ServerBootstrapAcceptor ServerBootstrap 向 Pipeline 添加连接分发器,负责将新连接交给 childGroup

经过这 9 个步骤,一个 NioServerSocketChannel 就彻底初始化完成了。此时它已经:

  • 持有了 JDK 的 ServerSocketChannel 实例
  • 被设置为非阻塞模式
  • 有了唯一的 ID
  • 有了 Unsafe 可以操作底层 Socket
  • 有了 Pipeline(含 Head / Tail / ServerBootstrapAcceptor)
  • 有了 Config 可以管理 Socket 参数

万事俱备,只欠注册。 下一步就是把这个初始化好的 Channel 注册到 parentGroup 中的某个 NioEventLoop 的 Selector 上,让它开始监听 OP_ACCEPT 事件。这部分内容,我们留给下一篇来讲。


八、总结

  1. Netty 通过反射创建 NioServerSocketChannel,底层持有 JDK 的 ServerSocketChannel 实例,没有"重复造轮子"。
  2. configureBlocking(false)AbstractNioChannel 中完成,对应 NIO 原生 Demo 中的手动设置,是 Selector 注册的前提。
  3. readInterestOp 在构造时传入OP_ACCEPT = 16 表示服务端 Channel 关注"接受连接"事件,供后续注册阶段使用。
  4. Unsafe 是 Netty 的底层操作门面,屏蔽了不同 IO 模型的差异,上层 Handler 不直接操作 JDK Socket。
  5. Pipeline 默认就有 Head 和 Tail,用户自定义的 Handler 会被插入到它们之间,形成双向链表。
  6. ServerBootstrapAcceptor 是连接分发器,架起 parentGroup 和 childGroup 之间的桥梁,是 Reactor 主从模型在代码中的具体体现。
初始化的本质,就是把 NIO 的底层组件(Channel、Selector、Socket)按照 Netty 的设计组装起来,为后续的注册和事件循环做好准备。

下一篇,我们将深入 initAndRegister 中的 register 阶段,看看 Channel 是如何被注册到 NioEventLoop 的 Selector 上的,以及注册完成后又发生了什么。


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

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