Pipeline 的事件传播机制
前面章节中,我们已经知道 AbstractChannelHandlerContext 中有 inbound 和 outbound 两个 boolean 变量,分别用于标识 Context 所对应的 handler 的类型,即:
1、inbound 为 true 是,表示其对应的 ChannelHandler 是 ChannelInboundHandler 的子类。
2、outbound 为 true 时,表示对应的 ChannelHandler 是 ChannelOutboundHandler 的子类。
这里大家肯定还有很多疑惑,不知道这两个字段到底有什么作用? 这还要从 ChannelPipeline 的事件传播类型说起。
Netty 中的传播事件可以分为两种:Inbound 事件和 Outbound 事件。如下是从 Netty 官网针对这两个事件的说明:
从上图可以看出,inbound 事件和 outbound 事件的流向是不一样的,inbound 事件的流行是从下至上,而 outbound刚好相反,是从上到下。并且 inbound 的传递方式是通过调用相应的 ChannelHandlerContext.fireIN_EVT()方法,而outbound 方法的的传递方式是通过调用 ChannelHandlerContext.OUT_EVT()方法。例如:ChannelHandlerContext 的 fireChannelRegistered()调用会发送一个 ChannelRegistered 的 inbound 给下一个 ChannelHandlerContext,而 ChannelHandlerContext 的 bind()方法调用时会发送一个 bind 的 outbound 事件给下一个 ChannelHandlerContext。
Inbound 事件传播方法有:
public interface ChannelInboundHandler extends ChannelHandler {
void channelRegistered(ChannelHandlerContext ctx) throws Exception;
void channelUnregistered(ChannelHandlerContext ctx) throws Exception;
void channelActive(ChannelHandlerContext ctx) throws Exception;
void channelInactive(ChannelHandlerContext ctx) throws Exception;
void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;
void channelReadComplete(ChannelHandlerContext ctx) throws Exception;
void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;
void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}
Outbound 事件传播方法有:
public interface ChannelOutboundHandler extends ChannelHandler {
void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;
void connect(
ChannelHandlerContext ctx, SocketAddress remoteAddress,
SocketAddress localAddress, ChannelPromise promise) throws Exception;
void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
void read(ChannelHandlerContext ctx) throws Exception;
void flush(ChannelHandlerContext ctx) throws Exception;
}
大家应该发现了规律:inbound 类似于是事件回调(响应请求的事件),而 outbound 类似于主动触发(发起请求的 事件)。注意,如果我们捕获了一个事件,并且想让这个事件继续传递下去,那么需要调用 Context 对应的传播方法 fireXXX,例如:
public class MyInboundHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("连接成功");
ctx.fireChannelActive();
}
}
public class MyOutboundHandler extends ChannelOutboundHandlerAdapter {
@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
System.out.println("客户端关闭");
ctx.close(promise);
}
}
如上面的示例代码:MyInboundHandler 收到了一个 channelActive 事件,它在处理后,如果希望将事件继续传播下去,那么需要接着调用 ctx.fireChannelActive()方法。
Outbound 事件传播方式
Outbound 事件都是请求事件(request event),即请求某件事情的发生,然后通过 Outbound 事件进行通知。
Outbound 事件的传播方向是 tail -> customContext -> head。
我们接下来以 connect 事件为例,分析一下 Outbound 事件的传播机制。
首先,当用户调用了 Bootstrap 的 connect()方法时,就会触发一个 Connect 请求事件,此调用会触发如下调用链:
继续跟踪,我们就发现 AbstractChannel 的 connect()其实由调用了 DefaultChannelPipeline 的 connect()方法:
public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
return pipeline.connect(remoteAddress, promise);
}
而 pipeline.connect()方法的实现如下:
public final ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
return tail.connect(remoteAddress, promise);
}
可以看到,当 outbound 事件(这里是 connect 事件)传递到 Pipeline 后,它其实是以 tail 为起点开始传播的。
而 tail.connect()其实调用的是 AbstractChannelHandlerContext 的 connect()方法:
public ChannelFuture connect(
final SocketAddress remoteAddress,
final SocketAddress localAddress, final ChannelPromise promise) {
//此处省略 N 句
final AbstractChannelHandlerContext next = findContextOutbound();
EventExecutor executor = next.executor();
next.invokeConnect(remoteAddress, localAddress, promise);
//此处省略 N 句
return promise;
}
findContextOutbound()方法顾名思义,它的作用是以当前 Context 为起点,向 Pipeline 中的 Context 双向链表的前端 寻找第一个 outbound 属性为 true 的 Context(即关联 ChannelOutboundHandler 的 Context),然后返回。
findContextOutbound()方法代码实现如下:
private AbstractChannelHandlerContext findContextOutbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.prev;
} while (!ctx.outbound);
return ctx;
}
当我们找到了一个 outbound 的 Context 后,就调用它的 invokeConnect()方法,这个方法中会调用 Context 其关联的 ChannelHandler 的 connect()方法:
private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
if (invokeHandler()) {
try {
((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
} else {
connect(remoteAddress, localAddress, promise);
}
}
如果用户没有重写 ChannelHandler 的 connect()方法,那么会调用 ChannelOutboundHandlerAdapter 的 connect() 实现:
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
SocketAddress localAddress, ChannelPromise promise) throws Exception {
ctx.connect(remoteAddress, localAddress, promise);
}
我们看到,ChannelOutboundHandlerAdapter 的 connect()仅仅调用了 ctx.connect(),而这个调用又回到了:
Context.connect -> Connect.findContextOutbound -> next.invokeConnect -> handler.connect -> Context.connect
这样的循环中,直到 connect 事件传递到 DefaultChannelPipeline 的双向链表的头节点,即 head 中。为什么会传递 到 head 中呢?回想一下,head 实现了 ChannelOutboundHandler,因此它的 outbound 属性是 true。
因为 head 本身既是一个 ChannelHandlerContext,又实现了 ChannelOutboundHandler 接口,因此当 connect()消息 传递到 head 后,会将消息转递到对应的 ChannelHandler 中处理,而 head 的 handler()方法返回的就是 head 本身:
public ChannelHandler handler() {
return this;
}
因此最终 connect()事件是在 head 中被处理。head 的 connect()事件处理逻辑如下:
public void connect(
ChannelHandlerContext ctx,
SocketAddress remoteAddress, SocketAddress localAddress,
ChannelPromise promise) throws Exception {
unsafe.connect(remoteAddress, localAddress, promise);
}
到这里, 整个 connect()请求事件就结束了。下图中描述了整个 connect()请求事件的处理过程:
我们仅仅以 connect()请求事件为例,分析了 outbound 事件的传播过程,但是其实所有的 outbound 的事件传播都遵循着一样的传播规律,小伙伴们可以试着分析一下其他的 outbound 事件,体会一下它们的传播过程。