netty的心跳发送的重连,主要在client端。前面有关于自定义协议的demo:https://blog.csdn.net/zc_ad/article/details/83829620
其实客户端心跳发送用到的是IdleStateHandler,详细看代码你就会明白为什么。
大神链接:https://www.iteye.com/blog/user/jinnianshilongnian,https://blog.csdn.net/linuu/article/details/51509847
//处理空闲状态事件的处理器
pipeline.addLast(new IdleStateHandler(6,7,8, TimeUnit.SECONDS));
在IdleStateHandler中前三个参数分别是:
1.当在6秒内收到消息,触发IdleStateEvent事件。
2.当在7秒内没有发送消息,触发IdleStateEvent事件。
3.当在8内没有接收到数据且在8秒内没有发送数据,触发IdleStateEvent事件。
下面是实现的代码,代码量有点多....:
常量数据:
/**
* Created by XiChuan on 2018-11-07.
*/
public class Constant {
public static final int HEAD = 0x76;
public static final String TYPE_PING = "PING";
public static final String TYPE_MESSAGE = "MESSAGE";
}
自定义协议:
/**
* Created by XiChuan on 2018-11-07.
*/
import java.util.Arrays;
/**
* <pre>
* 自己定义的协议
* 数据包格式
* +——----——+——-----——+——----——+
* |协议开始标志| 消息类型长度 | 消息类型 | 数据长度 | 数据 |
* +——----——+——-----——+——----——+
* 1.协议开始标志head_data,为int类型的数据,16进制表示为0X76
* 2.要传的协议类型长度(String的byte[]长度)
* 3.要传的协议类型(String)
* 4.传输数据的长度contentLength,int类型
* 5.要传输的数据
* </pre>
*/
public class MessageProtocol {
/**
* 消息的开头的信息标志
*/
private int headData = Constant.HEAD;
/**
* 消息类型长度
*/
private int typeLength;
/**
* 消息类型
*/
private String type;
/**
* 消息的长度
*/
private int contentLength;
/**
* 消息的内容
*/
private byte[] content;
/**
* 用于初始化,SmartCarProtocol
*
* @param contentLength
* 协议里面,消息数据的长度
* @param content
* 协议里面,消息的数据
*/
public MessageProtocol(int typeLength,String type,int contentLength, byte[] content) {
this.typeLength = typeLength;
this.type = type;
this.contentLength = contentLength;
this.content = content;
}
public int getHeadData() {
return headData;
}
public void setHeadData(int headData) {
this.headData = headData;
}
public int getTypeLength() {
return typeLength;
}
public void setTypeLength(int typeLength) {
this.typeLength = typeLength;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public int getContentLength() {
return contentLength;
}
public void setContentLength(int contentLength) {
this.contentLength = contentLength;
}
public byte[] getContent() {
return content;
}
public void setContent(byte[] content) {
this.content = content;
}
@Override
public String toString() {
return "MessageProtocol " +
"[head_data=" + headData
+", typeLength="+typeLength
+", type="+type
+ ", contentLength=" + contentLength
+ ", content=" + Arrays.toString(content) + "]";
}
}
定义解码器:
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
/**
* Created by XiChuan on 2018-11-06.
*/
public class MessageProtocolDecoder extends ByteToMessageDecoder {
/**
* <pre>
* 协议开始的标准head_data,int类型,占据4个字节.
* 表示数据的长度contentLength,int类型,占据4个字节.
* </pre>
*/
public final int BASE_LENGTH = 4 + 4;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer,
List<Object> out) throws Exception {
// 可读长度必须大于基本长度
//System.out.println("buff的可读长度是:"+buffer.readableBytes());
if (buffer.readableBytes() >= BASE_LENGTH) {
// 防止socket字节流攻击
// 防止,客户端传来的数据过大
// 因为,太大的数据,是不合理的
if (buffer.readableBytes() > 2048) {
buffer.skipBytes(buffer.readableBytes());
}
// 记录包头开始的index
int beginReader;
while (true) {
// 获取包头开始的index
beginReader = buffer.readerIndex();
//System.out.println("记录包头开始的index:"+beginReader);
// 标记包头开始的index
buffer.markReaderIndex();
// 读到了协议的开始标志,结束while循环
int head = buffer.readInt();
//System.out.println("读取的int:"+head);
if (head == Constant.HEAD) {
break;
}
// 未读到包头,略过一个字节
// 每次略过,一个字节,去读取,包头信息的开始标记
buffer.resetReaderIndex();
buffer.readByte();
// 当略过,一个字节之后,
// 数据包的长度,又变得不满足
// 此时,应该结束。等待后面的数据到达
if (buffer.readableBytes() < BASE_LENGTH) {
return;
}
}
//类型长度
int typeLength = buffer.readInt();
//消息类型
byte[] typeBytes = new byte[typeLength];
buffer.readBytes(typeBytes);
String type = new String(typeBytes);
// 内容长度
int dataLength = buffer.readInt();
// 读取data数据
byte[] data = new byte[dataLength];
buffer.readBytes(data);
MessageProtocol protocol = new MessageProtocol(typeLength,type,dataLength, data);
out.add(protocol);
}
}
}
定义编码器:
import ch.qos.logback.core.encoder.ByteArrayUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
/**
* Created by XiChuan on 2018-11-07.
*/
public class MessageProtocolEncoder extends MessageToByteEncoder<MessageProtocol> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, MessageProtocol msg, ByteBuf byteBuf) throws Exception {
// 写入消息SmartCar的具体内容
// 1.写入消息的开头的信息标志(int类型)
byteBuf.writeInt(msg.getHeadData());
//2写入消息类型长度信息(int)
byteBuf.writeInt(msg.getTypeLength());
//3写入消息类型(byte[])
byteBuf.writeBytes(msg.getType().getBytes());
// 4.写入消息的长度(int 类型)
byteBuf.writeInt(msg.getContentLength());
// 5.写入消息的内容(byte[]类型)
byteBuf.writeBytes(msg.getContent());
}
}
定义服务端:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.bytes.ByteArrayDecoder;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import java.util.concurrent.TimeUnit;
/**
* Created by XiChuan on 2018-11-05.
*/
public class Server {
private int port;
public Server(int port){this.port = port;}
public void run()throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup(); //用来接收进来的连接
EventLoopGroup workerGroup = new NioEventLoopGroup(); //用来处理已经被接收的连接
try {
ServerBootstrap bootstrap = new ServerBootstrap(); //启动NIO服务的辅助启动类
bootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class) //服务端
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//心跳机制 参数:1.读空闲超时时间 2.写空闲超时时间 3.所有类型的空闲超时时间(读、写) 4.时间单位
//在Handler需要实现userEventTriggered方法,在出现超时事件时会被触发
socketChannel.pipeline().addLast("idleStateHandler", new IdleStateHandler(10, 0, 0,TimeUnit.SECONDS));
//设置解码器
socketChannel.pipeline().addLast("decoder", new MessageProtocolDecoder());//new ByteArrayDecoder());//new FixedLengthFrameDecoder(4));
//设置自定义ChannelHandler
socketChannel.pipeline().addLast("channelHandler", new ServerHandler());
//设置编码器
socketChannel.pipeline().addLast("encoder",new MessageProtocolEncoder());//new ByteArrayEncoder());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture cf = bootstrap.bind(port).sync(); //绑定端口,开始接收进来的连接
cf.channel().closeFuture().sync(); //等待服务器socket关闭
}catch (Exception e){
e.printStackTrace();
}finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args)throws Exception{
new Server(8081).run();
}
}
定义服务段处理handler:
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.ssl.SslHandshakeCompletionEvent;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by XiChuan on 2018-11-05.
*/
public class ServerHandler extends ChannelInboundHandlerAdapter {
private AtomicInteger channelCount = new AtomicInteger(0); //通道数量
/**
* 读数据
* @param ctx
* @param msg
* @throws Exception
*/
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("read channel=" + ctx.channel() + ", total channel=" + channelCount);
try {
MessageProtocol message = (MessageProtocol)msg;
System.out.println("receive client message:"+message.toString()+",ip:"+ctx.channel().remoteAddress());
} finally {
// 抛弃收到的数据
ReferenceCountUtil.release(msg);
}
}
/**
* 心跳检测的超时时会触发
* @param ctx
* @param evt
* @throws Exception
*/
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent e = (IdleStateEvent) evt;
if (e.state() == IdleState.READER_IDLE) { //读取心跳超时后,会将此channel连接断开
System.out.println("trigger channel =" + ctx.channel());
ctx.close(); //如果超时,关闭这个通道
}
} else if (evt instanceof SslHandshakeCompletionEvent) {
System.out.println("ssl handshake done");
//super.userEventTriggered(ctx,evt);
}
}
/**
* 当通道活动时
* @param ctx
* @throws Exception
*/
public void channelActive(ChannelHandlerContext ctx) throws Exception {
channelCount.incrementAndGet();//当有新通道连接进来时,将通道数+1
System.out.println("active channel=" + ctx.channel() + ", total channel=" + channelCount + ", id=" + ctx.channel().id().asShortText());
}
/**
* 当通道不活动时
* @param ctx
* @throws Exception
*/
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
//当通道关闭时,将通道数-1
ctx.close();
channelCount.decrementAndGet();
System.out.println("inactive channel,channel=" + ctx.channel() +", id=" + ctx.channel().id().asShortText());
}
/**
* 异常获取
* @param ctx
* @param cause
* @throws Exception
*/
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("exception channel=" + ctx.channel() + " cause=" + cause); //如果不活跃关闭此通道
ctx.close();
}
}
后面主要就是客户端代码了,主要就在这里:
定义客户端的ChannelHandler集合:
import io.netty.channel.ChannelHandler;
/**
*
* 客户端的ChannelHandler集合,由子类实现,这样做的好处:
* 继承这个接口的所有子类可以很方便地获取ChannelPipeline中的Handlers
* 获取到handlers之后方便ChannelPipeline中的handler的初始化和在重连的时候也能很方便
* 地获取所有的handlers
*/
public interface ChannelHandlerHolder {
ChannelHandler[] handlers();
}
定义客户端重连检测代码:
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.util.Timeout;
import io.netty.util.Timer;
import io.netty.util.TimerTask;
import java.util.concurrent.TimeUnit;
/**
*
* 重连检测狗,当发现当前的链路不稳定关闭之后,进行12次重连
*/
@Sharable
public abstract class ConnectionWatchdog extends ChannelInboundHandlerAdapter implements TimerTask ,ChannelHandlerHolder{
private final Bootstrap bootstrap;
private final Timer timer;
private final int port;
private final String host;
private volatile boolean reconnect = true;
private int attempts;
public ConnectionWatchdog(Bootstrap bootstrap, Timer timer, int port,String host, boolean reconnect) {
this.bootstrap = bootstrap;
this.timer = timer;
this.port = port;
this.host = host;
this.reconnect = reconnect;
}
/**
* channel链路每次active的时候,将其连接的次数重新☞ 0
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("当前链路已经激活了,重连尝试次数重新置为:0");
attempts = 0;
ctx.fireChannelActive();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("链接关闭");
if(reconnect){
System.out.println("链接关闭,将进行重连,第"+(attempts+1)+"尝试");
if (attempts < 12) {
attempts++;
//重连的间隔时间会越来越长
int timeout = 2 << attempts;
timer.newTimeout(this, timeout, TimeUnit.MILLISECONDS);
}
}
ctx.fireChannelInactive();
}
public void run(Timeout timeout) throws Exception {
ChannelFuture future;
//bootstrap已经初始化好了,只需要将handler填入就可以了
synchronized (bootstrap) {
bootstrap.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
//handlers()方法在client住方法上已经初始化了
ch.pipeline().addLast(handlers());
}
});
future = bootstrap.connect(host,port);
}
//future对象
future.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture f) throws Exception {
boolean succeed = f.isSuccess();
//如果重连失败,则调用ChannelInactive方法,再次出发重连事件,一直尝试12次,如果失败则不再重连
if (!succeed) {
System.out.println("重连失败");
f.channel().pipeline().fireChannelInactive();
}else{
System.out.println("重连成功");
}
}
});
}
}
定义客户端:
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.HashedWheelTimer;
import java.util.concurrent.TimeUnit;
/**
* Created by XiChuan on 2018-11-05.
*/
public class Client {
protected final HashedWheelTimer timer = new HashedWheelTimer();
private Bootstrap b;
private String host;
private int port;
public Client(String host,int port){
this.host = host;
this.port = port;
}
public void run() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class).handler(new LoggingHandler(LogLevel.INFO));
final ConnectionWatchdog watchdog = new ConnectionWatchdog(b, timer, port,host, true) {
public ChannelHandler[] handlers() {
return new ChannelHandler[] {
this,
new IdleStateHandler(0, 4, 0, TimeUnit.SECONDS),
//idleStateTrigger,
new MessageProtocolDecoder(),
new MessageProtocolEncoder(),
new ClientHandler()
};
}
};
ChannelFuture future;
//进行连接
try {
synchronized (b) {
b.handler(new ChannelInitializer<Channel>() {
//初始化channel
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(watchdog.handlers());
}
});
future = b.connect(host,port);
}
// 以下代码在synchronized同步块外面是安全的
future.sync();
//future.channel().closeFuture().sync();
} catch (Throwable t) {
throw new Exception("connects to fails", t);
}finally {
//group.shutdownGracefully();
//System.out.println("client release resource...");
}
}
public static void main(String[] args) throws Exception {
new Client("127.0.0.1",8081).run();
}
}
定义客户端handler:
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.ReferenceCountUtil;
import java.util.Date;
/**
* Created by XiChuan on 2018-11-05.
*/
public class ClientHandler extends ChannelInboundHandlerAdapter {
// 客户端与服务端,连接成功的的处理
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("active channel:" + ctx.channel()+",time:"+new Date().toLocaleString());
ctx.fireChannelActive();
/*// 发送自定义Message协议的消息
// 要发送的信息
String type = Constant.TYPE_PING;
int typeLength = type.getBytes().length;
String str = "I am client ...";
// 获得要发送信息的字节数组
byte[] content = str.getBytes();
// 要发送信息的长度
int contentLength = content.length;
MessageProtocol protocol = new MessageProtocol(typeLength,type,contentLength, content);
System.out.println("send message:"+protocol.toString());
Channel channel = ctx.channel();
channel.writeAndFlush(protocol);*/
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("inactive channel:" + ctx.channel()+",time:"+new Date().toLocaleString());
}
// 只是读数据,没有写数据的话
// 需要自己手动的释放的消息
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
System.out.println("read channel:" + ctx.channel());
try {
MessageProtocol messageProtocol = (MessageProtocol) msg;
System.out.println("receive server message:" + messageProtocol.toString());
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.close();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleState state = ((IdleStateEvent) evt).state();
if (state == IdleState.WRITER_IDLE) {
// write heartbeat to server
String message = "heart beat ...";
MessageProtocol messageProtocol = new MessageProtocol(
Constant.TYPE_PING.getBytes().length,
Constant.TYPE_PING,
message.getBytes().length,
message.getBytes());
ctx.writeAndFlush(messageProtocol);
}
} else {
super.userEventTriggered(ctx, evt);
}
}
}
上述是全部的代码,先运行server然后运行client,效果如下:
服务端:
客户端:
当关闭服务端,客户端日志:
此心跳检测机制与重连机制参考大佬的代码:https://blog.csdn.net/linuu/article/details/51509847
————————————————
版权声明:本文为CSDN博主「兮川」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zc_ad/article/details/83859573