一、连接假死出现原因
在网络编程的领域当中,很多问题都会莫名其妙的出现,让人措手不及。
其中一种,就是连接假死,那么连接假死是如何出现的呢?可能存在以下几种情况:
1)网络设备出现故障。例如网卡,机房等,底层的 TCP 连接已经断开了,但应用程序没有感知到,仍然占用着资源。
2)公网网络不稳定,出现丢包。如果连续出现丢包,这时现象就是客户端数据发不出去,服务端也一直收不到数据,进程一直占据资源,耗在这儿。
3)应用程序线程阻塞,无法进行数据读写。
连接假死会产生以下问题:
1)假死的连接占用的资源不能自动释放。
2)向假死的连接发送数据,得到的反馈是发送超时。
二、如何解决连接假死?
Netty提供了一个叫做IdleStateHandler的处理器,用来进行空闲检测。
其实现方式是:
当Channel有一段时间没有执行读、写或两者操作时触发IdleStateEvent。
有三个主要参数:
- readerIdleTime:一个IdleStateEvent,其状态是IdleState.READER_IDLE时的指定时间段内,没有执行读操作将被触发。 指定0以禁用。
- writerIdleTime:一个IdleStateEvent,其状态是IdleState.WRITER_IDLE时的指定时间段内,没有执行写操作将被触发。 指定0以禁用。
- allIdleTime:一个IdleStateEvent,其状态是IdleState.ALL_IDLE时的指定时间段内,没有进行读取和写入都将被触发。 指定0以禁用
我们正常使用时,也使用这个三个参数的构造即可:
public IdleStateHandler(
int readerIdleTimeSeconds,
int writerIdleTimeSeconds,
int allIdleTimeSeconds) {
this(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds,
TimeUnit.SECONDS);
}
如上所示,其默认单位是秒,当然也有可以指定单位的构造,还能附带考虑buf处理数据时间的构造,这里就不多介绍了。
接下来我们看看如何使用,我们需要将这个处理器添加到pipeline当中,需要注意的是,我们需要自己去处理事件,IdleStateEvent。
关于这个事件的处理,我们不能使用常规的入站或者出站处理器了,此处需要学习一个新的处理器,专门用来处理特殊事件的,我通过下面的代码进行演示:
服务端接收数据,添加如下代码:
//空闲检测处理器,检测5秒内未读取
ch.pipeline().addLast(new IdleStateHandler(5, 0, 0));
//可以同时作为入站和出站处理器,此处用来处理特殊事件
ch.pipeline().addLast(new ChannelDuplexHandler() {
//用户事件触发
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
IdleStateEvent stateEvent = (IdleStateEvent) evt;
//我们的服务器端只进行数据接收
if (stateEvent.state() == IdleState.READER_IDLE) {
System.out.println("已经5秒没有接收到数据");
}
}
});
客户端发送数据,添加如下代码:
//空闲检测处理器,检测5秒未写入
ch.pipeline().addLast(new IdleStateHandler(0, 5, 0));
//可以同时作为入站和出站处理器,此处用来处理特殊事件
ch.pipeline().addLast(new ChannelDuplexHandler() {
//用户事件触发
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
IdleStateEvent stateEvent = (IdleStateEvent) evt;
//我们的服务器端只进行数据接收
if (stateEvent.state() == IdleState.WRITER_IDLE) {
System.out.println("已经5秒没有写入数据");
}
}
});
当客户端和服务端都启动并建立连接后,如果没有消息就会打印如下内容:
已经5秒没有写入数据
已经5秒没有写入数据
已经5秒没有写入数据
已经5秒没有写入数据
... ...
已经5秒没有读取到数据
已经5秒没有读取到数据
已经5秒没有读取到数据
已经5秒没有读取到数据
... ...
其实还有后续的操作,就是,当我们检测到在一定时间内没有发生数据写入或读取,应该怎么做?
可以制定一些策略,如连续多少次检测到空闲等。最终我们要做的其实就是释放资源:
ctx.channel().close();
上述的代码实际并不友好,其实比较好的处理方式,需要结合业务区考虑,没有消息读取并不代表连接存在问题,可以使用下面的方式,心跳检测:
客户端:当检测到没有数据写入时,可以定时向服务端发送心跳,比如每5秒就向服务端写入一次数据。这个时间要小于服务器设置的检测时间。
服务端:设置一个定时读取的检测时间,比如8秒,这样在8秒内,一定会受到客户端的心跳,如果超过8秒没有心跳,则可以认为连接出现问题,可以关闭channel。
关于空闲检测的内容,就介绍这么多,有用的话点个赞再走吧!!