前言
在上一篇博客中我们介绍了Pipeline数据结构以及节点的相关操作,在这一篇博客中将介绍Pipeline的事件传播
GitHub地址:https://github.com/RobertoHuang
事件传播
inBound事件传播
我们回顾一下Netty新连接接入的过程,在三次握手成功后新连接会调用pipeline.fireChannelActive方法,并且最后是会调用到AbstractNioChannel的doBeginRead方法对读事件进行绑定。所以我们以fireChannelActive方法为例讲述inBound事件传播
public final ChannelPipeline fireChannelActive() {
AbstractChannelHandlerContext.invokeChannelActive(head);
return this;
}
在上诉代码中将Head节点传入,因此我们可以猜测出pipeline的inBound事件传播是从Head节点开始的
static void invokeChannelActive(final AbstractChannelHandlerContext next) {
// ...
next.invokeChannelActive();
// ...
}
我们知道此处的next其实就是HeadContext节点,所以我们跟进HeadContext.fireChannelActive()
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelActive();
readIfIsAutoRead();
}
首先调用fireChannelActive将事件进行向下传播,然后调用readIfIsAutoRead方法(Head节点的readIfIsAutoRead方法即新连接接入对读事件进行绑定的入口),关于readIfIsAutoRead在此前已介绍过并且不是本博客的重点,所以接下来我们详细分析一下ctx.fireChannelActive()
public ChannelHandlerContext fireChannelActive() {
invokeChannelActive(findContextInbound());
return this;
}
首先调用findContextInbound()找到下一个Inbound节点,然后再将事件继续向下传播
private AbstractChannelHandlerContext findContextInbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.next;
} while (!ctx.inbound);
return ctx;
}
Netty寻找下一个inBound节点的过程是一个线性搜索的过程,他会遍历双向链表的下一个节点直到下一个节点为inBound。正常情况下如果用户不覆盖每个节点的事件传播操作,或者覆盖后手动将事件继续向下传播,Netty会一直寻找下一个节点并执行invokeChannelActive(next)递归调用,直到最后一个inBound节点Tail节点。即Tail节点是inBound事件的终点
final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler {
TailContext(DefaultChannelPipeline pipeline) {
super(pipeline, null, TAIL_NAME, true, false);
setAddComplete();
}
@Override
public ChannelHandler handler() {
return this;
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception { }
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { }
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception { }
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception { }
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { }
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception { }
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { }
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
ReferenceCountUtil.release(evt);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
onUnhandledInboundException(cause);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
onUnhandledInboundMessage(msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { }
}
从Tail节点源码可以看到Tail节点的大部分方法为空实现即会终止事件的传播,但是exceptionCaught()和channelRead()比较特殊,如果异常在Tail节点还没有被处理或者消息在Tail节点还没被处理,他们会触发相应的警告
onUnhandledInboundException
protected void onUnhandledInboundException(Throwable cause) {
try {
logger.warn("An exceptionCaught() event was fired, and it reached at the tail of the pipeline. " + "It usually means the last handler in the pipeline did not handle the exception.", cause);
} finally {
ReferenceCountUtil.release(cause);
}
}
onUnhandledInboundMessage
protected void onUnhandledInboundMessage(Object msg) {
try {
logger.debug("Discarded inbound message {} that reached at the tail of the pipeline. " + "Please check your pipeline configuration.", msg);
} finally {
ReferenceCountUtil.release(msg);
}
}
相信刚开始使用Netty进行开发的时候对以上警告不会陌生吧,至此Netty的inBound事件传播就结束了,其他fireXXX同理
outBound事件传播
在使用Netty写数据的时候我们经常会使用到channel.writeAndFlush()它就是一个典型的outBound事件
channel.writeAndFlush(pushInfo);
public ChannelFuture writeAndFlush(Object msg) {
return pipeline.writeAndFlush(msg);
}
public final ChannelFuture writeAndFlush(Object msg) {
return tail.writeAndFlush(msg);
}
从以上代码我们可以猜测到pipeline的outBound事件是从Tail节点开始传播的,接下来我们跟进Tail节点的writeAndFlush方法
public ChannelFuture writeAndFlush(Object msg) {
return writeAndFlush(msg, newPromise());
}
public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
// ...
write(msg, true, promise);
return promise;
}
private void write(Object msg, boolean flush, ChannelPromise promise) {
AbstractChannelHandlerContext next = findContextOutbound();
final Object m = pipeline.touch(msg, next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
if (flush) {
next.invokeWriteAndFlush(m, promise);
} else {
next.invokeWrite(m, promise);
}
} else {
AbstractWriteTask task;
if (flush) {
task = WriteAndFlushTask.newInstance(next, m, promise);
} else {
task = WriteTask.newInstance(next, m, promise);
}
safeExecute(executor, task, promise, m);
}
}
同inBound事件传播类似,outBound事件是先找到pipeline中的前一个节点,然后再将事件继续向前传播
private AbstractChannelHandlerContext findContextOutbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.prev;
} while (!ctx.outbound);
return ctx;
}
寻找outBound节点的过程和找inBound节点类似,反方向遍历pipeline中的双向链表直到第一个outBound节点next,然后递归调用next.invokeWriteAndFlush(m, promise)将事件像前传播,invokeWriteAndFlush代码如下
private void invokeWriteAndFlush(Object msg, ChannelPromise promise) {
if (invokeHandler()) {
invokeWrite0(msg, promise);
invokeFlush0();
} else {
writeAndFlush(msg, promise);
}
}
接着看invokeWrite0方法
private void invokeWrite0(Object msg, ChannelPromise promise) {
try {
((ChannelOutboundHandler) handler()).write(this, msg, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
}
我们在使用outBound类型的ChannelHandler过程中一般不会自己重写write方法(关于重写write方法的用法将在后续编解码源码解析章节中进行介绍),所以此处的write方法是调用了父类ChannelOutboundHandlerAdapter的write方法
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ctx.write(msg, promise);
}
在ChannelOutboundHandlerAdapter方法递归调用ctx.write(msg, promise)之外啥事也没干,我们知道pipeline的双向链表结构中最后一个outBound节点是Head节点,因此数据最终会透传到Head节点的write方法中
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
unsafe.write(msg, promise);
}
Head节点的write方法是通过unsafe对象对数据进行写入,关于Unsafe此前一直没详细介绍过,Unsafe是不安全的意思,Netty这么命名就是告诉你不要在应用程序里面直接使用Unsafe以及他的衍生类对象。我们只要把Unsafe想象成是Netty提供的工具类,它负责Socket的连接和关闭,Socket的读写等操作,这些操作都是和JDK底层相关
异常处理
我们通常在业务代码中会加入一个异常处理器统一处理pipeline过程中的所有的异常,并且一般该异常处理器需要加载自定义节点的最末尾,数据结构如下图所示
inBound异常的处理
我们仍然以invokeChannelActive为例讲解inBound异常的处理,invokeChannelActive完整的代码如下
private void invokeChannelActive() {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelActive(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelActive();
}
}
当向下传播Active事件出现异常时,Netty会调用notifyHandlerException(t)方法通知异常处理器
private void notifyHandlerException(Throwable cause) {
// ...
invokeExceptionCaught(cause);
}
private void invokeExceptionCaught(final Throwable cause) {
// ...
handler().exceptionCaught(this, cause);
// ...
}
Hander中异常优先由此Handelr中的exceptionCaught方法来处理,默认情况下如果不覆写此Handler中的exceptionCaught方法将调用ChannelInboundHandlerAdapter的exceptionCaught
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.fireExceptionCaught(cause);
}
接下来就是一个常规的inBound的事件传播过程,如果我们在自定义Handler中没有处理异常那么默认情况下该异常将一直传递下去,遍历每一个节点直到最后一个自定义异常处理器ExceptionHandler来终结
public Exceptionhandler extends ChannelDuplexHandler {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 处理该异常,并终止异常的传播
}
}
outBound异常的处理
从以上内容我们可以了解到异常的传播是一个inBound事件,那outBound事件异常为什么最后也会到异常处理器中处理呢?我们仍然以writeAndFlush方法为例讲解outBound异常的处理的过程
private void invokeWriteAndFlush(Object msg, ChannelPromise promise) {
if (invokeHandler()) {
invokeWrite0(msg, promise);
invokeFlush0();
} else {
writeAndFlush(msg, promise);
}
}
channel.writeAndFlush()方法最终也会调用到节点的invokeFlush0()方法
private void invokeFlush0() {
try {
((ChannelOutboundHandler) handler()).flush(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
}
假设在当前节点在flush的过程中发生了异常会调用notifyHandlerException(t)将异常进行向下传播,接下来的流程就和inBound异常的处理流程是一样的,这也就是为什么该异常处理器也能处理outBound异常的原因