在 5.不完全的 ServerBootstrap 启动过程源码分析 文章中, 发现有好多类没有了解过, 后续几篇文章都会先了解这些类的左右, 然后在开始说处理客户端连接的问题.
ChannelPipeline 是 ChannelHandler 的管理容器, 它内部维护了一个 ChannelHandler 的链表, 可以方便的实现 ChannelHandler 的查找、添加、删除、替换、遍历等.
每个 Channel 中都会包含一个 ChannelPipleline, 就像之前说的 NioServerSocketChannel 中就包含一个 ChannelPipeline.
- ChannelOutboundInvoker: 用来处理一些出站数据.
- ChannelInboundInvoker: 用来处理一些入站数据.
先不管这两个接口因为在 ChannelHandlerContext 会说, 主要是看 ChannelPipeLine 接口的默认实现 DefaultChannelPipeline.
ChannelPipeline 将多个 ChannelHandler 链接在一起来让事件在其中传播处理.
下面展示了一个同时具有入站处理器和出站处理器的 ChannelPipeline:
需要注意的是, 对于入站事件, 总是从左往右传播事件, 所以图中第一个处理入站数据的 ChannelHandler 是1, 然后是2, 然后是4. 对于出站事件来说, 总是从右往左传播事件, 所以图中第一个处理出站数据的 ChannelHandler 是5, 然后是3.
ChannelHandler 的存储
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
// 检查是不是添加了多次
checkMultiplicity(handler);
/*
* 1. filterName 方法, 如果名称重复抛出异常, 如果没有名字则会生成 类名#序号 的名称.
* 2. 创建 DefaultChannelHandlerContext 实例.
*/
newCtx = newContext(group, filterName(name, handler), handler);
/*
* 下面有详细解释
*/
addLast0(newCtx);
/*
* registered: 表示 io.netty.channel.AbstractChannel 还没有注册, 当注册后该值永远为 true.
* AbstractChannel 注册就表示 Java 中的 ServerSocketChannel 已经注册到 Selector.
*
* 主要用来保存, Channel 没有注册前添加的任务, 当注册完成后会立即执行这些任务.
*/
if (!registered) {
// 只有 handlerAdded 和 handlerRemoved 方法没调用前, 才能通过 CAS 操作将 newCtx 中的 INIT 设置为 ADD_PENDING.
// INIT: 默认值为 0.
// ADD_PENDING: 值为 1.
newCtx.setAddPending();
/*
* 该方法会根据参数二, 创建不同的任务:
* true: PendingHandlerAddedTask.
* false: PendingHandlerRemovedTask.
*
* 第一个任务会保存到 pendingHandlerCallbackHead 变量中, 后续任务不断追加.
*/
callHandlerCallbackLater(newCtx, true);
return this;
}
// 如果 ChannelHandler 没有绑定, 则会使用与当前 Channel 注册的 EventLoop.
EventExecutor executor = newCtx.executor();
// 如果是由另一个线程调用了该方法(addLast), 则会回调 ChannelHandlerContext 中的 callHandlerAdded 方法.
if (!executor.inEventLoop()) {
callHandlerAddedInEventLoop(newCtx, executor);
return this;
}
}
// 使用当前线程回调 ChannelHandlerContext 中的 callHandlerAdded 方法,
// 该方法最终会调用 ChannelHandler 的 handlerAdded 方法, 表示该 ChannelHandler 要被添加到 pipeline 中.
// 例如 ChannelInitializer 抽象类就实现了 handlerAdded 方法, 直接执行 initChannel 方法.
callHandlerAdded0(newCtx);
return this;
}
在看这个主要方法(addLast0)时, 先看两个主要属性和这两个属性的初始化:
- head: 双向链表头.
- tail: 双向链表尾.
protected DefaultChannelPipeline(Channel channel) {
// ...
// 这两个类都是简单的内部类, 没啥可说的
tail = new TailContext(this);
head = new HeadContext(this);
// 由于是双向链表, 所以 head 的下一个是 tail,
// 而 tail 的上一个是 head.
head.next = tail;
tail.prev = head;
}
首先已经知道了 ChannelPipeline 中, 不是使用数组来保存 ChannelHandler 的.
但为啥需要创建这两个内部类?
我个人认为只是为了方便实现 addLast0 方法, 还有就是保证传递给 ServerBootstrapAcceptor
.
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
}
下面是以图形的方式说明一下.
上面这张图是刚初始化完成后的 ChannelHandler 的链表.
下面这张图是添加了一个 ChannelHandler 后的结果, 也就是说, 无论你怎么添加或移除, 头和尾的 handler 永远不会改变.
ChannelPipeline 的传播
当服务端监听到客户端发生的事件时, 可以使用一个或多个 ChannelHandler 处理相应的业务逻辑.
第一个 ChannelHandler
的 channelRead
方法执行是在 NioEventLoop#processSelectedKey
方法中调用的, 该方法会根据不同的事件类型执行不同的方法.
OP_ACCEPT 事件传播
最终会调用 NioMessageUnsafe
中的 read()
方法.
public void read() {
private final List<Object> readBuf = new ArrayList<Object>();
// ...
try {
try {
// 会获取到所有的 java.nio.channels.SocketChannel 添加到 readBuf 集合中.
do {
// 使用 java.nio.channels.ServerSocketChannel#accept 方法,
// 获取到 java.nio.channels.SocketChannel 并封装成 NioSocketChannel.
int localRead = doReadMessages(readBuf);
// ...
} while (allocHandle.continueReading());
} catch (Throwable t) {
exception = t;
}
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
readPending = false;
// 然后调用 ChannelHandler 中的 channelRead 方法.
// 注意: 1. 至于会执行哪些 ChannelHandler, 是根据 6.不完全的ServerBootstrap启动过程源码分析
// 文章中的 handler() 方法所添加的.
// 2. 该方法的参数是 NioSocketChannel 啊!
pipeline.fireChannelRead(readBuf.get(i));
}
readBuf.clear();
allocHandle.readComplete();
// 执行 ChannelHandler 中的 channelReadComplete 方法.
pipeline.fireChannelReadComplete();
// 如果有异常则会执行 ChannelHandler 中的 exceptionCaught 方法.
if (exception != null) {
closed = closeOnReadError(exception);
pipeline.fireExceptionCaught(exception);
}
// ...
} finally {
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
注意 channelRead 方法, 在该方法中必须要显示的调用 super.channelRead(ctx, msg);
或 ctx.fireChannelRead(msg);
方法. 保证 ServerBootstrapAcceptor
这个 ChannelHandler
被调用到, 因为只有这样才能注册 NioSocketChannel.
而注册 NioSocketChannel
时, 都会做什么操作, 可以通过 ChannelHandler
文章得到答案.
OP_WRITE 事件传播
最终会调用 NioMessageUnsafe
中的 read()
方法.
public final void read() {
final ChannelConfig config = config();
// ...
final ChannelPipeline pipeline = pipeline();
final ByteBufAllocator allocator = config.getAllocator();
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
allocHandle.reset(config);
ByteBuf byteBuf = null;
boolean close = false;
try {
do {
byteBuf = allocHandle.allocate(allocator);
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= 0) {
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
if (close) {
readPending = false;
}
break;
}
allocHandle.incMessagesRead(1);
readPending = false;
// 注意这里执行的是 NioSocketChannel 中 pipeline 中的 ChannelHandler 链表的 channelRead 方法.
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
} while (allocHandle.continueReading());
allocHandle.readComplete();
// 最后还是执行 channelReadComplete 方法.
pipeline.fireChannelReadComplete();
if (close) {
closeOnRead(pipeline);
}
} catch (Throwable t) {
handleReadException(pipeline, byteBuf, t, close, allocHandle);
} finally {
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
}
该事件与 OP_ACCEPT
事件类似, 只不过 channelRead
方法传递的是数据, 而 OP_ACCEPT
事件传递的是 NioSocketChannel
也就是客户端信息.
参考资料
Netty学习:ChannelOutboundInvoker
ChannelHandlerContext简介
Netty主要类关系
拜托!面试请不要再问我 Netty 底层架构原理!
Netty源码解读(三)Channel与Pipeline
8.10 ChannelPipeline详解
死磕Netty源码之ChannelPipeline源码解析(一)
【Netty】(8)---理解ChannelPipeline
Netty学习笔记之ChannelHandler
接口ChannelPipeline
Netty 之 ChannelPipeline 源码解析