开始开发

This commit is contained in:
2026-05-12 17:45:40 +08:00
parent cbb94162e0
commit bde7494997
1217 changed files with 253889 additions and 210 deletions

View File

@@ -0,0 +1,156 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
namespace Unity.XR.XREAL.Samples.NetWork
{
public class LocalServerSearcher : SingletonMonoBehaviour<LocalServerSearcher>
{
public struct ServerInfoResult
{
public bool isSuccess;
public IPEndPoint endPoint;
}
public delegate void OnGetSearchResult(ServerInfoResult result);
private UdpClient client;
private IPEndPoint endpoint;
private Thread m_ReceiveThread = null;
private const string SEARCHSERVERIP = "FIND-SERVER";
private const int BroadCastPort = 6001;
private static float TimeoutWaittingTime = 3f;
private IPEndPoint m_LocalServer;
private Queue<OnGetSearchResult> m_Tasks = new Queue<OnGetSearchResult>();
private Coroutine m_TimeOutCoroutine = null;
public LocalServerSearcher()
{
client = new UdpClient(new IPEndPoint(IPAddress.Any, 0));
endpoint = new IPEndPoint(IPAddress.Parse("255.255.255.255"), BroadCastPort);
}
public void Search(OnGetSearchResult callback)
{
lock (m_Tasks)
{
m_Tasks.Enqueue(callback);
}
if (m_ReceiveThread == null)
{
m_ReceiveThread = new Thread(new ThreadStart(RecvThread));
m_ReceiveThread.IsBackground = true;
m_ReceiveThread.Start();
}
RequestForServerIP();
TryStopTimeOutCoroutine();
m_TimeOutCoroutine = StartCoroutine(TimeOut());
}
private void TryStopTimeOutCoroutine()
{
if (m_TimeOutCoroutine != null)
{
Debug.Log("[LocalServerSearcher] StopTimeOutCoroutine");
StopCoroutine(m_TimeOutCoroutine);
m_TimeOutCoroutine = null;
}
}
private void RequestForServerIP()
{
Debug.Log("[LocalServerSearcher] RequestForServerIP");
byte[] buf = Encoding.Default.GetBytes(SEARCHSERVERIP);
client.Send(buf, buf.Length, endpoint);
}
private void RecvThread()
{
IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, BroadCastPort);
while (true)
{
try
{
byte[] buf = client.Receive(ref endpoint);
string data = Encoding.Default.GetString(buf);
Debug.Log("[LocalServerSearcher] Get the server info:" + data);
if (!string.IsNullOrEmpty(data))
{
string[] param = data.Split(':');
if (param != null && param.Length == 2)
{
m_LocalServer = new IPEndPoint(IPAddress.Parse(param[0]), int.Parse(param[1]));
Response(m_LocalServer);
TryStopTimeOutCoroutine();
}
}
}
catch (Exception e)
{
throw e;
}
}
}
private IEnumerator TimeOut()
{
float time_last = 0f;
while (true)
{
yield return new WaitForEndOfFrame();
time_last += Time.deltaTime;
if (time_last > TimeoutWaittingTime)
{
Debug.Log("[LocalServerSearcher] Get the server TimeOut");
Response(null);
TryStopTimeOutCoroutine();
}
}
}
private void Response(IPEndPoint endpoint)
{
if (m_Tasks.Count == 0)
{
return;
}
XREALMainThreadDispatcher.Singleton.QueueOnMainThread(() =>
{
ServerInfoResult result = new ServerInfoResult();
result.endPoint = endpoint;
result.isSuccess = endpoint != null;
lock (m_Tasks)
{
if (m_Tasks.Count == 0)
{
return;
}
var callback = m_Tasks.Dequeue();
while (callback != null)
{
try
{
callback?.Invoke(result);
}
catch (Exception e)
{
throw e;
}
if (m_Tasks.Count == 0)
{
return;
}
callback = m_Tasks.Dequeue();
}
}
});
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1361081bc4466d94796ef0d593a960d4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,91 @@
using System;
namespace Unity.XR.XREAL.Samples.NetWork
{
/// <summary> Net msg type. </summary>
public enum MessageType
{
/// <summary> Empty type. </summary>
None = 0,
/// <summary> Connect server. </summary>
Connected = 1,
/// <summary> Disconnect from server. </summary>
Disconnect = 2,
/// <summary> Heart beat. </summary>
HeartBeat = 3,
/// <summary> Enter room. </summary>
EnterRoom = 4,
/// <summary> Enter room. </summary>
ExitRoom = 5,
/// <summary> An enum constant representing the update camera Parameter option. </summary>
UpdateCameraParam = 6,
/// <summary> Used to synchronization message with the server. </summary>
MessageSynchronization = 7,
}
/// <summary> (Serializable) an enter room data. </summary>
[Serializable]
public class EnterRoomData
{
/// <summary> Enter room result. </summary>
public bool result;
}
/// <summary> (Serializable) an exit room data. </summary>
[Serializable]
public class ExitRoomData
{
/// <summary> Exit room result. </summary>
public bool Suc;
}
/// <summary> (Serializable) a camera parameter. </summary>
[Serializable]
public class CameraParam
{
/// <summary> Camera fov. </summary>
public Fov4f fov;
}
/// <summary> (Serializable) a fov 4f. </summary>
[Serializable]
public class Fov4f
{
/// <summary> The left. </summary>
public double left;
/// <summary> The right. </summary>
public double right;
/// <summary> The top. </summary>
public double top;
/// <summary> The bottom. </summary>
public double bottom;
/// <summary> Default constructor. </summary>
public Fov4f() { }
/// <summary> Constructor. </summary>
/// <param name="l"> A double to process.</param>
/// <param name="r"> A double to process.</param>
/// <param name="t"> A double to process.</param>
/// <param name="b"> A double to process.</param>
public Fov4f(double l, double r, double t, double b)
{
this.left = l;
this.right = r;
this.top = t;
this.bottom = b;
}
/// <summary> Convert this object into a string representation. </summary>
/// <returns> A string that represents this object. </returns>
public override string ToString()
{
return string.Format("{0} {1} {2} {3}", left, right, top, bottom);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a310a14a9e3851f47a5041b928fdf819
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,211 @@
using LitJson;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
namespace Unity.XR.XREAL.Samples.NetWork
{
/// <summary> An observer view net worker. </summary>
public class NetWorkBehaviour
{
/// <summary> The net work client. </summary>
protected NetWorkClient m_NetWorkClient;
/// <summary> The limit waitting time. </summary>
private const float limitWaittingTime = 5f;
/// <summary> True if is connected, false if not. </summary>
private bool m_IsConnected = false;
/// <summary> True if is jonin success, false if not. </summary>
private bool m_IsJoninSuccess = false;
/// <summary> True if is closed, false if not. </summary>
private bool m_IsClosed = false;
private Coroutine checkServerAvailableCoroutine = null;
private Dictionary<ulong, Action<JsonData>> _ResponseEvents = new Dictionary<ulong, Action<JsonData>>();
public virtual void Listen()
{
if (m_NetWorkClient == null)
{
m_NetWorkClient = new NetWorkClient();
m_NetWorkClient.OnDisconnect += OnDisconnect;
m_NetWorkClient.OnConnect += OnConnected;
m_NetWorkClient.OnJoinRoomResult += OnJoinRoomResult;
m_NetWorkClient.OnMessageResponse += OnMessageResponse;
}
}
private void OnMessageResponse(byte[] data)
{
ulong msgid = BitConverter.ToUInt64(data, 0);
Action<JsonData> callback;
if (!_ResponseEvents.TryGetValue(msgid, out callback))
{
Debug.LogWarning("[NetWorkBehaviour] can not find the msgid bind event:" + msgid);
return;
}
// Remove the header to get the msg.
byte[] result = new byte[data.Length - sizeof(ulong)];
Array.Copy(data, sizeof(ulong), result, 0, result.Length);
string json = Encoding.UTF8.GetString(result);
callback?.Invoke(JsonMapper.ToObject(json));
Debug.Log("[NetWorkBehaviour] OnMessageResponse hit...");
_ResponseEvents.Remove(msgid);
}
/// <summary> Check server available. </summary>
/// <param name="ip"> The IP.</param>
/// <param name="callback"> The callback.</param>
public void CheckServerAvailable(string ip, int port, Action<bool> callback)
{
if (string.IsNullOrEmpty(ip))
{
callback?.Invoke(false);
}
else
{
if (checkServerAvailableCoroutine != null)
{
XREALMainThreadDispatcher.Singleton.StopCoroutine(checkServerAvailableCoroutine);
}
checkServerAvailableCoroutine = XREALMainThreadDispatcher.Singleton.StartCoroutine(CheckServerAvailableCoroutine(ip, port, callback));
}
}
/// <summary> Check server available coroutine. </summary>
/// <param name="ip"> The IP.</param>
/// <param name="callback"> The callback.</param>
/// <returns> An IEnumerator. </returns>
private IEnumerator CheckServerAvailableCoroutine(string ip, int port, Action<bool> callback)
{
Debug.Log($"[ObserverView] CheckServerAvailableCoroutine: {ip}:{port}");
// Start to connect the server.
m_NetWorkClient.Connect(ip, port);
float timeLast = 0;
while (!m_IsConnected)
{
if (timeLast > limitWaittingTime || m_IsClosed)
{
Debug.Log("[ObserverView] Connect the server TimeOut!");
callback?.Invoke(false);
yield break;
}
timeLast += Time.deltaTime;
yield return new WaitForEndOfFrame();
}
// Start to enter the room.
m_NetWorkClient.EnterRoomRequest();
timeLast = 0;
while (!m_IsJoninSuccess)
{
if (timeLast > limitWaittingTime || m_IsClosed)
{
Debug.Log("[ObserverView] Join the server TimeOut!");
callback?.Invoke(false);
yield break;
}
timeLast += Time.deltaTime;
yield return new WaitForEndOfFrame();
}
callback?.Invoke(true);
}
public void SendMsg(JsonData data, Action<JsonData> onResponse, float timeout = 3)
{
XREALMainThreadDispatcher.Singleton.StartCoroutine(SendMessage(data, onResponse, timeout));
}
private IEnumerator SendMessage(JsonData data, Action<JsonData> onResponse, float timeout)
{
if (data == null)
{
Debug.LogError("[NetWorkBehaviour] data is null!");
yield break;
}
// Add msgid(current timestamp) as the header.
ulong msgid = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
byte[] json_data = Encoding.UTF8.GetBytes(data.ToJson());
byte[] total_data = new byte[json_data.Length + sizeof(ulong)];
Array.Copy(BitConverter.GetBytes(msgid), 0, total_data, 0, sizeof(ulong));
Array.Copy(json_data, 0, total_data, sizeof(ulong), json_data.Length);
if (onResponse != null)
{
Action<JsonData> onResult;
AsyncTask<JsonData> asyncTask = new AsyncTask<JsonData>(out onResult);
_ResponseEvents[msgid] = onResult;
m_NetWorkClient.SendMessage(total_data);
XREALMainThreadDispatcher.Singleton.StartCoroutine(SendMsgTimeOut(msgid, timeout));
yield return asyncTask.WaitForCompletion();
onResponse?.Invoke(asyncTask.Result);
}
else
{
m_NetWorkClient.SendMessage(total_data);
}
}
private IEnumerator SendMsgTimeOut(UInt64 id, float timeout)
{
yield return new WaitForSeconds(timeout);
Action<JsonData> callback;
if (_ResponseEvents.TryGetValue(id, out callback))
{
Debug.LogWarningFormat("[NetWorkBehaviour] Send msg timeout, id:{0}", id);
JsonData json = new JsonData();
json["success"] = false;
callback?.Invoke(json);
}
}
#region Net msg
/// <summary> Executes the 'connected' action. </summary>
private void OnConnected()
{
Debug.Log("[NetWorkBehaviour] OnConnected...");
m_IsConnected = true;
}
/// <summary> Executes the 'disconnect' action. </summary>
private void OnDisconnect()
{
Debug.Log("[NetWorkBehaviour] OnDisconnect...");
this.Close();
}
/// <summary> Executes the 'join room result' action. </summary>
/// <param name="result"> True to result.</param>
private void OnJoinRoomResult(bool result)
{
Debug.Log("[NetWorkBehaviour] OnJoinRoomResult :" + result);
m_IsJoninSuccess = result;
if (!result)
{
this.Close();
}
}
#endregion
/// <summary> Closes this object. </summary>
public virtual void Close()
{
if (checkServerAvailableCoroutine != null)
{
XREALMainThreadDispatcher.Singleton.StopCoroutine(checkServerAvailableCoroutine);
}
m_NetWorkClient.ExitRoomRequest();
m_NetWorkClient?.Dispose();
m_NetWorkClient = null;
checkServerAvailableCoroutine = null;
m_IsClosed = true;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 20c6ebff010f798409adcf0cfa4b57fe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,151 @@
using System;
using UnityEngine;
namespace Unity.XR.XREAL.Samples.NetWork
{
/// <summary> A net work client. </summary>
public class NetWorkClient : IDisposable
{
/// <summary> Event queue for all listeners interested in OnJoinRoomResult events. </summary>
public event Action<bool> OnJoinRoomResult;
/// <summary> Event queue for all listeners interested in OnCameraParamUpdate events. </summary>
public event Action<CameraParam> OnCameraParamUpdate;
/// <summary> Event queue for all listeners interested in OnMessageResponse events. </summary>
public event Action<byte[]> OnMessageResponse;
/// <summary> Event queue for all listeners interested in OnDisconnect events. </summary>
public event Action OnDisconnect;
/// <summary> Event queue for all listeners interested in OnConnect events. </summary>
public event Action OnConnect;
/// <summary> Default constructor. </summary>
public NetWorkClient()
{
NetworkSession.Register(MessageType.Connected, OnConnected);
NetworkSession.Register(MessageType.Disconnect, OnDisConnected);
NetworkSession.Register(MessageType.HeartBeat, HeartbeatResponse);
NetworkSession.Register(MessageType.EnterRoom, EnterRoomResponse);
NetworkSession.Register(MessageType.ExitRoom, ExitRoomResponse);
NetworkSession.Register(MessageType.UpdateCameraParam, UpdateCameraParamResponse);
NetworkSession.Register(MessageType.MessageSynchronization, MessageSynchronizationResponse);
}
/// <summary> Join the server's room. </summary>
public void EnterRoomRequest()
{
NetworkSession.Enqueue(MessageType.EnterRoom);
}
/// <summary> Exit room request. </summary>
public void ExitRoomRequest()
{
NetworkSession.Enqueue(MessageType.ExitRoom);
}
/// <summary> Updates the camera parameter request. </summary>
public void UpdateCameraParamRequest()
{
NetworkSession.Enqueue(MessageType.UpdateCameraParam);
}
/// <summary> Join the server's room. </summary>
public void SendMessage(byte[] data)
{
NetworkSession.Enqueue(MessageType.MessageSynchronization, data);
}
/// <summary> Connects. </summary>
/// <param name="ip"> The IP.</param>
/// <param name="port"> The port.</param>
public void Connect(string ip, int port)
{
NetworkSession.Connect(ip, port);
}
/// <summary> Executes the 'connected' action. </summary>
/// <param name="data"> The data.</param>
private void OnConnected(byte[] data)
{
OnConnect?.Invoke();
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged
/// resources. </summary>
public void Dispose()
{
NetworkSession.UnRegister(MessageType.Connected, OnConnected);
NetworkSession.UnRegister(MessageType.Disconnect, OnDisConnected);
NetworkSession.UnRegister(MessageType.HeartBeat, HeartbeatResponse);
NetworkSession.UnRegister(MessageType.EnterRoom, EnterRoomResponse);
NetworkSession.UnRegister(MessageType.ExitRoom, ExitRoomResponse);
NetworkSession.UnRegister(MessageType.UpdateCameraParam, UpdateCameraParamResponse);
NetworkSession.UnRegister(MessageType.MessageSynchronization, MessageSynchronizationResponse);
NetworkSession.Close();
}
/// <summary> Executes the 'dis connected' action. </summary>
/// <param name="data"> The data.</param>
private void OnDisConnected(byte[] data)
{
OnDisconnect?.Invoke();
}
#region Net msg response
/// <summary> Heartbeat response. </summary>
/// <param name="data"> The data.</param>
private void HeartbeatResponse(byte[] data)
{
NetworkSession.Received = true;
Debug.Log("Receive a heart beat package.");
}
/// <summary> Enter room response. </summary>
/// <param name="data"> The data.</param>
private void EnterRoomResponse(byte[] data)
{
EnterRoomData result = SerializerFactory.Create().Deserialize<EnterRoomData>(data);
if (result.result)
{
Debug.Log("Join the room success.");
OnJoinRoomResult?.Invoke(true);
}
else
{
Debug.LogWarning("Join the room faild.");
OnJoinRoomResult?.Invoke(false);
}
}
/// <summary> Exit room response. </summary>
/// <param name="data"> The data.</param>
private void ExitRoomResponse(byte[] data)
{
ExitRoomData result = SerializerFactory.Create().Deserialize<ExitRoomData>(data);
if (result.Suc)
{
Debug.Log("Exit the room success.");
}
else
{
Debug.LogWarning("Exit the room faild.");
}
}
/// <summary> Updates the camera parameter response described by data. </summary>
/// <param name="data"> The data.</param>
private void UpdateCameraParamResponse(byte[] data)
{
CameraParam result = SerializerFactory.Create().Deserialize<CameraParam>(data);
OnCameraParamUpdate?.Invoke(result);
Debug.Log(result.fov.ToString());
}
private void MessageSynchronizationResponse(byte[] data)
{
OnMessageResponse?.Invoke(data);
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 64d6788171afdaf4ea7aa77cd447bdf7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,379 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;
namespace Unity.XR.XREAL.Samples.NetWork
{
/// <summary> Call back. </summary>
/// <param name="data"> The data.</param>
public delegate void CallBack(byte[] data);
/// <summary> A network session. </summary>
public static class NetworkSession
{
/// <summary> Clent connect state. </summary>
private enum ClientState
{
/// <summary> Disconnect. </summary>
None,
/// <summary> Connect server success. </summary>
Connected,
}
/// <summary> Message type dictionary. </summary>
private static Dictionary<MessageType, CallBack> m_CallBacks = new Dictionary<MessageType, CallBack>();
/// <summary> Message queue. </summary>
private static Queue<byte[]> m_Messages;
/// <summary> Client current state. </summary>
private static ClientState m_CurState;
/// <summary> Gets or sets the current state. </summary>
/// <value> The current state. </value>
private static ClientState CurState
{
get
{
return m_CurState;
}
set
{
m_CurState = value;
if (m_CurState == ClientState.Connected)
{
CallBack callback;
if (m_CallBacks.TryGetValue(MessageType.Connected, out callback))
{
callback?.Invoke(null);
}
}
else
{
CallBack callback;
if (m_CallBacks.TryGetValue(MessageType.Disconnect, out callback))
{
callback?.Invoke(null);
}
}
}
}
/// <summary> The client. </summary>
private static TcpClient m_Client;
/// <summary> The stream. </summary>
private static NetworkStream m_Stream;
/// <summary> Target address. </summary>
private static IPAddress m_Address;
/// <summary> The port. </summary>
private static int m_Port;
/// <summary> Heart beat time stamp. </summary>
private const float HEARTBEAT_TIME = 1;
/// <summary> Time from last heart beat package. </summary>
private static float m_Timer = HEARTBEAT_TIME;
/// <summary> Get heart beat msg from server. </summary>
public static bool Received = true;
#region coroutines
/// <summary> Connects the coroutine. </summary>
/// <returns> An IEnumerator. </returns>
private static IEnumerator ConnectCoroutine()
{
m_Client = new TcpClient();
IAsyncResult async = m_Client.BeginConnect(m_Address, m_Port, null, null);
while (!async.IsCompleted)
{
Debug.Log("Contecting server...");
yield return null;
}
try
{
m_Client.EndConnect(async);
}
catch (Exception ex)
{
Debug.LogWarning("Conncet server faild :" + ex.Message);
yield break;
}
// Get data stream
try
{
m_Stream = m_Client.GetStream();
}
catch (Exception ex)
{
Debug.LogWarning("Connect server faild:" + ex.Message);
yield break;
}
if (m_Stream == null)
{
Debug.LogWarning("Connect server faild: data stream is empty");
yield break;
}
CurState = ClientState.Connected;
m_Messages = new Queue<byte[]>();
Debug.Log("Connect server success.");
// Set asyn msg send
NetworkCoroutine.Instance.StartCoroutine(HeartBeat());
// Set asyn msg receive
NetworkCoroutine.Instance.StartCoroutine(ReceiveCoroutine());
// Set quit event
NetworkCoroutine.Instance.SetQuitEvent(() => { m_Client.Close(); CurState = ClientState.None; });
}
/// <summary> Heart beat. </summary>
/// <returns> An IEnumerator. </returns>
private static IEnumerator HeartBeat()
{
while (CurState == ClientState.Connected)
{
m_Timer += Time.deltaTime;
if (m_Messages.Count > 0)
{
byte[] data = m_Messages.Dequeue();
yield return WriteCoroutine(data);
}
// Heart beat strategy
if (m_Timer >= HEARTBEAT_TIME)
{
// if dont receive last heart beat package.
if (!Received)
{
CurState = ClientState.None;
Debug.LogWarning("Heart beat error. disconnect server.");
yield break;
}
m_Timer = 0;
byte[] data = Pack(MessageType.HeartBeat);
yield return WriteCoroutine(data);
Debug.Log("Send a heart beat package.");
}
yield return null;
}
}
/// <summary> Receive coroutine. </summary>
/// <returns> An IEnumerator. </returns>
private static IEnumerator ReceiveCoroutine()
{
while (CurState == ClientState.Connected)
{
byte[] data = new byte[4];
int length; // msg len
MessageType type; // msg type
int receive = 0; // receive len
IAsyncResult async = m_Stream.BeginRead(data, 0, data.Length, null, null);
while (!async.IsCompleted)
{
yield return null;
}
try
{
receive = m_Stream.EndRead(async);
}
catch (Exception ex)
{
CurState = ClientState.None;
Debug.LogWarning("Receive msg package head erro:" + ex.Message);
yield break;
}
if (receive < data.Length)
{
CurState = ClientState.None;
Debug.LogWarning("Receive msg package head erro");
yield break;
}
using (MemoryStream stream = new MemoryStream(data))
{
BinaryReader binary = new BinaryReader(stream, Encoding.UTF8); // parase data using UTF-8
try
{
length = binary.ReadUInt16();
type = (MessageType)binary.ReadUInt16();
}
catch (Exception)
{
CurState = ClientState.None;
Debug.LogWarning("Receive msg package head erro");
yield break;
}
}
if (length - 4 > 0)
{
data = new byte[length - 4];
async = m_Stream.BeginRead(data, 0, data.Length, null, null);
while (!async.IsCompleted)
{
yield return null;
}
try
{
receive = m_Stream.EndRead(async);
}
catch (Exception ex)
{
CurState = ClientState.None;
Debug.LogWarning("Receive msg package head erro:" + ex.Message);
yield break;
}
if (receive < data.Length)
{
CurState = ClientState.None;
Debug.LogWarning("Receive msg package head erro");
yield break;
}
}
else
{
data = new byte[0];
receive = 0;
}
if (m_CallBacks.ContainsKey(type))
{
CallBack method = m_CallBacks[type];
method(data);
}
else
{
Debug.LogWarning("Did not regist the msg callback : " + type);
}
}
}
/// <summary> Writes a coroutine. </summary>
/// <param name="data"> The data.</param>
/// <returns> An IEnumerator. </returns>
private static IEnumerator WriteCoroutine(byte[] data)
{
if (CurState != ClientState.Connected || m_Stream == null)
{
Debug.LogWarning("Connect error, can not receive msg");
yield break;
}
IAsyncResult async = m_Stream.BeginWrite(data, 0, data.Length, null, null);
while (!async.IsCompleted)
{
yield return null;
}
try
{
m_Stream.EndWrite(async);
}
catch (Exception ex)
{
CurState = ClientState.None;
Debug.LogWarning("Send msg erro:" + ex.Message);
}
}
#endregion
/// <summary> Connect server. </summary>
/// <param name="address"> (Optional) The address.</param>
/// <param name="port"> (Optional) The port.</param>
public static void Connect(string address = null, int port = 8848)
{
// Can not connect again after connected.
if (CurState == ClientState.Connected)
{
Debug.Log("Has connected server.");
return;
}
if (address == null)
address = NetworkUtils.GetLocalIPv4();
// Cancle when get the ipaddress failed.
if (!IPAddress.TryParse(address, out m_Address))
{
Debug.LogWarning("IP erro, try again.");
return;
}
m_Port = port;
// Connect service.
NetworkCoroutine.Instance.StartCoroutine(ConnectCoroutine());
}
/// <summary> Closes this object. </summary>
public static void Close()
{
if (CurState == ClientState.Connected)
{
m_Client.Close();
m_CurState = ClientState.None;
}
NetworkCoroutine.Instance.StopAllCoroutines();
}
/// <summary> Regist callback event. </summary>
/// <param name="type"> The type.</param>
/// <param name="method"> The method.</param>
public static void Register(MessageType type, CallBack method)
{
if (!m_CallBacks.ContainsKey(type))
m_CallBacks.Add(type, method);
else
Debug.LogWarning("Regist the same msg type.");
}
/// <summary> Un register. </summary>
/// <param name="type"> The type.</param>
/// <param name="method"> The method.</param>
public static void UnRegister(MessageType type, CallBack method)
{
if (m_CallBacks.ContainsKey(type))
{
m_CallBacks.Remove(type);
}
}
/// <summary> Join the msg queue. </summary>
/// <param name="type"> The type.</param>
/// <param name="data"> (Optional) The data.</param>
public static void Enqueue(MessageType type, byte[] data = null)
{
// Pack the data
byte[] bytes = Pack(type, data);
if (CurState == ClientState.Connected)
{
m_Messages.Enqueue(bytes);
}
}
/// <summary> Pack the byte data. </summary>
/// <param name="type"> The type.</param>
/// <param name="data"> (Optional) The data.</param>
/// <returns> A byte[]. </returns>
private static byte[] Pack(MessageType type, byte[] data = null)
{
MessagePacker packer = new MessagePacker();
if (data != null)
{
packer.Add((ushort)(4 + data.Length)); // msg length
packer.Add((ushort)type); // msg type
packer.Add(data); // msg content
}
else
{
packer.Add(4);
packer.Add((ushort)type);
}
return packer.Package;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 81d4af6fac544cc498693b34606a6330
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e157ed67b5af0f74ba3dcbc64fce2cee
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
namespace Unity.XR.XREAL.Samples.NetWork
{
/// <summary> A message packer. </summary>
public class MessagePacker
{
/// <summary> The bytes. </summary>
private List<byte> bytes = new List<byte>();
/// <summary> Gets the package. </summary>
/// <value> The package. </value>
public byte[] Package
{
get { return bytes.ToArray(); }
}
/// <summary> Adds value. </summary>
/// <param name="data"> The data to add.</param>
/// <returns> A MessagePacker. </returns>
public MessagePacker Add(byte[] data)
{
bytes.AddRange(data);
return this;
}
/// <summary> Adds value. </summary>
/// <param name="value"> The value to add.</param>
/// <returns> A MessagePacker. </returns>
public MessagePacker Add(ushort value)
{
byte[] data = BitConverter.GetBytes(value);
bytes.AddRange(data);
return this;
}
/// <summary> Adds value. </summary>
/// <param name="value"> The value to add.</param>
/// <returns> A MessagePacker. </returns>
public MessagePacker Add(uint value)
{
byte[] data = BitConverter.GetBytes(value);
bytes.AddRange(data);
return this;
}
/// <summary> Adds value. </summary>
/// <param name="value"> The value to add.</param>
/// <returns> A MessagePacker. </returns>
public MessagePacker Add(ulong value)
{
byte[] data = BitConverter.GetBytes(value);
bytes.AddRange(data);
return this;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a3ad4b2ef850781438a4531d3c8843d3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,45 @@
using System;
using UnityEngine;
namespace Unity.XR.XREAL.Samples.NetWork
{
/// <summary> A network coroutine. </summary>
internal class NetworkCoroutine : MonoBehaviour
{
/// <summary> Event queue for all listeners interested in applicationQuit events. </summary>
private event Action ApplicationQuitEvent;
/// <summary> The instance. </summary>
private static NetworkCoroutine _instance;
/// <summary> Gets the instance. </summary>
/// <value> The instance. </value>
public static NetworkCoroutine Instance
{
get
{
if (!_instance)
{
GameObject socketClientObj = new GameObject("NetworkCoroutine");
_instance = socketClientObj.AddComponent<NetworkCoroutine>();
DontDestroyOnLoad(socketClientObj);
}
return _instance;
}
}
/// <summary> Sets quit event. </summary>
/// <param name="func"> The function.</param>
public void SetQuitEvent(Action func)
{
if (ApplicationQuitEvent != null) return;
ApplicationQuitEvent += func;
}
/// <summary> Executes the 'application quit' action. </summary>
private void OnApplicationQuit()
{
ApplicationQuitEvent?.Invoke();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 239487c972f53a549a19b8fe475f3610
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,135 @@
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
namespace Unity.XR.XREAL.Samples.NetWork
{
/// <summary> A network utilities. </summary>
public static class NetworkUtils
{
/// <summary> Get local ipv4, return null if faild. </summary>
/// <returns> The local IPv4. </returns>
public static string GetLocalIPv4()
{
string hostName = Dns.GetHostName(); //得到主机名
IPHostEntry iPEntry = Dns.GetHostEntry(hostName);
for (int i = 0; i < iPEntry.AddressList.Length; i++)
{
//从IP地址列表中筛选出IPv4类型的IP地址
if (iPEntry.AddressList[i].AddressFamily == AddressFamily.InterNetwork)
return iPEntry.AddressList[i].ToString();
}
return null;
}
/// <summary> Byte 2 string. </summary>
/// <param name="bytes"> The bytes.</param>
/// <returns> A string. </returns>
public static string Byte2String(byte[] bytes)
{
return Encoding.UTF8.GetString(bytes);
}
/// <summary> String 2 byte. </summary>
/// <param name="str"> The string.</param>
/// <returns> A byte[]. </returns>
public static byte[] String2Byte(string str)
{
return Encoding.UTF8.GetBytes(str);
}
}
/// <summary> Interface for serializer. </summary>
public interface ISerializer
{
/// <summary> Serialize this object to the given stream. </summary>
/// <param name="obj"> The object.</param>
/// <returns> A byte[]. </returns>
byte[] Serialize(object obj);
/// <summary> Deserialize this object to the given stream. </summary>
/// <typeparam name="T"> Generic type parameter.</typeparam>
/// <param name="data"> The data.</param>
/// <returns> A T. </returns>
T Deserialize<T>(byte[] data) where T : class;
}
/// <summary> An object for persisting JSON data. </summary>
public class JsonSerializer : ISerializer
{
/// <summary> Deserialize this object to the given stream. </summary>
/// <typeparam name="T"> Generic type parameter.</typeparam>
/// <param name="data"> The data.</param>
/// <returns> A T. </returns>
public T Deserialize<T>(byte[] data) where T : class
{
return LitJson.JsonMapper.ToObject<T>(Encoding.UTF8.GetString(data));
}
/// <summary> Serialize this object to the given stream. </summary>
/// <param name="obj"> The object.</param>
/// <returns> A byte[]. </returns>
public byte[] Serialize(object obj)
{
return Encoding.UTF8.GetBytes(LitJson.JsonMapper.ToJson(obj));
}
}
/// <summary> An object for persisting binary data. </summary>
public class BinarySerializer : ISerializer
{
/// <summary> obj -> bytes, return null if obj not mark as [Serializable]. </summary>
/// <param name="obj"> The object.</param>
/// <returns> A byte[]. </returns>
public byte[] Serialize(object obj)
{
//物体不为空且可被序列化
if (obj == null || !obj.GetType().IsSerializable)
return null;
BinaryFormatter formatter = new BinaryFormatter();
using (MemoryStream stream = new MemoryStream())
{
formatter.Serialize(stream, obj);
byte[] data = stream.ToArray();
return data;
}
}
/// <summary> bytes -> obj, return null if obj not mark as [Serializable]. </summary>
/// <typeparam name="T"> Generic type parameter.</typeparam>
/// <param name="data"> The data.</param>
/// <returns> A T. </returns>
public T Deserialize<T>(byte[] data) where T : class
{
//数据不为空且T是可序列化的类型
if (data == null || !typeof(T).IsSerializable)
return null;
BinaryFormatter formatter = new BinaryFormatter();
using (MemoryStream stream = new MemoryStream(data))
{
object obj = formatter.Deserialize(stream);
return obj as T;
}
}
}
/// <summary> A serializer factory. </summary>
public static class SerializerFactory
{
/// <summary> The serializer. </summary>
private static ISerializer _Serializer;
/// <summary> Creates a new ISerializer. </summary>
/// <returns> An ISerializer. </returns>
public static ISerializer Create()
{
if (_Serializer == null)
{
_Serializer = new JsonSerializer();
}
return _Serializer;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8b987fb1d08c23e4085e3f56c50c9b00
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: