1. 程式人生 > >坦克大戰 第8章 客戶端模組

坦克大戰 第8章 客戶端模組


非同步接收資料、粘包分包處理、 心跳、訊息分發功能
訊息分發 : 字串協議模型 //位元組流協議模型           //每幀處理訊息的數量     public int num = 15;     //訊息列表     public List < ProtocolBase
> msgList = new List < ProtocolBase >();     //委託型別     public delegate void Delegate ( ProtocolBase proto);     //訊息監聽表         private Dictionary
< string , Delegate > eventDict = new Dictionary < string , Delegate >();     private Dictionary < string , Delegate > onceDict = new Dictionary < string , Delegate >();         
      //訊息分發   區別呼叫onceDict的方法後悔清空對應的Key,使得“回掉一次註冊一次”,而eventDict沒有這種限制,“可以註冊一次終身執行”     public void DispatchMsgEvent( ProtocolBase protocol)     {         string name = protocol.GetName();         Debug .Log( "分發處理訊息 " + name);         if (eventDict.ContainsKey(name))         {             eventDict[name](protocol);         }         if (onceDict.ContainsKey(name))         {             onceDict[name](protocol);             onceDict[name] = null;             onceDict.Remove(name);         }     }


       //非同步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);     }