diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..11d5f15 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,71 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "HTTP/JSON Server", + "type": "coreclr", + "request": "launch", + "program": "${workspaceFolder}/Server/bin/Debug/net8.0/Server.dll", + "args": [ "http", "json" ], + "cwd": "${workspaceFolder}/Server", + "console": "integratedTerminal", + "stopAtEntry": false, + "presentation": { + "hidden": true + } + }, + { + "name": "HTTP/JSON Client", + "type": "coreclr", + "request": "launch", + "program": "${workspaceFolder}/Client/bin/Debug/net8.0/Client.dll", + "args": [ "http", "json" ], + "cwd": "${workspaceFolder}/Client", + "console": "integratedTerminal", + "stopAtEntry": false, + "presentation": { + "hidden": true + } + }, + { + "name": "HTTP/BIN Server", + "type": "coreclr", + "request": "launch", + "program": "${workspaceFolder}/Server/bin/Debug/net8.0/Server.dll", + "args": [ "http", "bin" ], + "cwd": "${workspaceFolder}/Server", + "console": "integratedTerminal", + "stopAtEntry": false, + "presentation": { + "hidden": true + } + }, + { + "name": "HTTP/BIN Client", + "type": "coreclr", + "request": "launch", + "program": "${workspaceFolder}/Client/bin/Debug/net8.0/Client.dll", + "args": [ "http", "bin" ], + "cwd": "${workspaceFolder}/Client", + "console": "integratedTerminal", + "stopAtEntry": false, + "presentation": { + "hidden": true + } + } + ], + "compounds": [ + { + "name": "HTTP/JSON: Server and Client", + "configurations": ["HTTP/JSON Server", "HTTP/JSON Client"], + "preLaunchTask": "dotnet: build", + "stopAll": true + }, + { + "name": "HTTP/BIN: Server and Client", + "configurations": ["HTTP/BIN Server", "HTTP/BIN Client"], + "preLaunchTask": "dotnet: build", + "stopAll": true + } + ] +} diff --git a/Client/Client.csproj b/Client/Client.csproj new file mode 100644 index 0000000..c861d57 --- /dev/null +++ b/Client/Client.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + diff --git a/Client/HttpClient.cs b/Client/HttpClient.cs new file mode 100644 index 0000000..0ab7d3b --- /dev/null +++ b/Client/HttpClient.cs @@ -0,0 +1,96 @@ +using System; +using System.Diagnostics; +using System.Net.Http; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Domain; +using Domain.Dto; +using MessagePack; + +namespace Client; + +public class HttpClientWrapper : IClient +{ + private readonly HttpClient _httpClient; + private readonly string _baseUrl; + private CancellationTokenSource _cts = new CancellationTokenSource(); + private Task? _runningTask; + private Func>? _responseConverter; + + public HttpClientWrapper(Func> responseConverter, string baseUrl = "http://localhost:5555/") + { + _httpClient = new HttpClient(); + _baseUrl = baseUrl; + _responseConverter = responseConverter; + } + + + public void Start() + { + _cts = new CancellationTokenSource(); + _runningTask = Task.Run(() => RunAsync(_cts.Token)); + } + + public void Stop() + { + _cts.Cancel(); + _runningTask?.Wait(); + _httpClient.Dispose(); + } + + private async Task RunAsync(CancellationToken token) + { + long index = 0; + var sw = Stopwatch.StartNew(); + var lastMs = 0L; + var lastIndex = 0L; + var ms = 1000; + while (!token.IsCancellationRequested) + { + try + { + var url = $"{_baseUrl}fetchpackage?index={index}"; + var response = await _httpClient.GetAsync(url, token); + if (response.IsSuccessStatusCode) + { + if (_responseConverter != null) + { + var data = await _responseConverter(response); + if (data != null) + { + var diff = sw.ElapsedMilliseconds - lastMs; + if (diff >= ms) + { + var fetched = index - lastIndex; + System.Console.WriteLine($"Fetched {fetched} data packages in {diff} ms."); + lastIndex = index; + lastMs = sw.ElapsedMilliseconds; + } + //System.Console.WriteLine(data); + } + } + else + { + Console.WriteLine("Response converter not set."); + } + } + else + { + Console.WriteLine($"Failed to fetch data for index {index}: {response.StatusCode}"); + } + index++; + //await Task.Delay(100, token); // Wait 1 second between requests + } + catch (TaskCanceledException) + { + break; + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + await Task.Delay(1000, token); + } + } + } +} \ No newline at end of file diff --git a/Client/IClient.cs b/Client/IClient.cs new file mode 100644 index 0000000..06e1712 --- /dev/null +++ b/Client/IClient.cs @@ -0,0 +1,7 @@ +namespace Client; + +interface IClient +{ + public void Start(); + public void Stop(); +} \ No newline at end of file diff --git a/Client/Program.cs b/Client/Program.cs new file mode 100644 index 0000000..50245b0 --- /dev/null +++ b/Client/Program.cs @@ -0,0 +1,53 @@ +using System; +using System.Net.Http; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Client; +using Domain; +using Domain.Dto; +using MessagePack; + +if (args.Length < 2) +{ + System.Console.WriteLine("Pass two args: http/tcp and json/bin"); + return -1; +} + +var protocol = args[0]; +var serialization = args[1]; +IClient? client = protocol == "http" ? new HttpClientWrapper(serialization == "json" ? ConvertJsonResponse : ConvertMessagePackResponse) : null; + +client?.Start(); +System.Console.WriteLine("Client started:"); +System.Console.WriteLine(client); + +// Обработка выхода по Ctrl+C +Console.CancelKeyPress += (sender, e) => +{ + e.Cancel = true; // Prevent immediate termination + Console.WriteLine("Shutdown signal received. Stopping client..."); + client?.Stop(); + Console.WriteLine("Goodbye!"); + Environment.Exit(0); +}; + +// Бесконечный цикл ожидания +while (true) +{ + Thread.Sleep(1000); +} + +static async Task ConvertJsonResponse(HttpResponseMessage response) +{ + var json = await response.Content.ReadAsStringAsync(); + var jsonData = JsonSerializer.Deserialize(json); + return jsonData?.ToData(); +} + +static async Task ConvertMessagePackResponse(HttpResponseMessage response) +{ + var bytes = await response.Content.ReadAsByteArrayAsync(); + var msgPackData = MessagePackSerializer.Deserialize(bytes); + return msgPackData?.ToData(); +} diff --git a/Domain/Data.cs b/Domain/Data.cs new file mode 100644 index 0000000..a0918eb --- /dev/null +++ b/Domain/Data.cs @@ -0,0 +1,17 @@ +namespace Domain; + +public record class Data( + double ConcentrationIndex, + double RelaxationIndex, + double CognitiveControl, + double CognitiveLoad, + double Alpha, + double Beta, + double Theta, + double Smr, + double MuWave, + bool Artifact, + double SignalQuality, + long PackageIndex, + DateTime TimeOfDataGenerate +); \ No newline at end of file diff --git a/Domain/Domain.csproj b/Domain/Domain.csproj new file mode 100644 index 0000000..3702e1f --- /dev/null +++ b/Domain/Domain.csproj @@ -0,0 +1,14 @@ + + + + Library + net8.0 + enable + enable + + + + + + + diff --git a/Domain/Dto/JsonData.cs b/Domain/Dto/JsonData.cs new file mode 100644 index 0000000..6aeae46 --- /dev/null +++ b/Domain/Dto/JsonData.cs @@ -0,0 +1,71 @@ +namespace Domain.Dto; + +using System.Text.Json.Serialization; + +public class JsonData +{ + [JsonPropertyName("concentrationIndex")] + public double ConcentrationIndex { get; set; } + [JsonPropertyName("relaxationIndex")] + public double RelaxationIndex { get; set; } + [JsonPropertyName("cognitiveControl")] + public double CognitiveControl { get; set; } + [JsonPropertyName("cognitiveLoad")] + public double CognitiveLoad { get; set; } + [JsonPropertyName("alpha")] + public double Alpha { get; set; } + [JsonPropertyName("beta")] + public double Beta { get; set; } + [JsonPropertyName("theta")] + public double Theta { get; set; } + [JsonPropertyName("smr")] + public double Smr { get; set; } + [JsonPropertyName("muWave")] + public double MuWave { get; set; } + [JsonPropertyName("artifact")] + public bool Artifact { get; set; } + [JsonPropertyName("signalQuality")] + public double SignalQuality { get; set; } + [JsonPropertyName("packageIndex")] + public long PackageIndex { get; set; } + [JsonPropertyName("timeOfDataGenerate")] + public DateTime TimeOfDataGenerate { get; set; } + + public JsonData() { } + + public JsonData(Data data) + { + ConcentrationIndex = data.ConcentrationIndex; + RelaxationIndex = data.RelaxationIndex; + CognitiveControl = data.CognitiveControl; + CognitiveLoad = data.CognitiveLoad; + Alpha = data.Alpha; + Beta = data.Beta; + Theta = data.Theta; + Smr = data.Smr; + MuWave = data.MuWave; + Artifact = data.Artifact; + SignalQuality = data.SignalQuality; + PackageIndex = data.PackageIndex; + TimeOfDataGenerate = data.TimeOfDataGenerate; + } + + public Data ToData() + { + return new Data( + ConcentrationIndex, + RelaxationIndex, + CognitiveControl, + CognitiveLoad, + Alpha, + Beta, + Theta, + Smr, + MuWave, + Artifact, + SignalQuality, + PackageIndex, + TimeOfDataGenerate + ); + } +} \ No newline at end of file diff --git a/Domain/Dto/MessagePackData.cs b/Domain/Dto/MessagePackData.cs new file mode 100644 index 0000000..054d086 --- /dev/null +++ b/Domain/Dto/MessagePackData.cs @@ -0,0 +1,72 @@ +namespace Domain.Dto; + +using MessagePack; + +[MessagePackObject] +public class MessagePackData +{ + [Key("concentrationIndex")] + public double ConcentrationIndex { get; set; } + [Key("relaxationIndex")] + public double RelaxationIndex { get; set; } + [Key("cognitiveControl")] + public double CognitiveControl { get; set; } + [Key("cognitiveLoad")] + public double CognitiveLoad { get; set; } + [Key("alpha")] + public double Alpha { get; set; } + [Key("beta")] + public double Beta { get; set; } + [Key("theta")] + public double Theta { get; set; } + [Key("smr")] + public double Smr { get; set; } + [Key("muWave")] + public double MuWave { get; set; } + [Key("artifact")] + public bool Artifact { get; set; } + [Key("signalQuality")] + public double SignalQuality { get; set; } + [Key("packageIndex")] + public long PackageIndex { get; set; } + [Key("timeOfDataGenerate")] + public DateTime TimeOfDataGenerate { get; set; } + + public MessagePackData() { } + + public MessagePackData(Data data) + { + ConcentrationIndex = data.ConcentrationIndex; + RelaxationIndex = data.RelaxationIndex; + CognitiveControl = data.CognitiveControl; + CognitiveLoad = data.CognitiveLoad; + Alpha = data.Alpha; + Beta = data.Beta; + Theta = data.Theta; + Smr = data.Smr; + MuWave = data.MuWave; + Artifact = data.Artifact; + SignalQuality = data.SignalQuality; + PackageIndex = data.PackageIndex; + TimeOfDataGenerate = data.TimeOfDataGenerate; + } + + public Data ToData() + { + return new Data( + ConcentrationIndex, + RelaxationIndex, + CognitiveControl, + CognitiveLoad, + Alpha, + Beta, + Theta, + Smr, + MuWave, + Artifact, + SignalQuality, + PackageIndex, + TimeOfDataGenerate + ); + } +} \ No newline at end of file diff --git a/NetworkTest.sln b/NetworkTest.sln new file mode 100644 index 0000000..1a26716 --- /dev/null +++ b/NetworkTest.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server", "Server\Server.csproj", "{A33376EF-FB88-4A2F-A1FD-0F9B0F89B976}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client", "Client\Client.csproj", "{B4ABD6BA-1C0A-49A4-8580-E5A5B9A4DD4D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Domain", "Domain\Domain.csproj", "{7EFE01A5-B489-4460-988D-E34D6C67711D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A33376EF-FB88-4A2F-A1FD-0F9B0F89B976}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A33376EF-FB88-4A2F-A1FD-0F9B0F89B976}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A33376EF-FB88-4A2F-A1FD-0F9B0F89B976}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A33376EF-FB88-4A2F-A1FD-0F9B0F89B976}.Release|Any CPU.Build.0 = Release|Any CPU + {B4ABD6BA-1C0A-49A4-8580-E5A5B9A4DD4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4ABD6BA-1C0A-49A4-8580-E5A5B9A4DD4D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B4ABD6BA-1C0A-49A4-8580-E5A5B9A4DD4D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4ABD6BA-1C0A-49A4-8580-E5A5B9A4DD4D}.Release|Any CPU.Build.0 = Release|Any CPU + {7EFE01A5-B489-4460-988D-E34D6C67711D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EFE01A5-B489-4460-988D-E34D6C67711D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EFE01A5-B489-4460-988D-E34D6C67711D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EFE01A5-B489-4460-988D-E34D6C67711D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Server/DataGenerator.cs b/Server/DataGenerator.cs new file mode 100644 index 0000000..56f9166 --- /dev/null +++ b/Server/DataGenerator.cs @@ -0,0 +1,88 @@ +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); + _cache.AddLast(firstData); + _dict[firstData.PackageIndex] = firstData; + + while (!token.IsCancellationRequested) + { + //await Task.Delay(_generationInterval, token); + var first = _cache.First!.Value; + var last = _cache.Last!.Value; + if (last.PackageIndex - _maxRequestedIndex < _minNewPackages) + { + var data = GenerateRandomData(last.PackageIndex + 1); + _cache.AddLast(data); + _dict[data.PackageIndex] = data; + } + if (_maxRequestedIndex - first.PackageIndex > _maxPreviousPackages) + { + _cache.RemoveFirst(); + _dict.TryRemove(first.PackageIndex, 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 packageNumber) + { + 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(), + RelaxationIndex: _random.NextDouble(), + CognitiveControl: _random.NextDouble(), + CognitiveLoad: _random.NextDouble(), + Alpha: alpha, + Beta: beta, + Theta: theta, + Smr: _random.NextDouble(), + MuWave: _random.NextDouble(), + Artifact: signalQuality < 0.5, + SignalQuality: signalQuality, + PackageIndex: packageNumber, + TimeOfDataGenerate: DateTime.Now + ); + } +} \ No newline at end of file diff --git a/Server/HttpServer.cs b/Server/HttpServer.cs new file mode 100644 index 0000000..9c9bc9e --- /dev/null +++ b/Server/HttpServer.cs @@ -0,0 +1,105 @@ +using System; +using System.Net; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Domain; +using Domain.Dto; + +namespace NetworkTest; + +public class HttpServer : IServer +{ + private readonly HttpListener _listener; + private readonly string _url; + private CancellationTokenSource _cts = new CancellationTokenSource(); + private Func _getData; + private Action _writeResponse; + + public HttpServer(Func getData, Action writeResponse, string url = "http://*:5555/") + { + _getData = getData; + _writeResponse = writeResponse; + _url = url; + _listener = new HttpListener(); + _listener.Prefixes.Add(_url); + } + + public void Start() + { + _cts = new CancellationTokenSource(); + _listener.Start(); + Task.Run(() => ListenAsync(_cts.Token)); + } + + public void Stop() + { + _cts.Cancel(); + _listener.Stop(); + } + + private async Task ListenAsync(CancellationToken token) + { + while (!token.IsCancellationRequested) + { + try + { + var context = await _listener.GetContextAsync(); + _ = Task.Run(() => HandleRequest(context)); + } + catch (HttpListenerException) + { + break; + } + catch (ObjectDisposedException) + { + break; + } + } + } + + private void HandleRequest(HttpListenerContext context) + { + 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 = _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); + } + } + else + { + context.Response.StatusCode = 400; + byte[] buffer = Encoding.UTF8.GetBytes("Invalid or missing 'index' parameter."); + context.Response.OutputStream.Write(buffer, 0, buffer.Length); + } + } + else + { + context.Response.StatusCode = 404; + byte[] buffer = Encoding.UTF8.GetBytes("Not Found"); + context.Response.OutputStream.Write(buffer, 0, buffer.Length); + } + context.Response.OutputStream.Close(); + } + + public override string ToString() + { + return $"HTTP: [{string.Join(", ", _listener.Prefixes)}]"; + } +} + diff --git a/Server/IServer.cs b/Server/IServer.cs new file mode 100644 index 0000000..5bcc516 --- /dev/null +++ b/Server/IServer.cs @@ -0,0 +1,8 @@ +namespace NetworkTest; + +interface IServer +{ + public void Start(); + public void Stop(); + +} \ No newline at end of file diff --git a/Server/Program.cs b/Server/Program.cs new file mode 100644 index 0000000..b35c6b9 --- /dev/null +++ b/Server/Program.cs @@ -0,0 +1,111 @@ +using System.Diagnostics; +using System.Net; +using System.Text; +using System.Text.Json; +using Domain; +using Domain.Dto; +using MessagePack; +using NetworkTest; +using Server; + +if (args.Length < 2) +{ + System.Console.WriteLine("Pass twp arg: test/http/tcp and json/bin"); + return -1; +} + +var dataGenerator = new DataGenerator(generationInterval: TimeSpan.FromMilliseconds(1)); + +var protocol = args[0]; +var serialization = args[1]; + +if (protocol == "test") +{ + int nullCount = 0; + long index = 0; + var sw = Stopwatch.StartNew(); + var lastMs = 0L; + var lastIndex = 0L; + var ms = 1000; + + var json = serialization == "json"; + while (true) + { + var data = dataGenerator.GetPackage(index); + if (data == null) + { + nullCount++; + continue; + } + + if (json) + { + JsonData jsonData = new JsonData(data); + var responseText = JsonSerializer.Serialize(jsonData); + byte[] buffer = Encoding.UTF8.GetBytes(responseText); + } + else + { + MessagePackData msgPackData = new MessagePackData(data); + byte[] buffer = MessagePackSerializer.Serialize(msgPackData); + } + + index++; + + var diff = sw.ElapsedMilliseconds - lastMs; + if (diff >= ms) + { + var serializrd = index - lastIndex; + System.Console.WriteLine($"Serialized {serializrd} data packages in {diff} ms."); + lastIndex = index; + lastMs = sw.ElapsedMilliseconds; + } + } +} + +IServer? server = protocol == "http" ? + new HttpServer(index => dataGenerator.GetPackage(index), + serialization == "json" ? PrepareResponseJson : PrepareResponseMessagePack) : + null; + +server?.Start(); +System.Console.WriteLine("Server started:"); +System.Console.WriteLine(server); + +// Обработка выхода по Ctrl+C +Console.CancelKeyPress += (sender, e) => +{ + e.Cancel = true; // Prevent immediate termination + Console.WriteLine("Shutdown signal received. Stopping server..."); + server?.Stop(); + Console.WriteLine("Goodbye!"); + Environment.Exit(0); +}; + + +// Бесконечный цикл ожидания +while (true) +{ + Thread.Sleep(1000); +} + + +void PrepareResponseJson(Data data, HttpListenerResponse response) +{ + JsonData jsonData = new JsonData(data); + var responseText = JsonSerializer.Serialize(jsonData); + byte[] buffer = Encoding.UTF8.GetBytes(responseText); + response.ContentType = "application/json"; + response.ContentLength64 = buffer.Length; + response.OutputStream.Write(buffer, 0, buffer.Length); +} + +void PrepareResponseMessagePack(Data data, HttpListenerResponse response) +{ + MessagePackData msgPackData = new MessagePackData(data); + byte[] buffer = MessagePackSerializer.Serialize(msgPackData); + response.ContentType = "application/x-msgpack"; + response.ContentLength64 = buffer.Length; + response.OutputStream.Write(buffer, 0, buffer.Length); +} + diff --git a/Server/Server.csproj b/Server/Server.csproj new file mode 100644 index 0000000..ace2cb9 --- /dev/null +++ b/Server/Server.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + +