Netty网络框架学习笔记-7((心跳)检测空闲连接以及超时)
1.0 前言:
为了能够及时的将资源释放出来,我们会检测空闲连接和超时。常见的方法是通过发送信息来测试一个不活跃的链接,通常被称为“心跳”,然后在远端确认它是否还活着。(还有一个方法是比较激进的,简单地断开那些指定的时间间隔的不活跃的链接)。
处理空闲连接是一项常见的任务,Netty 提供了几个
ChannelHandler
实现此目的。
名称 | 描述 |
IdleStateHandler | 如果连接闲置时间过长,则会触发 IdleStateEvent 事件。在 ChannelInboundHandler 中可以覆盖 userEventTriggered(…) 方法来处理 IdleStateEvent。 |
ReadTimeoutHandler | 在指定的时间间隔内没有接收到入站数据则会抛出 ReadTimeoutException 并关闭 Channel。ReadTimeoutException 可以通过覆盖 ChannelHandler 的 exceptionCaught(…) 方法检测到。 |
WriteTimeoutHandler | WriteTimeoutException 可以通过覆盖 ChannelHandler 的 exceptionCaught(…) 方法检测到。 |
2.0 简单例子
用来检测连接是否在空闲状态下!
2.1 带心跳检测的服务端
/**
* @Author: ZhiHao
* @Date: 2022/4/24 17:08
* @Description: 心跳服务器
* @Versions 1.0
**/
@Slf4j
public class HeartbeatServer {
public static void main(String[] args) {
NioEventLoopGroup boosGroup = new NioEventLoopGroup(2);
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boosGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childOption(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.handler(new LoggingHandler(LogLevel.INFO)) // boosGroup服务器处理程序, 日志
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
// 添加框架提供的字符串编解码处理器
pipeline.addLast(new StringEncoder());
pipeline.addLast(new StringDecoder());
//1. IdleStateHandler 是 netty 提供的处理空闲状态的处理器
//2. long readerIdleTime : 表示多长时间没有读, 就会发送一个心跳检测包检测是否连接
//3. long writerIdleTime : 表示多长时间没有写, 就会发送一个心跳检测包检测是否连接
//4. long allIdleTime : 表示多长时间没有读写, 就会发送一个心跳检测包检测是否连接
pipeline.addLast(new IdleStateHandler(5, 5, 10, TimeUnit.SECONDS));
//5. 当 IdleStateEvent 触发后 , 就会传递给管道 的下一个 handler 去处理, 通过调用(触发)下一个 handler 的 userEventTiggered ,
// 加入自己的自定义处理器(在处理器该userEventTriggered方法中去处理 IdleStateEvent(读 空闲,写空闲,读写空闲))
pipeline.addLast(new HeartbeatServerHandle());
}
});
try {
ChannelFuture channelFuture = serverBootstrap.bind(8888).sync();
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
log.info("HeartbeatServer==, 服务器已经启动成功, 等待连接中!");
}
}
});
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("HeartbeatServer===, 发生异常:{}", e);
} finally {
boosGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
2.1.1 服务端自定义处理器(包含处理空闲事件)
@Slf4j
public class HeartbeatServerHandle extends SimpleChannelInboundHandler<String> {
private static ConcurrentHashMap<String,Long> concurrentHashMap = new ConcurrentHashMap<>();
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
log.info("HeartbeatServerHandle-读取到信息:{}", msg);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
// 判断事件是否是IdleStateEvent
if (evt instanceof IdleStateEvent) {
IdleStateEvent idleStateHandler = (IdleStateEvent) evt;
IdleState state = idleStateHandler.state();
String evtState = null;
String key = ctx.channel().id().asLongText();
Long count = concurrentHashMap.getOrDefault(key, 0L);
switch(state) {
case READER_IDLE:
evtState = "读空闲";
break;
case WRITER_IDLE:
evtState = "写空闲";
break;
case ALL_IDLE:
evtState = "读写空闲";
count++;
break;
default:
break;
}
log.info("userEventTriggered-evtState:{}", evtState);
// 空闲计数达5次, 进行测试连接是否正常
if (count > 2L){
ctx.writeAndFlush("测试客户端是否能接收信息")
// 发送失败时关闭通道, 在或者可以在达到空闲多少次后, 进行关闭通道
.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
concurrentHashMap.remove(key);
return;
}
concurrentHashMap.put(key,count);
} else {
// 事件不是一个 IdleStateEvent 的话,就将它传递给下一个处理程序
super.userEventTriggered(ctx, evt);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.channel().close();
}
}
2.2 客户端
@Slf4j
public class HeartbeatClient {
public static void main(String[] args) {
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
// 添加框架提供的字符串编解码处理器
pipeline.addLast(new StringEncoder());
pipeline.addLast(new StringDecoder());
pipeline.addLast(new HeartbeatClientHandle());
}
});
try {
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8888)).sync();
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
log.info("HeartbeatClient==, 客户端已经启动成功, 连接成功!");
}
}
});
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("HeartbeatClient===, 发生异常:{}", e);
} finally {
workerGroup.shutdownGracefully();
}
}
}
2.2.1 客户端处理器
@Slf4j
public class HeartbeatClientHandle extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
log.info("HeartbeatClientHandle-读取到信息:{}", msg);
//ctx.writeAndFlush("服务端你好!");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.channel().close();
}
}
结果:
19:35:38.182 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:读空闲
19:35:38.182 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:写空闲
19:35:43.186 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:读写空闲
19:35:43.186 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:读空闲
19:35:43.186 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:写空闲
19:35:48.189 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:读空闲
19:35:48.189 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:写空闲
19:35:53.191 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:读写空闲
19:35:53.191 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:读空闲
19:35:53.191 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:写空闲
19:35:58.205 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:读空闲
19:35:58.205 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:写空闲
19:36:03.197 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:读写空闲
19:36:03.210 [nioEventLoopGroup-3-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@40d2cc5c
19:36:03.214 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.HeartbeatServerHandle - userEventTriggered-evtState:读空闲
// -------------------------------------------------------------------------------------------------------
19:36:03.225 [nioEventLoopGroup-2-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@3a94a792
19:36:03.231 [nioEventLoopGroup-2-1] INFO com.zhihao.netty.heartbeat.HeartbeatClientHandle - HeartbeatClientHandle-读取到信息:测试客户端是否能接收信息
一旦连接断开, 就不会在检测心跳!
3.0 另一种场景上报数据:
在物联网或者其他需要在连接空闲时候, 上报自身状态 (例如: 上报设备状态, 或上报服务器状态)
3.1 客户端改造
// 省略其他代码
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
// 添加框架提供的字符串编解码处理器
pipeline.addLast(new StringEncoder());
pipeline.addLast(new StringDecoder());
// 在写空闲30秒的时候进行上报数据
pipeline.addLast(new IdleStateHandler(0,30,0, TimeUnit.SECONDS));
pipeline.addLast(new HeartbeatClientReportHandle());
}
});
// 省略其他代码
3.1.1 客户端处理器
@Slf4j
public class HeartbeatClientReportHandle extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
log.info("HeartbeatClientReportHandle-读取到信息:{}", msg);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
// 判断事件是否是IdleStateEvent
if (evt instanceof IdleStateEvent) {
IdleStateEvent idleStateHandler = (IdleStateEvent) evt;
if (idleStateHandler.state().equals(IdleState.WRITER_IDLE)){
ctx.writeAndFlush(String.format("当前设备号: %s, 状态:%s", RandomUtil.randomInt(),RandomUtil.randomInt()));
}
} else {
// 事件不是一个 IdleStateEvent 的话,就将它传递给下一个处理程序
super.userEventTriggered(ctx, evt);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.channel().close();
}
}
结果:
// 服务端日志
15:58:45.697 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.report.HeartbeatReportServerHandle - HeartbeatReportServerHandle-读取到信息:当前设备号: -1691569318, 状态:-1274325485
15:59:15.680 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.report.HeartbeatReportServerHandle - HeartbeatReportServerHandle-读取到信息:当前设备号: 733278004, 状态:1290704661
15:59:45.683 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.report.HeartbeatReportServerHandle - HeartbeatReportServerHandle-读取到信息:当前设备号: 167749378, 状态:-1514067376
16:00:15.696 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.report.HeartbeatReportServerHandle - HeartbeatReportServerHandle-读取到信息:当前设备号: -156490593, 状态:-50335223
16:00:45.711 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.report.HeartbeatReportServerHandle - HeartbeatReportServerHandle-读取到信息:当前设备号: 1998916514, 状态:-1099924846
16:01:15.725 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.report.HeartbeatReportServerHandle - HeartbeatReportServerHandle-读取到信息:当前设备号: 1809189822, 状态:1953605308
16:01:45.731 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.report.HeartbeatReportServerHandle - HeartbeatReportServerHandle-读取到信息:当前设备号: -668619106, 状态:-1750520516
16:02:15.744 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.heartbeat.report.HeartbeatReportServerHandle - HeartbeatReportServerHandle-读取到信息:当前设备号: 2142985320, 状态:880620132
从结果可以看出, 客户端每隔39秒会上报一次数据。
PS: 这个是要在通道空闲时候才会进行催发, 我们可能模拟
IdleStateHandler
写一个定时上报的处理器, 里面创建发送定时上报的事件传递给下一个处理器。
1