Files
plugin-library/Assets/00.StaryEvo/Runtime/UnitySocket/SocketClient/Scripts/Socket/SocketServer.cs
2025-09-04 11:43:35 +08:00

325 lines
9.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 });
}
}