diff --git a/.vscode/launch.json b/.vscode/launch.json index e59ee1b..e659ce4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,7 @@ "type": "coreclr", "request": "launch", "program": "${workspaceFolder}/Server/bin/Debug/net8.0/Server.dll", - "args": [ "http", "json" ], + "args": [ "http", "json", "random" ], "cwd": "${workspaceFolder}/Server", "console": "integratedTerminal", "stopAtEntry": false, @@ -32,7 +32,7 @@ "type": "coreclr", "request": "launch", "program": "${workspaceFolder}/Server/bin/Debug/net8.0/Server.dll", - "args": [ "http", "bin" ], + "args": [ "http", "bin", "random" ], "cwd": "${workspaceFolder}/Server", "console": "integratedTerminal", "stopAtEntry": false, @@ -59,7 +59,7 @@ "type": "coreclr", "request": "launch", "program": "${workspaceFolder}/Server/bin/Debug/net8.0/Server.dll", - "args": [ "tcp", "json" ], + "args": [ "tcp", "json", "random" ], "cwd": "${workspaceFolder}/Server", "console": "integratedTerminal", "stopAtEntry": false, @@ -85,7 +85,7 @@ "type": "coreclr", "request": "launch", "program": "${workspaceFolder}/Server/bin/Debug/net8.0/Server.dll", - "args": [ "tcp", "bin" ], + "args": [ "tcp", "bin", "random" ], "cwd": "${workspaceFolder}/Server", "console": "integratedTerminal", "stopAtEntry": false, @@ -105,7 +105,21 @@ "presentation": { "hidden": true } - } + }, + + { + "name": "TCP/BIN (Predictable) Server", + "type": "coreclr", + "request": "launch", + "program": "${workspaceFolder}/Server/bin/Debug/net8.0/Server.dll", + "args": [ "tcp", "bin", "predictable", "2000" ], + "cwd": "${workspaceFolder}/Server", + "console": "integratedTerminal", + "stopAtEntry": false, + "presentation": { + "hidden": true + } + }, ], "compounds": [ { @@ -132,6 +146,12 @@ "configurations": ["TCP/BIN Server", "TCP/BIN Client"], "preLaunchTask": "dotnet: build", "stopAll": true + }, + { + "name": "TCP/BIN: (Predictable) Server and Client", + "configurations": ["TCP/BIN (Predictable) Server", "TCP/BIN Client"], + "preLaunchTask": "dotnet: build", + "stopAll": true } ] } diff --git a/Client/TcpClient.cs b/Client/TcpClient.cs index 888c435..396f3f0 100644 --- a/Client/TcpClient.cs +++ b/Client/TcpClient.cs @@ -123,8 +123,8 @@ public class TcpClientWrapper : IClient Console.WriteLine($"Error: {ex.Message}"); } } - System.Console.WriteLine("End cycle"); } + System.Console.WriteLine("End "); } public override string ToString() diff --git a/Domain/Dto/JsonData.cs b/Domain/Dto/JsonData.cs index b73304d..345909d 100644 --- a/Domain/Dto/JsonData.cs +++ b/Domain/Dto/JsonData.cs @@ -2,6 +2,7 @@ namespace Domain.Dto; using System.Text.Json.Serialization; +// Вообще то этот класс не следует размещать в Domain, но я не захотел дублировать его в двух проектах по соседству public class JsonData { [JsonPropertyName("concentrationIndex")] @@ -33,7 +34,7 @@ public class JsonData [JsonPropertyName("timeOfDataGenerate")] public DateTime? TimeOfDataGenerate { get; set; } - public JsonData() + public JsonData() { overlayPoint = ""; } diff --git a/Domain/Dto/MessagePackData.cs b/Domain/Dto/MessagePackData.cs index d3b0545..b877830 100644 --- a/Domain/Dto/MessagePackData.cs +++ b/Domain/Dto/MessagePackData.cs @@ -2,6 +2,8 @@ namespace Domain.Dto; using MessagePack; + +// Вообще то этот класс не следует размещать в Domain, но я не захотел дублировать его в двух проектах по соседству [MessagePackObject] public class MessagePackData { diff --git a/Server/DataGenerator.cs b/Server/DataGenerator.cs deleted file mode 100644 index 4669d28..0000000 --- a/Server/DataGenerator.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Domain; - -namespace Server; - -public class DataGenerator -{ - private static readonly Random _random = new Random(); - - private readonly int _minNewPackages; - private readonly int _maxPreviousPackages; - private readonly TimeSpan _generationInterval; - private readonly LinkedList _cache = new LinkedList(); - private readonly ConcurrentDictionary _dict = []; - private readonly object _lock = new object(); - private long _maxRequestedIndex = 0; - private Task _generationTask; - private CancellationTokenSource _cts = new(); - - public DataGenerator(int minNewPackages = 50, int maxPreviousPackages = 50, TimeSpan? generationInterval = null) - { - _minNewPackages = Math.Max(minNewPackages, 5); - _maxPreviousPackages = Math.Max(maxPreviousPackages, 5); - _generationInterval = generationInterval ?? TimeSpan.FromSeconds(1); - _generationTask = Task.Run(() => GenerateInBackground(_cts.Token)); - } - - private void GenerateInBackground(CancellationToken token) - { - var firstData = GenerateRandomData(0, "ALL"); - _cache.AddLast(firstData); - _dict[firstData.FrameIndex] = firstData; - - while (!token.IsCancellationRequested) - { - //await Task.Delay(_generationInterval, token); - var first = _cache.First!.Value; - var last = _cache.Last!.Value; - if (last.FrameIndex - _maxRequestedIndex < _minNewPackages) - { - var data = GenerateRandomData(last.FrameIndex + 1, "ALL"); - _cache.AddLast(data); - _dict[data.FrameIndex] = data; - } - if (_maxRequestedIndex - first.FrameIndex > _maxPreviousPackages) - { - _cache.RemoveFirst(); - _dict.TryRemove(first.FrameIndex, out _); - } - //System.Console.WriteLine($"[{first.PackageIndex}; {last.PackageIndex}]"); - } - } - - public Data? GetPackage(long packageIndex) - { - var res = _dict.TryGetValue(packageIndex, out var value); - _maxRequestedIndex = Math.Max(_maxRequestedIndex, packageIndex); - return res ? value : null; - } - - private Data GenerateRandomData(long frameIndex, string overlayPoint) - { - var alpha = _random.NextDouble(); - var beta = _random.NextDouble() * (1 - alpha); - var theta = 1 - alpha - beta; - var signalQuality = _random.NextDouble(); - - return new Data( - ConcentrationIndex: _random.NextDouble() < 0.1 ? null : _random.NextDouble(), - RelaxationIndex: _random.NextDouble() < 0.1 ? null : _random.NextDouble(), - CognitiveControl: _random.NextDouble() < 0.1 ? null : _random.NextDouble(), - CognitiveLoad: _random.NextDouble() < 0.1 ? null : _random.NextDouble(), - Alpha: _random.NextDouble() < 0.1 ? null : alpha, - Beta: _random.NextDouble() < 0.1 ? null : beta, - Theta: _random.NextDouble() < 0.1 ? null : theta, - Smr: _random.NextDouble() < 0.1 ? null : _random.NextDouble(), - MuWave: _random.NextDouble() < 0.1 ? null : _random.NextDouble(), - OverlayPoint: overlayPoint, - Artifact: _random.NextDouble() < 0.1 ? null : signalQuality < 0.5, - SignalQuality: _random.NextDouble() < 0.1 ? null : signalQuality, - FrameIndex: frameIndex, - TimeOfDataGenerate: _random.NextDouble() < 0.1 ? null : DateTime.Now - ); - } -} \ No newline at end of file diff --git a/Server/DataGenerator/IDataGenerator.cs b/Server/DataGenerator/IDataGenerator.cs new file mode 100644 index 0000000..e8f47da --- /dev/null +++ b/Server/DataGenerator/IDataGenerator.cs @@ -0,0 +1,18 @@ +using Domain; + +namespace Server.DataGenerator; + +interface IDataGenerator +{ + /// + /// Получить следующий пакет (если вызывающий код не умеет работать с фреймами, его следует закешировать и выдавать по пакетно) + /// + /// + public Data GetPackage(); + + /// + /// Получить новый фрейм данных + /// + /// + public Data[] GetFrame(); +} \ No newline at end of file diff --git a/Server/DataGenerator/PredictableDataGenerator.cs b/Server/DataGenerator/PredictableDataGenerator.cs new file mode 100644 index 0000000..b0836b4 --- /dev/null +++ b/Server/DataGenerator/PredictableDataGenerator.cs @@ -0,0 +1,80 @@ +using System; +using System.Linq; +using System.Threading; +using Domain; + +namespace Server.DataGenerator; + +public class PredictableDataGenerator : IDataGenerator +{ + private long _currentFrameIndex = 0; + private Data[] _currentFrame; + private int _currentPackageInFrame = 0; + private DateTime _lastFrameTime; + private bool _frameArtifact = false; + private int _delay; + + public PredictableDataGenerator(int frameDelay = 0) + { + _delay = frameDelay; + _lastFrameTime = DateTime.Now; + _currentFrame = GenerateFrame(0, _lastFrameTime, _frameArtifact); + _currentFrameIndex = 1; + } + + private Data[] GenerateFrame(long frameIndex, DateTime frameTime, bool artifact) + { + Thread.Sleep(_delay); + var overlayPoints = OverlayPoints.Points.Take(8).Append(OverlayPoints.All).ToArray(); + return overlayPoints.Select((op, idx) => GeneratePredictableData(frameIndex, op, idx, frameTime, artifact)).ToArray(); + } + + private Data GeneratePredictableData(long frameIndex, string overlayPoint, int index, DateTime frameTime, bool artifact) + { + double value; + if (overlayPoint == OverlayPoints.All) + { + value = frameIndex; + } + else + { + value = 1000 * (index + 1) + frameIndex; + } + + return new Data( + ConcentrationIndex: value, + RelaxationIndex: value, + CognitiveControl: value, + CognitiveLoad: value, + Alpha: value, + Beta: value, + Theta: value, + Smr: value, + MuWave: value, + OverlayPoint: overlayPoint, + Artifact: artifact, + SignalQuality: value, + FrameIndex: frameIndex, + TimeOfDataGenerate: frameTime + ); + } + + public Data GetPackage() + { + if (_currentPackageInFrame >= _currentFrame.Length) + { + _frameArtifact = !_frameArtifact; + _lastFrameTime = _lastFrameTime.AddSeconds(1); + _currentFrame = GenerateFrame(Interlocked.Increment(ref _currentFrameIndex), _lastFrameTime, _frameArtifact); + _currentPackageInFrame = 0; + } + return _currentFrame[_currentPackageInFrame++]; + } + + public Data[] GetFrame() + { + _frameArtifact = !_frameArtifact; + _lastFrameTime = _lastFrameTime.AddSeconds(1); + return GenerateFrame(Interlocked.Increment(ref _currentFrameIndex), _lastFrameTime, _frameArtifact); + } +} \ No newline at end of file diff --git a/Server/DataGenerator/RandomDataGenerator.cs b/Server/DataGenerator/RandomDataGenerator.cs new file mode 100644 index 0000000..89d0318 --- /dev/null +++ b/Server/DataGenerator/RandomDataGenerator.cs @@ -0,0 +1,67 @@ +using System; +using System.Linq; +using System.Threading; +using Domain; + +namespace Server.DataGenerator; + +public class RandomDataGenerator : IDataGenerator +{ + private static readonly Random _random = new Random(); + + private long _currentFrameIndex = 0; + private Data[] _currentFrame; + private int _currentPackageInFrame = 0; + + public RandomDataGenerator(int minNewPackages = 50, int maxPreviousPackages = 50, TimeSpan? generationInterval = null) + { + _currentFrame = GenerateFrame(0); + _currentFrameIndex = 1; + } + + private Data[] GenerateFrame(long frameIndex) + { + var overlayPoints = OverlayPoints.Points.Take(8).Append(OverlayPoints.All).ToArray(); + return overlayPoints.Select(op => GenerateRandomData(frameIndex, op)).ToArray(); + } + + private Data GenerateRandomData(long frameIndex, string overlayPoint) + { + var alpha = _random.NextDouble(); + var beta = _random.NextDouble() * (1 - alpha); + var theta = 1 - alpha - beta; + var signalQuality = _random.NextDouble(); + + return new Data( + ConcentrationIndex: _random.NextDouble() < 0.1 ? null : _random.NextDouble(), + RelaxationIndex: _random.NextDouble() < 0.1 ? null : _random.NextDouble(), + CognitiveControl: _random.NextDouble() < 0.1 ? null : _random.NextDouble(), + CognitiveLoad: _random.NextDouble() < 0.1 ? null : _random.NextDouble(), + Alpha: _random.NextDouble() < 0.1 ? null : alpha, + Beta: _random.NextDouble() < 0.1 ? null : beta, + Theta: _random.NextDouble() < 0.1 ? null : theta, + Smr: _random.NextDouble() < 0.1 ? null : _random.NextDouble(), + MuWave: _random.NextDouble() < 0.1 ? null : _random.NextDouble(), + OverlayPoint: overlayPoint, + Artifact: _random.NextDouble() < 0.1 ? null : signalQuality < 0.5, + SignalQuality: _random.NextDouble() < 0.1 ? null : signalQuality, + FrameIndex: frameIndex, + TimeOfDataGenerate: _random.NextDouble() < 0.1 ? null : DateTime.Now + ); + } + + public Data GetPackage() + { + if (_currentPackageInFrame >= _currentFrame.Length) + { + _currentFrame = GenerateFrame(Interlocked.Increment(ref _currentFrameIndex)); + _currentPackageInFrame = 0; + } + return _currentFrame[_currentPackageInFrame++]; + } + + public Data[] GetFrame() + { + return GenerateFrame(Interlocked.Increment(ref _currentFrameIndex)); + } +} \ No newline at end of file diff --git a/Server/HttpServer.cs b/Server/HttpServer.cs index 9c9bc9e..917503b 100644 --- a/Server/HttpServer.cs +++ b/Server/HttpServer.cs @@ -14,12 +14,12 @@ public class HttpServer : IServer private readonly HttpListener _listener; private readonly string _url; private CancellationTokenSource _cts = new CancellationTokenSource(); - private Func _getData; + private Func _getPackage; private Action _writeResponse; - public HttpServer(Func getData, Action writeResponse, string url = "http://*:5555/") + public HttpServer(Func getPackage, Action writeResponse, string url = "http://*:5555/") { - _getData = getData; + _getPackage = getPackage; _writeResponse = writeResponse; _url = url; _listener = new HttpListener(); @@ -63,28 +63,18 @@ public class HttpServer : IServer { if (context.Request.Url?.AbsolutePath.Equals("/fetchpackage", StringComparison.OrdinalIgnoreCase) == true) { - string? indexStr = context.Request.QueryString["index"]; - if (indexStr != null && long.TryParse(indexStr, out long index)) + var data = _getPackage(); + if (data != null) { - var data = _getData(index); - if (data != null) - { - _writeResponse(data, context.Response); - } - else - { - var responseText = JsonSerializer.Serialize(new { error = "Data not found" }); - context.Response.StatusCode = 404; - byte[] buffer = Encoding.UTF8.GetBytes(responseText); - context.Response.ContentType = "application/json"; - context.Response.ContentLength64 = buffer.Length; - context.Response.OutputStream.Write(buffer, 0, buffer.Length); - } + _writeResponse(data, context.Response); } else { - context.Response.StatusCode = 400; - byte[] buffer = Encoding.UTF8.GetBytes("Invalid or missing 'index' parameter."); + var responseText = JsonSerializer.Serialize(new { error = "Data not found" }); + context.Response.StatusCode = 404; + byte[] buffer = Encoding.UTF8.GetBytes(responseText); + context.Response.ContentType = "application/json"; + context.Response.ContentLength64 = buffer.Length; context.Response.OutputStream.Write(buffer, 0, buffer.Length); } } diff --git a/Server/Program.cs b/Server/Program.cs index 4335385..c45de30 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -7,17 +7,34 @@ using Domain.Dto; using MessagePack; using NetworkTest; using Server; +using Server.DataGenerator; -if (args.Length < 2) +if (args.Length < 3) { - System.Console.WriteLine("Pass twp arg: test/http/tcp and json/bin"); + System.Console.WriteLine("Pass three args: test/http/tcp, json/bin, and random/predictable"); return -1; } -var dataGenerator = new DataGenerator(generationInterval: TimeSpan.FromMilliseconds(1)); - var protocol = args[0]; var serialization = args[1]; +var generatorType = args[2]; +int delay = 0; + +if (generatorType == "predictable" && args.Length > 3) +{ + if (!int.TryParse(args[3], out delay)) + { + System.Console.WriteLine("Invalid delay value. Must be an integer."); + return -1; + } +} + +IDataGenerator dataGenerator = generatorType switch +{ + "random" => new RandomDataGenerator(generationInterval: TimeSpan.FromMilliseconds(1)), + "predictable" => new PredictableDataGenerator(delay), + _ => throw new ArgumentException("Generator type must be 'random' or 'predictable'") +}; if (protocol == "test") { @@ -31,7 +48,7 @@ if (protocol == "test") var json = serialization == "json"; while (true) { - var data = dataGenerator.GetPackage(index); + var data = dataGenerator.GetPackage(); if (data == null) { nullCount++; @@ -65,9 +82,9 @@ if (protocol == "test") IServer? server = protocol switch { - "http" => new HttpServer(index => dataGenerator.GetPackage(index), + "http" => new HttpServer(() => dataGenerator.GetPackage(), serialization == "json" ? PrepareResponseJson : PrepareResponseMessagePack), - "tcp" => new TcpServer(index => dataGenerator.GetPackage(index), + "tcp" => new TcpServer(() => dataGenerator.GetPackage(), serialization == "json" ? PrepareBytesJson : PrepareBytesMessagePack), _ => null }; diff --git a/Server/TcpServer.cs b/Server/TcpServer.cs index 6032142..9e5ebe6 100644 --- a/Server/TcpServer.cs +++ b/Server/TcpServer.cs @@ -12,14 +12,14 @@ public class TcpServer : IServer { private const uint MagicHeader = 0xDEADBEEF; private readonly TcpListener _listener; - private readonly Func _getData; + private readonly Func _getPackage; private readonly Func _prepareBytes; private readonly int _port; private CancellationTokenSource _cts = new CancellationTokenSource(); - public TcpServer(Func getData, Func prepareBytes, int port = 5555) + public TcpServer(Func getPackage, Func prepareBytes, int port = 5555) { - _getData = getData; + _getPackage = getPackage; _prepareBytes = prepareBytes; _port = port; _listener = new TcpListener(IPAddress.Any, _port); @@ -63,10 +63,9 @@ public class TcpServer : IServer using (client) using (var stream = client.GetStream()) { - var index = 0L; while (!token.IsCancellationRequested && client.Connected) { - var data = _getData(index); + var data = _getPackage(); if (data != null) { var bytes = _prepareBytes(data); @@ -75,7 +74,6 @@ public class TcpServer : IServer await stream.WriteAsync(magicBytes, 0, magicBytes.Length, token); await stream.WriteAsync(lengthBytes, 0, lengthBytes.Length, token); await stream.WriteAsync(bytes, 0, bytes.Length, token); - index++; } // await Task.Delay(50, token); // Removed delay to test maximum throughput }