如何在Netty中捕获所有异常

15
据我所知,Netty通过重写exceptionCaught()方法来处理异常。但是我想要一个可以处理所有输入输出异常的Handler。因此,管道应该如下所示:
InboundExceptionHandler - inboundHandler1 - inboundHandler2 - outboundHandler1 - outboundHandler2 - OutboundExceptionHandler 这意味着我应该在我的管道中放置2个异常处理程序,分别位于头部和尾部。但我认为它看起来很丑陋。有更好的想法吗?
2个回答

26

你可以在你的管道的顶部/尾部只设置单个的入站和出站异常处理程序。如果你想要捕获所有异常,你可以采取以下措施(我假设这是Netty 4.0):

import io.netty.channel.*;

import java.net.SocketAddress;

public class ExceptionHandler extends ChannelDuplexHandler {
    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // Uncaught exceptions from inbound handlers will propagate up to this handler 
    }

    @Override
    public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
        ctx.connect(remoteAddress, localAddress, promise.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) {
                if (!future.isSuccess()) {
                    // Handle connect exception here...
                    Throwable failureCause = future.cause();
                }
            }
        }));
    }

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
        ctx.write(msg, promise.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) {
                if (!future.isSuccess()) {
                    // Handle write exception here...
                    Throwable failureCause = future.cause();
                }
            }
        }));
    }
    
    // ... override more outbound methods to handle their exceptions as well
}

所有由入站处理程序引发的异常都将向上传播并调用此处理程序的 exceptionCaught() 方法,假设下面没有处理程序来消耗它们。

对于像 write()connect() 这样的出站操作,您需要添加一个 ChannelFutureListener 来捕获它们的异常。 exceptionCaught() 方法仅针对入站事件(例如 channelRead()channelActive() 等)中的异常被调用。

这个处理程序位于管道的顶部,我们可以捕获下面所有出站处理程序的异常。假设您的某个出站处理程序正在进行编码,但由于发生异常导致失败,那么这将由我们添加到 write() 操作承诺中的通道未来监听器处理。

如果将此异常处理程序安装在管道的“底部”或头部,就如您最初建议的那样,那么它将无法看到来自其上方处理程序的异常,因为如果写入在前一个处理程序失败,那么它的 write() 方法将永远不会被调用。这就是为什么必须将此处理程序放置在顶部的原因。

为了避免关于管道顶部/底部的混淆,这是我如何配置您的示例管道:

pipeline.addLast(outboundHandler2)        // bottom
        .addLast(outboundHandler1)
        .addLast(inboundHandler2)
        .addLast(inboundHandler1)
        .addLast(new ExceptionHandler()); // top

这不能与write的voidPromise一起使用,也不能以最小化不必要的对象创建的方式使用。您能详细说明一下优化版本吗? - Martin Asenov
如果您正在使用voidPromise进行写操作,则无法调用addListener(),但是任何出站写入异常都将传播到exceptionCaught(),因此仍然可以在那里处理它们。在4.1上,您可以调用promise.isVoid()来处理任一情况。为了最小化对象创建,我上面使用的ChannelFutureListener匿名类可以声明为实例成员,以便它们每个处理程序只被创建一次。 - Ben Evans
嗨Ben,感谢你的回答。我理解了入站部分,但对出站部分感到困惑。假设有一条消息通过管道传输。inboundHandler1接收到消息后,通过调用ctx.write()进行响应。由于ExceptionHandler从未触及此消息,那么它的write()方法将如何被调用以设置承诺回调?我想我可能漏掉了什么... - Dmitry Minkovsky
你是正确的,如果在处理入站消息时从另一个处理程序调用了 ctx.write()ExceptionHandler 将不会知道它。你有几个选项:
  • 创建一个 ChannelPromise (ctx.newPromise()) 并向其中添加自己的监听器,以处理当前 ChannelHandler 中的异常。
  • 使用 ctx.write(message, ctx.voidPromise()) - 如果可以使用 void promises 安全地使用 - 那么任何写入异常都将传播到 ExceptionHandlerexceptionCaught 方法中。
- Ben Evans
你的出站异常处理程序无法捕获所有异常。只有在它上面的所有(出站)处理程序都将承诺传递下去时,它才会被通知。我已经编写了几个测试来记录这种行为,链接在这里:https://github.com/fatso83/test-driven-learning/commit/332cea2d07fc5ca47ebd568eaa8ae14f12113ef5 - oligofren
显示剩余2条评论

0
最终的解决方案是自定义ChannelInitializer,您甚至可以添加更多的逻辑。

1
你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接