Unity组件开发--长连接webSocket

news2024/11/17 11:36:15

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);
                } 
            }
        }
        
    }

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uniapp微信小程序投票系统实战 (SpringBoot2+vue3.2+element plus ) -我参与的投票列表实现

锋哥原创的uniapp微信小程序投票系统实战&#xff1a; uniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )_哔哩哔哩_bilibiliuniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )共计21条视频…

LeetCode刷题--- 按摩师

个人主页&#xff1a;元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客 个人专栏 力扣递归算法题 http://t.csdnimg.cn/yUl2I 【C】 ​​​​​​http://t.csdnimg.cn/6AbpV 数据结构与算法 ​​​http://t.csdnimg.cn/hKh2l 前言&#xff1a;这个专栏主要讲述动…

监督学习 - 支持向量回归(Support Vector Regression,SVR)

什么是机器学习 支持向量回归&#xff08;Support Vector Regression&#xff0c;SVR&#xff09;是一种基于支持向量机&#xff08;Support Vector Machine&#xff0c;SVM&#xff09;的回归算法&#xff0c;用于解决回归问题。与传统的回归算法不同&#xff0c;SVR的目标是…

微短剧市场暴涨267.65%,用微短剧场景AUI Kit精巧入局

微短剧&#xff0c;不仅上头&#xff0c;更要上心。 微短剧&#xff0c;深度“拿捏”了这个碎片化时代&#xff0c;也是刚过去的2023年绕不开的热词。 与传统影视剧制作精益求精、耗时长相反&#xff0c;门槛与耗时“双低”恰恰成为了微短剧的独特优势&#xff0c;使其走上以量…

使用numpy处理图片——图片拼接

大纲 左右拼接上下拼接 在《使用numpy处理图片——图片切割》一文中&#xff0c;我们介绍了如何使用numpy将一张图片切割成4部分。本文我们将反其道而行之&#xff0c;将4张图片拼接成1张图片。 基本的思路就是先用两张图以左右结构拼接成上部&#xff0c;另外两张图也以左右拼…

台灯实测PK:书客、明基、孩视宝护眼台灯,谁能为你带来舒适的光明体验?

现代生活中&#xff0c;我们频繁面对电子屏幕&#xff0c;对眼睛造成一定压力&#xff0c;长时间后会感到眼睛疲劳。特别是家长留意到&#xff0c;孩子在不适宜的灯光下长时间学习或玩耍时&#xff0c;会揉眼或表现出不适。为了更好地保护视力健康&#xff0c;选择一款护眼台灯…

Java多线程并发篇----第八篇

系列文章目录 文章目录 系列文章目录前言一、简述一下你对线程池的理解二、线程生命周期(状态)三、新建状态(NEW)四、就绪状态(RUNNABLE)五、运行状态(RUNNING)前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站…

爬虫到底违法吗?你离违法还有多远?

最近&#xff0c;国家依法查处了部分编写爬虫程序&#xff0c;盗取其他公司数据的不良企业。一时间风声鹤唳&#xff0c;关于爬虫程序是否违法的讨论遍布程序员圈子。那么到底编写爬虫程序是否违法呢&#xff1f; 其爬虫下载数据&#xff0c;一般而言都不违法&#xff0c;因为…

PHP+MySQL开发组合:开源在线课堂知识付费小程序源码系统,附带完整的搭建教程

互联网的快速发展&#xff0c;知识付费已经成为一种新的商业模式。越来越多的人希望通过在线学习来提升自己的知识和技能。为了满足这一市场需求&#xff0c;罗峰给大家分享一款基于PHP和MySQL的在线课堂知识付费小程序源码系统。该系统可帮助用户快速搭建自己的在线课堂平台&a…

亚信安全发布2024年网络安全威胁十大预测

2024年将是迎接网络安全新挑战的一年。随着经济和政治领域数字化发展&#xff0c;企业将越来越多地利用人工智能、机器学习 (AI/ML)、云等新型技术。虽然这些创新为企事业单位发展及运营提供了帮助&#xff0c;提高了效率&#xff0c;但它们同时也为攻击者提供了机会。 2024年&…

四川古力未来科技有限公司:抖音小店的崛起之路

随着互联网的飞速发展&#xff0c;电子商务已经成为人们日常生活中不可或缺的一部分。作为一家以科技为核心的公司&#xff0c;四川古力未来科技有限公司在电子商务领域中崭露头角&#xff0c;特别是其抖音小店的发展引人注目。 四川古力未来科技有限公司的抖音小店自开业以来&…

对象存储MinIO的简介与部署

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 相关文章推荐&#xff1a; 记录一次跨越16个月的minio版本升级与数据迁移 MinIO集群怎么接入Prometheus监控&#xff1f;(上) Mi…

vue3、vue2文件导入事件

一、vue3写法 1、html部分 <el-buttontype"info"plainicon"Upload"click"handleImport"v-hasPermi"[system:user:import]">导入</el-button><!-- 导入对话框 --><el-dialog :title"upload.title" v-…

如何使用网络测试仪构造特殊流量

为什么要仿真特殊流量 在现网中&#xff0c;网络流量时常伴随着突发&#xff0c;突发流量可能会造成网络的拥塞&#xff0c;从而产生丢包、抖动和时延&#xff0c;导致网络服务质量整体下降。面对宏观上的突发&#xff0c;通常采用在网络设备入向限速或者流量整形功能来消除突…

【Vue】引入路径正确,不影响正常运行但文件爆红

现象&#xff1a;引入路径正确但文件爆红&#xff0c;不影响运行但不美观&#xff08;按住Ctrl可以跳转到该文件&#xff0c;关闭后过段时间再打开还是爆红&#xff09; 原因 &#xff08;1&#xff09;相对路径使用了不正确的大小写 &#xff08;2&#xff09;项目不支持force…

PM50-40、PM50-45、PM50-52比例电控柱塞泵控制器

电比例轴向变量柱塞泵控制放大器技术是一种高效的液压动力传动技术。它通过将电液比例控制技术与轴向变量柱塞泵相结合&#xff0c;实现了液压泵的高精度、高效率和低噪声运行。以下是该技术的主要特点和应用&#xff1a;控制原理&#xff1a;基于双向电液比例变排量轴向柱塞泵…

ubuntu18.04.6 搭建mqtt服务器emqx 之docker方式

ubuntu18.04.6 搭建mqtt服务器emqx 之docker方式 前提docker环境已经安装好 如未安装&#xff0c;请参考博文https://blog.csdn.net/a554521655/article/details/134251763 文章目录 ubuntu18.04.6 搭建mqtt服务器emqx 之docker方式安装emqx查看是否安装并启动成功登录前端查 安…

记录一次数据中包含转义字符\引发的bug

后端返回给前端的数据是: { "bizObj": { "current": 1, "orders": [ ], "pages": 2, "records": [ { "from": "1d85b8a4bd33aaf99adc2e71ef02960e", …

Linux(Centos7)安装 jenkins(jdk11+jenkins2.375),并配置JDK,Maven,Git,GitLab

安装步骤 1. JDK11安装2. Maven安装3. git安装4. Jenkins2.375安装4.1 设置中文显示4.2 端口,用户权限修改4.3 插件下载4.4 全局工具配置4.4.1 Maven配置4.4.2 JDK配置4.4.3 Git配置 4.5 系统配置4.5.1 Gitee配置 4.6 构建测试 1. JDK11安装 #下载 yum -y install fontconfig …

2023 年最值得推荐的11个视频转换器(免费和付费)

拥有一个视频转换器供您使用意味着您可以轻松地在任何设备上播放所有视频。我们展示了适用于 Windows 的最佳视频转换器&#xff0c;这样您就不必浪费时间使用不合格的工具。 录制、编辑和分享视频是人生最大的消遣之一。有如此多的设备能够捕捉视频——而且共享它们的途径也很…