728 lines
22 KiB
C#
728 lines
22 KiB
C#
using System.Collections.Concurrent;
|
||
using System.Net;
|
||
using System.Net.NetworkInformation; // 添加这个引用
|
||
using System.Net.Sockets;
|
||
using System.Runtime.CompilerServices;
|
||
using System.Text;
|
||
using Common;
|
||
using DotNext;
|
||
using DotNext.Threading;
|
||
using Newtonsoft.Json;
|
||
using WebProtocol;
|
||
|
||
/// <summary> UDP接受数据包格式 </summary>
|
||
public class UDPData
|
||
{
|
||
/// <summary>
|
||
/// 接受到的时间
|
||
/// </summary>
|
||
public required DateTime DateTime { get; set; }
|
||
/// <summary>
|
||
/// 数据包时间戳
|
||
/// </summary>
|
||
public required UInt32 Timestamp { get; set; }
|
||
/// <summary>
|
||
/// 发送来源的IP地址
|
||
/// </summary>
|
||
public required string Address { get; set; }
|
||
/// <summary>
|
||
/// 发送来源的端口号
|
||
/// </summary>
|
||
public required int Port { get; set; }
|
||
|
||
/// <summary>
|
||
/// 任务ID
|
||
/// </summary>
|
||
public required int TaskID { get; set; }
|
||
/// <summary>
|
||
/// 接受到的数据
|
||
/// </summary>
|
||
public required byte[] Data { get; set; }
|
||
/// <summary>
|
||
/// 是否被读取过
|
||
/// </summary>
|
||
public required bool HasRead { get; set; }
|
||
|
||
/// <summary>
|
||
/// 深度拷贝对象
|
||
/// </summary>
|
||
/// <returns>UDPData</returns>
|
||
public UDPData DeepClone()
|
||
{
|
||
var cloneData = new byte[this.Data.Length];
|
||
Buffer.BlockCopy(this.Data, 0, cloneData, 0, this.Data.Length);
|
||
return new UDPData()
|
||
{
|
||
DateTime = this.DateTime,
|
||
Timestamp = this.Timestamp,
|
||
Address = new string(this.Address),
|
||
Port = this.Port,
|
||
TaskID = this.TaskID,
|
||
Data = cloneData,
|
||
HasRead = this.HasRead
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将UDP Data 转化为Json 格式字符串
|
||
/// </summary>
|
||
/// <returns>json字符串</returns>
|
||
public override string ToString()
|
||
{
|
||
return JsonConvert.SerializeObject(this);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// UDP 服务器
|
||
/// </summary>
|
||
public class UDPServer
|
||
{
|
||
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||
|
||
private ConcurrentDictionary<string, SortedList<UInt32, UDPData>> udpData
|
||
= new ConcurrentDictionary<string, SortedList<UInt32, UDPData>>();
|
||
private readonly AsyncReaderWriterLock udpDataLock = new AsyncReaderWriterLock();
|
||
|
||
private int listenPort;
|
||
private List<UdpClient> listeners = new List<UdpClient>();
|
||
private List<Task> tasks = new List<Task>();
|
||
private IPEndPoint groupEP;
|
||
|
||
private CancellationTokenSource? cancellationTokenSource;
|
||
private bool disposed = false;
|
||
|
||
/// <summary>
|
||
/// 是否正在工作
|
||
/// </summary>
|
||
public bool IsRunning => cancellationTokenSource?.Token.IsCancellationRequested == false;
|
||
|
||
/// <summary> UDP 服务器的错误代码 </summary>
|
||
public enum ErrorCode
|
||
{
|
||
/// <summary> [TODO:description] </summary>
|
||
Success = 0,
|
||
/// <summary> [TODO:description] </summary>
|
||
GetNoneAfterTimeout,
|
||
/// <summary> [TODO:description] </summary>
|
||
ResponseWrong,
|
||
/// <summary> [TODO:description] </summary>
|
||
NotRecvDataPackage,
|
||
}
|
||
|
||
/// <summary>
|
||
/// Construct a udp server with fixed port
|
||
/// </summary>
|
||
/// <param name="port"> Device UDP Port </param>
|
||
/// <param name="num"> UDP Client Num </param>
|
||
/// <returns> UDPServer class </returns>
|
||
public UDPServer(int port, int num)
|
||
{
|
||
// Construction
|
||
this.listenPort = port;
|
||
try
|
||
{
|
||
for (int i = 0; i < num; i++)
|
||
{
|
||
int currentPort = this.listenPort + i;
|
||
if (IsPortInUse(currentPort))
|
||
{
|
||
throw new ArgumentException(
|
||
$"端口{currentPort}已被占用,无法启动UDP Server",
|
||
nameof(port)
|
||
);
|
||
}
|
||
listeners.Add(new UdpClient(currentPort));
|
||
}
|
||
this.groupEP = new IPEndPoint(IPAddress.Any, listenPort);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Console.WriteLine(e.ToString());
|
||
throw new ArgumentException(
|
||
$"Failed to set up server with this port: {port}",
|
||
nameof(port)
|
||
);
|
||
}
|
||
}
|
||
|
||
private bool IsPortInUse(int port)
|
||
{
|
||
bool inUse = false;
|
||
try
|
||
{
|
||
var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
|
||
var udpListeners = ipGlobalProperties.GetActiveUdpListeners();
|
||
foreach (var ep in udpListeners)
|
||
{
|
||
if (ep.Port == port)
|
||
{
|
||
inUse = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Warn($"Failed to check port usage for port {port}: {ex.Message}");
|
||
}
|
||
return inUse;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 异步寻找目标发送的内容
|
||
/// </summary>
|
||
/// <param name="ipAddr"> 目标IP地址 </param>
|
||
/// <param name="taskID">[TODO:parameter]</param>
|
||
/// <param name="timeout">超时时间</param>
|
||
/// <param name="cycle">延迟时间</param>
|
||
/// <param name="callerName">调用函数名称</param>
|
||
/// <param name="callerLineNum">调用函数位置</param>
|
||
/// <returns>
|
||
/// 异步Optional 数据包:
|
||
/// Optional 为空时,表明找不到数据;
|
||
/// Optional 存在时,为最先收到的数据
|
||
/// </returns>
|
||
public async ValueTask<Optional<UDPData>> FindDataAsync(
|
||
string ipAddr, int taskID, int timeout = 1000, int cycle = 0,
|
||
[CallerMemberName] string callerName = "",
|
||
[CallerLineNumber] int callerLineNum = 0
|
||
)
|
||
{
|
||
UDPData? data = null;
|
||
var key = $"{ipAddr}-{taskID}";
|
||
|
||
var startTime = DateTime.Now;
|
||
var isTimeout = false;
|
||
|
||
try
|
||
{
|
||
while (!isTimeout)
|
||
{
|
||
var elapsed = DateTime.Now - startTime;
|
||
isTimeout = elapsed >= TimeSpan.FromMilliseconds(timeout);
|
||
if (isTimeout) break;
|
||
|
||
using (await udpDataLock.AcquireWriteLockAsync(TimeSpan.FromMilliseconds(timeout)))
|
||
{
|
||
if (udpData.TryGetValue(key, out var sortedList) && sortedList.Count > 0)
|
||
{
|
||
// 获取最早的数据(第一个元素)
|
||
var firstKey = sortedList.Keys[0];
|
||
data = sortedList[firstKey];
|
||
sortedList.RemoveAt(0);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (data is null)
|
||
throw new TimeoutException("Get nothing even after time out");
|
||
else return new(data.DeepClone());
|
||
}
|
||
catch
|
||
{
|
||
logger.Trace("Get nothing even after time out");
|
||
return new(null);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 异步寻找目标发送的所有内容,并清空队列
|
||
/// </summary>
|
||
/// <param name="ipAddr">目标IP地址</param>
|
||
/// <param name="taskID">任务ID</param>
|
||
/// <param name="timeout">超时时间</param>
|
||
/// <returns>异步Optional 数据包列表</returns>
|
||
public async ValueTask<Optional<List<UDPData>>> FindDataArrayAsync(string ipAddr, int taskID, int timeout = 1000)
|
||
{
|
||
List<UDPData>? data = null;
|
||
var key = $"{ipAddr}-{taskID}";
|
||
|
||
|
||
var startTime = DateTime.Now;
|
||
var isTimeout = false;
|
||
while (!isTimeout)
|
||
{
|
||
var elapsed = DateTime.Now - startTime;
|
||
isTimeout = elapsed >= TimeSpan.FromMilliseconds(timeout);
|
||
if (isTimeout) break;
|
||
|
||
try
|
||
{
|
||
using (await udpDataLock.AcquireWriteLockAsync(TimeSpan.FromMilliseconds(timeout)))
|
||
{
|
||
if (udpData.TryGetValue(key, out var sortedList) && sortedList.Count > 0)
|
||
{
|
||
data = new List<UDPData>(sortedList.Values);
|
||
// 输出数据
|
||
// PrintDataArray(data);
|
||
sortedList.Clear();
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
logger.Trace("Get nothing even after time out");
|
||
return new(null);
|
||
}
|
||
}
|
||
|
||
if (data is null)
|
||
{
|
||
logger.Trace("Get nothing even after time out");
|
||
return new(null);
|
||
}
|
||
else
|
||
{
|
||
return new(data);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取还未被读取的数据列表
|
||
/// </summary>
|
||
/// <param name="ipAddr">IP地址</param>
|
||
/// <param name="taskID">任务ID</param>
|
||
/// <param name="timeout">超时时间</param>
|
||
/// <returns>数据列表</returns>
|
||
public async ValueTask<Optional<List<UDPData>>> GetDataArrayAsync(string ipAddr, int taskID, int timeout = 1000)
|
||
{
|
||
List<UDPData>? data = null;
|
||
|
||
try
|
||
{
|
||
using (await udpDataLock.AcquireReadLockAsync(TimeSpan.FromMilliseconds(timeout)))
|
||
{
|
||
var key = $"{ipAddr}-{taskID}";
|
||
if (udpData.TryGetValue(key, out var sortedList) && sortedList.Count > 0)
|
||
{
|
||
data = new List<UDPData>(sortedList.Values);
|
||
}
|
||
}
|
||
}
|
||
catch (TimeoutException)
|
||
{
|
||
logger.Trace("Failed to acquire read lock within timeout");
|
||
return new(null);
|
||
}
|
||
|
||
if (data is null)
|
||
{
|
||
logger.Trace("Get nothing even after time out");
|
||
return new(null);
|
||
}
|
||
else
|
||
{
|
||
return new(data);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 异步获取指定IP和任务ID的数据队列长度
|
||
/// </summary>
|
||
/// <param name="ipAddr">IP地址</param>
|
||
/// <param name="taskID">任务ID</param>
|
||
/// <param name="timeout">超时时间</param>
|
||
/// <returns>数据队列长度</returns>
|
||
public async ValueTask<Optional<int>> GetDataCountAsync(string ipAddr, int taskID, int timeout = 1000)
|
||
{
|
||
int? count = null;
|
||
|
||
try
|
||
{
|
||
using (await udpDataLock.AcquireReadLockAsync(TimeSpan.FromMilliseconds(timeout)))
|
||
{
|
||
var key = $"{ipAddr}-{taskID}";
|
||
if (udpData.TryGetValue(key, out var sortedList))
|
||
{
|
||
count = sortedList.Count;
|
||
}
|
||
}
|
||
}
|
||
catch (TimeoutException)
|
||
{
|
||
logger.Trace("Failed to acquire read lock within timeout");
|
||
return Optional<int>.None;
|
||
}
|
||
|
||
if (count is null)
|
||
{
|
||
logger.Trace("Get nothing even after time out");
|
||
return Optional<int>.None;
|
||
}
|
||
else
|
||
{
|
||
return new(count.Value);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 异步等待写响应
|
||
/// </summary>
|
||
/// <param name="endPoint">IP地址及端口</param>
|
||
/// <param name="taskID">[TODO:parameter]</param>
|
||
/// <param name="timeout">超时时间范围</param>
|
||
/// <returns>接收响应包</returns>
|
||
public async ValueTask<Result<WebProtocol.RecvRespPackage>> WaitForAckAsync
|
||
(IPEndPoint endPoint, int taskID, int timeout = 1000)
|
||
{
|
||
var address = endPoint.Address.ToString();
|
||
var port = endPoint.Port;
|
||
|
||
var data = await FindDataAsync(address, taskID, timeout);
|
||
if (!data.HasValue)
|
||
{
|
||
await UDPClientPool.SendResetSignal(endPoint);
|
||
return new(new Exception("Get None even after time out!"));
|
||
}
|
||
|
||
var recvData = data.Value;
|
||
if (recvData.Address != address || (port > 0 && recvData.Port != port))
|
||
return new(new Exception("Receive Data From Wrong Board!"));
|
||
|
||
var retPack = WebProtocol.RecvRespPackage.FromBytes(recvData.Data);
|
||
if (!retPack.IsSuccessful)
|
||
return new(new Exception("Not RecvDataPackage!", retPack.Error));
|
||
|
||
return retPack.Value;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 异步等待数据
|
||
/// </summary>
|
||
/// <param name="endPoint">IP地址</param>
|
||
/// <param name="taskID">任务ID</param>
|
||
/// <param name="timeout">超时时间范围</param>
|
||
/// <returns>接收数据包</returns>
|
||
public async ValueTask<Result<RecvDataPackage>> WaitForDataAsync
|
||
(IPEndPoint endPoint, int taskID, int timeout = 1000)
|
||
{
|
||
var address = endPoint.Address.ToString();
|
||
var port = endPoint.Port;
|
||
|
||
var data = await FindDataAsync(address, taskID, timeout);
|
||
if (!data.HasValue)
|
||
{
|
||
await UDPClientPool.SendResetSignal(endPoint);
|
||
return new(new Exception("Get None even after time out!"));
|
||
}
|
||
|
||
var recvData = data.Value;
|
||
if (recvData.Address != address || (port >= 0 && recvData.Port != port))
|
||
return new(new Exception("Receive Data From Wrong Board!"));
|
||
|
||
var retPack = WebProtocol.RecvDataPackage.FromBytes(recvData.Data);
|
||
if (!retPack.IsSuccessful)
|
||
return new(new Exception("Not RecvDataPackage!", retPack.Error));
|
||
|
||
return retPack.Value;
|
||
}
|
||
|
||
private async Task ReceiveHandler(byte[] data, IPEndPoint endPoint, DateTime time)
|
||
{
|
||
// 异步锁保护 udpData
|
||
await Task.Run(async () =>
|
||
{
|
||
try
|
||
{
|
||
// Handle RemoteEP
|
||
if (endPoint is null)
|
||
{
|
||
logger.Debug($"Receive Data from Unknown at {DateTime.Now.ToString()}:");
|
||
logger.Debug($" Original Data : {BitConverter.ToString(data).Replace("-", " ")}");
|
||
return;
|
||
}
|
||
|
||
var udpDataObj = await RecordUDPData(data, endPoint, time, Convert.ToInt32(data[1 + 4]));
|
||
PrintData(udpDataObj);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
logger.Error($"Got Error when handle receive:{e}");
|
||
}
|
||
});
|
||
}
|
||
|
||
private async Task<UDPData> RecordUDPData(byte[] bytes, IPEndPoint remoteEP, DateTime time, int taskID)
|
||
{
|
||
var remoteAddress = remoteEP.Address.ToString();
|
||
var remotePort = remoteEP.Port;
|
||
var data = new UDPData()
|
||
{
|
||
DateTime = time,
|
||
Timestamp = Number.BytesToUInt32(bytes[..4]).Value,
|
||
Address = remoteAddress,
|
||
Port = remotePort,
|
||
TaskID = taskID,
|
||
Data = bytes,
|
||
HasRead = false,
|
||
};
|
||
|
||
var key = $"{remoteAddress}-{taskID}";
|
||
|
||
try
|
||
{
|
||
using (await udpDataLock.AcquireWriteLockAsync(TimeSpan.FromMilliseconds(5000)))
|
||
{
|
||
var sortedList = udpData.GetOrAdd(key, _ => new SortedList<UInt32, UDPData>());
|
||
|
||
// 处理相同时间戳的情况,添加微小的时间差
|
||
var uniqueTime = data.Timestamp;
|
||
while (sortedList.ContainsKey(uniqueTime))
|
||
{
|
||
logger.Warn(
|
||
$"Duplicate timestamp detected for {remoteAddress}:{remotePort} at {uniqueTime}.");
|
||
uniqueTime += 1;
|
||
}
|
||
|
||
sortedList.Add(uniqueTime, data);
|
||
// 输出单个数据
|
||
// PrintData(data);
|
||
}
|
||
}
|
||
catch (TimeoutException)
|
||
{
|
||
logger.Error($"Failed to acquire write lock for recording UDP data from {remoteAddress}:{remotePort}");
|
||
throw;
|
||
}
|
||
|
||
return data;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 输出UDP Data到log中
|
||
/// </summary>
|
||
/// <param name="data">UDP数据</param>
|
||
public string PrintData(UDPData data)
|
||
{
|
||
var bytes = data.Data;
|
||
var sign = bytes[4];
|
||
string recvData = "";
|
||
if (sign == (byte)WebProtocol.PackSign.RecvData)
|
||
{
|
||
var resData = WebProtocol.RecvDataPackage.FromBytes(bytes);
|
||
if (resData.IsSuccessful)
|
||
recvData = resData.Value.Options.ToString();
|
||
else
|
||
recvData = resData.Error.ToString();
|
||
}
|
||
else if (sign == (byte)WebProtocol.PackSign.RecvResp)
|
||
{
|
||
var resData = WebProtocol.RecvRespPackage.FromBytes(bytes);
|
||
if (resData.IsSuccessful)
|
||
recvData = resData.Value.Options.ToString();
|
||
else
|
||
recvData = resData.Error.ToString();
|
||
}
|
||
else
|
||
{
|
||
recvData = Encoding.ASCII.GetString(bytes, 0, bytes.Length);
|
||
}
|
||
|
||
logger.Debug($"Receive Data from {data.Address}:{data.Port} at {data.DateTime.ToString()} - {data.Timestamp}:");
|
||
logger.Debug($" Original Data : {BitConverter.ToString(bytes).Replace("-", " ")}");
|
||
if (recvData.Length != 0) logger.Debug($" Decoded Data : {recvData}");
|
||
return $@"
|
||
Receive Data from {data.Address}:{data.Port} at {data.DateTime.ToString()}:
|
||
Original Data : {BitConverter.ToString(bytes).Replace("-", " ")}
|
||
Decoded Data : {recvData}
|
||
";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 输出UDP Data数组到log中
|
||
/// </summary>
|
||
/// <param name="dataArray">UDP数据列表</param>
|
||
public void PrintDataArray(IEnumerable<UDPData> dataArray)
|
||
{
|
||
foreach (var data in dataArray)
|
||
{
|
||
logger.Debug(PrintData(data));
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 将所有数据输出到log中
|
||
/// </summary>
|
||
/// <returns> void </returns>
|
||
public async Task PrintAllDataAsync()
|
||
{
|
||
logger.Debug("Ready Data:");
|
||
|
||
try
|
||
{
|
||
using (await udpDataLock.AcquireReadLockAsync(TimeSpan.FromMilliseconds(5000)))
|
||
{
|
||
foreach (var kvp in udpData)
|
||
{
|
||
foreach (var data in kvp.Value.Values)
|
||
{
|
||
logger.Debug(PrintData(data));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (TimeoutException)
|
||
{
|
||
logger.Error("Failed to acquire read lock for printing all data");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清空指定IP地址的数据
|
||
/// </summary>
|
||
/// <param name="ipAddr">IP地址</param>
|
||
/// <param name="taskID">[TODO:parameter]</param>
|
||
/// <returns>无</returns>
|
||
public void ClearUDPData(string ipAddr, int taskID)
|
||
{
|
||
var key = $"{ipAddr}-{taskID}";
|
||
|
||
using (udpDataLock.AcquireWriteLock())
|
||
{
|
||
if (udpData.TryGetValue(key, out var sortedList))
|
||
{
|
||
sortedList.Clear();
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// Start UDP Server
|
||
/// </summary>
|
||
/// <returns>None</returns>
|
||
public void Start()
|
||
{
|
||
if (cancellationTokenSource != null && !cancellationTokenSource.Token.IsCancellationRequested)
|
||
{
|
||
logger.Warn("UDP Server is already running");
|
||
return;
|
||
}
|
||
|
||
cancellationTokenSource = new CancellationTokenSource();
|
||
var cancellationToken = cancellationTokenSource.Token;
|
||
|
||
try
|
||
{
|
||
foreach (var client in listeners)
|
||
{
|
||
tasks.Add(Task.Run(async () =>
|
||
{
|
||
while (!cancellationToken.IsCancellationRequested)
|
||
{
|
||
try
|
||
{
|
||
// 使用 CancellationToken 来取消接收操作
|
||
var result = await client.ReceiveAsync(cancellationToken);
|
||
_ = ReceiveHandler(result.Buffer, result.RemoteEndPoint, DateTime.Now);
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
logger.Debug("UDP receive operation was cancelled");
|
||
break;
|
||
}
|
||
catch (ObjectDisposedException)
|
||
{
|
||
logger.Debug("UDP client was disposed");
|
||
break;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
if (!cancellationToken.IsCancellationRequested)
|
||
{
|
||
logger.Error($"Error in UDP receive: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
}, cancellationToken));
|
||
}
|
||
|
||
logger.Info("UDP Server started successfully");
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
logger.Error($"Failed to start UDP server: {e}");
|
||
cancellationTokenSource?.Cancel();
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Close UDP Server
|
||
/// </summary>
|
||
/// <returns>None</returns>
|
||
public void Stop()
|
||
{
|
||
if (cancellationTokenSource == null || cancellationTokenSource.Token.IsCancellationRequested)
|
||
{
|
||
logger.Warn("UDP Server is not running or already stopped");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
logger.Info("Stopping UDP Server...");
|
||
|
||
// 取消所有操作
|
||
cancellationTokenSource.Cancel();
|
||
|
||
// 等待所有任务完成,设置超时时间
|
||
var waitTasks = Task.WhenAll(tasks);
|
||
if (!waitTasks.Wait(TimeSpan.FromSeconds(5)))
|
||
{
|
||
logger.Warn("Some tasks did not complete within timeout period");
|
||
}
|
||
|
||
// 关闭所有UDP客户端
|
||
foreach (var client in listeners)
|
||
{
|
||
try
|
||
{
|
||
client.Close();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Warn($"Error closing UDP client: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 清理任务列表
|
||
tasks.Clear();
|
||
|
||
logger.Info("UDP Server stopped successfully");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error($"Error stopping UDP server: {ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
cancellationTokenSource?.Dispose();
|
||
cancellationTokenSource = null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 实现IDisposable接口,确保资源正确释放
|
||
/// </summary>
|
||
public void Dispose()
|
||
{
|
||
if (!disposed)
|
||
{
|
||
Stop();
|
||
|
||
foreach (var client in listeners)
|
||
{
|
||
client?.Dispose();
|
||
}
|
||
|
||
udpDataLock?.Dispose();
|
||
disposed = true;
|
||
}
|
||
}
|
||
}
|