在我的上一篇文章中已经介绍过了rts游戏的基本架构,下面来看一下心跳协议的使用
在netty中重写ChannelInboundHandlerAdapter的userEventTriggered方法可以实现心跳协议的检测,写起来也比较简单,网上的demo很多,但是这样的心跳协议都是基于channelHandler,如果我们的客户端使用的不是netty不是java,是c#或者其他语言呢,这个方法可能就不显得那么实用了。
心跳机制的作用是检测客户端和服务端之间的连接是否还存在,如果连接断开我们就要进行断线重连操作,基本原理就是每隔一个时间间隔客户端向服务端发送一个协议,服务端每隔一个时间间隔检测一次是否收到了客户端发送来的心跳协议,如果服务端没有收到我们就认为当前的tcp长连接已经断开,我们要清楚缓存中储存着这个连接的相关信息。
原理很简单,现在来看一下服务端代码:
/** * 心跳handler * @author miracle * */ public class HeartBeatReqHandler extends ChannelHandlerAdapter{ public static final Logger logger = LoggerFactory.getLogger(HeartBeatReqHandler.class); private boolean heart = false; @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { logger.error(ctx.channel().remoteAddress()+" 错误关闭"); cause.printStackTrace(); ctx.close(); } /** * 用于获取客户端发送的信息 */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //用于获取客户端发送的消息 RtsProtocal body = (RtsProtocal)msg; //logger.info("LobbyServer接受的客户端的信息:"+body.toString()); //如果是心跳协议 if(body.getType() == 2){ heart = true; }else{ //通知下一个channelHandler执行 ctx.fireChannelRead(msg); } } /** * 多个active同时存在的时候 * 根据handler注册的先后顺序active只在第一次 */ @Override public void channelActive(final ChannelHandlerContext ctx) throws Exception { final String sessionId = ctx.channel().id().asLongText(); LobbyManager lobbyManger = LobbyManager.getInstance(); lobbyManger.addChannel(sessionId, (SocketChannel)ctx.channel()); logger.info("HeartBeatReq active...1"); //当连接调通后将启动心跳检测机制,心跳的作用是通知对方我们之间的连接还存在着,如果收不到心跳协议那么 //客户端将会调用重练机制,这里为了简单使用内部类的方式 Runnable runnable = new Runnable(){ public void run() { while(true){ //如果heart 为true try { //每隔500毫秒检测一次 Thread.sleep(500); if(heart){ heart = false; }else{ //断开心跳 logger.info("为及时收到心跳协议,断开连接"); LobbyManager.getInstance().destroy(sessionId); ctx.close(); String abc = "小明"; byte[] by = abc.getBytes(); RtsProtocal scp = new RtsProtocal((byte)3,234,by.length,by); ctx.writeAndFlush(scp); return; } } catch (InterruptedException e) { e.printStackTrace(); } } } }; Thread thread = new Thread(runnable); thread.start(); } }
在lobbyManager中销毁掉需要销毁的对象
public class LobbyManager { public static final Logger logger = LoggerFactory.getLogger(LobbyManager.class); /** * 协议管理器 */ public static Map<Integer,Processors> protocolManager; /** * 客户端连接管理器 */ public static Map<String, SocketChannel> gateManager; /** * GameSession管理器 */ public static Map<String,LobbyGameSession> sessionManager; /** * 玩家管理器 */ public static Map<String,Player> playerManager;
/** * 销毁方法 */ public void destroy(String sessionId){ logger.info("连接断开! sessionId={}",sessionId); LobbyGameSession gameSession = sessionManager.get(sessionId); if(gameSession != null && !StringUtils.isEmpty(gameSession.getUuid())){ removePlayer(gameSession.getUuid()); } removeChannel(sessionId); logger.info("销毁完成!"); } /** * 玩家离线,将玩家从gateManager中删掉 * @param uuid */ public void removeChannel(String sessionId){ gateManager.remove(sessionId); //同时 删除掉玩家的session信息 sessionManager.remove(sessionId); } /** * 从玩家管理器中删除用户 * @param uuid */ public void removePlayer(String uuid){ playerManager.remove(uuid); } }
客户端一样也可以启动一个定时任务来执行心跳检测机制,每隔500毫秒向服务器发送一个心跳包,检测是否收到心跳包的回调,收不到表示网络断开
根据业务不同的需要我们可以自定义心跳包,我的心跳包是这样定义的,int类型的消息头,用来判断收到的协议是否是我们要的协议,byte类型的协议type 如果收到type == 2
表示是心跳协议,心跳协议不需要消息长度和消息内容,可以不用进行判断。
public class RtsProtocal { /** * 消息头,消息开始的标志 */ private int head_data = ConstantValue.HEAD_DATA; /** * 协议类型 * 1 登录相关协议(连接服务器检测,登录,注册...) * 2 心跳协议 * 3 大厅内协议(查看战绩,排行,查找,添加好友等...) * 4 游戏(创建房间,解散房间等) */ private byte type; /** * 协议号 */ private int protocloNumber; /** * 消息的长度 */ private int contentLength; /** * 消息的内容 */ private byte[] content; }