接上篇:Netty学习——源码篇7 Pipeline的事件传播机制
1 Outbound事件传播方式
Outbound事件都是请求事件(Request Event),即请求某件事情的发生,然后通过Outbound事件进行通知。
Outbound事件的传播方向是从Tail到customContext再到Head。下面以Connect事件为例,分析一下Outbound事件的传播机制。
首先,当用户调用了Bootstrap的connect方法时,就会触发一个Connect请求事件,此调用会触发调用链,如下图所示。
继续跟踪,发现AbstractChannel的connect方法又调用了DefaultChannelPipeline的connect 方法,代码如下:
public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
return pipeline.connect(remoteAddress, localAddress, promise);
}
而pipeline.connect()方法的代码如下:
public final ChannelFuture connect(
SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
return tail.connect(remoteAddress, localAddress, promise);
}
可以看到,当Outbound事件(这里是Connect事件)传递到Pipeline后,其实是以Tail为起点开始传播的。
而tail.connect调用的是AbstractChannelHandlerContext的connect方法。
public ChannelFuture connect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
if (remoteAddress == null) {
throw new NullPointerException("remoteAddress");
}
if (!validatePromise(promise, false)) {
// cancelled
return promise;
}
final AbstractChannelHandlerContext next = findContextOutbound();
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeConnect(remoteAddress, localAddress, promise);
} else {
safeExecute(executor, new Runnable() {
@Override
public void run() {
next.invokeConnect(remoteAddress, localAddress, promise);
}
}, promise, null);
}
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方法,代码如下:
@Override
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
SocketAddress localAddress, ChannelPromise promise) throws Exception {
ctx.connect(remoteAddress, localAddress, promise);
}
可以看到,ChannelOutboundHandlerAdapter的connect方法仅调用了ctx.connect方法,而这个调用又回到了Cotext.connect方法调用Connect.findContextOutbound方法,然后调用next.invokeConect方法,其次调用handler.connect方法,最后又调用Context.connect方法,如此循环下去,直到Connect事件传递到DefaultChannelPipeline的双向链表的头节点,即Head中。为什么会传递到Head中呢?回想一下,Head实现了ChannelOutboundHandler,因此它的Outbound属性是true。
因为Head本身即是一个ChannelHandlerCont,又实现了ChannelOutboundHandler接口,所以当connect消息传递到Head后,会将消息传递到对应的ChannelHandler中处理,而Head的handler方法返回的就是Head本身。因此最终Connect事件是在Head中被处理的。Head的Connect事件处理逻辑的代码如下:
@Override
public void connect(
ChannelHandlerContext ctx,
SocketAddress remoteAddress, SocketAddress localAddress,
ChannelPromise promise) throws Exception {
unsafe.connect(remoteAddress, localAddress, promise);
}
到这里,整个Connect请求事件就结束了。下图描述了整个 Connect请求事件的处理过程。
仅仅以Connect请求事件为例,分析了Outbound事件的传播过程,但是其实所有的Outbound的事件传播都遵循着一样的传播规律。
2 Inbound事件传播方式
Inbound和Outbound事件的处理过程是类似的,只是传播方向不同。
Inbound事件是一个通知事件,即某件事已经发生了,然后通过Inbound事件进行通知。Inbound通常发生在Channel的状态改变或I/O事件就绪时。
Inbound的热点是其传播方向从Head到customContext再到Tail.
上面分析了connect()方法其实是一个Outbound事件,那么接着分析connect()事件后会发生什么Inbound事件,并最终找到Outbound和Inbound事件之间的联系。当Connect()事件传播到Unsafe后,其实是在AbstractNioUnsafe的connect方法中进行处理的,代码如下:
public final void connect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
try {
if (connectPromise != null) {
// Already a connect in process.
throw new ConnectionPendingException();
}
boolean wasActive = isActive();
if (doConnect(remoteAddress, localAddress)) {
fulfillConnectPromise(promise, wasActive);
} else {
connectPromise = promise;
requestedRemoteAddress = remoteAddress;
// Schedule connect timeout.
int connectTimeoutMillis = config().getConnectTimeoutMillis();
if (connectTimeoutMillis > 0) {
connectTimeoutFuture = eventLoop().schedule(new Runnable() {
@Override
public void run() {
ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
ConnectTimeoutException cause =
new ConnectTimeoutException("connection timed out: " + remoteAddress);
if (connectPromise != null && connectPromise.tryFailure(cause)) {
close(voidPromise());
}
}
}, connectTimeoutMillis, TimeUnit.MILLISECONDS);
}
promise.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isCancelled()) {
if (connectTimeoutFuture != null) {
connectTimeoutFuture.cancel(false);
}
connectPromise = null;
close(voidPromise());
}
}
});
}
} catch (Throwable t) {
promise.tryFailure(annotateConnectException(t, remoteAddress));
closeIfClosed();
}
}
在AbstractNioUnsafe的connect方法中,先调用doConnect方法进行实际的Socket连接,当连接后会调用fulfillConnectPromise方法,代码如下:
private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
if (promise == null) {
return;
}
boolean active = isActive();
boolean promiseSet = promise.trySuccess();
if (!wasActive && active) {
pipeline().fireChannelActive();
}
if (!promiseSet) {
close(voidPromise());
}
}
可以看到,在fulfillConnectPromise方法中,会通过调用pipeline.fireChannelActive方法将通道激活的消息(即Socket连接成功)发送出去。而这里,当调用pipeline.fireXXX后,就是Inbound事件的起点。因此当调用pipeline.fireChannelActive方法时,就产生了一个ChannelActive Inbound事件,接下来看一下Inbound事件是怎么传播的,代码如下:
@Override
public final ChannelPipeline fireChannelActive() {
AbstractChannelHandlerContext.invokeChannelActive(head);
return this;
}
在fireChannelActive方法中调用了invokeChannelActive(head)方法,因此可以证明Inbound事件在pipeline中传输的起点是Head。head.invokeChannelActive(head)方法代码如下:
static void invokeChannelActive(final AbstractChannelHandlerContext next) {
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelActive();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelActive();
}
});
}
}
回想一下在Outbound事件(例如Connect事件)的传输过程中,也有类似的如下操作:
1、首先调用findContextInbound(),从Pipeline的双向链表中找到第一个Inbound属性为true的Context,然后将其返回。
2、调用Context的invokeChannelActive()方法,invokeChannelActive方法代码如下:
private void invokeChannelActive() {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelActive(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelActive();
}
}
这个方法和Outbound的对应方法如出一辙。与Outbound一样,如果用户没有重写channelActive方法,就会调用ChannelInboundHandlerAdapter的channelActive方法,代码如下:
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelActive();
}
同样的,在ChannelInboundHandlerAdapter的channelActive方法中,仅仅调用了ctx.fireChannelActive方法,因此就调用Context.fireChannelActive方法,其次调用Connect.findContextInbound()方法,然后调用nextContext.invokeChannelActive方法。再然后调用nextHandler.channelActive()方法,最后调用nextContext.fireChannelActive方法,如此循环。下图描述了Inbound事件的传输过程。
3 小结
3.1 Outbound事件传播过程总结如下
1、Outbound 事件是请求事件(由Context发起一个请求,并最终由Unsafe处理这个请求)。
2、Outbound事件 发起者是Channel。
3、Outbound事件的处理者是Unsafe。
4、Outbound事件在Pipeline中的传输方向是从Tail到Head。
5、在ChannelHandler中处理事件时,如果这个Handler不是最后一个Handler,则需要调用ctx的方法将此事件继续传播下去。如果不这样做,那么此事件的传播会提前终止。
6、Outbound事件传播方向是,从Context.OUT_EVT()方法到Connect.findContextOutbound()方法,再到nextContext.invokeOUT_EVT方法,再到nextHandler.OUT_EVT方法,最后到nextContext.OUT_EVT方法。
3.2 Inbound事件传播过程总结如下
1、Inbound事件为通知事件,当某件事情已经就绪后,会通知上层。
2、Inbound事件的发起者是Unsafe。
3、Inbound事件的处理者是Channel,如果用户没有实现自定义的处理方法,那么Inbound事件默认的处理这是TailContext,并且其处理方法是空实现。
4、Inbound事件在Pipeline中的传输方法是从Head到Tail。
5、在ChannelHandler中处理事件时,如果这个Handler不是最后一个Handler,则需要调用ctx.fireIN_EVT()方法将此事件继续传播下去,如果不这样做,那么此事件的传播会提前终止。
6、Inbound事件的传播方向是,从Context.fireIN_EVT()方法到Connect.findContextInbound()方法,再到nextContext.invokeIN_EVT()方法,再到nextHandler.IN_EVT方法,最后到nextContext.fireIN_EVT方法。