深入了解Netty之ChannelHandler与Pipeline(六)

本文深入探讨了Netty中异常的传播机制,指出异常在发生后会沿着ChannelHandler的Pipeline,不论入站还是出站处理器,一直向下一个节点传播,直到被处理或到达tail节点。通过示例分析了异常从抛出到传播的详细过程,强调了在Pipeline中定义专门的异常处理器的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2021SC@SDUSC


异常的传播

在Netty中如果发生了异常,那么异常事件的传播和当前的节点是 入站处理器还是出站处理器是无关的,异常会一直往下一个节点传播,如果一直没有handler处理异常,最后会由tail节点处理

为了说明这一点,看一个例子:

public class InBoundHandlerA extends ChannelInboundHandlerAdapter {
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
        System.out.println("InBoundHandlerA.exceptionCaught()");
        ctx.fireExceptionCaught(cause);
    }
}
public class InBoundHandlerB extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception{
        throw new Exception("from inboundHandlerB");
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
        System.out.println("InBoundHandlerB.exceptionCaught()");
        ctx.fireExceptionCaught(cause);
    }
}
public class InBoundHandlerC extends ChannelInboundHandlerAdapter {
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
        System.out.println("InBoundHandlerC.exceptionCaught()");
        ctx.fireExceptionCaught(cause);
    }
}
public class OutBoundHandlerA extends ChannelOutboundHandlerAdapter {
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
        System.out.println("OutBoundHandlerA.exceptionCaught()");
        ctx.fireExceptionCaught(cause);
    }
}
public class OutBoundHandlerB extends ChannelOutboundHandlerAdapter {
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
        System.out.println("OutBoundHandlerB.exceptionCaught()");
        ctx.fireExceptionCaught(cause);
    }
}
public class OutBoundHandlerC extends ChannelOutboundHandlerAdapter {
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
        System.out.println("OutBoundHandlerC.exceptionCaught()");
        ctx.fireExceptionCaught(cause);
    }
}

创建三个InBoundHandler和三个OutBoundHandler,并且都重写了exceptionCaught()方法,这个方法在异常传播到当前节点的时候被调用。另外,inBoundB还重写了channelRead()方法,当有read事件进来的时候会抛出一个异常来模拟异常事件的发生。

启动类如下:

public class MyServer {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup parentGroup = new NioEventLoopGroup();
        EventLoopGroup childGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(parentGroup, childGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new ChannelInboundHandler1());
                            pipeline.addLast(new ChannelInboundHandler2());  // 异常抛出
                            pipeline.addLast(new OutBoundHandlerA());
                            pipeline.addLast(new OutBoundHandlerB());
                            pipeline.addLast(new OutBoundHandlerC());                     
                        }
                    });
            ChannelFuture future = bootstrap.bind(8888).sync();
            System.out.println("服务器已启动");
            future.channel().closeFuture().sync();
        } finally {
            parentGroup.shutdownGracefully();
            childGroup.shutdownGracefully();
        }
    }
}

启动服务端后,向服务端发送消息,出现异常后,打印情况如下:

InBoundHandlerB.exceptionCaught()
InBoundHandlerC.exceptionCaught()
OutBoundHandlerA.exceptionCaught()
OutBoundHandlerB.exceptionCaught()
OutBoundHandlerC.exceptionCaught()

可以看到,异常在传递的时候是不分Inbound、Outbound,而是从发生异常的节点往后传递

下面来看一下异常是怎么往后传递的:

从抛出异常的地方InBoundHandlerB.channelRead() 开始分析:

public class InBoundHandlerB extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception{
        throw new Exception("from inboundHandlerB");
    }
}

抛出异常后,被捕捉的地方在AbstractChannelHandlerContext类的invokeChannelRead()方法里:

private void invokeChannelRead(Object msg) {
        if (invokeHandler()) {  
            try {
                ((ChannelInboundHandler) handler()).channelRead(this, msg);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            fireChannelRead(msg);
        }
    }

当前节点执行channelRead方法时抛异常了,所以跳到了catch部分,执行了notifyHandlerException ()方法:

 private void notifyHandlerException(Throwable cause) {
        if (inExceptionCaught(cause)) {
            if (logger.isWarnEnabled()) {
                logger.warn(
                        "An exception was thrown by a user handler " +
                                "while handling an exceptionCaught event", cause);
            }
            return;
        }       
        invokeExceptionCaught(cause);
    }

最后一句 invokeExceptionCaught(cause); 调用当前发生异常的节点处理器的exceptionCaught()(当前节点是B):

 private void invokeExceptionCaught(final Throwable cause) {
        if (invokeHandler()) {
            try {
                //handler()返回当前节点
                handler().exceptionCaught(this, cause);
            } catch (Throwable error) {
                if (logger.isDebugEnabled()) {
                    logger.debug(
                        "An exception {}" +
                        "was thrown by a user handler's exceptionCaught() " +
                        "method while handling the following exception:",
                        ThrowableUtil.stackTraceToString(error), cause);
                } else if (logger.isWarnEnabled()) {
                    logger.warn(
                        "An exception '{}' [enable DEBUG level for full stacktrace] " +
                        "was thrown by a user handler's exceptionCaught() " +
                        "method while handling the following exception:", error, cause);
                }
            }
        } else {
            fireExceptionCaught(cause);
        }
    }
}

当前节点的exceptionCaught()就是上面我们自定义的方法:

public class InBoundHandlerB extends ChannelInboundHandlerAdapter {
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
        System.out.println("InBoundHandlerB.exceptionCaught()");
        ctx.fireExceptionCaught(cause);
    }
}

这个方法执行了 ctx.fireExceptionCaught(cause); 这个方法是AbstractChannelHandlerContext类中的一个方法:

 public ChannelHandlerContext fireExceptionCaught(final Throwable cause) {
	    // 此时会查询出ChannelInboundHandler3节点
        // 调用指定节点的exceptionCaught()
        invokeExceptionCaught(findContextInbound(MASK_EXCEPTION_CAUGHT), cause);
        return this;
    }

findContextInbound是从当前节点的后面进行查询,查询到节点C,然后invokeExceptionCaught()调用指定节点(也就是节点C)的exceptionCaught():

static void invokeExceptionCaught(final AbstractChannelHandlerContext next, final Throwable cause) {
        ObjectUtil.checkNotNull(cause, "cause");
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeExceptionCaught(cause);
        } else {
            try {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        next.invokeExceptionCaught(cause);
                    }
                });
            } catch (Throwable t) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Failed to submit an exceptionCaught() event.", t);
                    logger.warn("The exceptionCaught() event that was failed to submit was:", cause);
                }
            }
        }
    }
}

这里跟节点B的逻辑是一样的,按照这个逻辑,异常事件以inBoundHandlerB-> inBoundHandlerC-> outBoundHandlerA-> outBoundHandlerB-> outBoundHandlerC->tail的顺序进行传播,即根据pipeline中ChannelHandler的顺序从当前节点向后依次传递事件。当异常传播到tail节点后,会执行到tail节点的exceptionCaught:

final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler {
	@Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        onUnhandledInboundException(cause);
    }
    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);
        }
    }
}

logger.warn()会打印出一个警告信息:异常传递到pipline尾部,通常意味着最后一个处理器都没有处理这个异常,然后ReferenceCountUtil.release(cause);释放异常

当然,我们可以定义一个专门的异常处理器,放到pipline链的最后(也就是tail之前):

public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
      //在pipeline的最后添加异常处理handler
      channelPipeline.addLast(new ExceptionCaughtHandler());
    }
}

最终全部的异常都会来到这里:

public class ExceptionCaughtHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        if (cause instanceof 自定义异常1){
    
        }else if(cause instanceof  自定义异常2){

        }
        // 不再向下传递了
        // ctx.fireExceptionCaught(cause);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值