461 lines
14 KiB
C#
461 lines
14 KiB
C#
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text;
|
|
using DotNext;
|
|
using DotNext.Threading;
|
|
using Newtonsoft.Json;
|
|
|
|
/// <summary> UDP接受数据包格式 </summary>
|
|
public class UDPData
|
|
{
|
|
/// <summary>
|
|
/// 接受到的时间
|
|
/// </summary>
|
|
public required DateTime DateTime { get; set; }
|
|
/// <summary>
|
|
/// 发送来源的IP地址
|
|
/// </summary>
|
|
public required string Address { get; set; }
|
|
/// <summary>
|
|
/// 发送来源的端口号
|
|
/// </summary>
|
|
public required int Port { 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,
|
|
Address = new string(this.Address),
|
|
Port = this.Port,
|
|
Data = cloneData,
|
|
HasRead = this.HasRead
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 将UDP Data 转化为Json 格式字符串
|
|
/// </summary>
|
|
/// <returns>json字符串</returns>
|
|
public override string ToString()
|
|
{
|
|
return JsonConvert.SerializeObject(this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// UDP Server
|
|
/// </summary>
|
|
/// <summary>
|
|
/// UDP 服务器
|
|
/// </summary>
|
|
public class UDPServer
|
|
{
|
|
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
|
|
|
private int listenPort;
|
|
private UdpClient listener;
|
|
private IPEndPoint groupEP;
|
|
private Dictionary<string, List<UDPData>> udpData = new Dictionary<string, List<UDPData>>();
|
|
private AsyncReaderWriterLock udpDataLock = new AsyncReaderWriterLock(1);
|
|
|
|
/// <summary>
|
|
/// Construct a udp server with fixed port
|
|
/// </summary>
|
|
/// <param name="port"> Device UDP Port </param>
|
|
/// <returns> UDPServer class </returns>
|
|
public UDPServer(int port)
|
|
{
|
|
// Construction
|
|
listenPort = port;
|
|
try
|
|
{
|
|
listener = new UdpClient(listenPort);
|
|
groupEP = new IPEndPoint(IPAddress.Any, listenPort);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Console.WriteLine(e.ToString());
|
|
throw new ArgumentException(
|
|
$"Not currect port num: {port}",
|
|
nameof(port)
|
|
);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Find UDP Receive Data According to ip address
|
|
/// </summary>
|
|
/// <param name="ipAddr"> IP Address</param>
|
|
/// <param name="timeout"> Read and Write Wait for Milliseconds </param>
|
|
/// <param name="callerName">调用函数名称</param>
|
|
/// <param name="callerLineNum">调用函数位置</param>
|
|
/// <returns>UDP Data</returns>
|
|
public Optional<UDPData> FindData(
|
|
string ipAddr, int timeout = 1000,
|
|
[CallerMemberName] string callerName = "",
|
|
[CallerLineNumber] int callerLineNum = 0)
|
|
{
|
|
UDPData? data = null;
|
|
|
|
logger.Debug($"Caller \"{callerName}|{callerLineNum}\": Try to find {ipAddr} UDP Data");
|
|
|
|
var startTime = DateTime.Now;
|
|
var isTimeout = false;
|
|
var timeleft = TimeSpan.FromMilliseconds(timeout);
|
|
while (!isTimeout)
|
|
{
|
|
|
|
using (udpDataLock.AcquireWriteLock(timeleft))
|
|
{
|
|
if (udpData.ContainsKey(ipAddr) && udpData[ipAddr].Count > 0)
|
|
{
|
|
data = udpData[ipAddr][0].DeepClone();
|
|
udpData[ipAddr].RemoveAt(0);
|
|
logger.Debug($"Find UDP Data: {data.ToString()}");
|
|
break;
|
|
}
|
|
}
|
|
|
|
timeleft = DateTime.Now.Subtract(startTime);
|
|
isTimeout = timeleft >= TimeSpan.FromMilliseconds(timeout);
|
|
}
|
|
|
|
if (data == null)
|
|
{
|
|
logger.Trace("Get nothing even after time out");
|
|
return Optional.None<UDPData>();
|
|
}
|
|
else
|
|
{
|
|
return Optional.Some((UDPData)data);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 异步寻找目标发送的内容
|
|
/// </summary>
|
|
/// <param name="ipAddr"> 目标IP地址 </param>
|
|
/// <param name="timeout">超时时间</param>
|
|
/// <param name="callerName">调用函数名称</param>
|
|
/// <param name="callerLineNum">调用函数位置</param>
|
|
/// <returns>
|
|
/// 异步Optional 数据包:
|
|
/// Optional 为空时,表明找不到数据;
|
|
/// Optional 存在时,为最先收到的数据
|
|
/// </returns>
|
|
public async ValueTask<Optional<UDPData>> FindDataAsync(
|
|
string ipAddr, int timeout = 1000,
|
|
[CallerMemberName] string callerName = "",
|
|
[CallerLineNumber] int callerLineNum = 0)
|
|
{
|
|
UDPData? data = null;
|
|
|
|
logger.Debug($"Caller \"{callerName}|{callerLineNum}\": Try to find {ipAddr} UDP Data");
|
|
|
|
var startTime = DateTime.Now;
|
|
var isTimeout = false;
|
|
var timeleft = TimeSpan.FromMilliseconds(timeout);
|
|
while (!isTimeout)
|
|
{
|
|
|
|
using (await udpDataLock.AcquireWriteLockAsync(timeleft))
|
|
{
|
|
if (udpData.ContainsKey(ipAddr) && udpData[ipAddr].Count > 0)
|
|
{
|
|
data = udpData[ipAddr][0].DeepClone();
|
|
udpData[ipAddr].RemoveAt(0);
|
|
logger.Debug($"Find UDP Data: {data.ToString()}");
|
|
break;
|
|
}
|
|
}
|
|
|
|
timeleft = DateTime.Now.Subtract(startTime);
|
|
isTimeout = timeleft >= TimeSpan.FromMilliseconds(timeout);
|
|
}
|
|
|
|
if (data is null)
|
|
{
|
|
logger.Trace("Get nothing even after time out");
|
|
return Optional.None<UDPData>();
|
|
}
|
|
else
|
|
{
|
|
return Optional.Some((UDPData)data);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取还未被读取的数据列表
|
|
/// </summary>
|
|
/// <param name="ipAddr">IP地址</param>
|
|
/// <param name="timeout">超时时间</param>
|
|
/// <returns>数据列表</returns>
|
|
public async ValueTask<Optional<List<UDPData>>> GetDataArrayAsync(string ipAddr, int timeout = 1000)
|
|
{
|
|
List<UDPData>? data = null;
|
|
|
|
var startTime = DateTime.Now;
|
|
var isTimeout = false;
|
|
var timeleft = TimeSpan.FromMilliseconds(timeout);
|
|
while (!isTimeout)
|
|
{
|
|
|
|
using (await udpDataLock.AcquireReadLockAsync(timeleft))
|
|
{
|
|
if (udpData.ContainsKey(ipAddr))
|
|
{
|
|
data = udpData[ipAddr];
|
|
logger.Debug($"Find UDP Data Array: {data.ToString()}");
|
|
break;
|
|
}
|
|
}
|
|
|
|
timeleft = DateTime.Now.Subtract(startTime);
|
|
isTimeout = timeleft >= TimeSpan.FromMilliseconds(timeout);
|
|
}
|
|
|
|
if (data is null)
|
|
{
|
|
logger.Trace("Get nothing even after time out");
|
|
return Optional.None<List<UDPData>>();
|
|
}
|
|
else
|
|
{
|
|
return Optional.Some((List<UDPData>)data);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 异步等待写响应
|
|
/// </summary>
|
|
/// <param name="address">IP地址</param>
|
|
/// <param name="port">UDP 端口</param>
|
|
/// <param name="timeout">超时时间范围</param>
|
|
/// <returns>接收响应包</returns>
|
|
public async ValueTask<Result<WebProtocol.RecvRespPackage>> WaitForAckAsync
|
|
(string address, int port, int timeout = 1000)
|
|
{
|
|
var data = await FindDataAsync(address, timeout);
|
|
if (!data.HasValue)
|
|
throw new Exception("Get None even after time out!");
|
|
|
|
var recvData = data.Value;
|
|
if (recvData.Address != address || recvData.Port != port)
|
|
throw new Exception("Receive Data From Wrong Board!");
|
|
|
|
var retPack = WebProtocol.RecvRespPackage.FromBytes(recvData.Data);
|
|
if (!retPack.IsSuccessful)
|
|
throw new Exception("Not RecvDataPackage!", retPack.Error);
|
|
|
|
return retPack.Value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 异步等待数据
|
|
/// </summary>
|
|
/// <param name="address">IP地址</param>
|
|
/// <param name="port">UDP 端口</param>
|
|
/// <param name="timeout">超时时间范围</param>
|
|
/// <returns>接收数据包</returns>
|
|
public async ValueTask<Result<WebProtocol.RecvDataPackage>> WaitForDataAsync
|
|
(string address, int port, int timeout = 1000)
|
|
{
|
|
var data = await FindDataAsync(address, timeout);
|
|
if (!data.HasValue)
|
|
throw new Exception("Get None even after time out!");
|
|
|
|
var recvData = data.Value;
|
|
if (recvData.Address != address || recvData.Port != port)
|
|
throw new Exception("Receive Data From Wrong Board!");
|
|
|
|
var retPack = WebProtocol.RecvDataPackage.FromBytes(recvData.Data);
|
|
if (!retPack.IsSuccessful)
|
|
throw new Exception("Not RecvDataPackage!", retPack.Error);
|
|
|
|
return retPack.Value;
|
|
}
|
|
|
|
private void ReceiveHandler(IAsyncResult res)
|
|
{
|
|
logger.Trace("Enter handler");
|
|
var remoteEP = new IPEndPoint(IPAddress.Any, listenPort);
|
|
byte[] bytes = listener.EndReceive(res, ref remoteEP);
|
|
|
|
// Handle RemoteEP
|
|
if (remoteEP is null)
|
|
{
|
|
logger.Debug($"Receive Data from Unknown at {DateTime.Now.ToString()}:");
|
|
logger.Debug($" Original Data : {BitConverter.ToString(bytes).Replace("-", " ")}");
|
|
goto BEGIN_RECEIVE;
|
|
}
|
|
|
|
|
|
// Handle Package
|
|
PrintData(RecordUDPData(bytes, remoteEP));
|
|
|
|
BEGIN_RECEIVE:
|
|
listener.BeginReceive(new AsyncCallback(ReceiveHandler), null);
|
|
}
|
|
|
|
private bool SendBytes(IPEndPoint endPoint, byte[] buf)
|
|
{
|
|
var sendLen = listener.Send(buf, endPoint);
|
|
|
|
if (sendLen == buf.Length) { return true; }
|
|
else { return false; }
|
|
}
|
|
|
|
private bool SendString(IPEndPoint endPoint, string text)
|
|
{
|
|
byte[] buf = Encoding.ASCII.GetBytes(text);
|
|
var sendLen = listener.Send(buf, endPoint);
|
|
|
|
if (sendLen == buf.Length) { return true; }
|
|
else { return false; }
|
|
}
|
|
|
|
private UDPData RecordUDPData(byte[] bytes, IPEndPoint remoteEP)
|
|
{
|
|
using (udpDataLock.AcquireWriteLock())
|
|
{
|
|
var remoteAddress = remoteEP.Address.ToString();
|
|
var remotePort = remoteEP.Port;
|
|
var data = new UDPData()
|
|
{
|
|
Address = remoteAddress,
|
|
Port = remotePort,
|
|
Data = bytes,
|
|
DateTime = DateTime.Now,
|
|
HasRead = false,
|
|
};
|
|
|
|
// Record UDP Receive Data
|
|
if (udpData.ContainsKey(remoteAddress))
|
|
{
|
|
var listData = udpData[remoteAddress];
|
|
listData.Add(data);
|
|
logger.Trace("Receive data from old client");
|
|
}
|
|
else
|
|
{
|
|
var list = new List<UDPData>();
|
|
list.Add(data);
|
|
udpData.Add(remoteAddress, list);
|
|
logger.Trace("Receive data from new client");
|
|
}
|
|
|
|
return data;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 输出UDP Data到log中
|
|
/// </summary>
|
|
/// <param name="data">UDP数据</param>
|
|
public void PrintData(UDPData data)
|
|
{
|
|
var bytes = data.Data;
|
|
var sign = bytes[0];
|
|
string recvData = "";
|
|
if (sign == (byte)WebProtocol.PackSign.SendAddr)
|
|
{
|
|
var resData = WebProtocol.SendAddrPackage.FromBytes(bytes);
|
|
if (resData.IsSuccessful)
|
|
recvData = resData.Value.ToString();
|
|
else
|
|
recvData = resData.Error.ToString();
|
|
}
|
|
else if (sign == (byte)WebProtocol.PackSign.SendData) { }
|
|
else 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()}:");
|
|
logger.Debug($" Original Data : {BitConverter.ToString(bytes).Replace("-", " ")}");
|
|
if (recvData.Length != 0) logger.Debug($" Decoded Data : {recvData}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 将所有数据输出到log中
|
|
/// </summary>
|
|
/// <returns> void </returns>
|
|
public void PrintAllData()
|
|
{
|
|
using (udpDataLock.AcquireReadLock())
|
|
{
|
|
logger.Debug("Ready Data:");
|
|
|
|
foreach (var ip in udpData)
|
|
{
|
|
foreach (var data in ip.Value)
|
|
{
|
|
logger.Debug(data.ToString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start UDP Server
|
|
/// </summary>
|
|
/// <returns>None</returns>
|
|
public void Start()
|
|
{
|
|
try
|
|
{
|
|
listener.BeginReceive(new AsyncCallback(ReceiveHandler), null);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Console.WriteLine(e.ToString());
|
|
}
|
|
finally
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Close UDP Server
|
|
/// </summary>
|
|
/// <returns>None</returns>
|
|
public void Stop()
|
|
{
|
|
listener.Close();
|
|
}
|
|
}
|
|
|