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{//获取cmdpublic static uint getCmd(uint merge){return merge >> 16;}//获取subCmdpublic static uint getSubCmd(uint merge){return merge & 0xFFFF;}//获取mergeCmdpublic 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 zagvalue = (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> : IProtopublic class Proto : IProto{public string name; //Э������public uint mid { get; set; } //��idpublic uint sid { get; set; } //��idpublic 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_DEBUGDebug.Log(string.Format("[Push] <<-- [{0}] {1}", pack.route, JsonUtility.ToJson(pack)));
#endifpackAction[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_DEBUGDebug.Log(string.Format("[Notify] -->> [{0}] {1}", route, JsonUtility.ToJson(info)));
#endifprotocol.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_DEBUGDebug.Log(string.Format("[{0}][Request] -->> [{1}] {2}", rqID, route, JsonUtility.ToJson(info)));
#endifExternalMessage pack = await protocol.RequestAsync<T>(rqID, cmdMerge, info, modelName);S msg = PackageProtocol.DecodeInfo<S>(pack.data);#if SOCKET_DEBUGDebug.Log(string.Format("[{0}][Request] <<-- [{1}] {2} {3}", rqID, route, JsonUtility.ToJson(msg), JsonUtility.ToJson(msg.info)));
#endifreturn 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_DEBUGDebug.Log(string.Format("[{0}][Request] -->> [{1}] {2}", rqID, route, JsonUtility.ToJson(info)));
#endifExternalMessage pack = await protocol.RequestAsync<T>(rqID, cmdMerge, info, modelName);S msg = PackageProtocol.DecodeInfo<S>(pack.data);
#if SOCKET_DEBUGDebug.Log(string.Format("[{0}][Request] <<-- [{1}] {2} {3}", rqID, route, JsonUtility.ToJson(msg), JsonUtility.ToJson(msg.info)));
#endifreturn 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.netDictionary<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_EDITORvar filepath = Path.Combine(Application.dataPath.Replace("Assets", ""), "config.txt");
#elsevar filepath = Path.Combine(Application.dataPath, "config.txt");
#endifDebug.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_EDITORvar serverMode = AppConst.serverMode;
#elsevar serverMode  = (ServerMode)int.Parse(HttpHelper.GetUrlParam("severAPI"));
#endifAppConst.serverMode = serverMode;switch (serverMode) {case ServerMode.preview:Host.ApiHost = (string)data["preview"];break;case ServerMode.dev:Host.ApiHost = (string)data["dev"];break;}#if !UNITY_EDITORvar 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);}
#endifDebug.Log("LoadFileSetNetwork SpaceId" + PlayerData.Instance.SpaceId);//如果在编辑器下面,并且不填sapceId,那么则进入发布模式if (string.IsNullOrEmpty(PlayerData.Instance.SpaceId) == false) {await HttpHelper.Instance.GetSceneConfig();}#if !UNITY_EDITORif (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");
#elsestring stateUrl = string.IsNullOrEmpty(PlayerData.Instance.SpaceId) == true ? AppConst.PublicMode : AppConst.ViewMode;
#endifvar 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_EDITORawait HttpHelper.Instance.GetServerConfig();
#elseHost.gameServer = (string)data["local"];
#endifCreateConeccetionBtn();}}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();}elseDebug.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.rhkb.cn/news/235927.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【java八股文】之Java基础篇

1、Java有哪几种数据类型 基本数据类型&#xff1a;byte(1字节) short&#xff08;2字节&#xff09; int&#xff08;4字节&#xff09; long&#xff08;8字节&#xff09; float&#xff08;4字节&#xff09; double&#xff08;8字节&#xff09; char&#xff08;2字节&a…

【动态规划】 【字典树】C++算法:472 连接词

作者推荐 【动态规划】458:可怜的小猪 涉及知识点 动态规划 字典树 LeetCode472 连接词 给你一个 不含重复 单词的字符串数组 words &#xff0c;请你找出并返回 words 中的所有 连接词 。 连接词 定义为&#xff1a;一个完全由给定数组中的至少两个较短单词&#xff08;不…

DUET: Cross-Modal Semantic Grounding for Contrastive Zero-Shot Learning论文阅读

文章目录 摘要1.问题的提出引出当前研究的不足与问题属性不平衡问题属性共现问题 解决方案 2.数据集和模型构建数据集传统的零样本学习范式v.s. DUET学习范式DUET 模型总览属性级别对比学习正负样本解释&#xff1a; 3.结果分析VIT-based vision transformer encoder.消融研究消…

RTL编码(1)——概述

一、RTL级描述 RTL&#xff08;Register Transfer Level&#xff09;级&#xff1a;寄存器&#xff0b;组合逻辑&#xff0c;其功能与时序用Verilog HDL&#xff08;以下简称Verilog&#xff09;或VHDL代码描述。 RTL描述包含了同步数字电路最重要的三个特征&#xff1a;组合逻…

【Python】编程练习的解密与实战(三)

​&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《Python | 编程解码》⏰诗赋清音&#xff1a;云生高巅梦远游&#xff0c; 星光点缀碧海愁。 山川深邃情难晤&#xff0c; 剑气凌云志自修。 目录 &#x1fa90;1. 初识Python &a…

[BJDCTF2020]ZJCTF,不过如此

题目源码&#xff1a; <?phperror_reporting(0); $text $_GET["text"]; $file $_GET["file"]; if(isset($text)&&(file_get_contents($text,r)"I have a dream")){echo "<br><h1>".file_get_contents($tex…

vscode 创建文件自动添加注释信息

随机记录 目录 1. 背景介绍 2. "Docstring Generator"扩展 2.1 安装 2.2 设置注释信息 3. 自动配置py 文件头注释 1. 背景介绍 在VS Code中&#xff0c;您可以使用扩展来为新创建的Python文件自动添加头部注释信息。有几个常用的扩展可以实现此功能&#xff0…

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

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

Mysql是怎么运行的(上)

文章目录 Mysql是怎么运行的Mysql处理一条语句的流程连接管理解析与优化存储引擎 基本配置配置文件系统变量状态变量字符集四种重要的字符集MySQL中的utf8和utf8mb4各级别的字符集和比较规则MySQL中字符集的转换排序规则产生的不同的排序结果 InnoDB存储引擎介绍COMPACT行格式介…

PostgreSQL内存浅析

体系结构 &#xff08;https://www.postgresql.fastware.com/blog/lets-get-back-to-basics-postgresql-memory-components&#xff09; &#xff08;http://geekdaxue.co/read/fcantsql/qts5is) 共享内存 linux的共享内存实现 (https://momjian.us/main/writings/pgsql/insi…

解锁前端新潜能:如何使用 Rust 锈化前端工具链

前言 近年来&#xff0c;Rust的受欢迎程度不断上升。首先&#xff0c;在操作系统领域&#xff0c;Rust 已成为 Linux 内核官方认可的开发语言之一&#xff0c;Windows 也宣布将使用 Rust 来重写内核&#xff0c;并重写部分驱动程序。此外&#xff0c;国内手机厂商 Vivo 也宣布…

如何利用ChatGPT快速生成月报?

随着每个月的结束&#xff0c;个人和团队经常需要编写月报来回顾和总结。这项任务通常消耗大量时间和精力。幸运的是&#xff0c;借助ChatGPT&#xff0c;这个过程可以变得更加简单和高效。接下来&#xff0c;我将详细介绍如何利用ChatGPT快速生成月报&#xff0c;从而帮助你节…

回归预测 | Matlab基于CPO-BP基于冠豪猪算法优化BP神经网络的数据多输入单输出回归预测

回归预测 | Matlab基于CPO-BP基于冠豪猪算法优化BP神经网络的数据多输入单输出回归预测 目录 回归预测 | Matlab基于CPO-BP基于冠豪猪算法优化BP神经网络的数据多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.CPO-BP回归基于冠豪猪优化算法[24年新…

Redis(四)事务

文章目录 事务Redis事务 vs 数据库事务常用命令总结 事务 一个队列中、一次性、顺序性、排他性执行一系列命令 官网https://redis.io/docs/interact/transactions/ Redis事务 vs 数据库事务 概述详述1、单独的隔离操作Redis的事务仅仅是保证事务里的操作会被连续独占的执行&a…

【AI视野·今日Sound 声学论文速览 第四十三期】Mon, 8 Jan 2024

AI视野今日CS.Sound 声学论文速览 Mon, 8 Jan 2024 Totally 6 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Sound Papers MusicAOG: an Energy-Based Model for Learning and Sampling a Hierarchical Representation of Symbolic Music Authors Yikai Qian, Tia…

leetcode面试经典150题——50 快乐数

题目&#xff1a;快乐数 描述&#xff1a; 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。 然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变…

Element-ui图片懒加载

核心代码 <el-image src"https://img-blog.csdnimg.cn/direct/2236deb5c315474884599d90a85d761d.png" alt"我是图片" lazy><img slot"error" src"https://img-blog.csdnimg.cn/direct/81bf096a0dff4e5fa58e5f43fd44dcc6.png&quo…

【Redis】Redis面试热点

Redis 集群有哪些方案&#xff1f; 主从复制&#xff1a;解决了高并发问题 哨兵模式&#xff1a;解决了高并发&#xff0c;高可用问题 分片集群&#xff1a;解决了海量数据存储&#xff0c;高并发写的问题 主从复制 图示&#xff1a; 主从复制&#xff1a;单节点 Redis 并发…

2023 Gartner® 云数据库管理系统魔力象限发布 PingCAP 入选“荣誉提及”

近日&#xff0c;全球 IT 市场研究和咨询 公司 Gartner 发布最新报告《Magic Quadrant™ for Cloud Database Management Systems》&#xff08;云数据库管理系统魔力象限&#xff09;&#xff0c; 企业级开源分布式数据库厂商 PingCAP 入选“荣誉提及” 。前不久&#xff0c;P…

STL之list

目录 list定义和结构 list容器模板接受两个参数&#xff1a; list容器的特点 双向性 动态大小 不连续存储 实例 代码输出 需要注意的点 list常用函数 代码示例 list定义和结构 list的使用频率不高&#xff0c;在做题时极少遇到需要使用list的情景。 list是一种双向…