心跳与断线重连主要解决客户端连接时出现的三种问题:
(1)client初次连接失败,需要重连
(2)server进程关闭,client检测tcp断开重连
(3)server断电,client通过心跳检测重连
(注:所有情况都默认是在单一server的情况下实现的,即暂时不考虑多服务器多channel的负载均衡实现)
使用ConnectionListener
b.connect().addListener(new ConnectionListener());
覆盖operationComplete方法
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (!channelFuture.isSuccess()) {
System.out.println(Thread.currentThread());
System.out.println("Reconnect");
RpcClient.client.times++;
if(RpcClient.client.times==5) {
RpcClient.client.flag=false;
return;
}
final EventLoop loop = channelFuture.channel().eventLoop();
loop.schedule(new Runnable() {
@Override
public void run() {
RpcClient.client.connect(loop);
}
}, 1L, TimeUnit.SECONDS);
}
}
如果连接不成功,在1s后在channel所在eventloop执行重新连接
并且设置当前times数量加一,如果已失败四次,则不考虑重连,停止进行“递归”调用。同时设置flag为false,通知consumer线程出现异常
public class RpcClient:
public static volatile boolean flag=true;
public void send(byte[] bytes) throws Exception {
while(channel==null){
if(!flag){
throw new Exception("Can't connect to server");
}
}
ByteBuf firstMessage=Unpooled.directBuffer();
firstMessage.writeInt(bytes.length);
firstMessage.writeBytes(bytes);
channel.writeAndFlush(firstMessage);
}
consumer线程轮询channel与flag
server进程关闭时,操作系统会释放掉进程的资源,tcp进行挥手操作断开连接,ChannelInboundHandlerAdapter会检测到连接断开,并调用覆盖的channelInactive方法。
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
final EventLoop eventLoop = ctx.channel().eventLoop();
eventLoop.schedule(new Runnable() {
@Override
public void run() {
RpcClient.client.connect(eventLoop);
}
}, 1L, TimeUnit.SECONDS);
super.channelInactive(ctx);
}
channelInactive调用connect(),进入初次连接的流程
如果重新连接成功,则重新发送requestCache中的请求
int cacheSize;
if((cacheSize=rpcClient.requestCache.size())>0){
for(Map.Entry entry:rpcClient.requestCache.entrySet()){
try {
rpcClient.send(entry.getValue());
} catch (Exception e) {
e.printStackTrace();
}
}
}
事实上,心跳检测分为两种:
(1)client向server发送心跳检测,channel短时间没有收到server的消息,就会向server发送心跳包。这是发生比较频繁的情况,防止server断电。
(2)server对client的存活检测,如果超过client心跳检测间隔时间,却没有收到心跳信息,则默认为client断电关闭,关闭对应channel。
下面看下client端具体实现:
client加入netty自带的IdleStateHandler,检测channel读超时,主要关注读超时参数,这里笔者设置成了240s,是RFC规范MSL(max segment lifetime)——2min的2倍。
ch.pipeline().addLast(new IdleStateHandler(240, 1000, 1000, TimeUnit.SECONDS));
若发生读超时的事件,则回调userEventTriggered方法
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
super.userEventTriggered(ctx, evt);
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state().equals(IdleState.READER_IDLE)) {
System.out.println("No message from server for 5s");
RpcClient.client.times++;
if(RpcClient.client.times==3){
RpcClient.client.times=0;
ctx.channel().close();
channelInactive(ctx);
}
ByteBuf heartbeat=Unpooled.directBuffer();
heartbeat.writeBytes(this.heartbeat);
ctx.channel().writeAndFlush(heartbeat);
} else if (event.state().equals(IdleState.WRITER_IDLE)) {
} else if (event.state().equals(IdleState.ALL_IDLE)) {
}
}
}
若已经发送三次心跳包,则调用channelInactive()进行重连,并关闭原有channel
heartbeat实现如下:
private byte[] heartbeat;
ByteBuf heartbeat=Unpooled.directBuffer();
byte ping=0;
heartbeat.writeInt(1);
heartbeat.writeByte(ping);
this.heartbeat=new byte[heartbeat.readableBytes()];
heartbeat.readBytes(this.heartbeat);
heartbeat是只有1byte的信息,由option(ChannelOption.TCP_NODELAY,true)保证发送
在server接收时
if(request.length==1){
ByteBuf heartbeat=Unpooled.directBuffer();
heartbeat.writeBytes(this.heartbeat);
ctx.channel().writeAndFlush(heartbeat);
System.out.println("ping");
}
判断request长度,如果只为1byte则为收到心跳包
下面看下server端具体实现:
同样加入IdleStateHandler
ch.pipeline().addLast(new IdleStateHandler(360, 1000, 1000, TimeUnit.SECONDS));
if (event.state().equals(IdleState.READER_IDLE)) {
ctx.channel().close();
如果发生读超时,则默认client关闭,关闭对应channel
另:
==写了一大段connect的分析被吞了,不想再写了,就附上两个非常好的netty解析教程吧
beautyboss.farbox.com/post/netty/nettylian-jie-chao-shi-fen-xi
https://www.jianshu.com/p/f1227ee26088
多写一句,ConnectListener不会提前运行的关键在于,connect()过程成功||超时||发生异常,才会调用ChannelFuture的trySuccess或tryFailure,唤醒ChannelFuture绑定的listeners
la-rpc项目地址:
https://github.com/bysoul/la-rpc