Netty 心跳机制实现(客户端与服务端)
Netty 心跳机制实现(客户端与服务端)
Netty 的心跳机制是保持长连接有效性的重要手段,可以检测连接是否存活并及时释放无效连接。下面介绍客户端和服务端的完整实现方案。
一、服务端实现
1. 基础心跳检测
public class HeartbeatServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 添加编解码器
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
// 心跳检测
// 参数说明:readerIdleTime, writerIdleTime, allIdleTime, 时间单位
pipeline.addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS));
pipeline.addLast(new HeartbeatServerHandler());
}
}
public class HeartbeatServerHandler extends ChannelInboundHandlerAdapter {
// 心跳丢失计数器
private int lossConnectCount = 0;
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.READER_IDLE) {
lossConnectCount++;
if (lossConnectCount > 2) {
System.out.println("关闭不活跃连接: " + ctx.channel());
ctx.channel().close();
}
}
} else {
super.userEventTriggered(ctx, evt);
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 收到任何消息都重置计数器
if ("HEARTBEAT".equals(msg)) {
lossConnectCount = 0;
System.out.println("收到心跳: " + ctx.channel());
ctx.writeAndFlush("HEARTBEAT_RESPONSE");
} else {
// 处理其他业务消息
}
}
}
2. 完整心跳交互方案
public class AdvancedHeartbeatServerHandler extends ChannelInboundHandlerAdapter {
private static final ByteBuf HEARTBEAT_SEQUENCE =
Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("HEARTBEAT", CharsetUtil.UTF_8));
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleState state = ((IdleStateEvent) evt).state();
if (state == IdleState.READER_IDLE) {
// 读空闲(没有收到客户端消息)
System.out.println("读空闲,关闭连接: " + ctx.channel());
ctx.close();
} else if (state == IdleState.WRITER_IDLE) {
// 写空闲(可以主动发送心跳包)
System.out.println("写空闲,发送心跳包");
ctx.writeAndFlush(HEARTBEAT_SEQUENCE.duplicate())
.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}
} else {
super.userEventTriggered(ctx, evt);
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
String message = (String) msg;
if ("HEARTBEAT_REQUEST".equals(message)) {
// 响应客户端心跳
ctx.writeAndFlush("HEARTBEAT_RESPONSE");
} else {
// 处理业务消息
}
}
}
二、客户端实现
1. 基础心跳实现
public class HeartbeatClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
// 客户端设置写空闲检测(定期发送心跳)
pipeline.addLast(new IdleStateHandler(0, 4, 0, TimeUnit.SECONDS));
pipeline.addLast(new HeartbeatClientHandler());
}
}
public class HeartbeatClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.WRITER_IDLE) {
// 写空闲时发送心跳
ctx.writeAndFlush("HEARTBEAT");
System.out.println("客户端发送心跳");
}
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if ("HEARTBEAT_RESPONSE".equals(msg)) {
System.out.println("收到服务端心跳响应");
}
}
}
2. 完整心跳交互方案
public class AdvancedHeartbeatClientHandler extends ChannelInboundHandlerAdapter {
private static final ByteBuf HEARTBEAT_SEQUENCE =
Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("HEARTBEAT_REQUEST", CharsetUtil.UTF_8));
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 连接建立后立即发送一次心跳
sendHeartbeat(ctx);
super.channelActive(ctx);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleState state = ((IdleStateEvent) evt).state();
if (state == IdleState.WRITER_IDLE) {
// 写空闲时发送心跳
sendHeartbeat(ctx);
} else if (state == IdleState.READER_IDLE) {
// 读空闲(未收到服务端响应)
System.out.println("服务端无响应,关闭连接");
ctx.close();
}
} else {
super.userEventTriggered(ctx, evt);
}
}
private void sendHeartbeat(ChannelHandlerContext ctx) {
ctx.writeAndFlush(HEARTBEAT_SEQUENCE.duplicate())
.addListener(future -> {
if (!future.isSuccess()) {
System.err.println("心跳发送失败: " + future.cause());
}
});
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
String message = (String) msg;
if ("HEARTBEAT".equals(message)) {
// 响应服务端心跳
ctx.writeAndFlush("HEARTBEAT_RESPONSE");
} else if ("HEARTBEAT_RESPONSE".equals(message)) {
// 收到服务端对客户端心跳的响应
System.out.println("心跳正常");
}
}
}
三、WebSocket 心跳实现
对于 WebSocket 连接,心跳机制需要特殊处理:
服务端实现
public class WebSocketHeartbeatServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
String text = msg.text();
if ("HEARTBEAT".equals(text)) {
ctx.writeAndFlush(new TextWebSocketFrame("HEARTBEAT_RESPONSE"));
} else {
// 处理其他WebSocket消息
}
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent idleEvent = (IdleStateEvent) evt;
if (idleEvent.state() == IdleState.READER_IDLE) {
ctx.close();
} else if (idleEvent.state() == IdleState.WRITER_IDLE) {
ctx.writeAndFlush(new TextWebSocketFrame("HEARTBEAT"));
}
}
}
}
客户端实现
public class WebSocketHeartbeatClientHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
String text = msg.text();
if ("HEARTBEAT".equals(text)) {
ctx.writeAndFlush(new TextWebSocketFrame("HEARTBEAT_RESPONSE"));
}
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.WRITER_IDLE) {
ctx.writeAndFlush(new TextWebSocketFrame("HEARTBEAT"));
}
}
}
}
四、最佳实践建议
合理设置超时时间:
- 生产环境建议读空闲时间设置为60-120秒
- 写空闲时间设置为30-60秒
心跳协议设计:
- 使用固定格式的心跳消息(如"HEARTBEAT")
- 可以考虑携带时间戳或序列号用于调试
重连机制:
// 客户端重连示例
public class ReconnectHandler extends ChannelInboundHandlerAdapter {
private final Bootstrap bootstrap;
private int retries = 0; @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if (retries < 3) {
long delay = 1L << retries;
retries++;
ctx.channel().eventLoop().schedule(() -> {
System.out.println("尝试重连..." + retries);
bootstrap.connect();
}, delay, TimeUnit.SECONDS);
}
ctx.fireChannelInactive();
}
}
监控与日志:
- 记录心跳异常情况
- 监控连接存活率
性能考虑:
- 使用共享的ByteBuf作为心跳消息
- 避免在心跳处理器中执行耗时操作
通过以上实现,可以构建健壮的Netty心跳机制,有效维护长连接的可靠性。
Netty 心跳机制中写空闲检测的考量
在 Netty 心跳机制中,写空闲(WRITER_IDLE)检测和读空闲(READER_IDLE)检测各有不同的应用场景和考量因素。是否需要同时使用两者取决于具体业务需求。
一、写空闲检测的主要考虑场景
客户端主动保活(最常见场景)
- 当客户端需要维持与服务端的连接时(如移动设备通过NAT网关连接)
- 防止中间设备(路由器、防火墙等)因长时间无数据流动而断开连接
- 典型实现:客户端定期发送心跳包
服务端主动检测(特殊场景)
- 当服务端需要确认客户端是否存活但客户端无法主动发送心跳时
- 双向心跳检测机制中
- 需要服务端主动推送数据的场景(如实时监控系统)
对称性心跳设计
- 在金融、支付等对可靠性要求高的系统中
- 双方向都保持活跃检测,提高连接可靠性
二、是否只需要读空闲检测?
可以仅使用读空闲检测的场景:
纯服务端检测模式
- 客户端会定期发送数据(包括业务数据和心跳)
- 服务端只需要检测是否在指定时间内收到任何数据
客户端可靠主动发送心跳
- 客户端能保证按时发送心跳包
- 网络环境稳定(如内网通信)
节省资源考虑
- 减少不必要的写操作
- 简化心跳逻辑
需要同时使用写空闲检测的场景:
NAT环境下的长连接
// 典型NAT环境下的客户端配置
pipeline.addLast(new IdleStateHandler(0, 30, 0, TimeUnit.SECONDS)); // 只检测写空闲
需要服务端主动保活的系统
// 服务端需要保持连接活跃
pipeline.addLast(new IdleStateHandler(60, 30, 0, TimeUnit.SECONDS)); // 读写都检测
双向心跳验证
// 高可靠性系统的心跳设计
// 服务端:
pipeline.addLast(new IdleStateHandler(60, 45, 0, TimeUnit.SECONDS)); // 客户端:
pipeline.addLast(new IdleStateHandler(75, 30, 0, TimeUnit.SECONDS));
三、实际应用建议
推荐方案1:客户端单边心跳(最常见)
// 客户端配置
pipeline.addLast(new IdleStateHandler(0, 30, 0, TimeUnit.SECONDS)); // 只检测写空闲
pipeline.addLast(new HeartbeatClientHandler());
// 服务端配置
pipeline.addLast(new IdleStateHandler(90, 0, 0, TimeUnit.SECONDS)); // 只检测读空闲
适用场景:大多数移动应用、WebSocket通信等
优点:
- 客户端主动保活,避免NAT超时
- 服务端只需检测客户端是否存活
- 实现简单
推荐方案2:双向心跳检测
// 服务端配置
pipeline.addLast(new IdleStateHandler(60, 45, 0, TimeUnit.SECONDS));
// 客户端配置
pipeline.addLast(new IdleStateHandler(75, 30, 0, TimeUnit.SECONDS));
适用场景:
- 金融支付系统
- 物联网关键设备通信
- 对连接可靠性要求极高的场景
优点:
- 双方向连接状态确认
- 更高的可靠性
- 能更快发现单向网络中断情况
推荐方案3:自适应心跳
// 可根据网络条件动态调整
public class AdaptiveIdleStateHandler extends IdleStateHandler {
private boolean isMobileNetwork;
public AdaptiveIdleStateHandler() {
super(60, 30, 0, TimeUnit.SECONDS);
}
@Override
protected long nextDelay(IdleState state) {
if (isMobileNetwork && state == IdleState.WRITER_IDLE) {
return 25; // 移动网络下更频繁发送
}
return super.nextDelay(state);
}
}
四、关键决策因素
网络环境:
- 公网/NAT环境:需要写空闲检测
- 内网环境:可能只需读空闲检测
客户端类型:
- 移动设备:需要主动保活(写空闲)
- 服务端:通常只需检测客户端是否存活(读空闲)
业务需求:
- 普通消息推送:单边检测足够
- 金融交易:建议双向检测
资源消耗:
- 写空闲检测会增加少量网络流量
- 读空闲检测不会产生额外流量
五、典型案例
案例1:IM即时通讯系统
// 客户端(移动设备)
pipeline.addLast(new IdleStateHandler(0, 25, 0, TimeUnit.SECONDS)); // 只写空闲
// 服务端
pipeline.addLast(new IdleStateHandler(120, 0, 0, TimeUnit.SECONDS)); // 只读空闲
理由:移动设备需要保持NAT映射,服务端只需确认客户端是否在线
案例2:物联网数据采集
// 设备端(客户端)
pipeline.addLast(new IdleStateHandler(0, 60, 0, TimeUnit.SECONDS));
// 服务端
pipeline.addLast(new IdleStateHandler(180, 120, 0, TimeUnit.SECONDS));
理由:设备可能处于不稳定网络环境,需要双方向检测
总结
是否需要写空闲检测取决于具体场景:
- 大多数情况下:客户端需要写空闲检测(主动保活),服务端只需读空闲检测
- 高可靠性系统:建议使用双向检测
- 内网稳定环境:可能只需读空闲检测
最佳实践是根据实际网络条件和业务需求,选择适当的组合方式。对于公网应用,特别是移动端,写空闲检测通常是必要的。
Netty 心跳机制实现(客户端与服务端)的更多相关文章
- Netty实现客户端和服务端通信简单例子
Netty是建立在NIO基础之上,Netty在NIO之上又提供了更高层次的抽象. 在Netty里面,Accept连接可以使用单独的线程池去处理,读写操作又是另外的线程池来处理. Accept连接和读写 ...
- Netty 学习(一):服务端启动 & 客户端启动
Netty 学习(一):服务端启动 & 客户端启动 作者: Grey 原文地址: 博客园:Netty 学习(一):服务端启动 & 客户端启动 CSDN:Netty 学习(一):服务端启 ...
- Netty 学习(二):服务端与客户端通信
Netty 学习(二):服务端与客户端通信 作者: Grey 原文地址: 博客园:Netty 学习(二):服务端与客户端通信 CSDN:Netty 学习(二):服务端与客户端通信 说明 Netty 中 ...
- Netty4 学习笔记之二:客户端与服务端心跳 demo
前言 在上一篇Netty demo 中,了解了Netty中的客户端和服务端之间的通信.这篇则介绍Netty中的心跳. 之前在Mina 中心跳的使用是通过继承 KeepAliveMessageFacto ...
- Netty入门之客户端与服务端通信(二)
Netty入门之客户端与服务端通信(二) 一.简介 在上一篇博文中笔者写了关于Netty入门级的Hello World程序.书接上回,本博文是关于客户端与服务端的通信,感觉也没什么好说的了,直接上代码 ...
- Netty入门——客户端与服务端通信
Netty简介Netty是一个基于JAVA NIO 类库的异步通信框架,它的架构特点是:异步非阻塞.基于事件驱动.高性能.高可靠性和高可定制性.换句话说,Netty是一个NIO框架,使用它可以简单快速 ...
- linux epoll机制对TCP 客户端和服务端的监听C代码通用框架实现
1 TCP简介 tcp是一种基于流的应用层协议,其“可靠的数据传输”实现的原理就是,“拥塞控制”的滑动窗口机制,该机制包含的算法主要有“慢启动”,“拥塞避免”,“快速重传”. 2 TCP socket ...
- java在线聊天项目1.1版 ——开启多个客户端,分别实现注册和登录功能,使用客户端与服务端信息request机制,重构线程,将单独的登录和注册线程合并
实现效果图: eclipse项目中初步整合之前的各个客户端和服务端的窗口与工具类,效果如下图: 已将注册服务器线程RegServer功能放到LoginServer中,使用客户端与服务端的request ...
- netty-4.客户端与服务端心跳
(原) 第四篇,客户端与服务端心跳 心跳事件有三种,读空闲,写空闲,读写空闲,定义在了IdleState枚举类中,分别为READER_IDLE,WRITER_IDLE,ALL_IDLE 服务端: ma ...
- 连接管理 与 Netty 心跳机制
一.前言 踏踏实实,动手去做,talk is cheap, show me the code.先介绍下基础知识,然后做个心跳机制的Demo. 二.连接 长连接:在整个通讯过程,客户端和服务端只用一个S ...
随机推荐
- 天翼云发布边缘安全加速平台AccessOne,四大产品能力助力企业安全高速发展
本文分享自天翼云开发者社区<天翼云发布边缘安全加速平台AccessOne,四大产品能力助力企业安全高速发展>,作者:天翼云社区官方账号 2023年5月30日全国科技工作者日,以" ...
- 还堵在高速路上吗?带你进入Scratch世界带你飞
国庆假期高速路的风景 国庆假期正式启动人从众模式,无论是高速公路还是景区,不管是去程还是回程,每一次都堪称经典. 一些网友在经历漫长的拥堵后 哭笑不得地表示 "假期都在堵车中度过了" ...
- jenkins+gitee+tomcat
1.Jenkins [系统配置]添加gitee服务 2.项目配置 General 配置之前配置的gitee服务连接 3.源码配置 4.构建配置 5.构建触发器配置 最重要的是: 6.在gitee中配置 ...
- Windows 网络存储ISCSI
本文介绍网络存储ISCSI的主要知识点以及如何通过代码控制挂载. Windows网络存储有很多协议,我目前学习.稍微有了解的是FTP.SMB.ISCSI,FTP.SMB类似可以用来添加共享文件夹,或者 ...
- 如何在Spring Boot项目中添加国密SM4加密支持?——基于过滤器的实现
如何在Spring Boot项目中添加国密SM4加密支持呢?--基于过滤器的实现 引言 在数字化时代,数据安全至关重要,尤其是在API交互过程中,确保传输数据的安全性是保护隐私和机密信息的关键.中 ...
- 多项式算法初探:从 FFT 到 NTT
注:由于发现 FWT 解决的问题和 FFT,NTT 差别有点大,加之 FMT 的存在,本文就只解决 FFT 和 NTT,剩下两个放在别的算法总结里讲. 多项式一向是算法竞赛中相当博大精深的东西,作为一 ...
- linux报错-bash: ./xx.sh: Permission denied
linux报错-bash: ./xx.sh: Permission denied 在linux下执行sh文件时提示:-bash: ./xx.sh: Permission denied 进行授权:chm ...
- Java微信小程序登录接口获取openid
根据官方文档,wx.login()的回调函数中,需要我们传递生成的用户登录凭证到code2accessToken的接口中 小程序登录方法 code2accessToken的方法中要求传入如下参数 ...
- JS ellipse 转 PathData
绘制Path function ellipse2path(cx, cy, rx, ry, degree) { //cx cy:圆心 //rx ry:x y 轴长 //degree:度数,顺时针方向为正 ...
- Postman 接口测试工具详解
一.引言 在软件开发和测试过程中,接口测试是至关重要的环节.Postman 作为一款功能强大的接口测试工具,为开发者和测试人员提供了便捷高效的测试解决方案. 二.Postman 简介 Postman ...