报错详细内容如下:
java.io.IOException: 打开的文件过多
at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:421)
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:249)
at io.netty.util.internal.SocketUtils$5.run(SocketUtils.java:119)
at io.netty.util.internal.SocketUtils$5.run(SocketUtils.java:116)
at java.security.AccessController.doPrivileged(Native Method)
at io.netty.util.internal.SocketUtils.accept(SocketUtils.java:116)
at io.netty.channel.socket.nio.NioServerSocketChannel.doReadMessages(NioServerSocketChannel.java:147)
at io.netty.channel.nio.AbstractNioMessageChannel$NioMessageUnsafe.read(AbstractNioMessageChannel.java:75)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)
问题背景
使用netty做了一个TCP/IP的通讯服务,我们作为TCP的服务端接收客户端发送的请求,项目部署在Linux环境下运行。但是运行一段时间,服务就崩了,报错内容如上:java.io.IOException: 打开的文件过多 或 java.io.IOException: Too many open files。反正意思一样,都是当前服务文件句柄数超出上限,内存达到上限,服务承受不住了。这个时候把netty服务重启一下就又恢复了…循环往复,苦不堪言
一般出现这类问题,都是在网上一通搜索,基本的方案不外乎以下几种:
🚀方案一,升级netty的版本
结果:没用
版本升级之后,运行一段时间又成这样了,肯定不是版本的问题了
🚀方案二,初始化NioEventLoopGroup的时候,传参限制1个
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
结果:没用
网上有些人真的是误人子弟,你抄我、我抄你的,本来实例化的也是1个对象,这样改有什么意义呢?
🚀方案三,修改/etc/security/limits.conf中的数量限制,默认是1024,改为65536
* soft nofile 65536
* hard nofile 65536
结果:短时间有用,运行几天之后又报这个错了
典型的治标不治本。这种情况针对的是并发很高,TCP客户端超过1024了,但是我们总共就100多个客户端,怎么会超出呢。改了这个只把句柄数量上限提高了,根本上还是没有解决问题,过几天还是要崩的!
💔行了,网上的解决方案都没用,只能靠自己分析报错原因了。客户端总共就100多个,怎么过几天就超过65536了呢?肯定是单个客户端建立了多次连接,但是服务端的连接没有及时关闭所致。正常客户端会主动断开连接并重新建立连接,而这个项目的客户端可能就没有做断开操作,而是不断向服务端建立新的连接,所以我们要在服务端做好处理了。
通过三条命令,更加佐证了我的想法:
lsof -n|awk '{print $2}'|sort|uniq -c|sort -nr|more
查询服务倒序连接数,可以看到linux下所以服务进程号对应的连接数量,从高到低排序
netstat -lnaop|grep 5655|wc -l
查询某进程号连接数,此时我的netty服务对应的进程号是5655,间隔时间多查几次,发现连接数量在不断递增,早就超过了预留客户端的数量
netstat -ntu | grep 10072
查询某端口客户端连接的情况,此时我netty服务开放的TCP端口是10072,列表查到占用资源的客户端列表,发现大量重复的客户端IP地址
👍 重点来了,解决根本问题
最终解决方案:netty初始化的时候,设置连接超时时间 pipeline.addLast(new ReadTimeoutHandler(timeoutSeconds))
/**
* 设置初始化器,主要是给pipeLine添加Handler
*/
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//设置2min的超时时间,如果某个通道2min内未发送信号,则抛出异常删除当前通道
pipeline.addLast(new ReadTimeoutHandler(120));
pipeline.addLast("encoder",new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast("decoder",new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new MyServerHandler());
}
}
我这边设置的是2分钟,具体按照项目的实际需求设置,因为我这边的客户端是每1分钟发送一次报文的,我就暂时设置为2分钟了。超过2分钟没发数据,服务端就认为这个连接超时了,超时就会在执行到exceptionCaught方法中并且断开当前连接。我们的MyServerHandler类继承了ChannelInboundHandlerAdapter类或SimpleChannelInboundHandler类,自然而然就重写了exceptionCaught这个方法。而netty框架中某通道发生异常的时候,会在执行到exceptionCaught方法中。
这样我们就完美解决了这个问题,间隔时间多跑几次【netstat -lnaop|grep 5655|wc -l】命令,会发现连接数稳定在100多个了!