Unity组件开发--长连接webSocket

1.下载安装UnityWebSocket 插件

https://gitee.com/cambright/UnityWebSocket/

引入unity项目:

Unity组件开发--长连接webSocket_第1张图片

2.定义消息体结构:ExternalMessage和包结构Package:

using ProtoBuf;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace UTNET
{

    [ProtoContract]
    public class ExternalMessage
    {
        [ProtoMember(1)]
        // 请求命令类型: 0 心跳,1 业务
        public int cmdCode;
        // 协议开关,用于一些协议级别的开关控制,比如 安全加密校验等。 : 0 不校验
        [ProtoMember(2)]
        public int protocolSwitch;
        // 业务路由(高16为主, 低16为子)
        [ProtoMember(3)]
        public uint cmdMerge;
        // 响应码: 0:成功, 其他为有错误
        [ProtoMember(4,DataFormat = DataFormat.ZigZag)]
        public int responseStatus;
        // 验证信息(错误消息、异常消息),通常情况下 responseStatus == -1001 时, 会有值
        [ProtoMember(5)]
        public string validMsg;
        // 业务请求数据
        [ProtoMember(6)]
        public byte[] data;
        // 消息标记号;由前端请求时设置,服务器响应时会携带上;
        [ProtoMember(7, DataFormat = DataFormat.ZigZag)]
        public int msgId;
    }


    [ProtoContract]
    public class Package
    {
        [ProtoMember(1)]
        public uint packageType;

        [ProtoMember(2)]
        public string route = null;

        [ProtoMember(3)]
        public uint packID = 0;

        [ProtoMember(4)]
        public byte[] buff = null;

        [ProtoMember(5)]
        public string modelName = null;
    }

    //[ProtoContract]
    //public class Message
    //{
    //    [ProtoMember(1)]
    //    public uint err;
    //    [ProtoMember(2)]
    //    public string errMsg = default;
    //    [ProtoMember(3)]
    //    public T data = default;
    //    public Message() { }
    //    public Message(uint err, string errMsg, T info)
    //    {
    //        this.err = err;
    //        this.errMsg = errMsg;
    //        this.data = info;
    //    }
    //}

    [ProtoContract]
    public class HandShake
    {
        [ProtoMember(1)]
        public string token;
    }

    [ProtoContract]
    public class Heartbeat
    {
        [ProtoMember(1)]
        public uint heartbeat;
    }
}

3.定义包协议结构:PackageProtocol

using ProtoBuf;
using System;
using System.IO;
using System.Text;
using UnityEngine.XR;

namespace UTNET
{
    public enum PackageType
    {
        HEARTBEAT = 1,
        REQUEST = 2,
        PUSH = 3,
        KICK = 4,
        RESPONSE = 5,
        HANDSHAKE = 6,
        ERROR = 7,
        NOTIFY = 8
    }

    public class PackageProtocol
    {
        //获取cmd
        public static uint getCmd(uint merge)
        {
            return merge >> 16;
        }
        //获取subCmd
        public static uint getSubCmd(uint merge)
        {
            return merge & 0xFFFF;
        }
        //获取mergeCmd
        public static uint getMergeCmd(uint cmd, uint subCmd)
        {
            return (cmd << 16) + subCmd;
        }
        public static byte[] Encode(PackageType type)
        {
            Package sr = new Package()
            {
                packageType = (uint)type
            };
            return Serialize(sr);
        }

        public static byte[] Encode(PackageType type, uint packID, string route, T info, string modelName = null)
        {
            Package sr = new Package()
            {
                packageType = (uint)type,
                packID = packID,
                route = route,
                buff = Serialize(info),
                modelName = modelName
            };
            return Serialize(sr);
        }


        public static byte[] EncodeEx(uint packID, uint cmdMerge, T info)
        {
            //Package sr = new Package()
            //{
            //    packageType = (uint)type,
            //    packID = packID,
            //    route = route,
            //    buff = Encoding.Default.GetBytes(SerializeEx(info)),
            //};
            //return SerializeEx(sr);

            ExternalMessage sr = new ExternalMessage()
            {
                cmdCode = 100,
                protocolSwitch = 1,
                cmdMerge = cmdMerge,
                responseStatus = 0,
                validMsg = "",
                data = Encoding.UTF8.GetBytes(SerializeEx(info)),
            };
            return Serialize(sr);

        }



        public static byte[] Encode(uint packID, uint cmdMerge, T info)
        {
            ExternalMessage sr = new ExternalMessage()
            {
                cmdCode = 100,
                protocolSwitch = 1,
                cmdMerge = cmdMerge,
                responseStatus = 0,
                validMsg = "",
                data = Serialize(info),
                msgId = (int)packID,
            };
            return Serialize(sr);
        }

        public static string SerializeEx(T t)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                Serializer.Serialize(ms, t);
                return Encoding.UTF8.GetString(ms.ToArray());
            }
        }


        public static byte[] Serialize(T info)
        {
            if (typeof(T) == typeof(int))
            {
                int value = (int)Convert.ChangeType(info, typeof(int));
                value = (value << 1);
                info = (T)Convert.ChangeType(info, typeof(T));
            }

            MemoryStream ms = new MemoryStream();
            Serializer.Serialize(ms, info);
            byte[] buff = ms.ToArray();
            ms.Close();
            return buff;

            //try
            //{
            //    //涉及格式转换,需要用到流,将二进制序列化到流中
            //    using (MemoryStream ms = new MemoryStream())
            //    {
            //        //使用ProtoBuf工具的序列化方法
            //        Serializer.Serialize(ms, info);
            //        //定义二级制数组,保存序列化后的结果
            //        byte[] result = new byte[ms.Length];
            //        //将流的位置设为0,起始点
            //        ms.Position = 0;
            //        //将流中的内容读取到二进制数组中
            //        ms.Read(result, 0, result.Length);
            //        return result;
            //    }
            //}
            //catch (Exception ex)
            //{
            //    return null;
            //}



        }

        public static ExternalMessage Decode(byte[] buff)
        {
            //protobuf反序列化
            MemoryStream mem = new MemoryStream(buff);
            ExternalMessage rs = Serializer.Deserialize(mem);
            mem.Close();
            return rs;
        }
        public static T DecodeInfo(byte[] buff)
        {
            if (buff == null) return default;
            T rs;

            if (typeof(T) == typeof(int))
            {
                int value;
                using (var stream = new MemoryStream(buff))
                {
                    value = Serializer.Deserialize(stream);
                }//转zig zag
                value = (value >> 1) ^ -(value & 1);

                rs = (T)Convert.ChangeType(value, typeof(T));
            }
            else
            {
                MemoryStream mem = new MemoryStream(buff);
                rs = Serializer.Deserialize(mem);
                mem.Close();
            }

            return rs;
        }



        //public static T MyMethod(byte[] buff) where T : Object
        //{
        //    return null;
        //}



    }
}

4.引入UniTask插件:UniTask中文使用指南(一) - 知乎

UniTask保姆级教程_unitask安装-CSDN博客

项目地址:GitHub - Cysharp/UniTask: Provides an efficient allocation free async/await integration for Unity.

Unity组件开发--长连接webSocket_第2张图片

5.定义协议索引管理,添加长连接的协议:ProtoMaps





using System;
using System.Collections.Generic;


namespace UTNET
{


    public interface IProto
    {

    }



    //R ��������, S ��������
    //public class Proto : IProto
    public class Proto : IProto
    {
        public string name; //������
        public uint mid { get; set; } //��id
        public uint sid { get; set; } //��id

        public uint mergeid;
        public int typ; //������
        //public R r;
        //public S s;

        public Proto(string name, uint mid, uint sid, int typ)
        {
            this.name = name;
            this.mid = mid;
            this.sid = sid;
            this.typ = typ;
        }
    }

    public class ProtoMaps
    {

        private static readonly ProtoMaps instance = new ProtoMaps();


        public static ProtoMaps Instance
        {
            get { return instance; }
        }


        //public Dictionary protos = new Dictionary();

        public Dictionary protos = new Dictionary();

        public Dictionary id2proto = new Dictionary();


        public void Add(string name, Proto pb)
        {
            protos.Add(name, pb);
        }

        public void SortById()
        {
            foreach (var item in protos)
            {
                var pb = item.Value;
                uint mergeid = PackageProtocol.getMergeCmd(pb.mid, pb.sid);
                pb.mergeid = mergeid;
                id2proto.Add(mergeid, pb);
            }
        }


        public ProtoMaps()
        {
            this.Add("login", new Proto("login", 1, 2, 1));
            this.Add("registerInfo", new Proto("registerInfo", 1, 1, 1));
            this.Add("enterRoom", new Proto("enterRoom", 1, 13, 3));
            this.Add("roleMove", new Proto("roleMove", 4, 3, 3));
            //this.Add("onNewRoleEnter", new Proto("onNewRoleEnter", 7, 1, 2));
            //服务器主动广播的角色退出
            this.Add("roleExitRoom", new Proto("roleExitRoom", 7, 2, 2));
            this.Add("talkMrg", new Proto("talkMrg", 1, 9, 3));
           
            //玩家点击的角色退出
            this.Add("playerExitRoom", new Proto("playerExitRoom", 1, 5, 3));


            this.Add("gameFrame", new Proto("gameFrame", 4, 2, 1));
            this.Add("gameObjUsed", new Proto("gameObjUsed", 4, 4, 3));

            this.Add("getOtherInfo", new Proto("getOtherInfo", 1, 23, 1));

            this.Add("heatBeat", new Proto("heatBeat", 1, 120, 1));



            SortById();
        }

        public Proto Name2Pb(string name)
        {
            //Proto pb = ProtoMaps.Instance.protos[name];
            if (protos.ContainsKey(name))
            {
                return protos[name];
            }
            return null;
        }

        internal Proto GetByMergeId(uint cmdMerge)
        {
            if (id2proto.ContainsKey(cmdMerge))
            {
                return id2proto[cmdMerge];
            }
            return null;
        }
    }





}

6.定义心跳协议类:HeartBeatServiceGameObject

using System;
using UnityEngine;
using UnityWebSocket;
namespace UTNET
{
    public class HeartBeatServiceGameObject : MonoBehaviour
    {
        public Action OnServerTimeout;
        private WebSocket socket;
        public float interval = 0;

        public long lastReceiveHeartbeatTime;

        void Start()
        {
            
        }

        static DateTime dt = new DateTime(1970, 1, 1);
        public static long GetTimestamp()
        {
            TimeSpan ts = DateTime.Now.ToUniversalTime() - dt;
            return (long)ts.TotalSeconds;
        }

        public float t;

        void Update()
        {
            t += Time.deltaTime;
            if (t > interval)
            {
                CheckAndSendHearbeat();
                t = 0;
            }
        }

        private void CheckAndSendHearbeat()
        {
            //檢查最後一次取得心跳包的時間是否小於客戶端心跳間隔時間
            long curTime = GetTimestamp();
            long intervalSec = curTime - lastReceiveHeartbeatTime;
            if (intervalSec > interval)
            {
                //Debug.Log(string.Format("XXXX CheckAndSendHearbeat:s1:{0} l:{1} s:{2}", curTime, lastReceiveHeartbeatTime, intervalSec));
                this.enabled = false;
                OnServerTimeout?.Invoke();
            }
            else
            {
                //Debug.Log(string.Format(" CheckAndSendHearbeat:s1:{0} l:{1} s:{2}", curTime, lastReceiveHeartbeatTime, intervalSec));
                this.enabled = true;
                SendHeartbeatPack();
            }
        }

        public void HitHole()
        {
            lastReceiveHeartbeatTime = GetTimestamp();
        }

        private void SendHeartbeatPack()
        {
            //lastSendHeartbeatPackTime = DateTime.Now;
            byte[] package = PackageProtocol.Encode(
                PackageType.HEARTBEAT);
            socket.SendAsync(package);//*/
        }

        internal void Setup(uint interval, Action onServerTimeout, WebSocket socket)
        {
            this.socket = socket;
            this.interval = (interval / 1000 )/2;
            this.OnServerTimeout = onServerTimeout;
            this.enabled = true;
            SendHeartbeatPack();
        }

        internal void ResetTimeout(uint interval)
        {
            this.enabled = true;
            this.interval = (interval / 1000) / 2;
            t = 0;
            //long s1 = GetTimestamp();
            //long s = (s1 - lastReceiveHeartbeatTime);
            //Debug.Log(string.Format("ResetTimeout: s1:{0} l:{1} s:{2} s > interval:{3}", s1, lastReceiveHeartbeatTime, s, s > interval));
            lastReceiveHeartbeatTime = GetTimestamp();
            SendHeartbeatPack();
        }

        internal void Stop()
        {
            this.enabled = false;
            t = 0;
        }
    }
}

7.定义协议类Protocol:

using Cysharp.Threading.Tasks;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using UnityEditor;
using UnityEngine;
using UnityWebSocket;

namespace UTNET
{
    public class Protocol
    {

        Dictionary> packAction = new Dictionary>();
        UniTaskCompletionSource handshakeTcs;
        Dictionary> packTcs = new Dictionary>();

        //Dictionary> attention = new Dictionary>();

        WebSocket socket;
        public HeartBeatServiceGameObject heartBeatServiceGo;
        public Action OnReconected;
        public Action OnError;

        public void SetSocket(WebSocket socket)
        {
            this.socket = socket;
        }

        public UniTask HandsharkAsync(string token)
        {
            handshakeTcs = new UniTaskCompletionSource();
            return handshakeTcs.Task;
        }

        internal void Notify(string route, T info)
        {
            byte[] packBuff = PackageProtocol.Encode(
                PackageType.NOTIFY,
                0,
                route,
                info);
            socket.SendAsync(packBuff);
        }


        public UniTask RequestAsync(uint packID, uint route, T info = default, string modelName = null)
        {
            lock (packTcs)
            {
                UniTaskCompletionSource pack = new UniTaskCompletionSource();
                byte[] packBuff = PackageProtocol.Encode(packID, route, info);
                packTcs.Add(packID, pack);

                //packTcs.Add(route, pack); //暂不支持,同一协议多次发送
                //byte[] ints = System.BitConverter.GetBytes(IPAddress.HostToNetworkOrder(testInt));
                //ByteBuffer bbBuffer = ByteBuffer.wrap(bs);
                //bbBuffer.order(ByteOrder.BIG_ENDIAN);

                socket.SendAsync(packBuff);

                return pack.Task;
            }
        }

        public void CanceledAllUTcs()
        {
            lock (packTcs)
            {
                foreach (var tcs in packTcs)
                {
                    tcs.Value.TrySetCanceled();
                }
                packTcs.Clear();
                if(handshakeTcs!=null) handshakeTcs.TrySetCanceled();
            }
        }

        public async void OnReceive(byte[] bytes)
        {
            
            try
            {
                await UniTask.SwitchToMainThread();
                ExternalMessage package = PackageProtocol.Decode(bytes);
                uint mId = PackageProtocol.getCmd(package.cmdMerge);
                uint subId = PackageProtocol.getSubCmd(package.cmdMerge);

              


                Proto pb = ProtoMaps.Instance.GetByMergeId(package.cmdMerge);
                if (package.responseStatus != 0)
                {
                    Debug.LogError("收到 cmd:" + mId + " &subcmd:" + subId);
                    Debug.LogError("收到网络消息  " + package.cmdMerge);

                    Debug.LogError("协议返回错误信息,查询errcode,后面可能根据弹窗处理: Errmsg->" + package.validMsg + " &  Errcode->" + package.responseStatus );
                    onShowNetError(package.responseStatus, mId, subId);

                    return;
                }

                if (pb == null)
                {
                    Debug.Log("收到未在 ProtoMaps 注册的 网络消息  " + package.cmdCode);
                    return;
                }


                if (PackageProtocol.getCmd(package.cmdMerge) == 0)
                {
                    Debug.Log("心跳数据  ");
                    return;
                }

                ResponseHandler(package);
                PushHandler(package);

                //if (pb.typ == 1)
                //{
                //}
                //else if (pb.typ == 2)
                //{
                //    PushHandler(package);
                //}
                //else
                //{
                //    if (!ResponseHandler(package))
                //        PushHandler(package);
                //}


                //Package package = PackageProtocol.Decode(bytes);
                //Debug.Log(package.packageType);
                //switch ((PackageType)package.packageType)
                //{
                //    case PackageType.HEARTBEAT:
                //        //Debug.LogWarning("get HEARTBEAT");
                //        heartBeatServiceGo.HitHole();
                //        break;
                //    case PackageType.RESPONSE:
                //        ResponseHandler(package);
                //        break;
                //    case PackageType.PUSH:
                //        PushHandler(package);
                //        break;
                //    case PackageType.HANDSHAKE:
                //        HandshakeHandler(package);
                //        break;
                //    case PackageType.KICK:
                //        //HandleKick(package);
                //        break;
                //    case PackageType.ERROR:
                //        ErrorHandler(package);
                //        break;
                //    default:
                //        Debug.Log("No match packageType::" + package.packageType);
                //        break;
                //}
            }
            catch (Exception e)
            {
                ExternalMessage package = PackageProtocol.Decode(bytes);
                Proto pb = ProtoMaps.Instance.GetByMergeId(package.cmdMerge);
                Debug.Log("错误协议-----" + JsonUtility.ToJson(pb));
                await UniTask.SwitchToMainThread();
                Debug.LogError(e);
                throw e;
            }
        }

        public void StopHeartbeat()
        {
            if (heartBeatServiceGo != null)
            {
                Debug.Log("Stop Heartbeat");
                heartBeatServiceGo.Stop();
                //heartBeatServiceGo = null;
            }
        }

        public void onShowNetError(int responseStatus,uint mid,uint subid) {
            
           

        }
        public void SetOnNet(uint route, Action ac)
        {
            lock (packAction)
            {
                if (!packAction.ContainsKey(route))
                {
                    packAction.Add(route, ac);
                }
            }
        }

        private void PushHandler(ExternalMessage pack)
        {
            lock (packAction)
            {
                uint route = pack.cmdMerge;
                if (packAction.ContainsKey(route))
                {
#if SOCKET_DEBUG
                    Debug.Log(string.Format("[Push] <<-- [{0}] {1}", pack.route, JsonUtility.ToJson(pack)));
#endif
                    packAction[route]?.Invoke(pack);
                    //packAction.Remove(route); //监听事件不考虑删除由netmanager代理
                }
            }
        }

        private bool ResponseHandler(ExternalMessage package)
        {
            lock (packTcs)
            {
                uint msgId = (uint)package.msgId;
                //Debug.LogError("响应消息id" + msgId);
                if (packTcs.ContainsKey(msgId))
                {
                    packTcs[msgId].TrySetResult(package);
                    packTcs.Remove(msgId);
                    return true;
                    //if (packTcs.ContainsKey(package.cmdMerge))
                    //{
                    //    packTcs.Remove(package.cmdMerge);
                    //    return true;
                    //}
                }

            }
            return false;
        }

        //private void ResponseHandler(ExternalMessage package)
        //{
        //    lock (packTcs)
        //    {
        //        packTcs[package.packID].TrySetResult(package);
        //        if (packTcs.ContainsKey(package.packID))
        //        {
        //            packTcs.Remove(package.packID);
        //        }
        //    }
        //}


        private void HandshakeHandler(Package package)
        {
            Heartbeat msg = PackageProtocol.DecodeInfo(package.buff);
            if (msg.heartbeat > 0)
            {
                handshakeTcs.TrySetResult(false);
                return;
            }

            if (heartBeatServiceGo == null)
            {

            }
            else
            {
                OnReconected?.Invoke();
                heartBeatServiceGo.ResetTimeout(msg.heartbeat);
            }//*/
            handshakeTcs.TrySetResult(true);
        }

        private void ErrorHandler(Package package)
        {

        }

        private void OnServerTimeout()
        {
            if (socket.ReadyState == WebSocketState.Connecting)
            {
                socket.CloseAsync();
            }
            if (heartBeatServiceGo != null && socket.ReadyState != WebSocketState.Connecting && socket.ReadyState != WebSocketState.Open)
            {
                heartBeatServiceGo.Stop();
            }
        }
    }
}

8.定义服务器对应的协议消息:TestProto.cs


using ProtoBuf;
using ProtoBuf.Meta;
using System.Collections.Generic;

[ProtoContract]
public class RegisterInfo
{
    [ProtoMember(1)]
    public string phoneNum;
    [ProtoMember(2)]
    public string account;
    [ProtoMember(3)]
    public string pwd;
}


[ProtoContract]
public class LoginVerify 
{
   
    [ProtoMember(1)]
    public string account;
    [ProtoMember(2)]
    public string pwd;
    [ProtoMember(3, DataFormat = DataFormat.ZigZag)]
    public int loginBizCode;
    [ProtoMember(4, DataFormat = DataFormat.ZigZag)]
    public int spaceId;
}

[ProtoContract]
public class LobbyTalkMsg
{
    [ProtoMember(1, DataFormat = DataFormat.ZigZag)]
    public long id;
    [ProtoMember(2, DataFormat = DataFormat.ZigZag)]
    public long usrId;
    [ProtoMember(3)]
    public string nickName;
    [ProtoMember(4, DataFormat = DataFormat.ZigZag)]
    public int to_usrId;
    [ProtoMember(5)]
    public string msg;
    [ProtoMember(6, DataFormat = DataFormat.ZigZag)]
    public int msg_type;
}

[ProtoContract]
public class RequestSuccess
{
    [ProtoMember(1, DataFormat = DataFormat.ZigZag)]
    public int success;

}

[ProtoContract]
public class GameFrame {
    [ProtoMember(1, DataFormat = DataFormat.ZigZag)]
    public long frameId;
    [ProtoMember(2)]
    public string opc;
    [ProtoMember(3)]
    public string world;
    [ProtoMember(4)]
    public TankLocation location;

}


[ProtoContract]
public class UserInfo {
    [ProtoMember(1, DataFormat = DataFormat.ZigZag)]
    public long id;
    [ProtoMember(2)]
    public  string nickName;
    [ProtoMember(3)]
    public string account;
    [ProtoMember(4)]
    public string gender;
    
    [ProtoMember(5)]
    public string headImgUrl;

    [ProtoMember(6, DataFormat = DataFormat.ZigZag)]
    public long avatarId;

    [ProtoMember(7)]
    public string avatar;
    [ProtoMember(8)]
    public string hold;

    [ProtoMember(9)]
    public string mtk;
    
    [ProtoMember(10)]
    public string ltk;

    [ProtoMember(11, DataFormat = DataFormat.ZigZag)]
    public long roomId;


    [ProtoMember(12, DataFormat = DataFormat.ZigZag)]
    public long team;


    [ProtoMember(13)]
    public TankLocation tankLocation;

    [ProtoMember(14, DataFormat = DataFormat.ZigZag)]
    public int speed;
}

[ProtoContract]
public class TankLocation {
    [ProtoMember(1, DataFormat = DataFormat.ZigZag)]
    public  float x;
    [ProtoMember(2, DataFormat = DataFormat.ZigZag)]
    public float y;
    [ProtoMember(3, DataFormat = DataFormat.ZigZag)]
    public float z;
    [ProtoMember(4, DataFormat = DataFormat.ZigZag)]
    public float dx;
    [ProtoMember(5, DataFormat = DataFormat.ZigZag)]
    public float dy;
    [ProtoMember(6, DataFormat = DataFormat.ZigZag)]
    public float dz;
    [ProtoMember(7, DataFormat = DataFormat.ZigZag)]
    public long playerId;
    [ProtoMember(8, DataFormat = DataFormat.ZigZag)]
    public long time;

}


[ProtoContract]
public class TankPlayer {
    [ProtoMember(1, DataFormat = DataFormat.ZigZag)]
    public long id;
    [ProtoMember(2, DataFormat = DataFormat.ZigZag)]
    public int speed;
    [ProtoMember(3)]
    public TankLocation tankLocation;
    [ProtoMember(4)]
    public string nickname;
    [ProtoMember(5)]
    public string hold;
    // 其他属性: key: 子弹 id 1 玩具弹, 2 雪弹; value : 数量
    [ProtoMember(6)]
    public Dictionary tankBulletMap;
}


[ProtoContract]
public class TankEnterRoom {
    [ProtoMember(1, DataFormat = DataFormat.ZigZag)]
    public long roomId;
    [ProtoMember(2, DataFormat = DataFormat.ZigZag)]
    public long time;
    [ProtoMember(3, DataFormat = DataFormat.ZigZag)]
    public long team;
    [ProtoMember(4, DataFormat = DataFormat.ZigZag)]
    public long playerId;

    [ProtoMember(5)]
    public string sharetoken;

    [ProtoMember(6)]
    public string usertoken;

    [ProtoMember(7)]
    public string world;

    [ProtoMember(8)]
    public List tankPlayerList;

}

[ProtoContract]
public class UserIds {
    [ProtoMember(1, DataFormat = DataFormat.ZigZag)]
    public long[] userIds;
}

[ProtoContract]
public class UserInfos
{
    [ProtoMember(1, DataFormat = DataFormat.ZigZag)]
    public UserInfo[] users;
}

[ProtoContract]
public class HeartBeat
{
    [ProtoMember(1, DataFormat = DataFormat.ZigZag)]
    public int beat;
}




9.定义客户端处理消息类:Client:

using System;
using UnityEngine;
using Cysharp.Threading.Tasks;
using System.Threading;
using System.Collections;
using UnityWebSocket;

namespace UTNET
{
    public enum NetWorkState
    {
        CONNECTING,
        CONNECTED,
        DISCONNECTED,
        TIMEOUT,
        ERROR,
        KICK
    }

    public class Client
    {
        private static int RqID = 0;

        //public NetWorkState state;

        public Action OnReconected, OnDisconnect, OnConnected;
        public Action OnError;
        public uint retry;
        public bool isConnect = false;
        Protocol protocol;
        WebSocket socket;
        UniTaskCompletionSource utcs;
        private bool isForce;
        private bool isHeardbeat = false;
        private string host;

        public Client(string host)
        {
            Debug.Log("Client" + host);
            this.host = host;
            this.createSocket();
        }

        private void createSocket()
        {
            if (socket != null) {

            }
            Debug.Log("createSocket host=" + this.host);
            utcs = new UniTaskCompletionSource();
            if (socket != null) {
                socket.CloseAsync();
                socket.OnClose += ReCreateSocket;
            }
            else {
                socket = new WebSocket(this.host);
                socket.OnOpen += OnOpen;
                socket.OnMessage += OnMessage;
                socket.OnClose += OnClose;
                socket.OnError += OnErr;
            }
            

           
        }

        private void ReCreateSocket(object sender, CloseEventArgs e) {
            socket = new WebSocket(this.host);
            socket.OnOpen += OnOpen;
            socket.OnMessage += OnMessage;
            socket.OnClose += OnClose;
            socket.OnError += OnErr;
        }

        //断线重连逻辑
        public IEnumerator ReconectScoektAsync()
        {
            while (true)
            {
                yield return new WaitForSeconds(2);
                Debug.Log(" client : reconectScoekt");
                if (socket.ReadyState == WebSocketState.Connecting || socket.ReadyState == WebSocketState.Open) break;
                socket.ConnectAsync();
            }
        }


        //开始心跳逻辑
        public IEnumerator StartHeardBeat()
        {
            isHeardbeat = true;
            while (true)
            {
                Debug.Log("开始发送心跳");
                if (isHeardbeat == false) continue;
                yield return new WaitForSeconds(2);
                //发送心跳
                this.sendHeardbeat();
            }
        }

        private async void sendHeardbeat()
        {
            int u1 = await this.RequestAsync("heartbeat", 21);

        }


        public UniTask ConnectAsync()
        {
            socket.ConnectAsync();
            return utcs.Task;
        }

        private void OnOpen(object sender, OpenEventArgs e)
        {
            if (protocol == null)
                protocol = new Protocol();
            protocol.SetSocket(socket);
            protocol.OnReconected = OnReconected;
            protocol.OnError = OnError;
            //bool isOK = await protocol.HandsharkAsync(this.token);
            Debug.Log("open:" + e);
            isConnect = true;
            utcs.TrySetResult(true);
            OnConnected?.Invoke();
        }


        private async void OnClose(object sender, CloseEventArgs e)
        {
            if (socket.ReadyState == UnityWebSocket.WebSocketState.Connecting || socket.ReadyState == UnityWebSocket.WebSocketState.Open) return;
            await UniTask.SwitchToMainThread();
            isConnect = false;
            Cancel();
            if (!isForce)
            {
                await UniTask.Delay(1000);
                //socket.Open();
            }
            OnDisconnect?.Invoke();
        }

        public void OnErr(object sender, ErrorEventArgs e)
        {
            utcs.TrySetResult(false);
            isConnect = false;
            //Debug.LogError(e.Exception.Message);
        }

        private void OnMessage(object sender, MessageEventArgs e)
        {
            protocol.OnReceive(e.RawData);
            isConnect = true;
        }


        public void OnNet(uint mid, uint sid, Action cb)
        {
            uint merge = PackageProtocol.getMergeCmd(mid, sid);
            protocol.SetOnNet(merge, cb);
        }

        public void OnNetEx(string name, Action cb)
        {
            Proto pb = ProtoMaps.Instance.protos[name] as Proto;
            uint merge = PackageProtocol.getMergeCmd(pb.mid, pb.sid);
            protocol.SetOnNet(merge, cb);
        }

        public void Notify(string route, T info = default)
        {
            //uint rqID = (uint)Interlocked.Increment(ref RqID);
            try
            {
#if SOCKET_DEBUG
                Debug.Log(string.Format("[Notify] -->> [{0}] {1}", route, JsonUtility.ToJson(info)));
#endif
                protocol.Notify(route, info);
            }
            catch (Exception e)
            {
                Debug.Log(string.Format("[Notify Exception]{0}", e.Message));
                throw e;
            }
        }



        public async UniTask RequestAsyncEx(uint mid, uint sid, T info = default, string modelName = null)
        {

            uint cmdMerge = PackageProtocol.getMergeCmd(mid, sid);

            uint rqID = (uint)Interlocked.Increment(ref RqID);
            try
            {
#if SOCKET_DEBUG
                Debug.Log(string.Format("[{0}][Request] -->> [{1}] {2}", rqID, route, JsonUtility.ToJson(info)));
#endif

                ExternalMessage pack = await protocol.RequestAsync(rqID, cmdMerge, info, modelName);

                S msg = PackageProtocol.DecodeInfo(pack.data);


#if SOCKET_DEBUG
                Debug.Log(string.Format("[{0}][Request] <<-- [{1}] {2} {3}", rqID, route, JsonUtility.ToJson(msg), JsonUtility.ToJson(msg.info)));
#endif
                return msg;
            }
            catch (Exception e)
            {
                //Debug.Log(string.Format("[{0}][RequestAsync Exception]{1}", rqID, e.Message));
                throw e;
            }
        }


        public async UniTask RequestAsync(string name, T info = default, string modelName = null)
        {
            
            // #if UNITY_WEBGL && !UNITY_EDITOR  //只在WebGl下生效
            if (PlayerData.Instance.IsEditorMode) {
                S msg = PackageProtocol.DecodeInfo(null);
                return msg;
            }
            //#endif

            //Debug.Log($"sendMsg {name}");

            Proto pb = ProtoMaps.Instance.Name2Pb(name);

                uint cmdMerge = PackageProtocol.getMergeCmd(pb.mid, pb.sid);

                uint rqID = (uint)Interlocked.Increment(ref RqID);
                //Debug.LogError("调试消息id" + rqID);

                try
                {
#if SOCKET_DEBUG
                Debug.Log(string.Format("[{0}][Request] -->> [{1}] {2}", rqID, route, JsonUtility.ToJson(info)));
#endif

                    ExternalMessage pack = await protocol.RequestAsync(rqID, cmdMerge, info, modelName);


                    S msg = PackageProtocol.DecodeInfo(pack.data);
#if SOCKET_DEBUG
                Debug.Log(string.Format("[{0}][Request] <<-- [{1}] {2} {3}", rqID, route, JsonUtility.ToJson(msg), JsonUtility.ToJson(msg.info)));
#endif
                    return msg;
                }
                catch (Exception e)
                {
                    //Debug.Log(string.Format("[{0}][RequestAsync Exception]{1}", rqID, e.Message));
                    //Debug.Log("房间网络连接断开.......");
                    S msg = PackageProtocol.DecodeInfo(null);
                    return msg;
                    throw e;
                }

            

            
        }

        public void Cancel(bool isForce = false)
        {
            this.isForce = isForce;
            utcs.TrySetCanceled();
            if (socket.ReadyState != UnityWebSocket.WebSocketState.Closed)
            {
                socket.CloseAsync();
            }
            if (protocol != null)
            {
                protocol.StopHeartbeat();
                protocol.CanceledAllUTcs();
            }
        }


        public UnityWebSocket.WebSocketState getSocketState() {

            if (socket != null) {

                return socket.ReadyState;
            }
            return 0;
        }


        public void Close() {
            Debug.Log("UnityWebSocket......" + socket);
            Debug.Log("UnityWebSocket状态......"+ socket.ReadyState);
            if (socket!=null && socket.ReadyState == UnityWebSocket.WebSocketState.Open) {
                socket.CloseAsync();
                Debug.Log("强制玩家关闭连接......");
            }
        }


        void OnDestroy()
        {
            socket.CloseAsync(); 
        }
    }
}

10.最终定义网络管理器:NetManager.cs


using Cysharp.Threading.Tasks;
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
using UTNET;


public class NetMessage {
    public uint mergeid;
    delegate void Message(object sender, T msg);

}

public class NetManager : MonoBehaviour {

    private NetManager() { }

    public static NetManager Instance;


    public string token, version;
    public Client client;
    public bool isConeccet = false;
    public string HaiMetaSpaceUrl = "*****";

    //192.144.138.221  /game.icewhale.net
    Dictionary>> actions = new Dictionary>>();
    private Coroutine rec;



    private void Awake() {
        Instance = this;
    }

    async void Start() {
        DontDestroyOnLoad(this);
        Screen.sleepTimeout = SleepTimeout.NeverSleep;
        pushBtn();
    }

    //public Client getNewClient() {

    //    return newClient;
    //}

    //public void setNewClient(Client vNewClient) {

    //    newClient = vNewClient;
    //}


    public void webOpenHaiMetaUrl(string param = null) {

        //string url = this.HaiMetaSpaceUrl+param;
        //UnityEngine.Application.ExternalEval("window.location.href = \"" + url + "\";");
    }

    public void webOpenNewSpace(string spaceUrl,string spaceId)
    {

        string url = spaceUrl + "?param={\"spaceId\":"+ spaceId + ",\"userName\":"+PlayerData.Instance.Name+",\"templateId\":"+ PlayerData.Instance.TemplateId + "}" + "&token=" + PlayerData.Instance.ltk + "&type=4"+ "&state=1";
        UnityEngine.Application.ExternalEval("window.location.href = \"" + url + "\";");


    }

    private void Update() {
        //if (client == null && newClient != null) {

        //    client = getNewClient();
        //}
    }

    public async void CreateConeccetionBtn()
    {

        //string host = string.Format("wss://*****");
        Debug.Log("CreateConeccetionBtn" +Host.gameServer);
        client = new Client(Host.gameServer);
        client.OnDisconnect = OnDisconnect;
        client.OnReconected = OnReconected;
        client.OnError = OnError;
        client.OnConnected = OnConnected;

        bool isConeccet = await client.ConnectAsync();
        if (isConeccet)
        {
            Debug.Log("网络链接成功");
        }

    }

    //public void resetClient() {
    //    setNewClient(client);
    //}

    private void OnConnected() {
        Debug.Log("OnConnected");
        if (rec != null)
            StopCoroutine(rec);
        this.TriggerEvent(EventName.SocketOpen, null);

    }





    private void OnError(string msg) {
        Debug.LogError(string.Format("err msg:{0}", msg));
    }

    private void OnReconected() {
        Debug.Log("OnReconect");
    }

    private void OnDisconnect() {
        Debug.Log("OnDisconnect");
        //client = null;
        rec = StartCoroutine(client.ReconectScoektAsync()); //断线重连逻辑
    }

    public void pushBtn() {
        LoadNetCofig();
    }

    void LoadNetCofig() {
        var configPath = Application.dataPath;

#if UNITY_EDITOR
        var filepath = Path.Combine(Application.dataPath.Replace("Assets", ""), "config.txt");
#else
        var filepath = Path.Combine(Application.dataPath, "config.txt");
#endif
        Debug.Log("configPath" + filepath);
        filepath = filepath.Replace("\\", "/");
       LoadFileSetNetwork(filepath);
    }



    async void LoadFileSetNetwork(string filepath) {
        UnityWebRequest www = UnityWebRequest.Get(filepath);


        await www.SendWebRequest();

        if (www.result == UnityWebRequest.Result.ConnectionError || www.result == UnityWebRequest.Result.ProtocolError) {
            Debug.LogError(www.error);
        }
        else {
            string json = www.downloadHandler.text;
            var data = LitJson.JsonMapper.ToObject(json);

            if ((string)data["AssetBundleIP"] != string.Empty) {
                Host.AssetBundleIP = (string)data["AssetBundleIP"];
            }
#if UNITY_EDITOR
            var serverMode = AppConst.serverMode;
#else
            var serverMode  = (ServerMode)int.Parse(HttpHelper.GetUrlParam("severAPI"));
#endif
            AppConst.serverMode = serverMode;
            switch (serverMode) {
                case ServerMode.preview:
                    Host.ApiHost = (string)data["preview"];
                    break;
                case ServerMode.dev:
                    Host.ApiHost = (string)data["dev"];
                    break;
            }




#if !UNITY_EDITOR
            var paramUrl = HttpHelper.GetUrlParam("param");
            JsonData paramdata = JsonMapper.ToObject(UtilsFunc.UnicodeToString(paramUrl));
            try {
                string spaceId = paramdata["spaceId"].ToJson();
                PlayerData.Instance.SpaceId = spaceId;
                PlayerData.Instance.ltk = HttpHelper.GetUrlParam("token");
              
              
            }
            catch (Exception e) {
                Debug.LogError(e.Message + '\n' + e.StackTrace);
            }
#endif
            Debug.Log("LoadFileSetNetwork SpaceId" + PlayerData.Instance.SpaceId);

            //如果在编辑器下面,并且不填sapceId,那么则进入发布模式

            if (string.IsNullOrEmpty(PlayerData.Instance.SpaceId) == false) {
                await HttpHelper.Instance.GetSceneConfig();
            }


#if !UNITY_EDITOR
            if (paramdata.ContainsKey("templateId")) {

                if (paramdata["templateId"] != null) {
                    string tempStr = (string)paramdata["templateId"];
                    bool isExist = int.TryParse(tempStr,out var tempId);
                    if (isExist) PlayerData.Instance.TemplateId = tempId;
                }
                    
            }

            string stateUrl = HttpHelper.GetUrlParam("state");
#else
            string stateUrl = string.IsNullOrEmpty(PlayerData.Instance.SpaceId) == true ? AppConst.PublicMode : AppConst.ViewMode;
#endif
            var arg = new SceneLoadActionArgs();
            arg.state = stateUrl;


            


            EventManager.Instance.TriggerEvent(EventName.LoadSceneAction, arg);

            if (stateUrl == AppConst.PublicMode) {
                PlayerData.Instance.IsEditorMode = true;
                PlayerData.Instance.IsPublicMode = true;
                EventManager.Instance.TriggerEvent(EventName.OnPublishEnter);
                HttpHelper.Instance.GetDefaultSpaceImg();
                return;
            }

#if !UNITY_EDITOR
            await HttpHelper.Instance.GetServerConfig();
#else
            Host.gameServer = (string)data["local"];
#endif
            CreateConeccetionBtn();

        }
    }

    private async UniTaskVoid CreateConeccetion() {
        Debug.Log("开始...");

        bool isConeccet  = await client.ConnectAsync();

        if (isConeccet) {
            Debug.Log("网络链接成功");
            // this.Register("test", (int code) =>
            // {
            //     Debug.Log("网络事件" +code);
            // });
            if (client != null) client.isConnect = true;

            //this.SendAPI();

        }
        else
            Debug.Log("多次链接仍让未成功");
    }

    public void AddSyncHandler(string name, Action call) {
        NetManager.Instance.Register("gameFrame", (ExternalMessage pack) => {
            GameFrame info = PackageProtocol.DecodeInfo(pack.data);
            //if (info.location.playerId == PlayerData.Instance.PlayerId) {
            //    return; //自己不用给自己同步
            //}

            var opc = info.opc;
            SyncWrapData dataBase = null;
            try {

                dataBase = JsonUtility.FromJson(opc);

                if (dataBase.playerId != PlayerData.Instance.PlayerId.ToString()) { //只接受不同playerId的数据
                    call.Invoke(dataBase);
                }

            }
            catch (Exception e) {
                //Debug.LogError(e);
            }
        });
    }

    public async void SendSyncData(SyncWrapData syncBase) {
        string jsonData = JsonUtility.ToJson(syncBase);

        if (NetManager.Instance.client == null) {
            Debug.LogError("NetManager.Instance.client == null");
            return;
        }

        var gameFrame = new GameFrame();
        gameFrame.opc = jsonData;
        await NetManager.Instance.client.RequestAsync("gameFrame", gameFrame);
    }

    public async void SendSyncWorldData(SyncWorldData worldData) {
        string jsonData = LitJson.JsonMapper.ToJson(worldData);

        if (NetManager.Instance.client == null) {
            Debug.LogError("NetManager.Instance.client == null");
            return;
        }
        //Debug.Log("SendSyncWorldData worldData:" + jsonData);
        var gameFrame = new GameFrame();
        gameFrame.world = jsonData;
        await NetManager.Instance.client.RequestAsync("gameFrame", gameFrame);
    }

    public async void SendFrameLocationData(SyncTransfomData syncPos,SyncWrapData wrap) {

        if (NetManager.Instance.client == null) {
            Debug.LogError("NetManager.Instance.client == null");
            return;
        }

        var gameFrame = new GameFrame();

        gameFrame.opc = JsonUtility.ToJson(wrap);

        gameFrame.location = new TankLocation();
        SyncTransfomUtil.ToTankLocation(ref gameFrame.location, syncPos);
        gameFrame.location.playerId = PlayerData.Instance.PlayerId;

        await NetManager.Instance.client.RequestAsync("gameFrame", gameFrame);
    }




    public void Register(string name, Action ac) {
        //#if UNITY_WEBGL && !UNITY_EDITOR  //只在WebGl下生效
        if (PlayerData.Instance.IsEditorMode) return;
        //#endif
        //加入事件列表
        Proto pb = ProtoMaps.Instance.Name2Pb(name);

        List> list = null;
        if (actions.ContainsKey(name)) {
            list = actions[name];
        }
        else {
            list = new List>();
            actions.Add(name, list);
        }
        list.Add(ac);

        client.OnNetEx(name, (ExternalMessage pack) => {
            T info = PackageProtocol.DecodeInfo(pack.data);
            //Debug.Log("网络事件" + JsonUtility.ToJson(info));
            //遍历事件列表依次回调
            foreach (Action cb in list) {
                cb?.Invoke(pack);
            }

        });

    }


    public async void SendAPI() {

        //请求注册
        //RegisterInfo register = new RegisterInfo();
        //register.account = "abc";
        //register.phoneNum = "abc";
        //register.pwd = "abc";

        //RegisterInfo a = await client.RequestAsync("test", register);

        //Debug.Log(a.phoneNum);


        client.OnNetEx("login", (ExternalMessage pack) => {

            Debug.LogError("回调返回");
            //T info = PackageProtocol.DecodeInfo(pack.data);
            Debug.Log("网络事件" + JsonUtility.ToJson(info));
            遍历事件列表依次回调
            //foreach (Action cb in list)
            //{
            //    cb?.Invoke(pack);
            //}

        });



        LoginVerify loginVerify = new LoginVerify();
        loginVerify.account = "18060974935";
        loginVerify.pwd = "123456ab";
        loginVerify.loginBizCode = 1;

        UserInfo ret = await NetManager.Instance.client.RequestAsync("login", loginVerify);
        Debug.Log(ret.nickName);


        TankEnterRoom myEnterRoom = new TankEnterRoom();

        myEnterRoom.roomId = 1002;
        myEnterRoom.team = 1;

        TankEnterRoom ret2 = await NetManager.Instance.client.RequestAsync("enterRoom", myEnterRoom);
        Debug.Log(ret2.time);




        //int code = await client.RequestAsync("gameObjUsed", "ok");


    }

    public bool isConnected() {
        return isConeccet;
    }

    private void OnDestroy() {
        if (client != null) {
            client.Cancel();
        }
    }
}

11.发送网络消息的应用:

UserInfo ret = await NetManager.Instance.client.RequestAsync("login", loginVer);

12.监听网络消息:

NetManager.Instance.Register("enterRoom", (ExternalMessage pack) =>
{
    Debug.Log("新玩家进去");
    this.onNewRoleEnter2(pack);
});
    public void onNewRoleEnter2(ExternalMessage pack) {
        TankEnterRoom info = PackageProtocol.DecodeInfo(pack.data);
        List roleList = info.tankPlayerList;
        Debug.Log("新进入房间玩家列表" + roleList.ToString());
        long roleId = info.playerId;
        Debug.Log("新进入房间玩家id" + roleId);
        foreach (UserInfo vRole in roleList)
        {
            Debug.Log("新进入房间玩家" + JsonUtility.ToJson(vRole));
            if (vRole.id == roleId && vRole.id!=PlayerData.Instance.PlayerId)
            {
                if (!RolesManager.Instance.isExistRoleById(vRole.id)) {
                    GuestinfoDataModel.Instance.OnNewRoleIntoRoom(vRole);
                    RolesManager.Instance.synPlayer(vRole, true);
                } 
            }
        }
        
    }

你可能感兴趣的:(unity组件开发,unity,websocket,游戏引擎)