This commit is contained in:
2025-09-04 11:43:35 +08:00
parent 8872c20cf2
commit 60e4ef39ed
707 changed files with 1498 additions and 29309 deletions

View File

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

View File

@@ -0,0 +1,114 @@
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using Stary.Evo;
using UnityEngine;
namespace Main
{
public interface IClientSocketSystem : ISystem
{
/// <summary>
/// 连接
/// </summary>
void OnConnect(string ip);
/// <summary>
/// 向服务端发送消息
/// </summary>
/// <param name="type">消息类型</param>
/// <param name="message">消息内容</param>
void Send(SocketEvent type, string message);
/// <summary>
/// 断开连接
/// </summary>
void DisConnect();
}
public class ClientSocketSystem : AbstractSystem, IClientSocketSystem
{
private SocketClient _socketClient;
public override void Dispose()
{
// 注意由于Unity编译器环境下游戏开启/关闭只影响主线程的开关游戏关闭回调时需要通过Close函数来关闭服务端/客户端的线程。
if (_socketClient != null) _socketClient.Close();
}
/// <summary>
/// 向服务端发送消息
/// </summary>
/// <param name="type">消息类型</param>
/// <param name="message">消息内容</param>
public void Send(SocketEvent type, string message)
{
var bytes = Encoding.UTF8.GetBytes(message);
_socketClient.Send((ushort)type, bytes);
}
/// <summary>
/// 退出服务端
/// </summary>
public void DisConnect()
{
// 踢出连接
_socketClient.DisConnect();
}
public void OnConnect(string ip)
{
_socketClient = new SocketClient(ip, 10000);
_socketClient.Connect(() =>
{
Debug.Log("连接成功");
_socketClient.OnReceive += OnReceive;
// _client.DisConnect();
}, () => { Debug.Log("连接失败"); });
}
public void Connect()
{
}
protected override void OnInit()
{
}
/// <summary>
/// 接收客户端消息
/// </summary>
/// <param name="client">指定客户端</param>
/// <param name="data">消息数据</param>
private void OnReceive(SocketDataPack data)
{
Debug.LogFormat("接收到数据>>>{0} {1}", (SocketEvent)data.Type, data.Buff.Length);
switch ((SocketEvent)data.Type)
{
case SocketEvent.ServerMessage:
Debug.LogFormat("接收到服务器数据 >>> {0}", Encoding.UTF8.GetString(data.Data));
var message = Encoding.UTF8.GetString(data.Data);
break;
case SocketEvent.ClientDisconn:
break;
}
}
public string GetLocalIPv4()
{
return Dns.GetHostEntry(Dns.GetHostName())
.AddressList.First(f => f.AddressFamily == AddressFamily.InterNetwork)
.ToString();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cf683938375f4ecf8f186b6f27cc1090
timeCreated: 1753945542

View File

@@ -0,0 +1,165 @@
using System.Net;
using System.Net.Sockets;
using System.Text;
using Newtonsoft.Json;
using Stary.Evo;
using UnityEngine;
namespace Server
{
public interface IServerSocketSystem : ISystem
{
/// <summary>
/// 连接服务器
/// </summary>
/// <param name="ip"></param>
void OnConnect(string ip = "");
/// <summary>
/// 向指定客户端发送消息
/// </summary>
/// <param name="client">指定客户端</param>
/// <param name="type">消息类型</param>
/// <param name="message">消息内容</param>
void Send(Socket client, SocketEvent type, string message);
/// <summary>
/// 向所有客户端发送消息
/// </summary>
/// <param name="type">消息类型</param>
/// <param name="message">消息内容</param>
void SendAllClient(SocketEvent type, ResultEntity message);
/// <summary>
/// 踢出指定客户端
/// </summary>
/// <param name="client">指定客户端</param>
void KickOut(Socket client);
/// <summary>
/// 踢出所有客户端
/// </summary>
void KickOutAll();
}
public class ServerSocketSystem : AbstractSystem, IServerSocketSystem
{
private SocketServer _socketServer;
public override void Dispose()
{
// 注意由于Unity编译器环境下游戏开启/关闭只影响主线程的开关游戏关闭回调时需要通过Close函数来关闭服务端/客户端的线程。
if (_socketServer != null) _socketServer.Close();
}
/// <summary>
/// 向指定客户端发送消息
/// </summary>
/// <param name="client">指定客户端</param>
/// <param name="type">消息类型</param>
/// <param name="message">消息内容</param>
public void Send(Socket client, SocketEvent type, string message)
{
var bytes = Encoding.UTF8.GetBytes(message);
_socketServer.Send(client, (ushort)type, bytes);
}
/// <summary>
/// 向所有客户端发送消息
/// </summary>
/// <param name="type">消息类型</param>
/// <param name="message">消息内容</param>
public void SendAllClient(SocketEvent type, ResultEntity message)
{
Debug.Log("SendAllClient向客户端发送消息" + JsonConvert.SerializeObject(message));
foreach (var item in _socketServer.ClientInfoDic)
{
message.SetDeviceCode(item.Value.IP);
Debug.Log("向客户端发送消息:" + ResultEntity.ToEntityString(message));
var bytes = Encoding.UTF8.GetBytes(ResultEntity.ToEntityString(message));
_socketServer.Send(item.Key, (ushort)type, bytes);
}
}
/// <summary>
/// 服务器踢出客户端
/// </summary>
public void KickOut(Socket client)
{
// 踢出连接
_socketServer.KickOut(client);
}
/// <summary>
/// 服务器踢出客户端
/// </summary>
public void KickOutAll()
{
// 踢出连接
foreach (var item in _socketServer.ClientInfoDic.Keys)
_socketServer.KickOutAll();
}
public void OnConnect(string ip = "")
{
if (string.IsNullOrEmpty(ip))
ip = "0.0.0.0";
_socketServer = new SocketServer(ip, 10000);
_socketServer.OnReceive += OnReceive;
_socketServer.OnConnect += OnConnect;
_socketServer.OnDisconnect += OnDisconnect;
_socketServer.OnHeadTimeOut += OnHeadTimeOut;
}
public void OnConnect(Socket client)
{
var endPoint = client.RemoteEndPoint as IPEndPoint;
var clientIp = endPoint?.Address.ToString() ?? "未知IP";
this.SendEvent(ServerPanelType.QueryClient,
clientIp, RSSI.Good);
}
private void OnDisconnect(Socket client)
{
var endPoint = client.RemoteEndPoint as IPEndPoint;
var clientIp = endPoint?.Address.ToString() ?? "未知IP";
this.SendEvent(ServerPanelType.QueryClient,
clientIp, RSSI.Not);
}
private void OnHeadTimeOut(Socket client)
{
var endPoint = client.RemoteEndPoint as IPEndPoint;
var clientIp = endPoint?.Address.ToString() ?? "未知IP";
this.SendEvent(ServerPanelType.QueryClient,
clientIp, RSSI.Bad);
}
protected override void OnInit()
{
}
/// <summary>
/// 接收客户端消息
/// </summary>
/// <param name="client">指定客户端</param>
/// <param name="data">消息数据</param>
private void OnReceive(Socket client, SocketDataPack data)
{
Debug.LogFormat("[{0}]接收到数据>>>{1} {2}", client.LocalEndPoint, (SocketEvent)data.Type, data.Buff.Length);
switch ((SocketEvent)data.Type)
{
case SocketEvent.ScTest:
Debug.LogFormat("接收到测试数据 >>> {0}", Encoding.UTF8.GetString(data.Data));
break;
case SocketEvent.ClientDisconn:
Debug.LogFormat("客户端断开连接 >>> {0}", client.LocalEndPoint);
break;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 83370f66e6954f62a28b9e38c266819a
timeCreated: 1753940317

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 588ac15fc35e4262979673d60d5360d5
timeCreated: 1753941532

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e1043c3356e14496b3692c245bfb461b
timeCreated: 1753941548

View File

@@ -0,0 +1,33 @@
namespace Server
{
public enum ServerOpenSceneSchemaType
{
BirdView, //鸟瞰
Spot //临场
}
public enum ServerOpenSceneType
{
Unit1, //场景1
Unit2, //场景2
Unit3, //场景3
Unit4 //场景4
}
public enum ServerPanelType
{
OpenScene, //打开场景
ControlSchema, //控制场景开关
QueryClient //查询客户端
}
/// <summary>
/// 信号强度
/// </summary>
public enum RSSI
{
Good,
Bad,
Not
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cab61756e91e4736afde89c57340b821
timeCreated: 1753941563

View File

@@ -0,0 +1,105 @@
using System;
using Newtonsoft.Json;
namespace Server
{
[Serializable]
public struct ResultCodeEntity
{
/// <summary>
/// 状态码ServerPanelType
/// </summary>
private string code { get; set; }
private object message { get; set; }
private ResultCodeEntity(string code, object message)
{
this.code = code;
this.message = message;
}
public string Code()
{
return code;
}
public object Message()
{
return message;
}
public static readonly ResultCodeEntity OpenScene = new(ServerPanelType.OpenScene.ToString(), "");
public static readonly ResultCodeEntity ControlSchema = new(ServerPanelType.ControlSchema.ToString(), "");
}
[Serializable]
public struct ResultEntity
{
/// <summary>
/// 状态码ServerPanelType
/// </summary>
public string code { get; set; }
public string deviceCode { get; set; }
public object message { get; set; }
public void SetDeviceCode(string deviceCode)
{
this.deviceCode = deviceCode;
}
public string DeviceCode()
{
return deviceCode;
}
public void SetMessage(object message)
{
this.message = message;
}
public ResultEntity(string code, string deviceCode, object message)
{
this.code = code;
this.deviceCode = deviceCode;
this.message = message;
}
public ResultEntity(ResultCodeEntity resultCode, string deviceCode)
{
code = resultCode.Code();
this.deviceCode = deviceCode;
message = resultCode.Message();
}
public static string ToEntityString(ResultEntity entity)
{
var data = JsonConvert.SerializeObject(entity, Formatting.Indented);
return JsonConvert.SerializeObject(data);
}
public static ResultEntity SetResultCode(ResultCodeEntity resultCode, string deviceCode)
{
var result = new ResultEntity(resultCode, deviceCode);
return result;
}
public static ResultEntity SetResultCode(ResultCodeEntity resultCode, object message)
{
var result = new ResultEntity(resultCode, "");
result.message = message;
return result;
}
public static ResultEntity SetResultCode(ResultCodeEntity resultCode, string deviceCode, object message)
{
var result = new ResultEntity(resultCode, deviceCode);
result.message = message;
return result;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 41f092961d2c4f33a0d0e980158dd89a
timeCreated: 1753941907

View File

@@ -0,0 +1,15 @@
namespace Server
{
public struct ServerOpenSceneEntity
{
/// <summary>
/// ServerOpenSceneType
/// </summary>
public string serverOpenSceneType;
/// <summary>
/// ServerOpenSceneSchemaType
/// </summary>
public string serverOpenSceneSchemaType;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fbb30584a7ca4dd7afadf941cb67692e
timeCreated: 1753941679

View File

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

View File

@@ -0,0 +1,57 @@
using System;
/// <summary>
/// Socket传输过程的缓冲区尝试拆包获得数据
/// </summary>
public class DataBuffer
{
// 缓存区长度
private const int MIN_BUFF_LEN = 1024;
private byte[] _buff;
private int _buffLength = 0;
public DataBuffer(int minBuffLen = MIN_BUFF_LEN)
{
if (minBuffLen <= 0)
{
minBuffLen = MIN_BUFF_LEN;
}
_buff = new byte[minBuffLen];
}
/// <summary>
/// 添加缓存数据
/// </summary>
public void AddBuffer(byte[] data, int len)
{
byte[] buff = new byte[len];
Array.Copy(data, buff, len);
if (len > _buff.Length - _buffLength) //超过当前缓存
{
byte[] temp = new byte[_buffLength + len];
Array.Copy(_buff, 0, temp, 0, _buffLength);
Array.Copy(buff, 0, temp, _buffLength, len);
_buff = temp;
}
else
{
Array.Copy(data, 0, _buff, _buffLength, len);
}
_buffLength += len;//修改当前数据标记
}
public bool TryUnpack(out SocketDataPack dataPack)
{
dataPack = SocketDataPack.Unpack(_buff);
if (dataPack == null)
{
return false;
}
// 清理旧缓存
_buffLength -= dataPack.BuffLength;
byte[] temp = new byte[_buffLength < MIN_BUFF_LEN ? MIN_BUFF_LEN : _buffLength];
Array.Copy(_buff, dataPack.BuffLength, temp, 0, _buffLength);
_buff = temp;
return true;
}
}

View File

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

View File

@@ -0,0 +1,371 @@
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using UnityEngine;
using Timer = System.Timers.Timer;
/// <summary>
/// Socket客户端
/// </summary>
public class SocketClient
{
private const int TIMEOUT_CONNECT = 3000; // 连接超时时间 毫秒
private const int TIMEOUT_SEND = 3000; // 发送超时时间 毫秒
private const int TIMEOUT_RECEIVE = 3000; //接收超时时间 毫秒
private const int HEAD_OFFSET = 2000; //心跳包发送间隔 毫秒
private const int RECONN_MAX_SUM = 3; //最大重连次数
private readonly DataBuffer _dataBuffer = new();
private readonly string _ip;
/// <summary>
/// 主线程
/// </summary>
private readonly SynchronizationContext _mainThread;
private readonly int _port;
private readonly int HEARTBEAT_TIMEOUT = 5000;
private Socket _client;
private Timer _connTimeoutTimer;
private Timer _headTimer;
private Timer _heartbeatTimeoutTimer;
private bool _isConnect;
private bool _isReconnect;
private long _lastHeartbeatResponseTime;
private Thread _receiveThread;
public SocketClient(string ip, int port)
{
_mainThread = SynchronizationContext.Current;
_ip = ip;
_port = port;
}
public event Action OnConnectSuccess; // 连接成功回调
public event Action OnConnectError; // 连接失败回调
public event Action OnDisconnect; // 断开回调
public event Action<SocketDataPack> OnReceive; // 接收报文回调
public event Action<SocketDataPack> OnSend; // 发送报文回调
public event Action<SocketException> OnError; // 异常捕获回调
public event Action<int> OnReConnectSuccess; // 重连成功回调
public event Action<int> OnReConnectError; // 单次重连失败回调
public event Action<int> OnReconnecting; // 单次重连中回调
public void Connect(Action success = null, Action error = null)
{
OnDisconnect += () => { Debug.Log("UnityEvo:断开连接"); };
OnReceive += dataPack => { Debug.LogFormat("UnityEvo:接收数据>>>{0}", (SocketEvent)dataPack.Type); };
OnSend += dataPack => { Debug.LogFormat("UnityEvo:发送数据>>>{0}", (SocketEvent)dataPack.Type); };
OnError += ex => { Debug.LogFormat("UnityEvo:出现异常>>>{0}", ex); };
OnReConnectSuccess += num => { Debug.LogFormat("UnityEvo:第{0}次重连成功", num); };
OnReConnectError += num => { Debug.LogFormat("UnityEvo:第{0}次重连失败", num); };
OnReconnecting += num => { Debug.LogFormat("UnityEvo:正在进行第{0}次重连", num); };
void OnTrigger(bool flag)
{
if (flag)
{
PostMainThreadAction(success);
PostMainThreadAction(OnConnectSuccess);
}
else
{
PostMainThreadAction(error);
PostMainThreadAction(OnConnectError);
}
if (_connTimeoutTimer != null)
{
_connTimeoutTimer.Stop();
_connTimeoutTimer = null;
}
}
try
{
_client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //创建套接字
_client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, TIMEOUT_SEND);
_client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, TIMEOUT_RECEIVE);
var ipAddress = IPAddress.Parse(_ip); //解析IP地址
var ipEndpoint = new IPEndPoint(ipAddress, _port);
var result = _client.BeginConnect(ipEndpoint, iar =>
{
try
{
var client = (Socket)iar.AsyncState;
client.EndConnect(iar);
_isConnect = true;
// 开始发送心跳包
_headTimer = new Timer(HEAD_OFFSET);
_headTimer.AutoReset = true;
_headTimer.Elapsed += delegate { Send((ushort)SocketEvent.ClientHead); };
_headTimer.Start();
// 初始化心跳超时检测
_lastHeartbeatResponseTime = GetNowTime();
_heartbeatTimeoutTimer = new Timer(HEARTBEAT_TIMEOUT);
_heartbeatTimeoutTimer.AutoReset = true;
_heartbeatTimeoutTimer.Elapsed += (sender, e) =>
{
// 检测是否超时未收到服务端心跳响应
ReceiveHead();
};
_heartbeatTimeoutTimer.Start();
// 开始接收数据
_receiveThread = new Thread(ReceiveEvent);
_receiveThread.IsBackground = true;
_receiveThread.Start();
OnTrigger(true);
}
catch (SocketException ex)
{
OnTrigger(false);
}
}, _client); //异步连接
_connTimeoutTimer = new Timer(TIMEOUT_CONNECT);
_connTimeoutTimer.AutoReset = false;
_connTimeoutTimer.Elapsed += delegate { OnTrigger(false); };
_connTimeoutTimer.Start();
}
catch (SocketException ex)
{
OnTrigger(false);
// throw;
}
}
/// <summary>
/// 断线重连
/// </summary>
/// <param name="num"></param>
/// <param name="index"></param>
public void ReConnect(int num = RECONN_MAX_SUM, int index = 0)
{
_isReconnect = true;
num--;
index++;
if (num < 0)
{
onDisconnect();
_isReconnect = false;
return;
}
PostMainThreadAction(OnReconnecting, index);
Connect(() =>
{
PostMainThreadAction(OnReConnectSuccess, index);
_isReconnect = false;
}, () =>
{
PostMainThreadAction(OnReConnectError, index);
ReConnect(num, index);
});
}
public void Send(ushort e, byte[] buff = null, Action<SocketDataPack> onTrigger = null)
{
buff = buff ?? new byte[] { };
var dataPack = new SocketDataPack(e, buff);
var data = dataPack.Buff;
try
{
_client.BeginSend(data, 0, data.Length, SocketFlags.None, asyncSend =>
{
var c = (Socket)asyncSend.AsyncState;
c.EndSend(asyncSend);
PostMainThreadAction(onTrigger, dataPack);
PostMainThreadAction(OnSend, dataPack);
}, _client);
}
catch (SocketException ex)
{
onError(ex);
}
}
/// <summary>
/// 线程内接收数据的函数
/// </summary>
private void ReceiveEvent()
{
while (true)
try
{
if (!_isConnect) break;
if (_client.Available <= 0) continue;
var rbytes = new byte[8 * 1024];
var len = _client.Receive(rbytes);
if (len > 0)
{
_dataBuffer.AddBuffer(rbytes, len); // 将收到的数据添加到缓存器中
if (_dataBuffer.TryUnpack(out var dataPack)) // 尝试解包
{
//新增:处理服务器心跳响应
if (dataPack.Type == (ushort)SocketEvent.ServerHead)
_lastHeartbeatResponseTime = GetNowTime(); // 更新心跳响应时间戳
else if (dataPack.Type == (ushort)SocketEvent.ServerKickout)
// 服务端踢出
onDisconnect();
else if (dataPack.Type == (ushort)SocketEvent.ServerMessage)
// 收到消息
PostMainThreadAction(OnReceive, dataPack);
}
}
else if (len == 0)
{
// 服务端正常关闭连接
onDisconnect();
break;
}
}
catch (SocketException ex)
{
onError(ex);
break;
}
}
/// <summary>
/// 业务逻辑 - 客户端主动断开
/// </summary>
public void DisConnect()
{
Send((ushort)SocketEvent.ClientDisconn);
onDisconnect();
}
/// <summary>
/// 接收到心跳包
/// </summary>
private void ReceiveHead()
{
var now = GetNowTime();
var offset = now - _lastHeartbeatResponseTime;
Debug.Log("客户端更新服务端心跳时间戳 >>>" + GetNowTime() + " 间隔>>>" + offset);
if (GetNowTime() - _lastHeartbeatResponseTime > HEARTBEAT_TIMEOUT)
onError(new SocketException((int)SocketError.TimedOut));
}
/// <summary>
/// 缓存数据清理
/// </summary>
public void Close()
{
if (!_isConnect) return;
_isConnect = false;
if (_headTimer != null)
{
_headTimer.Stop();
_headTimer = null;
}
// 停止并释放心跳超时检测定时器
if (_heartbeatTimeoutTimer != null)
{
_heartbeatTimeoutTimer.Stop();
_heartbeatTimeoutTimer.Dispose(); // 释放资源
_heartbeatTimeoutTimer = null;
}
// if (_receiveThread != null)
// {
// _receiveThread.Abort();
// _receiveThread = null;
// }
if (_connTimeoutTimer != null)
{
_connTimeoutTimer.Stop();
_connTimeoutTimer = null;
}
if (_client != null)
{
_client.Close();
_client = null;
}
}
/// <summary>
/// 错误回调
/// </summary>
/// <param name="e"></param>
private void onError(SocketException ex)
{
Close();
PostMainThreadAction(OnError, ex);
if (!_isReconnect) ReConnect();
}
/// <summary>
/// 断开回调
/// </summary>
private void onDisconnect()
{
Close();
PostMainThreadAction(OnDisconnect);
}
/// <summary>
/// 通知主线程回调
/// </summary>
private void PostMainThreadAction(Action action)
{
_mainThread.Post(o =>
{
var e = (Action)o.GetType().GetProperty("action")?.GetValue(o);
if (e != null) e();
}, new { action });
}
private void PostMainThreadAction<T>(Action<T> action, T arg1)
{
_mainThread.Post(o =>
{
var e = (Action<T>)o.GetType().GetProperty("action")?.GetValue(o);
var t1 = (T)o.GetType().GetProperty("arg1")?.GetValue(o);
if (e != null) e(t1);
}, new { action, arg1 });
}
public void PostMainThreadAction<T1, T2>(Action<T1, T2> action, T1 arg1, T2 arg2)
{
_mainThread.Post(o =>
{
var e = (Action<T1, T2>)o.GetType().GetProperty("action")?.GetValue(o);
var t1 = (T1)o.GetType().GetProperty("arg1")?.GetValue(o);
var t2 = (T2)o.GetType().GetProperty("arg2")?.GetValue(o);
if (e != null) e(t1, t2);
}, new { action, arg1, arg2 });
}
/// <summary>
/// 获取当前时间戳
/// </summary>
/// <returns></returns>
private long GetNowTime()
{
var ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
return Convert.ToInt64(ts.TotalMilliseconds);
}
}

View File

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

View File

@@ -0,0 +1,8 @@
using UnityEngine;
[CreateAssetMenu(menuName = "Evo/SocketDataEntity")]
public class SocketDataEntity : ScriptableObject
{
public string serverIP;
public bool isConnected;
}

View File

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

View File

@@ -0,0 +1,105 @@
using System;
using System.IO;
/// <summary>
/// Socket通信过程中的数据包 处理具体拆包装包逻辑
/// </summary>
public class SocketDataPack
{
// 消息:数据总长度(4byte) + 数据类型(2byte) + 数据(N byte)
public const int HEAD_DATA_LEN = 4;
public const int HEAD_TYPE_LEN = 2;
public static int HeadLen
{
get { return HEAD_DATA_LEN + HEAD_TYPE_LEN; }
}
/// <summary>
/// 数据包类型
/// </summary>
public UInt16 Type;
/// <summary>
/// 数据包数据
/// </summary>
public byte[] Data;
public byte[] Buff;
public int BuffLength
{
get { return Buff.Length; }
}
public int DataLength
{
get { return Data.Length; }
}
public SocketDataPack()
{
}
public SocketDataPack(UInt16 type, byte[] data)
{
Type = type;
Data = data;
Buff = GetBuff(Type, Data);
}
public static byte[] GetBuff(UInt16 type, byte[] data)
{
byte[] buff = new byte[data.Length + HeadLen];
byte[] temp;
temp = BitConverter.GetBytes(buff.Length);
Array.Copy(temp, 0, buff, 0, HEAD_DATA_LEN);
temp = BitConverter.GetBytes(type);
Array.Copy(temp, 0, buff, HEAD_DATA_LEN, HEAD_TYPE_LEN);
Array.Copy(data, 0, buff, HeadLen, data.Length);
return buff;
}
public static SocketDataPack Unpack(byte[] buff)
{
try
{
if (buff.Length < HeadLen)
{
// 头部没取完则返回
return null;
}
byte[] temp;
// 取数据长度
temp = new byte[HEAD_DATA_LEN];
Array.Copy(buff, 0, temp, 0, HEAD_DATA_LEN);
int buffLength = BitConverter.ToInt32(temp, 0);
if (buffLength <= 0) return null;
if (buffLength > buff.Length)
{
// 数据没取完
return null;
}
int dataLength = buffLength - HeadLen;
// 取数据类型
temp = new byte[HEAD_TYPE_LEN];
Array.Copy(buff, HEAD_DATA_LEN, temp, 0, HEAD_TYPE_LEN);
UInt16 dataType = BitConverter.ToUInt16(temp, 0);
// 取数据
byte[] data = new byte[dataLength];
Array.Copy(buff, HeadLen, data, 0, dataLength);
var dataPack = new SocketDataPack(dataType, data);
// UnityEngine.Debug.LogFormat("buffLen:{0} type:{1} dataLength:{2}", buffLength, dataType, data.Length);
return dataPack;
}
catch
{
// 存在不完整数据解包 则返回null
return null;
}
}
}

View File

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

View File

@@ -0,0 +1,15 @@
/// <summary>
/// 网络事件协议ID枚举
/// </summary>
public enum SocketEvent
{
ClientHead = 0x0001, //心跳包
ServerHead = 0x0006, //服务端心跳包
ClientDisconn = 0x0002, //客户端主动断开
ServerKickout = 0x0003, //服务端踢出
ServerMessage = 0x0004, //服务端发送消息
ClientMessage = 0x0005, //客户端发送消息
ScTest = 0x1001 //测试用
}

View File

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

View File

@@ -0,0 +1,325 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using UnityEngine;
using Timer = System.Timers.Timer;
public class SocketInfo
{
public long HeadTime;
public string IP;
public Thread ReceiveThread;
}
/// <summary>
/// Socket服务端
/// </summary>
public class SocketServer
{
private const int HEAD_TIMEOUT = 5000; // 心跳超时 毫秒
private readonly DataBuffer _dataBuffer = new();
/// <summary>
/// 主线程
/// </summary>
private readonly SynchronizationContext _mainThread;
private readonly Socket _server;
public readonly Dictionary<Socket, SocketInfo> ClientInfoDic = new();
private Timer _headCheckTimer;
// 目前捕获异常将触发OnDisconnect回调 暂不单独处理
// public event Action<SocketException> OnError; // 异常捕获回调
private bool _isValid = true;
public SocketServer(string ip, int port)
{
OnConnect += client => { Debug.LogFormat("UnityEvo:连接成功 >> IP:{0}", client.LocalEndPoint); };
OnDisconnect += client =>
{
if (ClientInfoDic.TryGetValue(client, out var value))
Debug.LogFormat("UnityEvo:连接断开 >> IP:{0}", value.IP);
};
_mainThread = SynchronizationContext.Current;
_server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
var ipAddress = IPAddress.Parse(ip); //解析IP地址
_server.Bind(new IPEndPoint(ipAddress, port)); //绑定IP地址端口
_server.Listen(10); //设定最多10个排队连接请求
// 启动线程监听连接
var connectThread = new Thread(ListenClientConnect);
connectThread.Start();
// 心跳包定时检测
_headCheckTimer = new Timer(HEAD_TIMEOUT);
_headCheckTimer.AutoReset = true;
_headCheckTimer.Elapsed += delegate { CheckHeadTimeOut(); };
_headCheckTimer.Start();
Debug.Log($"SocketServer Start: {ip}:{port}");
}
public event Action<Socket> OnConnect; //客户端建立连接回调
public event Action<Socket> OnDisconnect; // 客户端断开连接回调
public event Action<Socket, SocketDataPack> OnReceive; // 接收报文回调
public event Action<Socket, SocketDataPack> OnSend; // 发送报文回调
public event Action<Socket> OnHeadTimeOut; // 心跳包收到超时回调
/// <summary>
/// 监听客户端连接
/// </summary>
private void ListenClientConnect()
{
while (true)
try
{
if (!_isValid) break;
var client = _server.Accept();
var ipEndPoint = (IPEndPoint)client.RemoteEndPoint;
var receiveThread = new Thread(ReceiveEvent);
ClientInfoDic.Add(client,
new SocketInfo
{ IP = ipEndPoint.Address.ToString(), ReceiveThread = receiveThread, HeadTime = GetNowTime() });
receiveThread.Start(client);
PostMainThreadAction(OnConnect, client);
}
catch
{
break;
}
}
/// <summary>
/// 获取当前时间戳
/// </summary>
/// <returns></returns>
private long GetNowTime()
{
var ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
return Convert.ToInt64(ts.TotalMilliseconds);
}
public void Send(Socket client, ushort e, byte[] buff = null, Action<SocketDataPack> onTrigger = null)
{
SendMessage(client, e, buff, onTrigger);
}
private void SendMessage(Socket client, ushort e, byte[] buff = null, Action<SocketDataPack> onTrigger = null)
{
buff = buff ?? new byte[] { };
var dataPack = new SocketDataPack(e, buff);
var data = dataPack.Buff;
try
{
client.BeginSend(data, 0, data.Length, SocketFlags.None, asyncSend =>
{
var c = (Socket)asyncSend.AsyncState;
c.EndSend(asyncSend);
PostMainThreadAction(onTrigger, dataPack);
PostMainThreadAction(OnSend, client, dataPack);
}, client);
}
catch (SocketException ex)
{
CloseClient(client);
// onError(ex);
}
}
/// <summary>
/// 线程内接收数据的函数
/// </summary>
private void ReceiveEvent(object client)
{
var tsocket = (Socket)client;
while (true)
{
if (!_isValid) return;
if (!ClientInfoDic.ContainsKey(tsocket)) return;
try
{
var rbytes = new byte[8 * 1024];
var len = tsocket.Receive(rbytes);
if (len > 0)
{
_dataBuffer.AddBuffer(rbytes, len); // 将收到的数据添加到缓存器中
if (_dataBuffer.TryUnpack(out var dataPack)) // 尝试解包
{
if (dataPack.Type == (ushort)SocketEvent.ClientHead)
{
// 接收到心跳包
ReceiveHead(tsocket);
//返回客户端心跳
Send(tsocket, (ushort)SocketEvent.ServerHead);
}
else if (dataPack.Type == (ushort)SocketEvent.ClientDisconn)
// 客户端断开连接
{
CloseClient(tsocket);
}
else if (dataPack.Type == (ushort)SocketEvent.ClientMessage)
// 收到消息
{
PostMainThreadAction(OnReceive, tsocket, dataPack);
}
}
}
else
{
if (tsocket.Poll(-1, SelectMode.SelectRead))
{
CloseClient(tsocket);
return;
}
}
}
catch (SocketException ex)
{
CloseClient(tsocket);
// onError(ex);
return;
}
}
}
/// <summary>
/// 接收到心跳包
/// </summary>
private void ReceiveHead(Socket client)
{
SocketInfo info;
if (ClientInfoDic.TryGetValue(client, out info))
{
var now = GetNowTime();
var offset = now - info.HeadTime;
Debug.Log("更新心跳时间戳 >>>" + now + " 间隔>>>" + offset);
if (offset > HEAD_TIMEOUT)
// 心跳包收到但超时逻辑
OnHeadTimeOut?.Invoke(client);
info.HeadTime = now;
}
}
/// <summary>
/// 检测心跳包超时
/// </summary>
private void CheckHeadTimeOut()
{
var tempList = new List<Socket>();
foreach (var socket in ClientInfoDic.Keys) tempList.Add(socket);
foreach (var socket in tempList)
{
var info = ClientInfoDic[socket];
var now = GetNowTime();
var offset = now - info.HeadTime;
if (offset > HEAD_TIMEOUT)
// 心跳包超时
KickOut(socket);
}
}
public void KickOut(Socket client)
{
// 踢出连接
Send(client, (ushort)SocketEvent.ServerKickout, null, dataPack => { CloseClient(client); });
}
public void KickOutAll()
{
var tempList = new List<Socket>();
foreach (var socket in ClientInfoDic.Keys) tempList.Add(socket);
foreach (var socket in tempList) KickOut(socket);
}
/// <summary>
/// 清理客户端连接
/// </summary>
/// <param name="client"></param>
private void CloseClient(Socket client)
{
PostMainThreadAction(socket =>
{
if (OnDisconnect != null) OnDisconnect(socket);
ClientInfoDic.Remove(socket);
socket.Close();
}, client);
}
/// <summary>
/// 关闭
/// </summary>
public void Close()
{
if (!_isValid) return;
_isValid = false;
// if (_connectThread != null) _connectThread.Abort();
var tempList = new List<Socket>();
foreach (var socket in ClientInfoDic.Keys) tempList.Add(socket);
foreach (var socket in tempList) CloseClient(socket);
if (_headCheckTimer != null)
{
_headCheckTimer.Stop();
_headCheckTimer = null;
}
_server.Close();
}
// /// <summary>
// /// 错误回调
// /// </summary>
// /// <param name="e"></param>
// private void onError(SocketException ex)
// {
// PostMainThreadAction<SocketException>(OnError, ex);
// }
// <summary>
/// 通知主线程回调
/// </summary>
private void PostMainThreadAction(Action action)
{
_mainThread.Post(o =>
{
var e = (Action)o.GetType().GetProperty("action")?.GetValue(o);
if (e != null) e();
}, new { action });
}
private void PostMainThreadAction<T>(Action<T> action, T arg1)
{
_mainThread.Post(o =>
{
var e = (Action<T>)o.GetType().GetProperty("action")?.GetValue(o);
var t1 = (T)o.GetType().GetProperty("arg1")?.GetValue(o);
if (e != null) e(t1);
}, new { action, arg1 });
}
public void PostMainThreadAction<T1, T2>(Action<T1, T2> action, T1 arg1, T2 arg2)
{
_mainThread.Post(o =>
{
var e = (Action<T1, T2>)o.GetType().GetProperty("action")?.GetValue(o);
var t1 = (T1)o.GetType().GetProperty("arg1")?.GetValue(o);
var t2 = (T2)o.GetType().GetProperty("arg2")?.GetValue(o);
if (e != null) e(t1, t2);
}, new { action, arg1, arg2 });
}
}

View File

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

View File

@@ -0,0 +1,50 @@
using System.Text;
using UnityEngine;
public class SocketClientMain : MonoBehaviour
{
private SocketClient _client;
private string ip = "127.0.0.1";
private int port = 8080;
private void Awake()
{
_client = new SocketClient("192.168.31.67", 6854);
_client.Connect(() =>
{
Debug.Log("连接成功");
// _client.DisConnect();
}, () => { Debug.Log("连接失败"); });
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
var bytes = Encoding.UTF8.GetBytes("我是测试数据");
_client.Send((ushort)SocketEvent.ScTest, bytes);
}
if (Input.GetKeyDown(KeyCode.D)) _client.DisConnect();
}
private void OnDestroy()
{
// 注意由于Unity编译器环境下游戏开启/关闭只影响主线程的开关游戏关闭回调时需要通过Close函数来关闭服务端/客户端的线程。
if (_client != null) _client.Close();
}
public void StartConnect(string ip = "", int port = 0)
{
}
public void SendServer(SocketEvent socketEvent, string message)
{
var bytes = Encoding.UTF8.GetBytes(message);
_client.Send((ushort)socketEvent, bytes);
Debug.LogFormat("客户端发送数据 >>> {0}", message);
}
}

View File

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

View File

@@ -0,0 +1,41 @@
using System.Text;
using UnityEngine;
public class SocketServerMain : MonoBehaviour
{
private SocketServer _server;
private void Awake()
{
_server = new SocketServer("192.168.31.67", 6854);
_server.OnReceive += (client, data) =>
{
Debug.LogFormat("[{0}]接收到数据>>>{1} {2}", client.LocalEndPoint, (SocketEvent)data.Type, data.Buff.Length);
switch ((SocketEvent)data.Type)
{
case SocketEvent.ScTest:
Debug.LogFormat("接收到测试数据 >>> {0}", Encoding.UTF8.GetString(data.Data));
break;
case SocketEvent.ClientDisconn:
Debug.LogFormat("客户端断开连接 >>> {0}", client.LocalEndPoint);
break;
}
};
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.A))
// 踢出连接
foreach (var item in _server.ClientInfoDic.Keys)
_server.KickOutAll();
}
private void OnDestroy()
{
// 注意由于Unity编译器环境下游戏开启/关闭只影响主线程的开关游戏关闭回调时需要通过Close函数来关闭服务端/客户端的线程。
if (_server != null) _server.Close();
}
}

View File

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