坦克大戰 第8章 客戶端模組
阿新 • • 發佈:2018-11-30
非同步接收資料、粘包分包處理、 心跳、訊息分發功能
訊息分發 : 字串協議模型 //位元組流協議模型 //每幀處理訊息的數量 public int num = 15; //訊息列表 public List < ProtocolBase
//非同步Socket執行緒與Update方法不在同一執行緒中,為了避免執行緒競爭,使用lock(msgList)鎖住列表 public void Update() { for ( int i = 0; i < num; i++) { if (msgList.Count > 0) { DispatchMsgEvent(msgList[0]);//處理一條刪除一條,保證訊息列表不會太長 lock (msgList) msgList.RemoveAt(0); } else { break; } }
//網路連結模組 流程 Socket-->connect-->receive
//接收回調 private void ReceiveCb( IAsyncResult ar) { try { int count = socket.EndReceive(ar); //讀緩衝區的長度大於0 buffCount = buffCount + count; ProcessData(); socket.BeginReceive(readBuff, buffCount, BUFFER_SIZE - buffCount, SocketFlags.None, ReceiveCb, readBuff); } catch ( Exception e) { Debug .Log( "ReceiveCb失敗:" + e.Message); status = Status.None; } }
//訊息處理 private void ProcessData() { //小於長度位元組 if (buffCount < sizeof ( Int32 )) return; //以訊息長度拷貝到新陣列 Array .Copy(readBuff, lenBytes, sizeof ( Int32 )); //拿到訊息長度 msgLength = BitConverter.ToInt32(lenBytes, 0); //流裡的訊息產長度小於 一條消(包體+包頭) if (buffCount < msgLength + sizeof ( Int32 )) return; //處理訊息 ProtocolBase protocol = proto.Decode(readBuff, sizeof ( Int32 ), msgLength); Debug .Log( "收到訊息 " + protocol.GetDesc()); //新增進訊息集合 Update中一直在判斷訊息集合的長度 而訊息的出車一定要在Update執行之前 lock (msgDist.msgList) { msgDist.msgList.Add(protocol); } //清除已處理的訊息 int count = buffCount - msgLength - sizeof ( Int32 ); //從第一條訊息末尾拷貝 自己拷貝自己的資料長度 Array.Copy(readBuff,sizeof(Int32)+ msgLength, readBuff, 0, count); //讀緩衝區的長度大於0,呼叫本身,實現迴圈 buffCount = count; if (buffCount > 0) { ProcessData(); } }
//心跳 public void Update() { //訊息 msgDist.Update(); //如果在連結狀態下的話 if (status == Status .Connected) {//當前時間-上次傳送時>30 就傳送心跳協議 if ( Time .time - lastTickTime > heartBeatTime) { ProtocolBase protocol = NetMgr .GetHeatBeatProtocol(); Send(protocol); lastTickTime = Time.time; } } }
//心跳 public static ProtocolBase GetHeatBeatProtocol() { //具體的傳送內容根據服務端設定改動 ProtocolBytes protocol = new ProtocolBytes (); protocol.AddString("HeatBeat"); return protocol; }
註冊傳送協議 例: ProtocolBytes //位元組流協議模型 //傳送 ProtocolBytes protocol = new ProtocolBytes (); protocol.AddString("Register"); protocol.AddString(idInput.text); protocol.AddString(pwInput.text); Debug .Log( "傳送 " + protocol.GetDesc()); NetMgr.srvConn.Send(protocol, OnRegBack);
//傳送協議後,客戶端需要建廳服務端的返回,傳送之後OnRegBack將被呼叫 public void OnRegBack( ProtocolBase protocol) { ProtocolBytes proto = ( ProtocolBytes )protocol; int start = 0; string protoName = proto.GetString(start, ref start); int ret = proto.GetInt(start, ref start); if (ret == 0) { Debug.Log("註冊成功!"); PanelMgr.instance.OpenPanel<LoginPanel>(""); Close(); } else { Debug.Log("註冊失敗!"); } 還有一種情況(傳送之後返回的不是同名的協議,可以寫成( Send( ProtocolBase protocol,"LoginRet", MsgDistribution . Delegate cb)的形式,監聽服務端返回的 LoginRet
具體方法: public bool Send( ProtocolBase protocol, string cbName, MsgDistribution . Delegate cb) { if (status != Status .Connected) return false ; msgDist.AddOnceListener(cbName, cb);//新增進委託集合,協議鍵對應方法值 return Send(protocol); } )) //實際傳送訊息的方法 public bool Send( ProtocolBase protocol) { if (status != Status .Connected) { Debug.LogError("[Connection]還沒連結就傳送資料是不好的"); return true ; } byte[] b = protocol.Encode(); byte [] length = BitConverter .GetBytes(b.Length); byte[] sendbuff = length.Concat(b).ToArray(); socket.Send(sendbuff); Debug .Log( "傳送訊息 " + protocol.GetDesc()); return true ; }
客戶端接收協議
產生自己 拿到自己的位置旋轉,寫入流 傳送協議 //新增玩家 void AddPlayer( string id, Vector3 pos, int score) { GameObject player = ( GameObject )Instantiate(prefab, pos, Quaternion .identity); TextMesh textMesh = player.GetComponentInChildren< TextMesh >(); textMesh.text = id + ":" + score; players.Add(id, player); }
*******************************
GetList UpdateInfo 他們首先解析協議引數然後執行出來裡 NetMgr.srvConn.Send(proto, GetList); NetMgr.srvConn.msgDist.AddListener("UpdateInfo", UpdateInfo); 在指令碼開始執行的時候註冊進集合
//更新列表 public void GetList( ProtocolBase protocol) { ProtocolBytes proto = ( ProtocolBytes )protocol; //獲取頭部數值 int start = 0; string protoName = proto.GetString(start, ref start); int count = proto.GetInt(start, ref start); //遍歷 for ( int i = 0; i < count; i++) { string id = proto.GetString(start, ref start); float x = proto.GetFloat(start, ref start); float y = proto.GetFloat(start, ref start); float z = proto.GetFloat(start, ref start); int score = proto.GetInt(start, ref start); Vector3 pos = new Vector3 (x, y, z); UpdateInfo(id, pos, score); } } //更新資訊 public void UpdateInfo( ProtocolBase protocol) { //獲取數值 ProtocolBytes proto = ( ProtocolBytes )protocol; int start = 0; string protoName = proto.GetString(start, ref start); string id = proto.GetString(start, ref start); float x = proto.GetFloat(start, ref start); float y = proto.GetFloat(start, ref start); float z = proto.GetFloat(start, ref start); int score = proto.GetInt(start, ref start); Vector3 pos = new Vector3 (x, y, z); UpdateInfo(id, pos, score); }
//更新資訊 public void UpdateInfo( string id, Vector3 pos, int score) { //只更新自己的分數 if (id == playerID) { UpdateScore(id, score); return; } //其他人 //已經初始化該玩家 if (players.ContainsKey(id)) { players[id].transform.position = pos; UpdateScore(id, score); } //尚未初始化該玩家 else { AddPlayer(id, pos, score); } } 、 //玩家離開 public void PlayerLeave( ProtocolBase protocol) { ProtocolBytes proto = ( ProtocolBytes )protocol; //獲取數值 int start = 0; string protoName = proto.GetString(start, ref start); string id = proto.GetString(start, ref start); DelPlayer(id); }