1. 程式人生 > >netty自定義心跳機制

netty自定義心跳機制

   在我的上一篇文章中已經介紹過了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;
}