ChannelHandler中异常的获取与处理是通过继承重写exceptionCaught方法来实现的,本篇文章我们对ChannelPipeline中exceptionCaught异常事件的传播进行梳理分析

1、出站事件的传播示例

首先我们继续在之前的代码上进行改造,模拟异常事件的传播

public class ServerApp {
public static void main(String[] args) {
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup work = new NioEventLoopGroup(2);
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.childOption(ChannelOption.SO_SNDBUF,2);
bootstrap.group(boss, work).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
// p.addLast(new LoggingHandler(LogLevel.INFO));
// 向ChannelPipeline中添加自定义channelHandler
p.addLast(new OutHandlerA());
p.addLast(new ServerHandlerA());
p.addLast(new ServerHandlerB());
p.addLast(new ServerHandlerC());
p.addLast(new OutHandlerB());
p.addLast(new OutHandlerC()); }
});
bootstrap.bind(8050).sync(); } catch (Exception e) {
// TODO: handle exception
} } } public class OutHandlerA extends ChannelOutboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
System.err.println(this.getClass().getName()+"---"+cause.getMessage());
ctx.fireExceptionCaught(cause);
}
} public class OutHandlerB extends ChannelOutboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
System.err.println(this.getClass().getName()+"---"+cause.getMessage());
ctx.fireExceptionCaught(cause);
}
} public class OutHandlerC extends ChannelOutboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
System.err.println(this.getClass().getName()+"---"+cause.getMessage());
ctx.fireExceptionCaught(cause);
}
} public class ServerHandlerB extends ChannelInboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
System.err.println(this.getClass().getName()+"---"+cause.getMessage());
ctx.fireExceptionCaught(cause);
}
} public class ServerHandlerC extends ChannelInboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
System.err.println(this.getClass().getName()+"---"+cause.getMessage());
ctx.fireExceptionCaught(cause);
}
}

然后我们在ServerHandlerA的channelRead方法中执行ctx的write方法,模拟异常事件的发生。

    @Override
public void channelRead(ChannelHandlerContext ctx, Object object) {
ctx.fireExceptionCaught(new Throwable("出现异常"));
//ctx.pipeline().fireExceptionCaught(new Throwable("出现异常")); }

我们首先看下运行结果

ctx.fireExceptionCaught

io.netty.example.echo.my.ServerHandlerB---出现异常
io.netty.example.echo.my.ServerHandlerC---出现异常
io.netty.example.echo.my.OutHandlerB---出现异常
io.netty.example.echo.my.OutHandlerC---出现异常
18:34:17.147 [nioEventLoopGroup-3-1] WARN i.n.channel.DefaultChannelPipeline - 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.
java.lang.Throwable: 出现异常
at io.netty.example.echo.my.ServerHandlerA.channelRead(ServerHandlerA.java:39)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:338)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1424)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:944)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:709)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:639)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:553)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:510)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:912)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)

ctx.pipeline().fireExceptionCaught

io.netty.example.echo.my.OutHandlerA---出现异常
io.netty.example.echo.my.ServerHandlerA---出现异常
io.netty.example.echo.my.ServerHandlerB---出现异常
io.netty.example.echo.my.ServerHandlerC---出现异常
io.netty.example.echo.my.OutHandlerB---出现异常
io.netty.example.echo.my.OutHandlerC---出现异常
20:08:53.723 [nioEventLoopGroup-3-1] WARN i.n.channel.DefaultChannelPipeline - 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.
java.lang.Throwable: 出现异常
at io.netty.example.echo.my.ServerHandlerA.channelRead(ServerHandlerA.java:40)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:338)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1424)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:944)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:709)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:639)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:553)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:510)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:912)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)

根据输出结果可以看出ctx.fireExceptionCaught 会从异常产生的ChannelHandler一直往后传播到tail尾节点,ctx.pipeline().fireExceptionCaught会从管道中第一个节点一直往后传播到tail尾节点,而上面结果中打印的异常信息则是在TailContext尾节点中统一处理的。

2、异常事件传播的分析

ctx.pipeline().fireExceptionCaught与ctx.fireExceptionCaught两种传播异常方法

前者调用的是DefaultChannelPipeline 的 fireExceptionCaught方法

    @Override
public final ChannelPipeline fireExceptionCaught(Throwable cause) {
AbstractChannelHandlerContext.invokeExceptionCaught(head, cause);
return this;
}

后者调用的是AbstractChannelHandlerContext 的 fireExceptionCaught方法

    @Override
public ChannelHandlerContext fireExceptionCaught(final Throwable cause) {
invokeExceptionCaught(next, cause);
return this;
}

可以看到DefaultChannelPipeline的fireExceptionCaught方法中默认传入了head头部节点,所以ctx.pipeline().fireExceptionCaught会从管道中第一个节点开始向后传播。

我们进入invokeExceptionCaught方法内部看下具体实现

    static void invokeExceptionCaught(final AbstractChannelHandlerContext next, final Throwable cause) {
ObjectUtil.checkNotNull(cause, "cause");//检查异常是否为空
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {//判断是否与当前线程一直
next.invokeExceptionCaught(cause);//触发回调,触发下一个AbstractChannelHandlerContext节点中handler的异常处理事件
} else {
try {
executor.execute(new Runnable() {//如果线程不一致,由其绑定的executor执行
@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);
}
}
}

invokeExceptionCaught方法内部实现

    private void invokeExceptionCaught(final Throwable cause) {
if (invokeHandler()) {//判断当前handler的状态
try {
handler().exceptionCaught(this, cause);//调用exceptionCaught方法实现
} 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);
}
}

3、异常处理机制的设计

通过上面的分析我们可以看到如果通过ctx.fireExceptionCaught一直向后传递异常事件,最终会触发尾节点的exceptionCaught事件打印异常日志;

        @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);
}
}

在实际项目中我们可以在ChannelPipeline尾部增加一个异常处理handle用来统一处理异常信息;

        public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
// p.addLast(new LoggingHandler(LogLevel.INFO));
// 向ChannelPipeline中添加自定义channelHandler
p.addLast(new OutHandlerA());
p.addLast(new ServerHandlerA());
p.addLast(new ServerHandlerB());
p.addLast(new ServerHandlerC());
p.addLast(new OutHandlerB());
p.addLast(new OutHandlerC());
p.addLast(new ExceptionHandler()); }

通过以上三点内容我们对异常信息在ChannelPipeline中的传播进行了模拟,梳理事件的传播流程以及应该怎样统一处理异常信息,其中如有不足与不正确的地方还望指出与海涵。

关注微信公众号,查看更多技术文章。

Netty源码分析之ChannelPipeline—异常事件的传播的更多相关文章

  1. 【Netty源码分析】ChannelPipeline(二)

    在上一篇博客[Netty源码学习]ChannelPipeline(一)中我们只是大体介绍了ChannelPipeline相关的知识,其实介绍的并不详细,接下来我们详细介绍一下ChannelPipeli ...

  2. Netty源码分析之ChannelPipeline—入站事件的传播

    之前的文章中我们说过ChannelPipeline作为Netty中的数据管道,负责传递Channel中消息的事件传播,事件的传播分为入站和出站两个方向,分别通知ChannelInboundHandle ...

  3. Netty源码分析之ChannelPipeline—出站事件的传播

    上篇文章中我们梳理了ChannelPipeline中入站事件的传播,这篇文章中我们看下出站事件的传播,也就是ChannelOutboundHandler接口的实现. 1.出站事件的传播示例 我们对上篇 ...

  4. Netty源码分析之ChannelPipeline(二)—ChannelHandler的添加与删除

    上篇文章中,我们对Netty中ChannelPipeline的构造与初始化进行了分析与总结,本篇文章我们将对ChannelHandler的添加与删除操作进行具体的的代码分析: 一.ChannelHan ...

  5. [编织消息框架][netty源码分析]6 ChannelPipeline 实现类DefaultChannelPipeline职责与实现

    ChannelPipeline 负责channel数据进出处理,如数据编解码等.采用拦截思想设计,经过A handler处理后接着交给next handler ChannelPipeline 并不是直 ...

  6. Netty源码分析之ChannelPipeline(一)—ChannelPipeline的构造与初始化

    Netty中ChannelPipeline实际上类似与一条数据管道,负责传递Channel中读取的消息,它本质上是基于责任链模式的设计与实现,无论是IO事件的拦截器,还是用户自定义的ChannelHa ...

  7. netty源码分析系列文章

    netty源码分析系列文章 nettynetty源码阅读netty源码分析  想在年终之际将对netty研究的笔记记录下来,先看netty3,然后有时间了再写netty4的,希望对大家有所帮助,这个是 ...

  8. Netty源码分析第4章(pipeline)---->第6节: 传播异常事件

    Netty源码分析第四章: pipeline 第6节: 传播异常事件 讲完了inbound事件和outbound事件的传输流程, 这一小节剖析异常事件的传输流程 首先我们看一个最最简单的异常处理的场景 ...

  9. Netty源码分析第4章(pipeline)---->第4节: 传播inbound事件

    Netty源码分析第四章: pipeline 第四节: 传播inbound事件 有关于inbound事件, 在概述中做过简单的介绍, 就是以自己为基准, 流向自己的事件, 比如最常见的channelR ...

随机推荐

  1. 武林高手?敏捷开发,唯velocity取胜

    去年学习软件工程教材,初识敏捷开发.目前所在的团队,也是使用敏捷开发方式.今天读了这篇文章,对于这段时间的效率有所警醒.其次,个人认同文章中的用户故事,结对编程思想. 文章援引:http://gitb ...

  2. [vijos1159&洛谷1494]岳麓山上打水<迭代深搜>

    题目链接:https://vijos.org/p/1159 https://www.luogu.org/problem/show?pid=1494 这是今天的第三道迭代深搜的题,虽然都是迭代深搜的模板 ...

  3. thinkphp5.1中使用链式操作的坑

    1.在进行tp5->tp5.1的时候,没有想太多,直接使用之前的代码:结果在该操作中,多次调用该get方法,tp5.1的链式操作一直保持了之前的搜索条件,截图如下:(具体的代码没有展示) 2.然 ...

  4. 面试中常问的五种IO模型和BIO,NIO,AIO

    一,五种IO模型: 一个IO操作可以分为两个步骤:发起IO请求和实际的IO操作例如:1.操作系统的一次写操作分为两步:第一步,将数据从用户空间拷贝到系统空间:第二步,从系统空间往网卡写.2.一次读操作 ...

  5. H - 覆盖的面积(线段树-线段扫描 + 离散化(板题))

    给定平面上若干矩形,求出被这些矩形覆盖过至少两次的区域的面积. Input 输入数据的第一行是一个正整数T(1<=T<=100),代表测试数据的数量.每个测试数据的第一行是一个正整数N(1 ...

  6. PyJWT 详解

    1.首先,我们需要先了解 JWT 的概念,所以我们先看pyjwt的官网 https://jwt.io/ 2.对于官方 JWT 有两篇博文写的不错分别如下: https://blog.csdn.net/ ...

  7. Linux 压缩备分篇(一 备份数据)

    备份文件                dump dump: -S                    仅列出待备份数据需要多少磁盘空间才能够备份完毕 -u                    将 ...

  8. [go]map基本使用和底层原理

    1.map基本使用 map声明 var m4 map[int]int //只是声明 没有开辟空间 m4[1]=100 //报错 log.Println(m4) 创建 //1 m3:=make(map[ ...

  9. Struts2-学习笔记系列(7)-PreResultListener

    在action处理完成之后,系统转入实际的物理试图之间被回调. Action,拦截器都可以添加该监听器.拦截器添加了该监听器后,该监听器会对该拦截器所有拦截的action其作用 public Stri ...

  10. Python设计模式(2)-策略模式

    # 策略模式和简单工厂模式相比,少了使用switch case 做判断,然后去实例化相应的 # 对象,比简单工厂模式更灵活. 它们代码的区别就在于此处使用了抽象类代替工厂类 # coding=utf- ...