diff --git a/Client/FrameAssembler.cs b/Client/FrameAssembler.cs new file mode 100644 index 0000000..5618de6 --- /dev/null +++ b/Client/FrameAssembler.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using Domain; + +namespace Client; + +public class FrameAssembler +{ + private readonly Action> _frameCallback; + private readonly List _buffer = new List(); + private const int FrameSize = 9; + + public FrameAssembler(Action> frameCallback) + { + _frameCallback = frameCallback ?? throw new ArgumentNullException(nameof(frameCallback)); + } + + public void AddData(Data data) + { + _buffer.Add(data); + if (_buffer.Count >= FrameSize) + { + _frameCallback(new List(_buffer)); + _buffer.Clear(); + } + } +} \ No newline at end of file diff --git a/Client/HttpClient.cs b/Client/HttpClient.cs index 0ab7d3b..c232811 100644 --- a/Client/HttpClient.cs +++ b/Client/HttpClient.cs @@ -17,6 +17,7 @@ public class HttpClientWrapper : IClient private CancellationTokenSource _cts = new CancellationTokenSource(); private Task? _runningTask; private Func>? _responseConverter; + private Action? _callback; public HttpClientWrapper(Func> responseConverter, string baseUrl = "http://localhost:5555/") { @@ -39,6 +40,11 @@ public class HttpClientWrapper : IClient _httpClient.Dispose(); } + public void RegisterCallback(Action callback) + { + _callback = callback; + } + private async Task RunAsync(CancellationToken token) { long index = 0; @@ -68,6 +74,7 @@ public class HttpClientWrapper : IClient lastMs = sw.ElapsedMilliseconds; } //System.Console.WriteLine(data); + _callback?.Invoke(data); } } else diff --git a/Client/IClient.cs b/Client/IClient.cs index 06e1712..f9f9492 100644 --- a/Client/IClient.cs +++ b/Client/IClient.cs @@ -4,4 +4,5 @@ interface IClient { public void Start(); public void Stop(); + public void RegisterCallback(Action callback); } \ No newline at end of file diff --git a/Client/Program.cs b/Client/Program.cs index 6c7100f..de30ad5 100644 --- a/Client/Program.cs +++ b/Client/Program.cs @@ -1,4 +1,6 @@ using System; +using System.IO; +using System.Linq; using System.Net.Http; using System.Text; using System.Text.Json; @@ -9,14 +11,32 @@ using Domain; using Domain.Dto; using MessagePack; -if (args.Length < 2) +if (args.Length == 0 || args.Contains("--help") || args.Contains("-h")) { - System.Console.WriteLine("Pass two args: http/tcp and json/bin"); + PrintHelp(); + return 0; +} + +if (args.Length < 2 || (args.Length > 2 && !args[2].StartsWith("--save-dir="))) +{ + Console.WriteLine("Error: Insufficient arguments provided."); + Console.WriteLine(); + PrintHelp(); return -1; } var protocol = args[0]; var serialization = args[1]; +string? saveDir = null; +if (args.Length >= 3) +{ + saveDir = args[2].Substring("--save-dir=".Length); + if (Directory.Exists(saveDir) == false) + { + System.Console.WriteLine("Save dir does not exist. Ignore."); + } +} + IClient? client = protocol switch { "http" => new HttpClientWrapper(serialization == "json" ? ConvertJsonResponse : ConvertMessagePackResponse), @@ -24,6 +44,17 @@ IClient? client = protocol switch _ => null }; +if (saveDir != null) +{ + // Create FrameAssembler + var frameAssembler = new FrameAssembler(frame => + { + SaveFrame(frame, saveDir); + }); + + client?.RegisterCallback(frameAssembler.AddData); +} + client?.Start(); System.Console.WriteLine("Client started:"); System.Console.WriteLine(client); @@ -44,6 +75,44 @@ while (true) Thread.Sleep(1000); } +static void PrintHelp() +{ + Console.WriteLine("NetworkTest Client - A network testing client application"); + Console.WriteLine(); + Console.WriteLine("USAGE:"); + Console.WriteLine(" Client [--save-dir=]"); + Console.WriteLine(); + Console.WriteLine("ARGUMENTS:"); + Console.WriteLine(" The protocol to use:"); + Console.WriteLine(" - http: Connect using HTTP"); + Console.WriteLine(" - tcp: Connect using TCP"); + Console.WriteLine(); + Console.WriteLine(" The serialization format:"); + Console.WriteLine(" - json: Use JSON serialization"); + Console.WriteLine(" - bin: Use MessagePack binary serialization"); + Console.WriteLine(); + Console.WriteLine("OPTIONS:"); + Console.WriteLine(" --save-dir= Directory to save received frames (optional)"); + Console.WriteLine(" --help, -h Show this help message"); + Console.WriteLine(); + Console.WriteLine("EXAMPLES:"); + Console.WriteLine(" Client http json"); + Console.WriteLine(" Client tcp bin --save-dir=./frames"); + Console.WriteLine(" Client --help"); + Console.WriteLine(); + Console.WriteLine("The client will connect to the server and start receiving data. Press Ctrl+C to stop."); +} + +static void SaveFrame(List frame, string saveDir) +{ + var fileName = $"frame_{frame[0].FrameIndex}"; + fileName += ".json"; + var jsonDatas = frame.Select(d => new JsonData(d)).ToList(); + var options = new JsonSerializerOptions { WriteIndented = true }; + var json = JsonSerializer.Serialize(jsonDatas, options); + File.WriteAllText(Path.Combine(saveDir, fileName), json); +} + static async Task ConvertJsonResponse(HttpResponseMessage response) { var json = await response.Content.ReadAsStringAsync(); diff --git a/Client/TcpClient.cs b/Client/TcpClient.cs index 396f3f0..ec40c3f 100644 --- a/Client/TcpClient.cs +++ b/Client/TcpClient.cs @@ -16,6 +16,7 @@ public class TcpClientWrapper : IClient private Task? _runningTask; private Func>? _responseConverter; private TcpClient? _tcpClient; + private Action? _callback; public TcpClientWrapper(Func> responseConverter, string host = "localhost", int port = 5555) { @@ -37,11 +38,37 @@ public class TcpClientWrapper : IClient _tcpClient?.Close(); } + public void RegisterCallback(Action callback) + { + _callback = callback; + } + private async Task RunAsync(CancellationToken token) { - _tcpClient = new TcpClient(); - await _tcpClient.ConnectAsync(_host, _port, token); - using (var stream = _tcpClient.GetStream()) + const int maxRetries = 5; + const int retryDelayMs = 1000; + + for (int attempt = 1; attempt <= maxRetries; attempt++) + { + try + { + _tcpClient = new TcpClient(); + await _tcpClient.ConnectAsync(_host, _port, token); + break; // Connection successful + } + catch (Exception ex) when (attempt < maxRetries) + { + Console.WriteLine($"Connection attempt {attempt} failed: {ex.Message}. Retrying in {retryDelayMs}ms..."); + await Task.Delay(retryDelayMs, token); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to connect after {maxRetries} attempts: {ex.Message}"); + throw; + } + } + + using (var stream = _tcpClient!.GetStream()) { long index = 0; var sw = Stopwatch.StartNew(); @@ -106,6 +133,7 @@ public class TcpClientWrapper : IClient lastMs = sw.ElapsedMilliseconds; } //Console.WriteLine(data); + _callback?.Invoke(data); } } else diff --git a/Server/Program.cs b/Server/Program.cs index c45de30..f46b87a 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -9,9 +9,17 @@ using NetworkTest; using Server; using Server.DataGenerator; +if (args.Length == 0 || args.Contains("--help") || args.Contains("-h")) +{ + PrintHelp(); + return 0; +} + if (args.Length < 3) { - System.Console.WriteLine("Pass three args: test/http/tcp, json/bin, and random/predictable"); + Console.WriteLine("Error: Insufficient arguments provided."); + Console.WriteLine(); + PrintHelp(); return -1; } @@ -115,6 +123,43 @@ while (!cts.Token.IsCancellationRequested) return 0; +static void PrintHelp() +{ + Console.WriteLine("NetworkTest Server - A network testing server application"); + Console.WriteLine(); + Console.WriteLine("USAGE:"); + Console.WriteLine(" Server [delay]"); + Console.WriteLine(); + Console.WriteLine("ARGUMENTS:"); + Console.WriteLine(" The protocol to use:"); + Console.WriteLine(" - test: Run serialization performance test mode"); + Console.WriteLine(" - http: Start HTTP server"); + Console.WriteLine(" - tcp: Start TCP server"); + Console.WriteLine(); + Console.WriteLine(" The serialization format:"); + Console.WriteLine(" - json: Use JSON serialization"); + Console.WriteLine(" - bin: Use MessagePack binary serialization"); + Console.WriteLine(); + Console.WriteLine(" The data generator type:"); + Console.WriteLine(" - random: Generate random data"); + Console.WriteLine(" - predictable: Generate predictable data with optional delay"); + Console.WriteLine(); + Console.WriteLine(" [delay] Optional delay in milliseconds for predictable generator"); + Console.WriteLine(" (only used when generator is 'predictable')"); + Console.WriteLine(); + Console.WriteLine("OPTIONS:"); + Console.WriteLine(" --help, -h Show this help message"); + Console.WriteLine(); + Console.WriteLine("EXAMPLES:"); + Console.WriteLine(" Server http json random"); + Console.WriteLine(" Server tcp bin predictable 1000"); + Console.WriteLine(" Server test json random"); + Console.WriteLine(" Server --help"); + Console.WriteLine(); + Console.WriteLine("The server will start and listen for connections. Press Ctrl+C to stop."); +} + + void PrepareResponseJson(Data data, HttpListenerResponse response) { JsonData jsonData = new JsonData(data);