死磕Netty源码之ChannelPipeline源码解析(二)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/RobertoHuang/article/details/81042457

前言

在上一篇博客中我们介绍了Pipeline数据结构以及节点的相关操作,在这一篇博客中将介绍Pipeline的事件传播

关于我:http://huangth.com

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过程中的所有的异常,并且一般该异常处理器需要加载自定义节点的最末尾,数据结构如下图所示
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异常的原因

猜你喜欢

转载自blog.csdn.net/RobertoHuang/article/details/81042457
今日推荐