Netty 中 IOException: Connection reset by peer 与 java.nio.channels.ClosedChannelException: null

最近发现系统中出现了很多 IOException: Connection reset by peer 与 ClosedChannelException: null

深入看了看代码, 做了些测试, 发现 Connection reset 会在客户端不知道 channel 被关闭的情况下, 触发了 eventloop 的 unsafe.read() 操作抛出

而 ClosedChannelException 一般是由 Netty 主动抛出的, 在 AbstractChannel 以及 SSLHandler 里都可以看到 ClosedChannel 相关的代码

AbstractChannel 

static final ClosedChannelException CLOSED_CHANNEL_EXCEPTION = new ClosedChannelException();...static {CLOSED_CHANNEL_EXCEPTION.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);NOT_YET_CONNECTED_EXCEPTION.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);}...@Overridepublic void write(Object msg, ChannelPromise promise) {ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;if (outboundBuffer == null) {// If the outboundBuffer is null we know the channel was closed and so// need to fail the future right away. If it is not null the handling of the rest// will be done in flush0()// See https://github.com/netty/netty/issues/2362
                safeSetFailure(promise, CLOSED_CHANNEL_EXCEPTION);// release message now to prevent resource-leak
                ReferenceCountUtil.release(msg);return;}outboundBuffer.addMessage(msg, promise);}

在代码的许多部分, 都会有这个 ClosedChannelException, 大概的意思是说在 channel close 以后, 如果还调用了 write 方法, 则会将 write 的 future 设置为 failure, 并将 cause 设置为 ClosedChannelException, 同样 SSLHandler 中也类似

-----------------

回到 Connection reset by peer, 要模拟这个情况比较简单, 就是在 server 端设置一个在 channelActive 的时候就 close channel 的 handler. 而在 client 端则写一个 Connect 成功后立即发送请求数据的 listener. 如下

client

    public static void main(String[] args) throws IOException, InterruptedException {Bootstrap b = new Bootstrap();b.group(new NioEventLoopGroup()).channel(NioSocketChannel.class).handler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {}});b.connect("localhost", 8090).addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture future) throws Exception {if (future.isSuccess()) {future.channel().write(Unpooled.buffer().writeBytes("123".getBytes()));future.channel().flush();}}});

server

public class SimpleServer {public static void main(String[] args) throws Exception {EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_REUSEADDR, true).childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new SimpleServerHandler());}});b.bind(8090).sync().channel().closeFuture().sync();}
}public class SimpleServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {ctx.channel().close().sync();}@Overridepublic void channelRead(ChannelHandlerContext ctx, final Object msg) throws Exception {System.out.println(123);}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {System.out.println("inactive");}
}

 

这种情况之所以能触发 connection reset by peer 异常, 是因为 connect 成功以后, client 段先会触发 connect 成功的 listener, 这个时候 server 段虽然断开了 channel, 也触发 channel 断开的事件 (它会触发一个客户端 read 事件, 但是这个 read 会返回 -1, -1 代表 channel 关闭, client 的 channelInactive 跟 channel  active 状态的改变都是在这时发生的), 但是这个事件是在 connect 成功的 listener 之后执行, 所以这个时候 listener 里的 channel 并不知道自己已经断开, 它还是会继续进行 write 跟 flush 操作, 在调用 flush 后, eventloop 会进入 OP_READ 事件里, 这时候 unsafe.read() 就会抛出 connection reset 异常. eventloop 代码如下

NioEventLoop

private static void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {final NioUnsafe unsafe = ch.unsafe();if (!k.isValid()) {// close the channel if the key is not valid anymore
            unsafe.close(unsafe.voidPromise());return;}try {int readyOps = k.readyOps();// Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead// to a spin loopif ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {unsafe.read();if (!ch.isOpen()) {// Connection already closed - no need to handle write.
                    return;}}if ((readyOps & SelectionKey.OP_WRITE) != 0) {// Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
                ch.unsafe().forceFlush();}if ((readyOps & SelectionKey.OP_CONNECT) != 0) {// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking// See https://github.com/netty/netty/issues/924int ops = k.interestOps();ops &= ~SelectionKey.OP_CONNECT;k.interestOps(ops);unsafe.finishConnect();}} catch (CancelledKeyException e) {unsafe.close(unsafe.voidPromise());}}

这就是 connection reset by peer 产生的原因

------------------

再来看 ClosedChannelException 如何产生, 要复现他也很简单. 首先要明确, 并没有客户端主动关闭才会出现 ClosedChannelException 这么一说. 下面来看两种出现 ClosedChannelException 的客户端写法

client 1, 主动关闭 channel

public class SimpleClient {private static final Logger logger = LoggerFactory.getLogger(SimpleClient.class);public static void main(String[] args) throws IOException, InterruptedException {Bootstrap b = new Bootstrap();b.group(new NioEventLoopGroup()).channel(NioSocketChannel.class).handler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {}});b.connect("localhost", 8090).addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture future) throws Exception {if (future.isSuccess()) { future.channel().close();future.channel().write(Unpooled.buffer().writeBytes("123".getBytes())).addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture future) throws Exception {if (!future.isSuccess()) {logger.error("Error", future.cause());}}});future.channel().flush();}}});}
}

 

只要在 write 之前主动调用了 close, 那么 write 必然会知道 close 是 close 状态, 最后 write 就会失败, 并且 future 里的 cause 就是 ClosedChannelException

--------------------

client 2. 由服务端造成的 ClosedChannelException

public class SimpleClient {private static final Logger logger = LoggerFactory.getLogger(SimpleClient.class);public static void main(String[] args) throws IOException, InterruptedException {Bootstrap b = new Bootstrap();b.group(new NioEventLoopGroup()).channel(NioSocketChannel.class).handler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {}});Channel channel = b.connect("localhost", 8090).sync().channel();Thread.sleep(3000);channel.writeAndFlush(Unpooled.buffer().writeBytes("123".getBytes())).addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture future) throws Exception {if (!future.isSuccess()) {logger.error("error", future.cause());}}});}
}

服务端

public class SimpleServer {public static void main(String[] args) throws Exception {EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_REUSEADDR, true).childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new SimpleServerHandler());}});b.bind(8090).sync().channel().closeFuture().sync();}
}

这种情况下,  服务端将 channel 关闭, 客户端先 sleep, 这期间 client 的 eventLoop 会处理客户端关闭的时间, 也就是 eventLoop 的 processKey 方法会进入 OP_READ, 然后 read 出来一个 -1, 最后触发 client channelInactive 事件, 当 sleep 醒来以后, 客户端调用 writeAndFlush, 这时候客户端 channel 的状态已经变为了 inactive, 所以 write 失败, cause 为 ClosedChannelException

转载于:https://www.cnblogs.com/zemliu/p/3864131.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/420749.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

注解方式实现aop

aop注解实现spring配置文件目标接口&#xff0c;目标实现类&#xff0c;切面类 注解写法使用spring-test测试spring配置文件 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmln…

文本聚类

&#xff08;纯属为了记录自己学习的点滴过程&#xff0c;引用资料都附在参考列表&#xff09; 1 基本概念 聚类(cluster analysis )指的是将给定对象的集合划分为不同子集的过程&#xff0c;目标是使得每个子集内部的元素尽量相似&#xff0c;不同子集间的元素尽量不相似。 …

sublime text3下BracketHighlighter的配置方法

st3的配置方法和st2是有区别的&#xff0c;所以网上搜索到的方法大多不能用&#xff0c;我google之后总结了一下。 一、 1、在st3中按preferences-->package settings-->Bracket highlighter-->Bracket settings-Default打开配置文件。 2、将配置文件信息全选复制一份…

利用spring注解创建bean

spring注解spring 原始注解1.1 Component注解1.2 Controller,Service,Repository同上1.3 注解方式依赖注入spring 新注解1. 用来解析配置类&#xff0c;利用配置类替代xml注解代替了xml的繁琐配置 spring 原始注解 1.1 Component注解 <!--spring 使用注解创建对象 compone…

文本分类--普通分类

1 基本概念 文本分类 文本分类&#xff08;text classification&#xff09;&#xff0c;指的是将一个文档归类到一个或多个类别的自然语言处理任务。文本分类的应用场景非常广泛&#xff0c;包括垃圾邮件过滤、自动打标等任何需要自动归档文本的场合。 文本分类在机器学习中属…

hdoj 2041 超级阶梯

代码&#xff1a; #include <stdio.h>int main(){int n;int i;int m;int count;int dp[50];while(scanf("%d",&n)!EOF){dp[1]1;dp[2]1;dp[3]2;while(n--){count0;scanf("%d",&m);for(i4; i<m; i){dp[i]dp[i-1]dp[i-2];}printf("%d\n…

文本分类--情感分析

&#xff08;纯属为了记录自己学习的点滴过程&#xff0c;引用资料都附在参考列表&#xff09; 1 基本概念 情感分析 对于情感分析而言&#xff0c;只需要准备标注了正负情感的大量文档&#xff0c;就能将其视作普通的文本分类任务来解决。此外&#xff0c;一些带有评分的电影…

websocket使用

websocket1. 概述2. websocket的用法3. js代码实现4. 服务器端代码实现maven下载地址&#xff1a;https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket 1. 概述 什么是websocket - WebSocket是一种网络传输协议&#xff0c; 可在单个TCP连接上进行全双工…

深度学习与自然语言处理

&#xff08;纯属为了记录自己学习的点滴过程&#xff0c;引用资料都附在参考列表&#xff09; 1 传统方法的局限 1.1 传统方法的套路 传统方法的处理流程简单来说就是&#xff1a;特征提取传统机器学习模型训练&#xff1b; 特征提取&#xff1a; 使用了特征模板、TF-IDF、…

linux 烧写(1)

第一部分: 一、BootLoader的概念 BootLoader是系统加电启运行的第一段软件代码&#xff0e;回忆一下PC的体系结构我们可以知道&#xff0c;PC机中的引导加载程序由BIOS&#xff08;其本质就是一段固件程序&#xff09;和位于硬盘MBR中的引导程序一起组成。BIOS在完成硬件检测和…

利用websocket实现一对一聊天

一对一聊天websocket1. 效果展示2. 业务分析&#xff08;逻辑展示...&#xff09;3. 技术点功能 即时发送消息||随时发送消息历史消息显示已读未读状态 1. 效果展示 由于没做登录&#xff0c;就以jack和rose两人聊天 两人可相互发消息 持续输出. . 当只有jack在线时 嘤…

中文分词--词典分词--最长匹配

&#xff08;个人学习笔记&#xff0c;慎重参考&#xff09; 1 基本概念 中文分词 指的是将一段文本拆分为一系列单词的过程&#xff0c;这些单词顺序拼接后等于原文本。 作为中文信息处理的第一站&#xff0c;是后续nlp任务的基础&#xff0c;中文分词算法大致可分为词典规则…

css3动画animation,transition

animation demo1 <!DOCTYPE html> <html><head><meta charset"UTF-8"><title>animation动画</title><style>#div1 {width: 100px;height: 100px;background-color: pink;position: absolute;top: 10%;left: 25%;}/* 延迟…

PLSQL Developer 运用Profiler 分析存储过程性能

最近应公司需要&#xff0c;需要编写ORACLE存储过程。本人新手&#xff0c;在完成存储过程的编写后&#xff0c;感觉需要对存储过程中各个语句的执行时间进行分析&#xff0c;以便 对整个存储过程进行优化。 由于用的是PLSQL Developer 客户端工具&#xff0c;而网上大多介绍的…

四、Dynamic-programming algorithm Dynamic--LCS

(学习笔记&#xff0c;无什参考价值&#xff01;) 1 问题 2 算法 2.1 Brute-force LCS algorithm 检查每一个subsequence是否是yyy的子列时&#xff0c;遍历yyy的每一个元素&#xff0c;看是否依次可以全部覆盖subsequence所有元素&#xff0c;所以其复杂度为O(n)O(n)O(n); …

Spring JdbcTemplate Curd

curd1. 实现步骤2. maven dependency3. curd代码database: oracledataSource: alibaba druid 1. 实现步骤 1. 导入spring-jdbc 和 spring-tx(事务)坐标 2. 创建数据库表和实体 3.创建JdbcTemplate对象JdbcTemplate jdbc new JdbcTemplate();jdbc.setDataSource(dataSource);…