欢迎微信搜索并关注“小猴子的技术笔记”公众号 私信我 领取丰富的视频学习资料!
关于心跳我们在长链接的开发中一般都是会遇到的,因为是长链接所以需要定时发送心跳保持连接的活跃。当服务端检测不到客户端的心跳之后就会释放资源,这个操作是一个很重要的操作。
如果你处理过原生socket的心跳检测机制,你会发现那是一个比较麻烦的处理。你需要起一个线程或者定时任务来不停的检测连接是否有心跳上送,如果没有心跳你就需要释放资源,关闭socket或者尝试重连机制。
Netty为我们提供了一个“IdleStateHandler”闲置状态处理器。如果在一定的时间内没有读、写或者两者都没有的操作将会被触发这个处理器的事件。
关于“IdleStateHandler”我们需要设置一些参数,一般的设置为:
public IdleStateHandler(
long readerIdleTime,
long writerIdleTime,
long allIdleTime,
TimeUnit unit) {
this(false, readerIdleTime, writerIdleTime, allIdleTime, unit);
}
“readerIdleTime”:读取数据的超时时间,也就是说连接建立之后在这个readerIdleTime时间内没有读取到数据就会被触发。
“writerIdleTime”:写超时事件,也就是说在连接建立之后在writerIdleTime时间内没有往外面写任何数据。
“allIdleTime”:读写超时事件,也就是说在连接建立之后在allIdleTime时间内既没有发生读的事件,也没有发生写的事件。
“unit”:前面参数的时间单位,是以分钟啊,还是秒啊,或者是小时为单位进行一次检测数据。
如果参数设置成0的话,那么是“无效”的,也就是说netty会忽略到这个配置。接下来看看心跳检测的示例。
public class HeartBeatServer {
public static void main(String[] args) {
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap server = new ServerBootstrap();
server.group(boss, worker).channel(NioServerSocketChannel.class).childHandler(new HeartBeatServerInitializer());
ChannelFuture channelFuture = server.bind(9999).sync();
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
public class HeartBeatServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LineBasedFrameDecoder(1024));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS));
pipeline.addLast(new HeartBeatServerHandler());
}
}
public class HeartBeatServerHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("接收到信息: " + msg);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
IdleStateEvent event = (IdleStateEvent) evt;
switch (event.state()) {
case READER_IDLE:
System.out.println("读空闲");
break;
case WRITER_IDLE:
System.out.println("写空闲");
break;
case ALL_IDLE:
System.out.println("读写空闲");
break;
default:
throw new IllegalStateException("非法状态!");
}
}
}
假设我们写一个客户端不进行数据的发送,看看会触发什么效果:
public class HeartBeatClient {
public static void main(String[] args) {
EventLoopGroup worker = new NioEventLoopGroup();
try {
Bootstrap client = new Bootstrap();
client.group(worker).channel(NioSocketChannel.class).handler(new HeartBeatClientInitializer());
Channel channel = client.connect("localhost", 9999).sync().channel();
channel.closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
worker.shutdownGracefully();
}
}
}
public class HeartBeatClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LineBasedFrameDecoder(1024));
pipeline.addLast(new StringEncoder());
pipeline.addLast(new StringDecoder());
pipeline.addLast(new HeartBeatClientHandler());
}
}
public class HeartBeatClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
}
}
启动服务端和客户端,等过一段时间你会发现:
读空闲
读空闲
读空闲
也许你会疑问,被触发了之后该怎么办呢?其实,想怎么办就怎办!也许你已经发现了,在事件被触发的方法中有一个比较重要的“ChannelHandlerContext”。这个“ChannelHandlerContext”是连接ChannelHandler和ChannelPipeline的重要上下文,有了它你就可以对channel进行操作了!
那么将之前的代码进行一下修改,没有读取到数据就断开连接。
public class HeartBeatServerHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("接收到信息: " + msg);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
IdleStateEvent event = (IdleStateEvent) evt;
switch (event.state()) {
case READER_IDLE:
System.out.println("读空闲");
ctx.channel().close();
break;
case WRITER_IDLE:
System.out.println("写空闲");
break;
case ALL_IDLE:
System.out.println("读写空闲");
break;
default:
throw new IllegalStateException("非法状态!");
}
}
}
你会发现就添加了一行代码。
ctx.channel().close();
再来试着运行一下服务端和客户端:都运行的时候可以到两个进程在运行
等过了5秒服务端打印出一句话之后就不会再继续打印了。
再看看进程就剩一个客户端了。
其实不仅如此,你完全可以结合业务对心跳检测信息做出处理。因为你已经拿到了它对应的“ChannelHandlerContext”,你可以利用它写数据,广播数据,关闭连接等操作。客户端也可以根据服务端上送的心跳进行重连等操作!
欢迎微信搜索并关注“小猴子的技术笔记”公众号 私信我 领取丰富的视频学习资料!