💓 项目八:心跳服务与断线重连
项目概述
项目名称:itstack-demo-netty-2-08
核心功能:实现心跳检测和断线自动重连机制
技术要点:IdleStateHandler、ChannelFutureListener、EventLoop调度
为什么需要这个项目?
实际问题:
- 长连接可能因网络波动、服务器重启等原因断开
- 无法及时发现连接已断开(半开连接)
- 连接断开后需要手动重连,用户体验差
- 需要保持连接存活,避免被防火墙关闭
解决方案:
- ✅ IdleStateHandler:自动检测空闲状态
- ✅ 心跳检测:定期发送心跳包,保持连接存活
- ✅ 自动重连:连接断开后自动重新连接
- ✅ 指数退避:避免频繁重连,保护服务器
适用场景:
- IM即时通讯(保持长连接,像微信一样)
- 物联网设备(设备与服务器保持心跳)
- 游戏服务器(玩家连接保活)
- 分布式系统(服务间心跳检测)
核心问题
为什么需要心跳和重连?
在实际应用中,网络连接可能因为各种原因断开(网络波动、服务器重启、超时等),需要:
- 心跳检测:定期检测连接是否存活
- 断线重连:连接断开后自动重新连接
核心知识点
1. 心跳检测机制(IdleStateHandler)
什么是IdleStateHandler?
Netty提供的空闲状态检测处理器,可以检测读空闲、写空闲、读写空闲。
Pipeline配置(服务端)
public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) {
// 心跳监测:2秒内没有读/写操作触发事件
channel.pipeline().addLast(new IdleStateHandler(2, 2, 2));
// 其他处理器
channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
channel.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));
channel.pipeline().addLast(new StringEncoder(Charset.forName("GBK")));
channel.pipeline().addLast(new MyServerHandler());
}
}
IdleStateHandler参数说明:
new IdleStateHandler(
2, // readerIdleTimeSeconds:2秒内没有读取到数据触发READER_IDLE
2, // writerIdleTimeSeconds:2秒内没有写入数据触发WRITER_IDLE
2 // allIdleTimeSeconds:2秒内没有读写操作触发ALL_IDLE
);
工作原理:
时间轴:
0s -----> 2s -----> 4s -----> 6s
| | | |
连接建立 读空闲 读空闲 读空闲
触发事件 触发事件 触发事件
2. 处理心跳事件
MyServerHandler.java
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof IdleStateEvent) {
IdleStateEvent e = (IdleStateEvent) evt;
if (e.state() == IdleState.READER_IDLE) {
// 读空闲:客户端2秒没发数据,可能掉线了
System.out.println("Reader Idle - 客户端可能掉线");
ctx.writeAndFlush("客户端你在吗?\r\n");
ctx.close(); // 关闭连接,触发客户端重连
} else if (e.state() == IdleState.WRITER_IDLE) {
// 写空闲:服务端2秒没发数据,发送心跳包
ctx.writeAndFlush("心跳包\r\n");
} else if (e.state() == IdleState.ALL_IDLE) {
// 读写都空闲
ctx.writeAndFlush("全部空闲检测\r\n");
}
}
}
三种空闲状态:
| 状态 | 触发条件 | 应用场景 |
|---|---|---|
| READER_IDLE | N秒内没有读取到数据 | 检测客户端是否掉线 |
| WRITER_IDLE | N秒内没有写入数据 | 主动发送心跳包 |
| ALL_IDLE | N秒内没有读写操作 | 综合检测 |
3. 断线重连机制
场景1:连接失败时重连(ChannelFutureListener)
// NettyClient.java
public void connect(String inetHost, int inetPort) {
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.AUTO_READ, true);
b.handler(new MyChannelInitializer());
ChannelFuture f = b.connect(inetHost, inetPort).sync();
f.addListener(new MyChannelFutureListener()); // 添加监听器
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
}
}
MyChannelFutureListener.java
public class MyChannelFutureListener implements ChannelFutureListener {
@Override
public void operationComplete(ChannelFuture channelFuture) {
if (channelFuture.isSuccess()) {
System.out.println("连接成功");
return;
}
// 连接失败,延迟1秒后重连
final EventLoop loop = channelFuture.channel().eventLoop();
loop.schedule(new Runnable() {
@Override
public void run() {
try {
new NettyClient().connect("127.0.0.1", 7397);
Thread.sleep(500);
} catch (Exception e) {
System.out.println("重连失败,继续尝试...");
}
}
}, 1L, TimeUnit.SECONDS); // 延迟1秒执行
}
}
场景2:连接断开时重连(channelInactive)
// MyClientHandler.java
@Override
public void channelInactive(ChannelHandlerContext ctx) {
System.out.println("断开链接,开始重连:" + ctx.channel().localAddress());
// 启动新线程进行重连
new Thread(new Runnable() {
@Override
public void run() {
try {
new NettyClient().connect("127.0.0.1", 7397);
System.out.println("重连成功");
Thread.sleep(500);
} catch (Exception e) {
System.out.println("重连失败,继续尝试...");
}
}
}).start();
}
4. 完整心跳重连流程
【正常心跳流程】
客户端 服务端
| |
|----连接建立------------>|
|<---"连接成功"-----------|
| |
| (2秒内无数据) |
| |--检测到READER_IDLE
|<---"客户端你在吗"-------|
| |--ctx.close()
| |
|--检测到channelInactive--|
| |
|--启动重连线程---------->|
|----重新连接------------>|
|<---"连接成功"-----------|
【连接失败重连流程】
客户端 服务端
| |
|----尝试连接------------>X (服务端未启动)
|
|--连接失败
|--触发MyChannelFutureListener
|--延迟1秒
|----重新尝试连接-------->X
|--继续重试...
项目总结
核心技术:
- ✅ IdleStateHandler:自动检测空闲状态
- ✅ userEventTriggered:处理空闲事件
- ✅ ChannelFutureListener:监听连接结果
- ✅ EventLoop.schedule:延迟重连
适用场景:
- IM即时通讯(保持长连接)
- 物联网设备(设备与服务器保持心跳)
- 游戏服务器(玩家连接保活)
- 分布式系统(服务间心跳检测)
注意事项:
- ⚠️ 每次重连都创建新的EventLoopGroup会导致资源泄漏
- ⚠️ 建议实现指数退避策略(1s, 2s, 4s, 8s…)
- ⚠️ 生产环境心跳间隔建议30秒以上
改进建议:
// 1. 复用EventLoopGroup
private static EventLoopGroup workerGroup = new NioEventLoopGroup();
// 2. 指数退避重连
int retryCount = 0;
long delay = Math.min(1 << retryCount, 60); // 最大60秒
loop.schedule(reconnectTask, delay, TimeUnit.SECONDS);
retryCount++;
2069



被折叠的 条评论
为什么被折叠?



