change udpData type from list to queue

This commit is contained in:
SikongJueluo 2025-04-16 21:19:55 +08:00
parent 84699708d5
commit 3b674f413a
No known key found for this signature in database
7 changed files with 176 additions and 131 deletions

View File

@ -13,4 +13,4 @@ run-server: _show-dir
[working-directory: "server.test"] [working-directory: "server.test"]
test-server: _show-dir test-server: _show-dir
dotnet test dotnet test --logger "console;verbosity=detailed"

View File

@ -1,27 +1,47 @@
using System.Net; using System.Net;
using System.Reflection;
using System.Text; using System.Text;
using Xunit.Sdk; using Common;
using Xunit.Abstractions;
namespace server.test; namespace server.test;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public sealed class RepeatAttribute : Xunit.Sdk.DataAttribute
public class UDPServerTest : BeforeAfterTestAttribute
{ {
const string address = "127.0.0.1"; private readonly int count;
const int port = 1234;
private static readonly UDPServer udpServer = new UDPServer(port);
public override void Before(MethodInfo methodUnderTest) public RepeatAttribute(int count)
{ {
udpServer.Start(); if (count < 1)
Console.WriteLine("Start UDP Server"); {
throw new System.ArgumentOutOfRangeException(
paramName: nameof(count),
message: "Repeat count must be greater than 0."
);
}
this.count = count;
} }
public override void After(MethodInfo methodUnderTest) public override System.Collections.Generic.IEnumerable<object[]> GetData(System.Reflection.MethodInfo testMethod)
{ {
udpServer.Stop(); foreach (var iterationNumber in Enumerable.Range(start: 1, count: this.count))
Console.WriteLine("Stop UDP Server"); {
yield return new object[] { iterationNumber };
}
}
}
public class UDPServerTest
{
const string address = "127.0.0.1";
const int port = 33000;
private static readonly UDPServer udpServer = new UDPServer(port);
private readonly ITestOutputHelper output;
public UDPServerTest(ITestOutputHelper output)
{
this.output = output;
udpServer.Start();
} }
[Fact] [Fact]
@ -46,9 +66,15 @@ public class UDPServerTest : BeforeAfterTestAttribute
udpData.DateTime = DateTime.Now; udpData.DateTime = DateTime.Now;
udpData.Address = "192.168.1.1"; udpData.Address = "192.168.1.1";
udpData.Port = 33000; udpData.Port = 33000;
udpData.Data = new byte[] { 0x0f, 00, 00, 00 }; udpData.Data = new byte[] { 0xFF, 00, 00, 00 };
udpData.HasRead = true; udpData.HasRead = true;
Assert.NotNull(cloneUdpData.DateTime);
Assert.NotNull(cloneUdpData.Address);
Assert.NotNull(cloneUdpData.Port);
Assert.NotNull(cloneUdpData.Data);
Assert.NotNull(cloneUdpData.HasRead);
Assert.NotEqual(udpData.DateTime, cloneUdpData.DateTime); Assert.NotEqual(udpData.DateTime, cloneUdpData.DateTime);
Assert.NotEqual(udpData.Address, cloneUdpData.Address); Assert.NotEqual(udpData.Address, cloneUdpData.Address);
Assert.NotEqual(udpData.Port, cloneUdpData.Port); Assert.NotEqual(udpData.Port, cloneUdpData.Port);
@ -57,33 +83,80 @@ public class UDPServerTest : BeforeAfterTestAttribute
} }
[Theory] [Theory]
[InlineData("Hello World!")] [InlineData(new object[] { new string[] { "Hello World!", "Hello Server!", "What is your problem?" } })]
[InlineData("Hello Server!")] public async Task UDPServerFindString(string[] textArray)
public async Task UDPServerFind(string text)
{ {
Assert.True(udpServer.IsRunning);
var serverEP = new IPEndPoint(IPAddress.Parse(address), port); var serverEP = new IPEndPoint(IPAddress.Parse(address), port);
foreach (var text in textArray)
Assert.True(UDPClientPool.SendString(serverEP, [text]));
{
var ret = udpServer.FindData(address);
Assert.True(ret.HasValue);
var data = ret.Value;
Assert.Equal(data.Address, address);
Assert.Equal(data.Port, port);
Assert.Equal(Encoding.ASCII.GetString(data.Data), text);
}
Assert.True(await UDPClientPool.SendStringAsync(serverEP, [text]));
{ {
Assert.True(await UDPClientPool.SendStringAsync(serverEP, [text]));
var ret = await udpServer.FindDataAsync(address); var ret = await udpServer.FindDataAsync(address);
Assert.True(ret.HasValue); Assert.True(ret.HasValue);
var data = ret.Value; var data = ret.Value;
Assert.Equal(data.Address, address); Assert.Equal(address, data.Address);
Assert.Equal(data.Port, port); Assert.Equal(text, Encoding.ASCII.GetString(data.Data));
Assert.Equal(Encoding.ASCII.GetString(data.Data), text); }
}
[Theory]
[InlineData(new object[] { new UInt32[] { 0xF0_00_00_00, 0xFF_00_00_00, 0xFF_FF_FF_FF } })]
public async Task UDPServerFindBytes(UInt32[] bytesArray)
{
Assert.True(udpServer.IsRunning);
var serverEP = new IPEndPoint(IPAddress.Parse(address), port);
foreach (var number in bytesArray)
{
Assert.True(await UDPClientPool.SendBytesAsync(serverEP, NumberProcessor.NumberToBytes(number, 4).Value));
var ret = await udpServer.FindDataAsync(address);
Assert.True(ret.HasValue);
var data = ret.Value;
Assert.Equal(address, data.Address);
Assert.Equal(number, NumberProcessor.BytesToNumber(data.Data));
}
}
[Theory]
[InlineData(new object[] { new UInt32[] { 0xF0_00_00_00, 0xF0_01_00_00 } })]
public async Task UDPServerWaitResp(UInt32[] bytesArray)
{
Assert.True(udpServer.IsRunning);
var serverEP = new IPEndPoint(IPAddress.Parse(address), port);
foreach (var number in bytesArray)
{
Assert.True(await UDPClientPool.SendBytesAsync(serverEP, NumberProcessor.NumberToBytes(number, 4).Value));
var ret = await udpServer.WaitForAckAsync(address);
Assert.True(ret.IsSuccessful);
var data = ret.Value;
Assert.True(data.IsSuccessful);
Assert.Equal(number, NumberProcessor.BytesToNumber(data.ToBytes()));
}
}
[Theory]
[InlineData(new object[] { new UInt64[] { 0x0F_00_00_00_01_02_02_02, 0x0F_01_00_00_FF_FF_FF_FF } })]
public async Task UDPServerWaitData(UInt64[] bytesArray)
{
Assert.True(udpServer.IsRunning);
var serverEP = new IPEndPoint(IPAddress.Parse(address), port);
foreach (var number in bytesArray)
{
Assert.True(await UDPClientPool.SendBytesAsync(serverEP, NumberProcessor.NumberToBytes(number, 8).Value));
var ret = await udpServer.WaitForDataAsync(address);
Assert.True(ret.IsSuccessful);
var data = ret.Value;
Assert.True(data.IsSuccessful);
Assert.Equal(number, NumberProcessor.BytesToNumber(data.ToBytes()));
} }
} }
} }

View File

@ -22,5 +22,9 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\server\server.csproj" /> <ProjectReference Include="..\server\server.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,3 @@
{
"showLiveOutput": true
}

View File

@ -2,7 +2,7 @@ using DotNext;
namespace Common namespace Common
{ {
class NumberProcessor public class NumberProcessor
{ {
public static Result<byte[]> NumberToBytes(ulong num, uint length, bool isRightHigh = false) public static Result<byte[]> NumberToBytes(ulong num, uint length, bool isRightHigh = false)
{ {

View File

@ -68,11 +68,17 @@ public class UDPServer
{ {
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private static Dictionary<string, Queue<UDPData>> udpData = new Dictionary<string, Queue<UDPData>>();
private int listenPort; private int listenPort;
private UdpClient listener; private UdpClient listener;
private IPEndPoint groupEP; private IPEndPoint groupEP;
private Dictionary<string, List<UDPData>> udpData = new Dictionary<string, List<UDPData>>();
private AsyncReaderWriterLock udpDataLock = new AsyncReaderWriterLock(1); private bool isRunning = false;
/// <summary>
/// 是否正在工作
/// </summary>
public bool IsRunning { get { return isRunning; } }
/// <summary> /// <summary>
/// Construct a udp server with fixed port /// Construct a udp server with fixed port
@ -98,55 +104,6 @@ public class UDPServer
} }
} }
/// <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.DeepClone());
}
}
/// <summary> /// <summary>
/// 异步寻找目标发送的内容 /// 异步寻找目标发送的内容
/// </summary> /// </summary>
@ -173,20 +130,21 @@ public class UDPServer
var timeleft = TimeSpan.FromMilliseconds(timeout); var timeleft = TimeSpan.FromMilliseconds(timeout);
while (!isTimeout) while (!isTimeout)
{ {
var elapsed = DateTime.Now - startTime;
isTimeout = elapsed >= TimeSpan.FromMilliseconds(timeout);
timeleft = TimeSpan.FromMilliseconds(timeout) - elapsed;
using (await udpDataLock.AcquireWriteLockAsync(timeleft)) using (await udpData.AcquireWriteLockAsync(timeleft))
{ {
if (udpData.ContainsKey(ipAddr) && udpData[ipAddr].Count > 0) if (udpData.TryGetValue(ipAddr, out var dataQueue) && dataQueue != null && dataQueue.Count > 0)
{ {
data = udpData[ipAddr][0].DeepClone(); data = dataQueue.Dequeue();
udpData[ipAddr].RemoveAt(0); // data = dataList[0].DeepClone();
// dataList.RemoveAt(0);
logger.Debug($"Find UDP Data: {data.ToString()}"); logger.Debug($"Find UDP Data: {data.ToString()}");
break; break;
} }
} }
timeleft = DateTime.Now.Subtract(startTime);
isTimeout = timeleft >= TimeSpan.FromMilliseconds(timeout);
} }
if (data is null) if (data is null)
@ -215,19 +173,19 @@ public class UDPServer
var timeleft = TimeSpan.FromMilliseconds(timeout); var timeleft = TimeSpan.FromMilliseconds(timeout);
while (!isTimeout) while (!isTimeout)
{ {
var elapsed = DateTime.Now - startTime;
isTimeout = elapsed >= TimeSpan.FromMilliseconds(timeout);
timeleft = TimeSpan.FromMilliseconds(timeout) - elapsed;
using (await udpDataLock.AcquireReadLockAsync(timeleft)) using (await udpData.AcquireReadLockAsync(timeleft))
{ {
if (udpData.ContainsKey(ipAddr)) if (udpData.TryGetValue(ipAddr, out var dataQueue) && dataQueue != null && dataQueue.Count > 0)
{ {
data = udpData[ipAddr]; data = dataQueue.ToList();
logger.Debug($"Find UDP Data Array: {data.ToString()}"); logger.Debug($"Find UDP Data Array: {JsonConvert.SerializeObject(data)}");
break; break;
} }
} }
timeleft = DateTime.Now.Subtract(startTime);
isTimeout = timeleft >= TimeSpan.FromMilliseconds(timeout);
} }
if (data is null) if (data is null)
@ -249,14 +207,14 @@ public class UDPServer
/// <param name="timeout">超时时间范围</param> /// <param name="timeout">超时时间范围</param>
/// <returns>接收响应包</returns> /// <returns>接收响应包</returns>
public async ValueTask<Result<WebProtocol.RecvRespPackage>> WaitForAckAsync public async ValueTask<Result<WebProtocol.RecvRespPackage>> WaitForAckAsync
(string address, int port, int timeout = 1000) (string address, int port = -1, int timeout = 1000)
{ {
var data = await FindDataAsync(address, timeout); var data = await FindDataAsync(address, timeout);
if (!data.HasValue) if (!data.HasValue)
throw new Exception("Get None even after time out!"); throw new Exception("Get None even after time out!");
var recvData = data.Value; var recvData = data.Value;
if (recvData.Address != address || recvData.Port != port) if (recvData.Address != address || (port >= 0 && recvData.Port != port))
throw new Exception("Receive Data From Wrong Board!"); throw new Exception("Receive Data From Wrong Board!");
var retPack = WebProtocol.RecvRespPackage.FromBytes(recvData.Data); var retPack = WebProtocol.RecvRespPackage.FromBytes(recvData.Data);
@ -274,14 +232,14 @@ public class UDPServer
/// <param name="timeout">超时时间范围</param> /// <param name="timeout">超时时间范围</param>
/// <returns>接收数据包</returns> /// <returns>接收数据包</returns>
public async ValueTask<Result<WebProtocol.RecvDataPackage>> WaitForDataAsync public async ValueTask<Result<WebProtocol.RecvDataPackage>> WaitForDataAsync
(string address, int port, int timeout = 1000) (string address, int port = -1, int timeout = 1000)
{ {
var data = await FindDataAsync(address, timeout); var data = await FindDataAsync(address, timeout);
if (!data.HasValue) if (!data.HasValue)
throw new Exception("Get None even after time out!"); throw new Exception("Get None even after time out!");
var recvData = data.Value; var recvData = data.Value;
if (recvData.Address != address || recvData.Port != port) if (recvData.Address != address || (port >= 0 && recvData.Port != port))
throw new Exception("Receive Data From Wrong Board!"); throw new Exception("Receive Data From Wrong Board!");
var retPack = WebProtocol.RecvDataPackage.FromBytes(recvData.Data); var retPack = WebProtocol.RecvDataPackage.FromBytes(recvData.Data);
@ -307,7 +265,8 @@ public class UDPServer
// Handle Package // Handle Package
PrintData(RecordUDPData(bytes, remoteEP)); var udpData = RecordUDPData(bytes, remoteEP);
PrintData(udpData);
BEGIN_RECEIVE: BEGIN_RECEIVE:
listener.BeginReceive(new AsyncCallback(ReceiveHandler), null); listener.BeginReceive(new AsyncCallback(ReceiveHandler), null);
@ -332,36 +291,41 @@ public class UDPServer
private UDPData RecordUDPData(byte[] bytes, IPEndPoint remoteEP) private UDPData RecordUDPData(byte[] bytes, IPEndPoint remoteEP)
{ {
using (udpDataLock.AcquireWriteLock()) var remoteAddress = remoteEP.Address.ToString();
var remotePort = remoteEP.Port;
var data = new UDPData()
{ {
var remoteAddress = remoteEP.Address.ToString(); Address = remoteAddress,
var remotePort = remoteEP.Port; Port = remotePort,
var data = new UDPData() Data = bytes,
{ DateTime = DateTime.Now,
Address = remoteAddress, HasRead = false,
Port = remotePort, };
Data = bytes,
DateTime = DateTime.Now,
HasRead = false,
};
using (udpData.AcquireWriteLock())
{
// Record UDP Receive Data // Record UDP Receive Data
if (udpData.ContainsKey(remoteAddress)) // if (udpData.ContainsKey(remoteAddress))
if (udpData.TryGetValue(remoteAddress, out var dataQueue))
{ {
var listData = udpData[remoteAddress]; // var listData = udpData[remoteAddress];
listData.Add(data); // listData.Add(data);
dataQueue.Enqueue(data);
logger.Trace("Receive data from old client"); logger.Trace("Receive data from old client");
} }
else else
{ {
var list = new List<UDPData>(); // var list = new List<UDPData>();
list.Add(data); // list.Add(data);
udpData.Add(remoteAddress, list); // udpData.Add(remoteAddress, list);
var queue = new Queue<UDPData>();
queue.Enqueue(data);
udpData.Add(remoteAddress, queue);
logger.Trace("Receive data from new client"); logger.Trace("Receive data from new client");
} }
return data;
} }
return data;
} }
/// <summary> /// <summary>
@ -414,7 +378,7 @@ public class UDPServer
/// <returns> void </returns> /// <returns> void </returns>
public void PrintAllData() public void PrintAllData()
{ {
using (udpDataLock.AcquireReadLock()) using (udpData.AcquireReadLock())
{ {
logger.Debug("Ready Data:"); logger.Debug("Ready Data:");
@ -436,7 +400,7 @@ public class UDPServer
{ {
try try
{ {
listener.BeginReceive(new AsyncCallback(ReceiveHandler), null); this.listener.BeginReceive(new AsyncCallback(ReceiveHandler), null);
} }
catch (Exception e) catch (Exception e)
{ {
@ -444,7 +408,7 @@ public class UDPServer
} }
finally finally
{ {
this.isRunning = true;
} }
} }
@ -454,7 +418,8 @@ public class UDPServer
/// <returns>None</returns> /// <returns>None</returns>
public void Stop() public void Stop()
{ {
listener.Close(); this.listener.Close();
this.isRunning = false;
} }
} }

View File

@ -375,7 +375,7 @@ namespace WebProtocol
{ {
if (bytes[0] != (byte)PackSign.RecvData) if (bytes[0] != (byte)PackSign.RecvData)
throw new ArgumentException( throw new ArgumentException(
"The sign of bytes is not RecvData Package!", $"The sign of bytes is not RecvData Package, Sign: 0x{BitConverter.ToString([bytes[0]])}",
nameof(bytes) nameof(bytes)
); );
return new RecvDataPackage(bytes[1], bytes[2], bytes[4..]); return new RecvDataPackage(bytes[1], bytes[2], bytes[4..]);
@ -478,7 +478,7 @@ namespace WebProtocol
{ {
if (bytes[0] != (byte)PackSign.RecvResp) if (bytes[0] != (byte)PackSign.RecvResp)
throw new ArgumentException( throw new ArgumentException(
"The sign of bytes is not RecvResp Package!", $"The sign of bytes is not RecvResp Package, Sign: 0x{BitConverter.ToString([bytes[0]])}",
nameof(bytes) nameof(bytes)
); );
return new RecvRespPackage(bytes[1], bytes[2]); return new RecvRespPackage(bytes[1], bytes[2]);