Netty 第六篇:initAndRegister 中的 init 阶段详解
上一篇我们拆解了 bind(8080) 方法的整体骨架,知道了它内部主要做了两件事:initAndRegister 和 doBind0。其中 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) 内部会通过 ReflectiveChannelFactory 把 NioServerSocketChannel 的 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 的操作(比如 bind、accept、configureBlocking 等),都是通过这个 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 的 ServerSocketChannel、SocketChannel,还是 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_BACKLOG、SO_REUSEADDR)逐个应用到 Channel 的配置中。
6.2 设置 Attrs
把我们之前通过 .attr() 设置的自定义属性绑定到 Channel 上。这些属性可以在后续的 Handler 中通过 channel.attr(key).get() 读取。
6.3 添加自定义的Handler与 ServerBootstrapAcceptor
这是 init 阶段最关键的一步——这一步向 Pipeline 中添加一个特殊的 Handler:ServerBootstrapAcceptor。
ServerBootstrapAcceptor 的作用:当服务端 Accept 到一个新的客户端连接时,这个 Acceptor 负责:
- 拿到新创建的
SocketChannel - 把它注册到
childGroup(workerGroup)中的某个 NioEventLoop 上 - 将
childHandler(我们在服务端代码中写的业务逻辑 Handler)添加到新 Channel 的 Pipeline 中 - 设置
childOptions和childAttrs
简单说,ServerBootstrapAcceptor 就是"连接分发器",它架起了 parentGroup 和 childGroup 之间的桥梁。
七、完整初始化流程总结
到这里,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 事件。这部分内容,我们留给下一篇来讲。
八、总结
- Netty 通过反射创建
NioServerSocketChannel,底层持有 JDK 的ServerSocketChannel实例,没有"重复造轮子"。 configureBlocking(false)在AbstractNioChannel中完成,对应 NIO 原生 Demo 中的手动设置,是 Selector 注册的前提。readInterestOp在构造时传入,OP_ACCEPT = 16表示服务端 Channel 关注"接受连接"事件,供后续注册阶段使用。- Unsafe 是 Netty 的底层操作门面,屏蔽了不同 IO 模型的差异,上层 Handler 不直接操作 JDK Socket。
- Pipeline 默认就有 Head 和 Tail,用户自定义的 Handler 会被插入到它们之间,形成双向链表。
- ServerBootstrapAcceptor 是连接分发器,架起 parentGroup 和 childGroup 之间的桥梁,是 Reactor 主从模型在代码中的具体体现。
初始化的本质,就是把 NIO 的底层组件(Channel、Selector、Socket)按照 Netty 的设计组装起来,为后续的注册和事件循环做好准备。
下一篇,我们将深入 initAndRegister 中的 register 阶段,看看 Channel 是如何被注册到 NioEventLoop 的 Selector 上的,以及注册完成后又发生了什么。
— 原创文章,转载请注明出处 —

浙公网安备 33010602011771号