坦克大战 第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);
    }


猜你喜欢

转载自blog.csdn.net/Pandora12138/article/details/80997976