diff --git a/thirdTestTask/thirdTestTask.sln b/thirdTestTask/thirdTestTask.sln new file mode 100644 index 0000000..85479f6 --- /dev/null +++ b/thirdTestTask/thirdTestTask.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35806.99 d17.13 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "thirdTestTask", "thirdTestTask\thirdTestTask.csproj", "{0B8D0C96-1815-4C28-962A-89EEA36DAC6C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0B8D0C96-1815-4C28-962A-89EEA36DAC6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B8D0C96-1815-4C28-962A-89EEA36DAC6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B8D0C96-1815-4C28-962A-89EEA36DAC6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B8D0C96-1815-4C28-962A-89EEA36DAC6C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B5862231-4F8A-4732-BBF1-C911C3298007} + EndGlobalSection +EndGlobal diff --git a/thirdTestTask/thirdTestTask/App.cs b/thirdTestTask/thirdTestTask/App.cs new file mode 100644 index 0000000..1f2e67c --- /dev/null +++ b/thirdTestTask/thirdTestTask/App.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace thirdTestTask; + +public static class App +{ + public static async Task Run(string[] args) + { + if (TryParseServer(args, out int serverPort)) + { + await ServerHost.RunOnce(serverPort); + return 0; + } + + if (TryParseClient(args, out IPAddress? ip, out int clientPort)) + { + await ClientHost.Run(ip!, clientPort); + return 0; + } + + PrintUsage(); + return 2; + } + + private static bool TryParseServer(string[] args, out int port) + { + port = 0; + return args.Length == 1 && int.TryParse(args[0], out port); + } + + private static bool TryParseClient(string[] args, out IPAddress? ip, out int port) + { + ip = null; + port = 0; + + return args.Length == 2 && IPAddress.TryParse(args[0], out ip) && int.TryParse(args[1], out port); + } + + private static void PrintUsage() + { + Console.WriteLine("Использование:"); + Console.WriteLine("Сервер: "); + Console.WriteLine("Клиент: "); + } +} diff --git a/thirdTestTask/thirdTestTask/ChatSession.cs b/thirdTestTask/thirdTestTask/ChatSession.cs new file mode 100644 index 0000000..90655bf --- /dev/null +++ b/thirdTestTask/thirdTestTask/ChatSession.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Text.Unicode; +using System.Threading.Tasks; + +namespace thirdTestTask; + +public static class ChatSession +{ + public static async Task RunAsync(TcpClient client, TextReader input, TextWriter output, CancellationToken token = default) + { + using var cts = CancellationTokenSource.CreateLinkedTokenSource(token); + CancellationToken token2 = cts.Token; + + NetworkStream stream = client.GetStream(); + + using var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: false, bufferSize: 1024, leaveOpen: true); + using var writer = new StreamWriter(stream, Encoding.UTF8, bufferSize: 1024, leaveOpen: true){ AutoFlush = true }; + Task receiveTask = Task.Run(async () => + { + try + { + while (!token2.IsCancellationRequested) + { + string? msg = await reader.ReadLineAsync(); + + if (msg == null) + { + await SafeWriteLineAsync(output, "\n[СИСТЕМА] Соединение разорвано."); + cts.Cancel(); + break; + } + + if (IsExit(msg)) + { + await SafeWriteLineAsync(output, "\n[СИСТЕМА] Собеседник завершил чат."); + cts.Cancel(); + break; + } + + await SafeWriteLineAsync(output, $"[Собеседник]: {msg}"); + } + } + catch + { + cts.Cancel(); + } + }, token2); + + Task sendTask = Task.Run(async () => + { + try + { + while (!token2.IsCancellationRequested) + { + string? line = await ReadLineWithCancelAsync(input, token2); + + if (line == null) + { + cts.Cancel(); + break; + } + + if (string.IsNullOrWhiteSpace(line)) + continue; + + await writer.WriteLineAsync(line); + + if (IsExit(line)) + { + cts.Cancel(); + break; + } + } + } + catch + { + cts.Cancel(); + } + }, token2); + + await Task.WhenAny(receiveTask, sendTask); + + try { client.Close(); } + catch { } + + cts.Cancel(); + try { await Task.WhenAll(receiveTask, sendTask); } + catch { } + } + + private static bool IsExit(string s) + => s.Trim().Equals("exit", StringComparison.OrdinalIgnoreCase); + + private static Task SafeWriteLineAsync(TextWriter output, string text) + { + output.WriteLine(text); + output.Flush(); + return Task.CompletedTask; + } + + private static async Task ReadLineWithCancelAsync(TextReader input, CancellationToken token) + { + Task readTask = input.ReadLineAsync(); + Task done = await Task.WhenAny(readTask, Task.Delay(Timeout.Infinite, token)); + + if (done != readTask) return null; + return await readTask; + } +} + diff --git a/thirdTestTask/thirdTestTask/ClientHost.cs b/thirdTestTask/thirdTestTask/ClientHost.cs new file mode 100644 index 0000000..5ff8639 --- /dev/null +++ b/thirdTestTask/thirdTestTask/ClientHost.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace thirdTestTask; + +public static class ClientHost +{ + public static async Task Run(IPAddress ip, int port) + { + using var client = new TcpClient(); + + Console.WriteLine($"[Клиент] подключение к {ip}: {port} "); + await client.ConnectAsync(ip, port); + + Console.WriteLine("[Клиент] подключено. Для выхода - exit"); + await ChatSession.RunAsync(client, Console.In, Console.Out); + + Console.WriteLine("[Клиент] завершение"); + } +} diff --git a/thirdTestTask/thirdTestTask/Program.cs b/thirdTestTask/thirdTestTask/Program.cs new file mode 100644 index 0000000..c91d3a8 --- /dev/null +++ b/thirdTestTask/thirdTestTask/Program.cs @@ -0,0 +1,3 @@ +using thirdTestTask; + +await App.Run(args); \ No newline at end of file diff --git a/thirdTestTask/thirdTestTask/ServerHost.cs b/thirdTestTask/thirdTestTask/ServerHost.cs new file mode 100644 index 0000000..1a4bfb7 --- /dev/null +++ b/thirdTestTask/thirdTestTask/ServerHost.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace thirdTestTask; + +public static class ServerHost +{ + public static async Task RunOnce(int port) + { + var listner = new TcpListener(IPAddress.Any, port); + listner.Start(); + + try + { + Console.WriteLine($"[Сервер] Ожидание подлюкчения на порту {port}..."); + + using TcpClient client = await listner.AcceptTcpClientAsync(); + + Console.WriteLine("[Сервер] Клиент подключился. Для выхода - exit"); + await ChatSession.RunAsync(client, Console.In, Console.Out); + } + + finally + { + listner.Stop(); + Console.WriteLine("[Сервер] Завершение"); + } + } +} diff --git a/thirdTestTask/thirdTestTask/thirdTestTask.csproj b/thirdTestTask/thirdTestTask/thirdTestTask.csproj new file mode 100644 index 0000000..fd4bd08 --- /dev/null +++ b/thirdTestTask/thirdTestTask/thirdTestTask.csproj @@ -0,0 +1,10 @@ + + + + Exe + net9.0 + enable + enable + + +