Unity组件开发--长连接webSocket

1.下载安装UnityWebSocket 插件

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

引入unity项目:

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<T>
    //{
    //    [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<T>(PackageType type, uint packID, string route, T info, string modelName = null)
        {
            Package sr = new Package()
            {
                packageType = (uint)type,
                packID = packID,
                route = route,
                buff = Serialize<T>(info),
                modelName = modelName
            };
            return Serialize(sr);
        }


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

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

        }



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

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


        public static byte[] Serialize<T>(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<T>(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<ExternalMessage>(mem);
            mem.Close();
            return rs;
        }
        public static T DecodeInfo<T>(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<int>(stream);
                }//转zig zag
                value = (value >> 1) ^ -(value & 1);

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

            return rs;
        }



        //public static T MyMethod<T>(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.

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





using System;
using System.Collections.Generic;


namespace UTNET
{


    public interface IProto
    {

    }



    //R ��������, S ��������
    //public class Proto<R, S> : 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<string, IProto> protos = new Dictionary<string, IProto>();

        public Dictionary<string, Proto> protos = new Dictionary<string, Proto>();

        public Dictionary<uint, Proto> id2proto = new Dictionary<uint, Proto>();


        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<uint, Action<ExternalMessage>> packAction = new Dictionary<uint, Action<ExternalMessage>>();
        UniTaskCompletionSource<bool> handshakeTcs;
        Dictionary<uint, UniTaskCompletionSource<ExternalMessage>> packTcs = new Dictionary<uint, UniTaskCompletionSource<ExternalMessage>>();

        //Dictionary<uint, Action<T>> attention = new Dictionary<uint, Action<T>>();

        WebSocket socket;
        public HeartBeatServiceGameObject heartBeatServiceGo;
        public Action OnReconected;
        public Action<string> OnError;

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

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

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


        public UniTask<ExternalMessage> RequestAsync<T>(uint packID, uint route, T info = default, string modelName = null)
        {
            lock (packTcs)
            {
                UniTaskCompletionSource<ExternalMessage> pack = new UniTaskCompletionSource<ExternalMessage>();
                byte[] packBuff = PackageProtocol.Encode<T>(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<ExternalMessage> 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<Heartbeat>(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<int, int> 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<UserInfo> 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<string> OnError;
        public uint retry;
        public bool isConnect = false;
        Protocol protocol;
        WebSocket socket;
        UniTaskCompletionSource<bool> 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<bool>();
            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<int, int>("heartbeat", 21);

        }


        public UniTask<bool> 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<ExternalMessage> cb)
        {
            uint merge = PackageProtocol.getMergeCmd(mid, sid);
            protocol.SetOnNet(merge, cb);
        }

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

        public void Notify<T>(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<T>(route, info);
            }
            catch (Exception e)
            {
                Debug.Log(string.Format("[Notify Exception]{0}", e.Message));
                throw e;
            }
        }



        public async UniTask<S> RequestAsyncEx<T, S>(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<T>(rqID, cmdMerge, info, modelName);

                S msg = PackageProtocol.DecodeInfo<S>(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<S> RequestAsync<T, S>(string name, T info = default, string modelName = null)
        {
            
            // #if UNITY_WEBGL && !UNITY_EDITOR  //只在WebGl下生效
            if (PlayerData.Instance.IsEditorMode) {
                S msg = PackageProtocol.DecodeInfo<S>(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<T>(rqID, cmdMerge, info, modelName);


                    S msg = PackageProtocol.DecodeInfo<S>(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<S>(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<T> {
    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<string, List<Action<ExternalMessage>>> actions = new Dictionary<string, List<Action<ExternalMessage>>>();
    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<RegisterInfo>("test", (int code) =>
            // {
            //     Debug.Log("网络事件" +code);
            // });
            if (client != null) client.isConnect = true;

            //this.SendAPI();

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

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

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

                dataBase = JsonUtility.FromJson<SyncWrapData>(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>("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>("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>("gameFrame", gameFrame);
    }




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

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

        client.OnNetEx(name, (ExternalMessage pack) => {
            T info = PackageProtocol.DecodeInfo<T>(pack.data);
            //Debug.Log("网络事件" + JsonUtility.ToJson(info));
            //遍历事件列表依次回调
            foreach (Action<ExternalMessage> 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<RegisterInfo, RegisterInfo>("test", register);

        //Debug.Log(a.phoneNum);


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

            Debug.LogError("回调返回");
            //T info = PackageProtocol.DecodeInfo<T>(pack.data);
            Debug.Log("网络事件" + JsonUtility.ToJson(info));
            遍历事件列表依次回调
            //foreach (Action<ExternalMessage> 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<LoginVerify, UserInfo>("login", loginVerify);
        Debug.Log(ret.nickName);


        TankEnterRoom myEnterRoom = new TankEnterRoom();

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

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




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


    }

    public bool isConnected() {
        return isConeccet;
    }

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

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

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

12.监听网络消息:

NetManager.Instance.Register<TankEnterRoom>("enterRoom", (ExternalMessage pack) =>
{
    Debug.Log("新玩家进去");
    this.onNewRoleEnter2(pack);
});
    public void onNewRoleEnter2(ExternalMessage pack) {
        TankEnterRoom info = PackageProtocol.DecodeInfo<TankEnterRoom>(pack.data);
        List<UserInfo> 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);
                } 
            }
        }
        
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/312249.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

c++全排列

目录 next_permutation()函数 例 perv_permutation()函数 例 next_permutation()函数 next_pernutation()函数用于生成当前序列的下一个排序。它按照字典序对序列进行重新排序&#xff0c;如果存在下一个排列&#xff0c;则将当前序列更改为下一个排列&#xff0c;并返回t…

LeetCode-657/1275/1041

1.机器人能否返回原点&#xff08;657&#xff09; 题目描述&#xff1a; 在二维平面上&#xff0c;有一个机器人从原点 (0, 0) 开始。给出它的移动顺序&#xff0c;判断这个机器人在完成移动后是否在 (0, 0) 处结束。 移动顺序由字符串 moves 表示。字符 move[i] 表示其第 …

达梦数据库的归档模式介绍

几种归档模式 归档是实现数据守护系统的重要技术手段&#xff0c;根据功能与实现方式的不同&#xff0c;DM 数据库的归档可以分为 6 类&#xff1a;本地归档、远程归档、实时归档、即时归档、异步归档和同步归档。 其中&#xff0c;本地归档日志的内容与写入时机与数据库模式…

C语言发展史

前言 当前&#xff0c;C语言是大学广泛应用的计算机教学语言之一&#xff0c;除了文科类专业&#xff0c;大部分理工科专业都会教授C语言&#xff0c;但是&#xff0c;C语言是谁搞出来的&#xff1f;是怎么编译的?相信很多同学对此并不清楚&#xff0c;今天&#xff0c;我们就…

安达发|APS智能排产系统之换产矩阵

在制造业中&#xff0c;生产计划和调度是至关重要的环节。为了提高生产效率、降低成本并满足客户需求&#xff0c;企业需要采用先进的生产管理系统。APS&#xff08;高级计划与排产&#xff09;智能排产系统正是为此而生的一种解决方案。它通过数学模型和算法&#xff0c;实现了…

视频监控设备通过onvif协议接入到视频监控平台

目 录 一、什么是onvif规范 1、onvif的定义 2、onvif的优势 二、AS-V1000监控平台对onvif的支持程度 二、通过onvif接入视频监控设备 1、onvif维护主页面 2、设备发现 3、设备验证 4、设备录入系统 5、通道配置 6、权限分配 三、对onvif设备进行…

im6ull学习总结(三-五)freetype显示正行字

知识补充 笛卡尔坐标系 这里笛卡尔坐标系就是初高中学的直角坐标系的第一象限 lcd坐标系则不同 这两个坐标系如何转换 观察两个坐标系 点&#xff08;x,y&#xff09;的x坐标在两个坐标系中相同&#xff0c;纵坐标&#xff08;y&#xff09;存在着yV-yV V是整个屏幕的行数的像…

小程序基础学习(组件化)

&#xff08;一&#xff09;创建 找到components文件夹下面创建新的文件夹 然后再文件夹内创建component格式的文件 创建后这样 我创建的是my-info的文件夹以及my-info的components文件&#xff0c;跟着普通的页面一样 &#xff08;二&#xff09; 注册组件 找到你需要使用组…

2023年全国职业院校技能大赛软件测试赛题—单元测试卷⑤

单元测试 一、任务要求 题目1&#xff1a;根据下列流程图编写程序实现相应处理&#xff0c;执行j10*x-y返回文字“j1&#xff1a;”和计算值&#xff0c;执行j(x-y)*(10⁵%7)返回文字“j2&#xff1a;”和计算值&#xff0c;执行jy*log(x10)返回文字“j3&#xff1a;”和计算值…

Vue 中修改 Element 组件的 下拉菜单(Dropdown) 的样式

Vue 中修改 Element 组件的 下拉菜单(Dropdown) 的样式 今天在项目中碰到一个 UI 改造的需求&#xff0c;需要根据设计图把页面升级成 UI 设计师提供的设计图样式。 到最后页面改造完了&#xff0c;但是 UI 提供的下拉菜单样式全部是黑色半透明的&#xff0c;只能硬着头皮改了。…

银河麒麟v10安装前端环境(Node、vue、Electron+vite)

此帖子所提到的所有依赖包都是基于银河麒麟v10真机的arm架构包&#xff0c;如果是在windows上的虚拟机上 把依赖包换成x64的包即可&#xff0c;方法步骤都是一样 一.node安装 原始方法安装&#xff08;建议用第二种nvm方法&#xff0c;因为更简单&#xff09;&#xff1a; 1…

探索媒体查询的世界:适应多种设备的技巧与实践(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

PDF结构详解

文章目录 介绍前言高保真的文件什么是PDF&#xff1f;PDF的一些优点版本摘要谁在使用PDF&#xff1f;有用的免费软件谁应该阅读 构建一个简单PDF文件基本PDF语法File StructureDocument ContentPage Content 构建简单PDF文件头目录&#xff0c;交叉引用表和文件尾主要对象图形内…

力扣hot100 二叉树的最近公共祖先 递归

Problem: 236. 二叉树的最近公共祖先 &#x1f468;‍&#x1f3eb; 参考大佬题解 &#x1f496; 图解 时间复杂度, 示例&#xff1a; O ( n ) O(n) O(n) 空间复杂度, 示例&#xff1a; O ( n ) O(n) O(n) &#x1f496; AC code /*** Definition for a binary tree node.*…

PostgreSQL之SEMI-JOIN半连接

什么是Semi-Join半连接 Semi-Join半连接&#xff0c;当外表在内表中找到匹配的记录之后&#xff0c;Semi-Join会返回外表中的记录。但即使在内表中找到多条匹配的记录&#xff0c;外表也只会返回已经存在于外表中的记录。而对于子查询&#xff0c;外表的每个符合条件的元组都要…

概率论与数理统计-第7章 假设检验

假设检验的基本概念 二、假设检验的基本思想 假设检验的基本思想实质上是带有某种概率性质的反证法&#xff0c;为了检验一个假设H0,是否正确&#xff0c;首先假定该假设H0正确&#xff0c;然后根据抽取到的样本对假设H0作出接受或拒绝的决策&#xff0c;如果样本观察值导致了…

如何从 Keras 中的深度学习目录加载大型数据集

一、说明 数据集读取&#xff0c;使用、在磁盘上存储和构建图像数据集有一些约定&#xff0c;以便在训练和评估深度学习模型时能够快速高效地加载。本文介绍Keras 深度学习库中的ImageDataGenerator类等工具自动加载训练、测试和验证数据集。 二、ImageDataGenerator加载数据集…

CMake入门教程【高级篇】配置文件(configure_file)

😈「CSDN主页」:传送门 😈「Bilibil首页」:传送门 😈「动动你的小手」:点赞👍收藏⭐️评论📝 文章目录 1.configure_file作用2.详细使用说明3.完整代码示例4.实战使用技巧与注意事项5.总结分析1.configure_file作用

添加气泡与菜单

目录 1、添加气泡 1.1、文本提示气泡 1.2、带按钮的提示气泡 1.3、自定义气泡 2、菜单 2.1、创建默认样式的菜单 2.2、创建自定义样式的菜单 1、添加气泡 Popup属性可绑定在组件上显示气泡弹窗提示&#xff0c;设置弹窗内容、交互逻辑和显示状态。主要用于…

【野火i.MX6ULL开发板】利用microUSB线烧入Debian镜像

0、前言 烧入Debian镜像有两种方式&#xff1a;SD卡、USB SD卡&#xff1a;需要SD卡&#xff08;不是所有型号都可以&#xff0c;建议去了解了解&#xff09;、SD卡读卡器 USB&#xff1a;需要microUSB线 由于SD卡的网上资料很多了&#xff0c;又因为所需硬件&#xff08;SD卡…