前言
本章将分析Netty中的重要对象ChannelPipeline,探索其生命周期中的一些重难点。
ChannelPipeline添加ChannelHandler
添加handler的核心方法如下,主要分为四个步骤
1.检查是否有重复的Handler。
2.创建节点。
3.添加节点。
4.回调用户方法。
scss
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
checkMultiplicity(handler);
newCtx = newContext(group, filterName(name, handler), handler);
addLast0(newCtx);
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}
- 检查是否有重复的Handler
java
private static void checkMultiplicity(ChannelHandler handler) {
if (handler instanceof ChannelHandlerAdapter) {
ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
if (!h.isSharable() && h.added) {
throw new ChannelPipelineException(
h.getClass().getName() +
" is not a @Sharable handler, so can't be added or removed multiple times.");
}
h.added = true;
}
}
handler如果集成了ChannelHandlerAdapter,那么会通过ChannelHandlerAdapter内部的成员变量added判断是否重复添加,如果重复添加且没有使用@Sharable标记为可重复添加,那么就会抛出异常。
✅每个连接独立一个 handler 实例
这里可以看到,每一个channel的MyHandler实例都是new出来的,这种方式下的添加的handler不是会重复的。
java
// 添加handler
ServerBootstrap b = new ServerBootstrap();
b.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new MyHandler()); // 每个连接都 new 一次
}
});
⚠️ 同一个 handler 被多个 Channel 共享 这种场景下,所有channel的pipeline共用同一个MyHandler对象
java
MyHandler shared = new MyHandler();
b.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(shared); // 重复使用同一个实例
}
});
值得注意的是,大部分场景下的Handler是有状态的,开发者需要根据场景选择合适的方式。
关于Unsafe
我们在前面的系列文章中提到过unsafe这个类,这个类是netty内部用来实际处理IO事件的类。Unsafe家族的继承关系如下

1.NioUnsafe增加了可以访问底层JDK的SelectableChannel的功能,定义了从SelectableChannel读取数据的read方法。
2.AbstractUnsafe实现了大部分Unsafe的功能。
3.AbstractNioUnsafe主要是通过代理到其外部类AbstractNioChannel获得了与JDK NIO相关的一些信息,比如SelectableChannel、SelectionKey等。
4.把NioSocketChannelUnsafe和NioByteUnsafe放到一起讲,实现了IO的基本操作------读和写,这些操作都与JDK底层相关。
5.NioMessageUnsafe和NioByteUnsafe是处在同一层次的抽象,Netty将一个新连接的建立也当作一个IO操作来处理,这里Message的含义我们可以当作一个SelectableChannel,读的意思就是接收一个SelectableChannel。
Unsafe 的分类
从 Netty 的继承结构来看,Unsafe 可以分成两大类:
一种负责处理新连接的接入 ,另一种负责处理已建立连接的字节读写。它们分别是:
NioMessageUnsafe(处理 accept)NioByteUnsafe(处理 read/write)
下面我们把它们的职责和来源讲清楚。
1. NioMessageUnsafe
NioMessageUnsafe 是 AbstractNioMessageChannel 的内部类。
而 AbstractNioMessageChannel 又是 NioServerSocketChannel 的父类。
这意味着:
服务端 ServerSocketChannel 使用的 Unsafe,就是 NioMessageUnsafe。
在 Netty 的设计里,新连接的到达同样被当成一种"读事件",因此 accept 流程也是由 read 方法驱动。
也就是说:
NioMessageUnsafe.read() 完成了服务端新连接的整个接入过程。
如果你看过【服务端启动流程】或【客户端连接接入流程解析】,应该知道其中包含:
- 调用 JDK 的 accept
- 创建 NioSocketChannel
- 配置 pipeline
- 注册到 worker EventLoop
这里就不重复展开。
2. NioByteUnsafe
NioByteUnsafe 是 AbstractNioByteChannel 的内部类。
而 AbstractNioByteChannel 是客户端 NioSocketChannel 的父类。
这说明:
客户端连接的 IO 读写,都是通过 NioByteUnsafe 来完成的。
它负责:
- 从 socket 中读取字节
- 写出字节到对端
- 处理半包、空读、自动读控制等逻辑
两类 Unsafe 的分工很明确:
| Unsafe 类型 | 适用对象 | 负责的事情 |
|---|---|---|
| NioMessageUnsafe | NioServerSocketChannel | 接受新连接(accept) |
| NioByteUnsafe | NioSocketChannel | 读写字节流(read/write) |
Unsafe 的调用路径
我们已经了解了两类 Unsafe 接口的功能------分别负责处理新连接和字节流读写。那么程序是如何执行到 Unsafe 的 read() 方法的呢?
实际上,这个调用路径与 Netty 的事件循环机制紧密相关:
NioServerSocketChannel 的 NioMessageUnsafe.read()
- 在 NioEventLoop 的事件轮询中,当检测到新连接(ACCEPT 事件)时被触发调用
- 对应的 NioEventLoop 轮询在 Channel 注册时启动
NioSocketChannel 的 NioByteUnsafe.read()
- 在 NioEventLoop 的事件轮询中,当检测到读写事件(READ/WRITE 事件)时被调用
- 对应的 NioEventLoop 轮询同样在 Channel 注册时启动
关键区别在于注册时机:
- Boss Group 的注册发生在
initAndRegister()中,绑定的是ServerSocketChannel - Worker Group 的注册发生在
ServerBootstrapAcceptor.channelRead()方法的childGroup.register(child)中,绑定的是普通的SocketChannel
这种设计确保了连接建立与数据读写在不同的事件循环中处理,实现了高效的分层架构。
小结
本章主要介绍了ChannelPipeline的一些疑难知识,包括重复handler的检查和unsafe类。下一章稍微提一下netty的writeAndFlush方法。