服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - netty中pipeline异常事件分析

netty中pipeline异常事件分析

2023-04-26 16:26宽仔的编程之路 Java教程

这篇文章主要为大家介绍了netty中pipeline异常事件分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

异常处理的场景

?
1
2
3
4
5
6
7
8
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    throw new Exception("throw Exception");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    System.out.println(cause.getMessage());
}

我们在handlerchannelRead方法中主动抛出异常, 模拟程序中出现异常的场景, 经测试会发现, 程序最终会走到exceptionCaught方法中, 获取异常对象并打印其信息

那么抛出异常之后, 是如何走到exceptionCaught方法的呢?

我们回顾之前小节channelRead事件的传播流程, channelRead方法是在AbstractChannelHandlerContext类的invokeChannelRead方法中被调用

AbstractChannelHandlerContext.invokeChannelRead

?
1
2
3
4
5
6
7
8
9
10
11
12
13
private void invokeChannelRead(Object msg) {
    if (invokeHandler()) {
        try {
            //调用了当前handler的channelRead方法, 其实就是head对象调用自身的channelRead方法
            ((ChannelInboundHandler) handler()).channelRead(this, msg);
        } catch (Throwable t) {
            //发生异常的时候在这里捕获异常
            notifyHandlerException(t);
        }
    } else {
        fireChannelRead(msg);
    }
}

这里不难看出, 当调用户自定义的handlerchannelRead方法发生异常之后, 会被捕获, 并调用notifyHandlerException方法, 并传入异常对象, 也就是我们示例中抛出的异常

AbstractChannelHandlerContext.notifyHandlerException(Throwable cause)

?
1
2
3
4
private void notifyHandlerException(Throwable cause) {
    //代码省略
    invokeExceptionCaught(cause);
}

AbstractChannelHandlerContext.invokeExceptionCaught(final Throwable cause)

?
1
2
3
4
5
6
7
8
9
10
11
12
private void invokeExceptionCaught(final Throwable cause) {
    if (invokeHandler()) {
        try {
            //当前handler调用exceptionCaught()方法
            handler().exceptionCaught(this, cause);
        } catch (Throwable error) {
            //代码省略
        }
    } else {
        fireExceptionCaught(cause);
    }
}

走到这里一切都明白了, 这里调用了当前handlerexceptionCaught方法, 也就是我们重写的exceptionCaught方法

知道了为什么会走到exceptionCaught方法之后, 我们再进行剖析异常事件的传播流程

两种写法

?
1
2
3
4
5
6
7
8
9
10
11
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    System.out.println(cause.getMessage());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    //写法1
    ctx.fireChannelRead(cause);
    //写法2
    ctx.pipeline().fireExceptionCaught(cause);
}

这两种写法我们并不陌生, 可能我们能直接猜到, 第一种写法是从当前节点进行传播, 第二种写法则从头结点或者尾节点进行转播, 那么和传播inbound事件或outbound事件有什么区别呢?我们先以第二种写法为例, 剖析异常事件传输的整个流程

DefualtChannelPipeline.fireExceptionCaught

?
1
2
3
4
public final ChannelPipeline fireExceptionCaught(Throwable cause) {
    AbstractChannelHandlerContext.invokeExceptionCaught(head, cause);
    return this;
}

我们看到invokeExceptionCaught传入了head节点, 我们可以猜测, 异常事件的传播是从head节点开始的

AbstractChannelHandlerContext.invokeExceptionCaught(head, cause)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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) {
            //忽略代码
        }
    }
}

因为这里是传入的是head节点, 所以这里的next指向head节点。

我们跟到invokeExceptionCaught方法中, 这里其实是headContext的父类AbstractChannelHandlerContext中的方法

AbstractChannelHandlerContext.invokeExceptionCaught(cause)

?
1
2
3
4
5
6
7
8
9
10
11
12
private void invokeExceptionCaught(final Throwable cause) {
    if (invokeHandler()) {
        try {
            //当前handler调用exceptionCaught()方法
            handler().exceptionCaught(this, cause);
        } catch (Throwable error) {
            //代码省略
        }
    } else {
        fireExceptionCaught(cause);
    }
}

这里又是我们熟悉的逻辑, 调用当前handlerexceptionCaught方法, 因为当前handlerhead, 所以首先会调用headContextexceptionCaught方法

exceptionCaught方法

?
1
2
3
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    ctx.fireExceptionCaught(cause);
}

这里仅仅是继续传播异常事件, 这时候我们发现, 这个写法和我们刚才提到传播异常事件的两种写法的第一种写法一样

?
1
2
3
4
5
6
7
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    //写法1
    ctx.fireChannelRead(cause);
    //写法2
    ctx.pipeline().fireExceptionCaught(cause);
}

根据我们之前的学习, 我们知道第一种写法是从当前节点传播, 而第二种写法是从头传播, 并且要求传播事件一定要使用第一种写法, 否则事件到这里会重新从头传播进而引发不可预知错误, 这个结论在异常传播同样适用, 同学们一定要注意这点

我们继续跟fireExceptionCaught方法, 这里会走到AbstractChannelHandlerContex类的fireExceptionCaught方法

AbstractChannelHandlerContex.fireExceptionCaught

?
1
2
3
4
5
public ChannelHandlerContext fireExceptionCaught(final Throwable cause) {
    //传播异常事件的时候, 直接拿了当前节点的下一个节点
    invokeExceptionCaught(next, cause);
    return this;
}

这个时候我们发现, 这里并没有去获取下一个的inbound节点还是outbound节点, 而是直接通过next拿到下一个节点, 这就说明在异常事件传播的过程中是不区分inbound事件还是outbound事件的, 都是直接从head节点按照链表结构往下传播

AbstractChannelHandlerContex.invokeExceptionCaught(next, cause)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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) {
            //代码省略
        }
    }
}

这里又是我们熟悉的逻辑, 我们知道invokeExceptionCaught中执行了nextexceptionCaught, 这里的next, 因为我们是从head节点开始剖析的, 所以这里很有可能就是用户自定义的handler, 如果用户没有重写exceptionCaught方法, 则会交给用户handler的父类处理

我们以ChannelInboundHandlerAdapter为例看它的该方法实现

?
1
2
3
4
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
        throws Exception {
    ctx.fireExceptionCaught(cause);
}

我们看到这里继续向下传播了异常事件

走到这里我们会知道, 如果我们没有重写exceptionCaught方法, 异常事件会一直传播到链表的底部, 就是tail节点

我们跟到TailConext的exceptionCaught方法

?
1
2
3
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    onUnhandledInboundException(cause);
}

我们看到最终这里释放了异常对象,以上就是netty中pipeline异常事件分析的详细内容,更多关于netty pipeline异常事件的资料请关注服务器之家其它相关文章!

原文链接:https://juejin.cn/post/7225790525437214777

延伸 · 阅读

精彩推荐
  • Java教程java中文及特殊字符的校验方法

    java中文及特殊字符的校验方法

    这篇文章主要为大家详细介绍了java中文及特殊字符的校验方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    Marydon13562020-12-30
  • Java教程springboot vue接口测试定义编辑功能的实现

    springboot vue接口测试定义编辑功能的实现

    这篇文章主要为大家介绍了springboot vue接口测试定义编辑功能的实现,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪...

    把苹果咬哭的测试笔记5722022-12-26
  • Java教程Java并发编程面试之线程池

    Java并发编程面试之线程池

    线程池和并发问题是Java技术面试中面试官比较喜欢问的问题之一。在这里,从面试的角度列出了大部分重要的问题,感兴趣的小伙伴们可以参考一下...

    蛋挞学姐4252021-10-15
  • Java教程swing分割窗口控件JSplitPane使用方法详解

    swing分割窗口控件JSplitPane使用方法详解

    这篇文章主要为大家详细介绍了swing分割窗口控件JSplitPane的使用方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    WilliamSun01229792021-02-24
  • Java教程java实现摄像头截图功能

    java实现摄像头截图功能

    这篇文章主要为大家详细介绍了java实现摄像头截图功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    penngo6672021-06-04
  • Java教程mybatis 遍历foreach中or拼接的操作

    mybatis 遍历foreach中or拼接的操作

    这篇文章主要介绍了mybatis 遍历foreach中or拼接的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    陌下微尘9322021-08-03
  • Java教程java实现图片的上传与展示实例代码

    java实现图片的上传与展示实例代码

    这篇文章主要给大家介绍了关于java实现图片的上传与展示的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值...

    MXC肖某某10742021-06-18
  • Java教程Java实现简易Web服务器

    Java实现简易Web服务器

    这篇文章主要为大家详细介绍了Java实现简易Web服务器的相关方法,想要制作Web服务器的朋友可以参考本文 ...

    蒋固金5092020-04-02