diff --git a/.justfile b/.justfile index 0770d72..5d979fb 100644 --- a/.justfile +++ b/.justfile @@ -13,4 +13,4 @@ run-server: _show-dir [working-directory: "server.test"] test-server: _show-dir - dotnet test + dotnet test --logger "console;verbosity=detailed" diff --git a/server.test/UDPServerTest.cs b/server.test/UDPServerTest.cs index 1bbfad9..0b04b97 100644 --- a/server.test/UDPServerTest.cs +++ b/server.test/UDPServerTest.cs @@ -1,27 +1,47 @@ using System.Net; -using System.Reflection; using System.Text; -using Xunit.Sdk; +using Common; +using Xunit.Abstractions; namespace server.test; -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] -public class UDPServerTest : BeforeAfterTestAttribute +public sealed class RepeatAttribute : Xunit.Sdk.DataAttribute { - const string address = "127.0.0.1"; - const int port = 1234; - private static readonly UDPServer udpServer = new UDPServer(port); + private readonly int count; - public override void Before(MethodInfo methodUnderTest) + public RepeatAttribute(int count) { - udpServer.Start(); - Console.WriteLine("Start UDP Server"); + if (count < 1) + { + 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 GetData(System.Reflection.MethodInfo testMethod) { - udpServer.Stop(); - Console.WriteLine("Stop UDP Server"); + foreach (var iterationNumber in Enumerable.Range(start: 1, count: this.count)) + { + 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] @@ -46,9 +66,15 @@ public class UDPServerTest : BeforeAfterTestAttribute udpData.DateTime = DateTime.Now; udpData.Address = "192.168.1.1"; udpData.Port = 33000; - udpData.Data = new byte[] { 0x0f, 00, 00, 00 }; + udpData.Data = new byte[] { 0xFF, 00, 00, 00 }; 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.Address, cloneUdpData.Address); Assert.NotEqual(udpData.Port, cloneUdpData.Port); @@ -57,33 +83,80 @@ public class UDPServerTest : BeforeAfterTestAttribute } [Theory] - [InlineData("Hello World!")] - [InlineData("Hello Server!")] - public async Task UDPServerFind(string text) + [InlineData(new object[] { new string[] { "Hello World!", "Hello Server!", "What is your problem?" } })] + public async Task UDPServerFindString(string[] textArray) { + Assert.True(udpServer.IsRunning); + var serverEP = new IPEndPoint(IPAddress.Parse(address), port); - - - 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])); + foreach (var text in textArray) { + Assert.True(await UDPClientPool.SendStringAsync(serverEP, [text])); var ret = await udpServer.FindDataAsync(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.Equal(address, data.Address); + Assert.Equal(text, Encoding.ASCII.GetString(data.Data)); + } + } + + [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())); } } } diff --git a/server.test/server.test.csproj b/server.test/server.test.csproj index 2bcffed..fd3a754 100644 --- a/server.test/server.test.csproj +++ b/server.test/server.test.csproj @@ -22,5 +22,9 @@ + + + + diff --git a/server.test/xunit.runner.json b/server.test/xunit.runner.json new file mode 100644 index 0000000..8e9fb78 --- /dev/null +++ b/server.test/xunit.runner.json @@ -0,0 +1,3 @@ +{ + "showLiveOutput": true +} diff --git a/server/src/Common.cs b/server/src/Common.cs index 4ba5513..b3fbc25 100644 --- a/server/src/Common.cs +++ b/server/src/Common.cs @@ -2,7 +2,7 @@ using DotNext; namespace Common { - class NumberProcessor + public class NumberProcessor { public static Result NumberToBytes(ulong num, uint length, bool isRightHigh = false) { diff --git a/server/src/UdpServer.cs b/server/src/UdpServer.cs index aad8993..65a2d82 100644 --- a/server/src/UdpServer.cs +++ b/server/src/UdpServer.cs @@ -68,11 +68,17 @@ public class UDPServer { private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + private static Dictionary> udpData = new Dictionary>(); + private int listenPort; private UdpClient listener; private IPEndPoint groupEP; - private Dictionary> udpData = new Dictionary>(); - private AsyncReaderWriterLock udpDataLock = new AsyncReaderWriterLock(1); + + private bool isRunning = false; + /// + /// 是否正在工作 + /// + public bool IsRunning { get { return isRunning; } } /// /// Construct a udp server with fixed port @@ -98,55 +104,6 @@ public class UDPServer } } - /// - /// Find UDP Receive Data According to ip address - /// - /// IP Address - /// Read and Write Wait for Milliseconds - /// 调用函数名称 - /// 调用函数位置 - /// UDP Data - public Optional 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(); - } - else - { - return Optional.Some((UDPData)data.DeepClone()); - } - } - /// /// 异步寻找目标发送的内容 /// @@ -173,20 +130,21 @@ public class UDPServer var timeleft = TimeSpan.FromMilliseconds(timeout); 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(); - udpData[ipAddr].RemoveAt(0); + data = dataQueue.Dequeue(); + // data = dataList[0].DeepClone(); + // dataList.RemoveAt(0); logger.Debug($"Find UDP Data: {data.ToString()}"); break; } } - - timeleft = DateTime.Now.Subtract(startTime); - isTimeout = timeleft >= TimeSpan.FromMilliseconds(timeout); } if (data is null) @@ -215,19 +173,19 @@ public class UDPServer var timeleft = TimeSpan.FromMilliseconds(timeout); 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]; - logger.Debug($"Find UDP Data Array: {data.ToString()}"); + data = dataQueue.ToList(); + logger.Debug($"Find UDP Data Array: {JsonConvert.SerializeObject(data)}"); break; } } - - timeleft = DateTime.Now.Subtract(startTime); - isTimeout = timeleft >= TimeSpan.FromMilliseconds(timeout); } if (data is null) @@ -249,14 +207,14 @@ public class UDPServer /// 超时时间范围 /// 接收响应包 public async ValueTask> WaitForAckAsync - (string address, int port, int timeout = 1000) + (string address, int port = -1, 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) + if (recvData.Address != address || (port >= 0 && recvData.Port != port)) throw new Exception("Receive Data From Wrong Board!"); var retPack = WebProtocol.RecvRespPackage.FromBytes(recvData.Data); @@ -274,14 +232,14 @@ public class UDPServer /// 超时时间范围 /// 接收数据包 public async ValueTask> WaitForDataAsync - (string address, int port, int timeout = 1000) + (string address, int port = -1, 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) + if (recvData.Address != address || (port >= 0 && recvData.Port != port)) throw new Exception("Receive Data From Wrong Board!"); var retPack = WebProtocol.RecvDataPackage.FromBytes(recvData.Data); @@ -307,7 +265,8 @@ public class UDPServer // Handle Package - PrintData(RecordUDPData(bytes, remoteEP)); + var udpData = RecordUDPData(bytes, remoteEP); + PrintData(udpData); BEGIN_RECEIVE: listener.BeginReceive(new AsyncCallback(ReceiveHandler), null); @@ -332,36 +291,41 @@ public class UDPServer 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(); - var remotePort = remoteEP.Port; - var data = new UDPData() - { - Address = remoteAddress, - Port = remotePort, - Data = bytes, - DateTime = DateTime.Now, - HasRead = false, - }; + Address = remoteAddress, + Port = remotePort, + Data = bytes, + DateTime = DateTime.Now, + HasRead = false, + }; + using (udpData.AcquireWriteLock()) + { // Record UDP Receive Data - if (udpData.ContainsKey(remoteAddress)) + // if (udpData.ContainsKey(remoteAddress)) + if (udpData.TryGetValue(remoteAddress, out var dataQueue)) { - var listData = udpData[remoteAddress]; - listData.Add(data); + // var listData = udpData[remoteAddress]; + // listData.Add(data); + dataQueue.Enqueue(data); logger.Trace("Receive data from old client"); } else { - var list = new List(); - list.Add(data); - udpData.Add(remoteAddress, list); + // var list = new List(); + // list.Add(data); + // udpData.Add(remoteAddress, list); + var queue = new Queue(); + queue.Enqueue(data); + udpData.Add(remoteAddress, queue); logger.Trace("Receive data from new client"); } - - return data; } + + return data; } /// @@ -414,7 +378,7 @@ public class UDPServer /// void public void PrintAllData() { - using (udpDataLock.AcquireReadLock()) + using (udpData.AcquireReadLock()) { logger.Debug("Ready Data:"); @@ -436,7 +400,7 @@ public class UDPServer { try { - listener.BeginReceive(new AsyncCallback(ReceiveHandler), null); + this.listener.BeginReceive(new AsyncCallback(ReceiveHandler), null); } catch (Exception e) { @@ -444,7 +408,7 @@ public class UDPServer } finally { - + this.isRunning = true; } } @@ -454,7 +418,8 @@ public class UDPServer /// None public void Stop() { - listener.Close(); + this.listener.Close(); + this.isRunning = false; } } diff --git a/server/src/WebProtocol.cs b/server/src/WebProtocol.cs index 14c246e..639b05b 100644 --- a/server/src/WebProtocol.cs +++ b/server/src/WebProtocol.cs @@ -375,7 +375,7 @@ namespace WebProtocol { if (bytes[0] != (byte)PackSign.RecvData) 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) ); return new RecvDataPackage(bytes[1], bytes[2], bytes[4..]); @@ -478,7 +478,7 @@ namespace WebProtocol { if (bytes[0] != (byte)PackSign.RecvResp) 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) ); return new RecvRespPackage(bytes[1], bytes[2]);