This commit is contained in:
Пытков Роман
2025-09-17 00:44:21 +03:00
parent 1f0e7c285c
commit bbe5a75dba
15 changed files with 783 additions and 0 deletions

71
.vscode/launch.json vendored Normal file
View File

@@ -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
}
]
}

18
Client/Client.csproj Normal file
View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../Domain/Domain.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MessagePack" Version="3.1.4" />
</ItemGroup>
</Project>

96
Client/HttpClient.cs Normal file
View File

@@ -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<HttpResponseMessage, Task<Data?>>? _responseConverter;
public HttpClientWrapper(Func<HttpResponseMessage, Task<Data?>> 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);
}
}
}
}

7
Client/IClient.cs Normal file
View File

@@ -0,0 +1,7 @@
namespace Client;
interface IClient
{
public void Start();
public void Stop();
}

53
Client/Program.cs Normal file
View File

@@ -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<Data?> ConvertJsonResponse(HttpResponseMessage response)
{
var json = await response.Content.ReadAsStringAsync();
var jsonData = JsonSerializer.Deserialize<JsonData>(json);
return jsonData?.ToData();
}
static async Task<Data?> ConvertMessagePackResponse(HttpResponseMessage response)
{
var bytes = await response.Content.ReadAsByteArrayAsync();
var msgPackData = MessagePackSerializer.Deserialize<MessagePackData>(bytes);
return msgPackData?.ToData();
}

17
Domain/Data.cs Normal file
View File

@@ -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
);

14
Domain/Domain.csproj Normal file
View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MessagePack" Version="3.1.4" />
</ItemGroup>
</Project>

71
Domain/Dto/JsonData.cs Normal file
View File

@@ -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
);
}
}

View File

@@ -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
);
}
}

34
NetworkTest.sln Normal file
View File

@@ -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

88
Server/DataGenerator.cs Normal file
View File

@@ -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<Data> _cache = new LinkedList<Data>();
private readonly ConcurrentDictionary<long, Data> _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
);
}
}

105
Server/HttpServer.cs Normal file
View File

@@ -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<long, Data?> _getData;
private Action<Data, HttpListenerResponse> _writeResponse;
public HttpServer(Func<long, Data?> getData, Action<Data, HttpListenerResponse> 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)}]";
}
}

8
Server/IServer.cs Normal file
View File

@@ -0,0 +1,8 @@
namespace NetworkTest;
interface IServer
{
public void Start();
public void Stop();
}

111
Server/Program.cs Normal file
View File

@@ -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);
}

18
Server/Server.csproj Normal file
View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../Domain/Domain.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MessagePack" Version="3.1.4" />
</ItemGroup>
</Project>