1. 定时发送心跳包
java
public class HeartbeatClientHandler extends ChannelInboundHandlerAdapter {
private static final ByteBuf HEARTBEAT =
Unpooled.copiedBuffer("HEARTBEAT", CharsetUtil.UTF_8);
@Override
public void channelActive(ChannelHandlerContext ctx) {
// 定时发送心跳
ctx.executor().scheduleAtFixedRate(() -> {
if (ctx.channel().isActive()) {
ctx.writeAndFlush(HEARTBEAT.duplicate());
}
}, 0, 5, TimeUnit.SECONDS);
}
}
客户端心跳发送的核心作用
1. 保持连接活跃
java
// 防止中间设备(如NAT、防火墙)断开空闲连接
// 在NAT环境下,长时间无数据会被路由器清除映射表
ctx.writeAndFlush(HEARTBEAT); // 定期发送,保持NAT映射
场景:移动网络、家庭路由器、公司防火墙会主动关闭长时间无数据的连接。
2. 检测服务端是否存活
java
// 客户端发送心跳并等待响应
ctx.writeAndFlush(ping).addListener(future -> {
if (!future.isSuccess()) {
// 发送失败,连接可能已断开
reconnect();
}
});
双端检测机制:
-
服务端检测客户端:通过
READER_IDLE -
客户端检测服务端:通过心跳响应
3. 快速发现网络故障
java
public class HeartbeatClientHandler extends ChannelInboundHandlerAdapter {
private int timeoutCount = 0;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (isPong(msg)) {
timeoutCount = 0; // 收到响应,重置计数器
}
}
// 定时发送心跳
private void scheduleHeartbeat() {
ctx.executor().schedule(() -> {
if (!receivedPong) {
timeoutCount++;
if (timeoutCount > 3) {
// 连续3次无响应,触发重连
reconnect();
}
}
}, 5, TimeUnit.SECONDS);
}
}
4. 维持会话状态
java
// 在某些协议中,心跳包可以携带附加信息
HeartbeatMessage heartbeat = HeartbeatMessage.builder()
.timestamp(System.currentTimeMillis())
.sessionId(sessionId)
.load(0.5) // 当前负载
.build();
ctx.writeAndFlush(heartbeat);
5. 实现重连机制
java
public class AutoReconnectHandler extends ChannelInboundHandlerAdapter {
private ScheduledFuture<?> reconnectFuture;
@Override
public void channelInactive(ChannelHandlerContext ctx) {
// 连接断开,启动重连
reconnectFuture = ctx.executor().scheduleAtFixedRate(
this::doReconnect,
1, // 1秒后开始重连
5, // 每5秒重试一次
TimeUnit.SECONDS
);
}
private void doReconnect() {
if (isConnected()) {
reconnectFuture.cancel(false);
} else {
bootstrap.connect();
}
}
}
具体应用场景分析
场景1:移动端APP长连接
java
// 移动网络下连接不稳定
new IdleStateHandler(0, 20, 0, TimeUnit.SECONDS);
// 客户端每20秒发送一次心跳
// 服务端60秒无读事件则断开连接
场景2:微服务间健康检查
java
// 客户端心跳包携带健康状态
HealthCheckHeartbeat heartbeat = new HealthCheckHeartbeat(
serviceId,
System.currentTimeMillis(),
HealthStatus.UP,
System.currentTimeMillis() - lastRequestTime
);
场景3:游戏客户端
java
// 游戏需要实时检测连接状态
// 心跳频率较高(如每秒1次)
ctx.executor().scheduleAtFixedRate(() -> {
GameHeartbeat heartbeat = new GameHeartbeat(
playerId,
sequence++,
getPlayerPosition()
);
ctx.writeAndFlush(heartbeat);
}, 0, 1, TimeUnit.SECONDS);
心跳策略建议
1. 智能心跳机制
java
public class AdaptiveHeartbeatHandler {
private long heartbeatInterval = 5000; // 初始5秒
// 根据网络状况调整心跳间隔
private void adjustHeartbeatInterval(boolean networkGood) {
if (networkGood) {
heartbeatInterval = Math.min(heartbeatInterval * 2, 30000); // 最大30秒
} else {
heartbeatInterval = Math.max(heartbeatInterval / 2, 1000); // 最小1秒
}
}
}
2. 心跳确认机制
java
// 需要服务端确认的心跳
AtomicLong lastPongTime = new AtomicLong();
// 发送心跳
ctx.writeAndFlush(ping).addListener(future -> {
if (future.isSuccess()) {
// 启动超时检测
ctx.executor().schedule(() -> {
if (System.currentTimeMillis() - lastPongTime.get() > 10000) {
// 10秒内未收到pong,认为连接异常
handleHeartbeatTimeout();
}
}, 10, TimeUnit.SECONDS);
}
});
// 接收pong响应
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (isPong(msg)) {
lastPongTime.set(System.currentTimeMillis());
}
}
注意事项
-
心跳频率权衡:
-
太频繁:浪费流量和CPU
-
太稀疏:无法及时检测断开
-
-
移动网络优化:
java
// 在移动网络下可以动态调整 if (isMobileNetwork()) { interval = 15000; // 移动网络15秒 } else { interval = 30000; // WiFi 30秒 } -
省电考虑(移动端):
java
// 应用进入后台时降低频率 public void onAppBackground() { heartbeatInterval = 60000; // 后台模式60秒一次 } -
与TCP Keepalive的区别:
-
TCP Keepalive:操作系统层面,默认2小时,不可控
-
应用层心跳:应用控制,可携带业务数据,更灵活
-
客户端心跳发送的本质是:在不可靠的网络环境中,通过主动的、周期性的探针来维持和验证连接的可靠性,为上层业务提供稳定的通信基础。