异常处理
摘要:异常处理在任何系统中都是重要的组成部分,Netty 的异常处理是通过在 ChannelHandler 中重写 exceptionCaught 方法来实现,这篇文章聚焦于此。
Netty 版本:4.1.70.Final
一、异常处理方式
1、捕获异常处理
1 public static class InboundHandler extends ChannelInboundHandlerAdapter {
2 @Override
3 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
4 // 处理异常
5 System.err.println(this.getClass().getSimpleName() + " ---- " + cause.getMessage());
6 // 向下一个handler传递异常
7 ctx.fireExceptionCaught(cause);
8 }
9 }
P.S. 传递异常将从下个 handler 一直往后传递,不会跳过 OutboundHandler。
2、通过监听 Funture、Promise 处理
1)在调研write
、writeAndFlush
可以获得得到 ChannelFeature,进而可以监听执行结果是否成功、异常。通常在 InboundHandler 中使用。
1 ctx.executor().schedule(()->{
2 channelFuture.addListener(new ChannelFutureListener() {
3 @Override
4 public void operationComplete(ChannelFuture future) throws Exception {
5 System.out.println("writeAndFlush done.");
6 if(future.isSuccess()){
7 System.out.println("send success.");
8 }else{
9 // 处理异常
10 System.out.println("send fail!");
11 }
12 }
13 });
14 }, 0, TimeUnit.SECONDS);
2)在 OutboundHandler 中,write 方法的入参会带有个Promise
参数,通过 Promise 对象可以处理异常,处理后将通知之前的 handler。使用时通过setSuccess
、setFailure
设置。
三、源码分析
1、是谁触发 exceptionCaught
方法
主要是 AbstractChannelhandlerContext#invokeExceptionCaught 方法。
1 private void invokeExceptionCaught(final Throwable cause) {
2 if (invokeHandler()) {
3 try {
4 //
5 handler().exceptionCaught(this, cause);
6 } catch (Throwable error) {
7 if (logger.isDebugEnabled()) {
8 logger.debug(
9 "An exception {}" +
10 "was thrown by a user handler's exceptionCaught() " +
11 "method while handling the following exception:",
12 ThrowableUtil.stackTraceToString(error), cause);
13 } else if (logger.isWarnEnabled()) {
14 logger.warn(
15 "An exception '{}' [enable DEBUG level for full stacktrace] " +
16 "was thrown by a user handler's exceptionCaught() " +
17 "method while handling the following exception:", error, cause);
18 }
19 }
20 } else {
21 fireExceptionCaught(cause);
22 }
23 }
当 channel 触发事件调用 invokeChannelActive、invokeChannelRead 过程中出现异常调用 invokeExceptionCaught。
1 private void invokeChannelActive() {
2 if (invokeHandler()) {
3 try {
4 ((ChannelInboundHandler) handler()).channelInactive(this);
5 } catch (Throwable t) {
6 // 处理异常
7 invokeExceptionCaught(t);
8 }
9 } else {
10 fireChannelInactive();
11 }
12 }
13
14 private void invokeChannelRead(Object msg) {
15 if (invokeHandler()) {
16 try {
17 ((ChannelInboundHandler) handler()).channelRead(this, msg);
18 } catch (Throwable t) {
19 // 处理异常
20 invokeExceptionCaught(t);
21 }
22 } else {
23 fireChannelRead(msg);
24 }
25 }
2、如果我们创建的 Handler 不处理异常,那么会由 Pipeline 中名为tail
的 ChannelHandlerContext 来捕获异常并打印。
可以看到 DefaultChannelPipeline 中有两个内部类:TailContext、HeadContext,最后的异常就是被 TailContext 的实力tail
处理的,可以看到 onUnhandledInboundException
方法中使用 warn
级别输出了异常信息。
1 // A special catch-all handler that handles both bytes and messages.
2 final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler {
3
4 TailContext(DefaultChannelPipeline pipeline) {
5 super(pipeline, null, TAIL_NAME, TailContext.class);
6 setAddComplete();
7 }
8
9 @Override
10 public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
11 onUnhandledInboundUserEventTriggered(evt);
12 }
13
14 @Override
15 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
16 // 处理最后的异常,方法如下
17 onUnhandledInboundException(cause);
18 }
19 // 省略...
20 }
21
22 /**
23 * 处理最后的异常
24 * Called once a {@link Throwable} hit the end of the {@link ChannelPipeline} without been handled by the user
25 * in {@link ChannelHandler#exceptionCaught(ChannelHandlerContext, Throwable)}.
26 */
27 protected void onUnhandledInboundException(Throwable cause) {
28 try {
29 logger.warn(
30 "An exceptionCaught() event was fired, and it reached at the tail of the pipeline. " +
31 "It usually means the last handler in the pipeline did not handle the exception.",
32 cause);
33 } finally {
34 ReferenceCountUtil.release(cause);
35 }
36 }
37
38 final class HeadContext extends AbstractChannelHandlerContext
39 implements ChannelOutboundHandler, ChannelInboundHandler {
40
41 private final Unsafe unsafe;
42
43 HeadContext(DefaultChannelPipeline pipeline) {
44 super(pipeline, null, HEAD_NAME, HeadContext.class);
45 unsafe = pipeline.channel().unsafe();
46 setAddComplete();
47 }
48
49 @Override
50 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
51 ctx.fireExceptionCaught(cause);
52 }
53 // 省略...
54 }
EOF