Netty实现心跳机制有一个专门的类 IdleStateHandler
public class IdleStateHandler extends ChannelDuplexHandler {
// 构造方法
public IdleStateHandler(int readerIdleTimeSeconds, int writerIdleTimeSeconds, int allIdleTimeSeconds) {
this((long)readerIdleTimeSeconds, (long)writerIdleTimeSeconds, (long)allIdleTimeSeconds, TimeUnit.SECONDS);
}
参数说明:
readerIdleTimeSeconds 读超时,规定时间内没有读到数据会触发读超时事件
writerIdleTimeSeconds 写超时,规定时间内没有写数据就会触发写超时事件
allIdleTimeSeconds 读写超时,规定时间内没有读写操作就会触发读写超时事件
IdleStateHandler 触发的事件对象 IdleStateEvent
public class IdleStateEvent {
public static final IdleStateEvent FIRST_READER_IDLE_STATE_EVENT;
public static final IdleStateEvent READER_IDLE_STATE_EVENT;
public static final IdleStateEvent FIRST_WRITER_IDLE_STATE_EVENT;
public static final IdleStateEvent WRITER_IDLE_STATE_EVENT;
public static final IdleStateEvent FIRST_ALL_IDLE_STATE_EVENT;
public static final IdleStateEvent ALL_IDLE_STATE_EVENT;
private final IdleState state;
private final boolean first;
protected IdleStateEvent(IdleState state, boolean first) {
this.state = (IdleState)ObjectUtil.checkNotNull(state, "state");
this.first = first;
}
每个事件对应一个枚举
public enum IdleState {
READER_IDLE,
WRITER_IDLE,
ALL_IDLE;
private IdleState() {
}
}
基于以上的机制 开始代码实现 首先实现心跳
客户端发送心跳的handler
package com.bowei.netty.heartbeat;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.ScheduledFuture;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class SendHeartHandler extends ChannelInboundHandlerAdapter {
private Random random= new Random();
private int baseRandom = 8;
private Channel channel;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
this.channel=ctx.channel();
send(channel);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
public void send(Channel channel){
int seaconds= Math.max(1,random.nextInt(baseRandom));
System.out.println("下次心跳将在"+seaconds+"秒后发送");
//定时向服务端发送心跳
ScheduledFuture<?> future = channel.eventLoop().schedule(new Runnable() {
@Override
public void run() {
if (channel.isActive()) {
System.out.println("通道活跃可以发送心跳");
channel.writeAndFlush("hello");
} else {
System.out.println("通道断开不能发送心跳");
channel.closeFuture();
throw new RuntimeException();
}
}
}, seaconds, TimeUnit.SECONDS);
//心态发送成功后继续发送
future.addListener(new GenericFutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if(future.isSuccess()){
send(channel);
}
}
});
}
}
客户端的ChannelInitializer
package com.bowei.netty.heartbeat;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import org.springframework.util.Assert;
public class ClientHandlersInitializer extends ChannelInitializer<SocketChannel> {
private ReconnectHandler reconnectHandler;
public ClientHandlersInitializer(TcpClient tcpClient){
Assert.notNull(tcpClient,"tcpClient can not be null");
// ReconnectHandler 只在初始化客户端的时候new一个新的,每个客户端都对应一个自己的 每个客户端的多个channel共享一个ReconnectHandler
this.reconnectHandler=new ReconnectHandler(tcpClient);
}
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(this.reconnectHandler);
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new SendHeartHandler());
}
}
客户端的启动类
package com.bowei.netty.heartbeat;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
public class TcpClient {
private String host;
private int port;
private Bootstrap bootstrap;
private Channel channel;
private RetryPolicy retryPolicy;
public TcpClient(String host, int port) {
//这个地方不能new new创造了一个新的TcpClient 其他的方法在用到这个类的属性的时候是没有初始化的
// new TcpClient(host,port,new ExponentialBackOffRetry(2,20,10));
this(host,port,new ExponentialBackOffRetry(1000, Integer.MAX_VALUE, 60 * 1000) );
}
public TcpClient(String host, int port,RetryPolicy retryPolicy){
this.host=host;
this.port=port;
this.retryPolicy=retryPolicy;
init();
}
private void init() {
EventLoopGroup loopGroup = new NioEventLoopGroup();
bootstrap = new Bootstrap();
bootstrap.group(loopGroup)
.channel(NioSocketChannel.class)
.handler(new ClientHandlersInitializer(TcpClient.this));
}
private ChannelFutureListener getConnectionListener() {
return new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
future.channel().pipeline().fireChannelInactive();
}
}
};
}
public void connect() {
synchronized (bootstrap) {
// bootstrap可以公用
ChannelFuture channelFuture = bootstrap.connect(host, port);
channelFuture.addListener(getConnectionListener());
this.channel = channelFuture.channel();
}
}
public RetryPolicy getRetryPolicy() {
return retryPolicy;
}
public static void main(String[] args) {
TcpClient tcpClient = new TcpClient("localhost", 6667);
tcpClient.connect();
}
}
服务器端处理心跳的handler
package com.bowei.netty.heartbeat;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
public class ServerIdleStateTrigger extends ChannelInboundHandlerAdapter {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if(evt instanceof IdleStateEvent){
IdleState state = ((IdleStateEvent) evt).state();
//读超时
if(state==IdleState.READER_IDLE){
// 在规定时间内没有受到客户端的心跳 主动断开链接
ctx.disconnect();
}else {
super.userEventTriggered(ctx, evt);
}
}
}
}
服务器端的ChannelInitializer
package com.bowei.netty.heartbeat;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
public class ServerHandlerInitializer extends ChannelInitializer {
@Override
protected void initChannel(Channel ch) throws Exception {
// 设置5s读超时
ch.pipeline().addLast("idleStateHandler", new IdleStateHandler(5, 0, 0));
ch.pipeline().addLast("serverIdleStateTrigger", new ServerIdleStateTrigger());
ch.pipeline().addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
ch.pipeline().addLast("frameEncoder", new LengthFieldPrepender(4));
ch.pipeline().addLast("decoder", new StringDecoder());
ch.pipeline().addLast("encoder", new StringEncoder());
ch.pipeline().addLast("bizHandler", new ServerHandler());
}
}
服务端的启动类
package com.bowei.netty.heartbeat;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class TcpServer {
private int port;
private ServerHandlerInitializer serverHandlerInitializer;
public TcpServer(int port) {
this.port = port;
this.serverHandlerInitializer = new ServerHandlerInitializer();
}
public void start() {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(this.serverHandlerInitializer);
// 绑定端口,开始接收进来的连接
ChannelFuture future = bootstrap.bind(port).sync();
System.out.println("服务端启动 开始监听端口 " + port);
future.channel().closeFuture().sync();
} catch (Exception e) {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
int port = 6667;
new TcpServer(port).start();
}
}
先启动服务端然后在启动客户端 打印日志如下
下面我们实现超时后的重新链接。超时后服务端主动断开客户端(或者客户端链接不上服务端的时候),会触发客户端的的
channelInactive 所以我们需要在这个地方实现的我们的重连
重连的接口,该接口定义了重连的各种策略,跟随业务变化,一般的重连都应该有 重连的最大次数,不应该是每次都是立即重连
可能这个时候服务器端繁忙,应等待一段时间然后在重连,一般随着重连次数的增加,等待的时间应该越来越长
package com.bowei.netty.heartbeat;
public interface RetryPolicy {
//是否可以重连
boolean allowRetry(int retryCount);
//获取重连需要等待的时间
long getSleepTimeMs(int retryCount);
}
package com.bowei.netty.heartbeat;
import java.util.Random;
public class ExponentialBackOffRetry implements RetryPolicy {
/**
* 最大可以重连的次数
*/
private static final int MAX_RETRY_LIMIT = 30;
/**
* 默认重连最长的等待时间
*/
private static final int DEFAULT_MAX_SLEEP_MS = Integer.MAX_VALUE;
private final Random random = new Random();
private final long baseSleepTimeMs;
private final int maxRetries;
private final int maxSleepMs;
public ExponentialBackOffRetry(int baseSleepTimeMs, int maxRetries) {
this(baseSleepTimeMs, maxRetries, DEFAULT_MAX_SLEEP_MS);
}
public ExponentialBackOffRetry(int baseSleepTimeMs, int maxRetries, int maxSleepMs) {
this.maxRetries = maxRetries;
this.baseSleepTimeMs = baseSleepTimeMs;
this.maxSleepMs = maxSleepMs;
}
@Override
public boolean allowRetry(int retryCount) {
if (retryCount < maxRetries) {
return true;
}
return false;
}
@Override
public long getSleepTimeMs(int retryCount) {
if (retryCount < 0) {
throw new IllegalArgumentException("重试次数必须大于0");
}
if(retryCount>MAX_RETRY_LIMIT){
System.out.println("重试次数已达上限");
retryCount = MAX_RETRY_LIMIT;
}
long sleepMs = baseSleepTimeMs * Math.max(1, random.nextInt(1 << retryCount));
if(sleepMs>maxSleepMs){
System.out.println("睡眠时间太长");
sleepMs=maxSleepMs;
}
return sleepMs;
}
}
重连的handler
package com.bowei.netty.heartbeat;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.EventLoop;
import java.util.concurrent.TimeUnit;
/*
* @Sharable 注解用来说明ChannelHandler可以在多个channel直接共享使用
* 每次失败重连都是一个新的管道Channel 多个管道要共享这个ReconnectHandler
*/
@ChannelHandler.Sharable
public class ReconnectHandler extends ChannelInboundHandlerAdapter {
private int retries=0;
private RetryPolicy retryPolicy;
private TcpClient tcpClient;
public ReconnectHandler(TcpClient tcpClient){
this.tcpClient=tcpClient;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端链接成功");
retries=0;
ctx.fireChannelActive();//作用:触发事件告知Inbound ChannelHandler:ChannelHandlerContext的Channel现在处于活动状态,调用ChannelInboundHandler的channelActive
}
private RetryPolicy getRetryPolicy(){
if(this.retryPolicy==null){
this.retryPolicy = tcpClient.getRetryPolicy();
}
return this.retryPolicy;
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if(retries==0){
System.out.println("未链接到服务端");
ctx.close();
}
boolean allowRetry = getRetryPolicy().allowRetry(retries);
if(allowRetry) {
long sleepTimeMs = getRetryPolicy().getSleepTimeMs(retries);
System.out.println("尝试去重试 重试次数" + ++retries + " 等待时间" + sleepTimeMs);
final EventLoop eventLoop = ctx.channel().eventLoop();
eventLoop.schedule(new Runnable() {
@Override
public void run() {
System.out.println("Reconnecting ...");
tcpClient.connect();
}
},sleepTimeMs,TimeUnit.MILLISECONDS);
}
//现在处于不活动状态,调用ChannelInboundHandler的channelInactive
ctx.fireChannelInactive();
}
}
先启动我们的客户端 服务端这时还咩有启动 客户端会不断的重试连接 然后在启动服务端