FPGA_WebLab/server/src/UdpServer.cs

728 lines
22 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.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;
}
}
}