一文聊透Netty IO事件的编排利器pipeline |详解所有IO事件(五)
4. 向pipeline添加channelHandler
在我们详细介绍了全部的 inbound 类事件和 outbound 类事件的掩码表示以及事件的触发和传播路径后,相信大家现在可以通过 ChannelInboundHandler 和 ChannelOutboundHandler 来根据具体的业务场景选择合适的 ChannelHandler 类型以及监听合适的事件来完成业务需求了。
本小节就该介绍一下自定义的 ChannelHandler 是如何添加到 pipeline 中的,netty 在这个过程中帮我们作了哪些工作?
final EchoServerHandler serverHandler = new EchoServerHandler();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.............
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(serverHandler);
......可添加多个channelHandler......
}
});
以上是笔者简化的一个 netty 服务端配置 ServerBootstrap 启动类的一段示例代码。我们可以看到再向 channel 对应的 pipeline 中添加 ChannelHandler 是通过 ChannelPipeline#addLast 方法将指定 ChannelHandler 添加到 pipeline 的末尾处。
public interface ChannelPipeline
extends ChannelInboundInvoker, ChannelOutboundInvoker, Iterable<Entry<String, ChannelHandler>> {
//向pipeline的末尾处批量添加多个channelHandler
ChannelPipeline addLast(ChannelHandler... handlers);
//指定channelHandler的executor,由指定的executor执行channelHandler中的回调方法
ChannelPipeline addLast(EventExecutorGroup group, ChannelHandler... handlers);
//为channelHandler指定名称
ChannelPipeline addLast(String name, ChannelHandler handler);
//为channelHandler指定executor和name
ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler);
}
public class DefaultChannelPipeline implements ChannelPipeline {
@Override
public final ChannelPipeline addLast(ChannelHandler... handlers) {
return addLast(null, handlers);
}
@Override
public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
ObjectUtil.checkNotNull(handlers, "handlers");
for (ChannelHandler h: handlers) {
if (h == null) {
break;
}
addLast(executor, null, h);
}
return this;
}
@Override
public final ChannelPipeline addLast(String name, ChannelHandler handler) {
return addLast(null, name, handler);
}
}
最终 addLast 的这些重载方法都会调用到 DefaultChannelPipeline#addLast(EventExecutorGroup, String, ChannelHandler) 这个方法从而完成 ChannelHandler 的添加。
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
//检查同一个channelHandler实例是否允许被重复添加
checkMultiplicity(handler);
//创建channelHandlerContext包裹channelHandler并封装执行传播事件相关的上下文信息
newCtx = newContext(group, filterName(name, handler), handler);
//将channelHandelrContext插入到pipeline中的末尾处。双向链表操作
//此时channelHandler的状态还是ADD_PENDING,只有当channelHandler的handlerAdded方法被回调后,状态才会为ADD_COMPLETE
addLast0(newCtx);
//如果当前channel还没有向reactor注册,则将handlerAdded方法的回调添加进pipeline的任务队列中
if (!registered) {
//这里主要是用来处理ChannelInitializer的情况
//设置channelHandler的状态为ADD_PENDING 即等待添加,当状态变为ADD_COMPLETE时 channelHandler中的handlerAdded会被回调
newCtx.setAddPending();
//向pipeline中添加PendingHandlerAddedTask任务,在任务中回调handlerAdded
//当channel注册到reactor后,pipeline中的pendingHandlerCallbackHead任务链表会被挨个执行
callHandlerCallbackLater(newCtx, true);
return this;
}
//如果当前channel已经向reactor注册成功,那么就直接回调channelHandler中的handlerAddded方法
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
//这里需要确保channelHandler中handlerAdded方法的回调是在channel指定的executor中
callHandlerAddedInEventLoop(newCtx, executor);
return this;
}
}
//回调channelHandler中的handlerAddded方法
callHandlerAdded0(newCtx);
return this;
}
这个方法的逻辑还是比较复杂的,涉及到很多细节,为了清晰地为大家讲述,笔者这里还是采用总分总的结构,先描述该方法的总体逻辑,然后在针对核心细节要点展开细节分析。
因为向 pipeline 中添加 channelHandler 的操作可能会在多个线程中进行,所以为了确保添加操作的线程安全性,这里采用一个 synchronized 语句块将整个添加逻辑包裹起来。
1.通过 checkMultiplicity 检查被添加的 ChannelHandler 是否是共享的(标注 @Sharable 注解),如果不是共享的那么则不会允许该 ChannelHandler 的同一实例被添加进多个 pipeline 中。如果是共享的,则允许该 ChannelHandler 的同一个实例被多次添加进多个 pipeline 中。
private static void checkMultiplicity(ChannelHandler handler) {
if (handler instanceof ChannelHandlerAdapter) {
ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
//只有标注@Sharable注解的channelHandler,才被允许同一个实例被添加进多个pipeline中
//注意:标注@Sharable之后,一个channelHandler的实例可以被添加到多个channel对应的pipeline中
//可能被多线程执行,需要确保线程安全
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;
}
}
这里大家需要注意的是,如果一个 ChannelHandler 被标注了 @Sharable 注解,这就意味着它的一个实例可以被多次添加进多个 pipeline 中(每个 channel 对应一个 pipeline 实例),而这多个不同的 pipeline 可能会被不同的 reactor 线程执行,所以在使用共享 ChannelHandler 的时候需要确保其线程安全性。
比如下面的实例代码:
@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
.............需要确保线程安全.......
}
final EchoServerHandler serverHandler = new EchoServerHandler();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
..................
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(serverHandler);
}
});
EchoServerHandler 为我们自定义的 ChannelHandler ,它被 @Sharable 注解标注,全局只有一个实例,被添加进多个 Channel 的 pipeline 中。从而会被多个 reactor 线程执行到。
共享channelHandler.png
2.为 ChannelHandler 创建其 ChannelHandlerContext ,用于封装 ChannelHandler 的名称,状态信息,执行上下文信息,以及用于感知 ChannelHandler 在 pipeline 中的位置信息。newContext 方法涉及的细节较多,后面我们单独介绍。
3.通过 addLast0 将新创建出来的 ChannelHandlerContext 插入到 pipeline 中末尾处。方法的逻辑很简单其实就是一个普通的双向链表插入操作。
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
}
但是这里大家需要注意的点是:虽然此时 ChannelHandlerContext 被物理的插入到了 pipeline 中,但是此时 channelHandler 的状态依然为 INIT 状态,从逻辑上来说并未算是真正的插入到 pipeline 中,需要等到 ChannelHandler 的 handlerAdded 方法被回调时,状态才变为 ADD_COMPLETE ,而只有 ADD_COMPLETE 状态的 ChannelHandler 才能响应 pipeline 中传播的事件。
channelhandelr的状态.png
在上篇文章《一文搞懂Netty发送数据全流程》中的《3.1.5 触发nextChannelHandler的write方法回调》小节中我们也提过,在每次 write 事件或者 flush 事件传播的时候,都需要通过 invokeHandler 方法来判断 channelHandler 的状态是否为 ADD_COMPLETE ,否则当前 channelHandler 则不能响应正在 pipeline 中传播的事件。必须要等到对应的 handlerAdded 方法被回调才可以,因为 handlerAdded 方法中可能包含一些 ChannelHandler 初始化的重要逻辑。
private boolean invokeHandler() {
// 这里是一个优化点,netty 用一个局部变量保存 handlerState
// 目的是减少 volatile 变量 handlerState 的读取次数
int handlerState = this.handlerState;
return handlerState == ADD_COMPLETE || (!ordered && handlerState == ADD_PENDING);
}
void invokeWrite(Object msg, ChannelPromise promise) {
if (invokeHandler()) {
invokeWrite0(msg, promise);
} else {
// 当前channelHandler虽然添加到pipeline中,但是并没有调用handlerAdded
// 所以不能调用当前channelHandler中的回调方法,只能继续向前传递write事件
write(msg, promise);
}
}
private void invokeFlush() {
if (invokeHandler()) {
invokeFlush0();
} else {
//如果该ChannelHandler虽然加入到pipeline中但handlerAdded方法并未被回调,则继续向前传递flush事件
flush();
}
}
事实上不仅仅是 write 事件和 flush 事件在传播的时候需要判断 ChannelHandler 的状态,所有的 inbound 类事件和 outbound 类事件在传播的时候都需要通过 invokeHandler 方法来判断当前 ChannelHandler 的状态是否为 ADD_COMPLETE ,需要确保在 ChannelHandler 响应事件之前,它的 handlerAdded 方法被回调。
4.如果向 pipeline 中添加 ChannelHandler 的时候, channel 还没来得及注册到 reactor中,那么需要将当前 ChannelHandler 的状态先设置为 ADD_PENDING ,并将回调该 ChannelHandler 的 handlerAdded 方法封装成 PendingHandlerAddedTask 任务添加进 pipeline 中的任务列表中,等到 channel 向 reactor 注册之后,reactor 线程会挨个执行 pipeline 中任务列表中的任务。
这段逻辑主要用来处理 ChannelInitializer 的添加场景,因为目前只有 ChannelInitializer 这个特殊的 channelHandler 会在 channel 没有注册之前被添加进 pipeline 中
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
向 pipeline 的任务列表 pendingHandlerCallbackHead 中添加 PendingHandlerAddedTask 任务:
public class DefaultChannelPipeline implements ChannelPipeline {
// pipeline中的任务列表
private PendingHandlerCallback pendingHandlerCallbackHead;
// 向任务列表尾部添加PendingHandlerAddedTask
private void callHandlerCallbackLater(AbstractChannelHandlerContext ctx, boolean added) {
assert !registered;
PendingHandlerCallback task = added ? new PendingHandlerAddedTask(ctx) : new PendingHandlerRemovedTask(ctx);
PendingHandlerCallback pending = pendingHandlerCallbackHead;
if (pending == null) {
pendingHandlerCallbackHead = task;
} else {
// Find the tail of the linked-list.
while (pending.next != null) {
pending = pending.next;
}
pending.next = task;
}
}
}
PendingHandlerAddedTask 任务负责回调 ChannelHandler 中的 handlerAdded 方法。
private final class PendingHandlerAddedTask extends PendingHandlerCallback {
...............
@Override
public void run() {
callHandlerAdded0(ctx);
}
...............
}
private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
try {
ctx.callHandlerAdded();
} catch (Throwable t) {
...............
}
}
pipeline任务列表.png
除了 ChannelInitializer 这个特殊的 ChannelHandler 的添加是在 channel 向 reactor 注册之前外,剩下的这些用户自定义的 ChannelHandler 的添加,均是在 channel 向 reactor 注册之后被添加进 pipeline 的。这种场景下的处理就会变得比较简单,在 ChannelHandler 被插入到 pipeline 中之后,就会立即回调该 ChannelHandler 的 handlerAdded 方法。但是需要确保 handlerAdded 方法的回调在 channel 指定的 executor 中进行。
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
callHandlerAddedInEventLoop(newCtx, executor);
return this;
}
callHandlerAdded0(newCtx);
如果当前执行线程并不是 ChannelHandler 指定的 executor ( !executor.inEventLoop() ),那么就需要确保 handlerAdded 方法的回调在 channel 指定的 executor 中进行。
private void callHandlerAddedInEventLoop(final AbstractChannelHandlerContext newCtx, EventExecutor executor) {
newCtx.setAddPending();
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
}
这里需要注意的是需要在回调 handlerAdded 方法之前将 ChannelHandler 的状态提前设置为 ADD_COMPLETE 。 因为用户可能在 ChannelHandler 中的 handerAdded 回调中触发一些事件,而如果此时 ChannelHandler 的状态不是 ADD_COMPLETE 的话,就会停止对事件的响应,从而错过事件的处理。
这种属于一种用户极端的使用情况。
final void callHandlerAdded() throws Exception {
if (setAddComplete()) {
handler().handlerAdded(this);
}
}