服务端在一段时间内没有收到客户端的数据,这个现象产生的原因可以分为以下两种。
1.连接假死。
2.非假死状态下确实没有发送数据。
我们只需要排除第二种可能,那么连接自然就是假死的。要排查第二种情况,我们可以在客户端定期发送数据包到服务端,通常这个数据包被称为心跳数据包。我们定义一个Handler,定期发送心跳数据包给服务端。
客户端发送心跳数据给服务端 HeartBeatTimerHandler.java
package com.example.demo.im;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.concurrent.TimeUnit;
public class HeartBeatTimerHandler extends ChannelInboundHandlerAdapter {
private static final int HEARTBEAT_INTERVAL = 5;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
scheduleSendHeartBeat(ctx);
super.channelActive(ctx);
}
private void scheduleSendHeartBeat(ChannelHandlerContext ctx) {
ctx.executor().schedule(() -> {
if (ctx.channel().isActive()) {
ctx.writeAndFlush(new HeartBeatRequestPacket());
scheduleSendHeartBeat(ctx);
}
}, HEARTBEAT_INTERVAL, TimeUnit.SECONDS);
}
}
ctx.executor()方法返回的是当前Channel绑定的NIO线程。NIO线程有一个schedule()方法,类似JDK的延时任务机制,可以隔一段时间执行一个任务。这里实现了每隔5秒向服务端发送一个心跳数据包,这个间隔时间通常要比服务端的空闲检测时间的一半短一些,可以直接定义为空闲检测时间的三分之一,主要是为了排除公网偶发的秒级抖动。在实际生产环境中,发送心跳数据包间隔时间和空闲检测时间可以略长一些,设置为几分钟级别,具体应用可以具体对待,没有强制规定。
上面我们其实解决了服务端的空闲检测问题,服务端这个时候能够在一定时间段内关掉假死的连接,释放连接的资源,但是对于客户端来说,我们也需要检测假死的连接。
heartbeat请求消息
package com.example.demo.im;
public class HeartBeatRequestPacket extends Packet {
/**
* 指令
*
* @return
*/
@Override
public Byte getCmd() {
return Cmd.HEARTBEAT_REQUEST;
}
}
Cmd命令
package com.example.demo.im;
public interface Cmd {
Byte LOGIN_REQUEST = 1;
Byte LOGIN_RESPONSE = 2;
Byte MESSAGE_REQUEST = 3;
Byte MESSAGE_RESPONSE = 4;
Byte LOGOUT_REQUEST = 5;
Byte LOGOUT_RESPONSE = 6;
Byte CREATE_GROUP_REQUEST = 7;
Byte CREATE_GROUP_RESPONSE = 8;
Byte LIST_GROUP_MEMBERS_REQUEST = 9;
Byte LIST_GROUP_MEMBERS_RESPONSE = 10;
Byte JOIN_GROUP_REQUEST = 11;
Byte JOIN_GROUP_RESPONSE = 12;
Byte QUIT_GROUP_REQUEST = 13;
Byte QUIT_GROUP_RESPONSE = 14;
Byte GROUP_MESSAGE_REQUEST = 15;
Byte GROUP_MESSAGE_RESPONSE = 16;
Byte HEARTBEAT_REQUEST = 17;
Byte HEARTBEAT_RESPONSE = 18;
}
服务端回复心跳与客户端空闲检测
服务端回复客户端发送的心跳数据
HeartBeatRequestHandler
package com.example.demo.im;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.cha