From aaa5d21d3895f21443448828000930db5d2f7c8b Mon Sep 17 00:00:00 2001 From: IgnatSergeev Date: Thu, 2 Nov 2023 16:58:53 +0300 Subject: [PATCH] Added --- C#/forSpbu/ConsoleNetChat/AsyncConsole.cs | 31 +++++ C#/forSpbu/ConsoleNetChat/Chatter.cs | 95 +++++++++++++++ C#/forSpbu/ConsoleNetChat/ChatterException.cs | 12 ++ C#/forSpbu/ConsoleNetChat/Client.cs | 32 +++++ .../ConsoleNetChat/ConsoleNetChat.csproj | 10 ++ C#/forSpbu/ConsoleNetChat/Program.cs | 110 ++++++++++++++++++ C#/forSpbu/ConsoleNetChat/Server.cs | 36 ++++++ C#/forSpbu/forSpbu.sln | 20 +++- 8 files changed, 342 insertions(+), 4 deletions(-) create mode 100644 C#/forSpbu/ConsoleNetChat/AsyncConsole.cs create mode 100644 C#/forSpbu/ConsoleNetChat/Chatter.cs create mode 100644 C#/forSpbu/ConsoleNetChat/ChatterException.cs create mode 100644 C#/forSpbu/ConsoleNetChat/Client.cs create mode 100644 C#/forSpbu/ConsoleNetChat/ConsoleNetChat.csproj create mode 100644 C#/forSpbu/ConsoleNetChat/Program.cs create mode 100644 C#/forSpbu/ConsoleNetChat/Server.cs diff --git a/C#/forSpbu/ConsoleNetChat/AsyncConsole.cs b/C#/forSpbu/ConsoleNetChat/AsyncConsole.cs new file mode 100644 index 0000000..510a42a --- /dev/null +++ b/C#/forSpbu/ConsoleNetChat/AsyncConsole.cs @@ -0,0 +1,31 @@ +namespace ConsoleNetChat; + +/// +/// Represents asynchronous console +/// +public static class AsyncConsole +{ + private static readonly Mutex ConsoleMutex = new (); + + /// + /// Async version of Console.WriteLine + /// + /// Message to write + public static void WriteLine(string message) + { + ConsoleMutex.WaitOne(); + Console.WriteLine(message); + ConsoleMutex.ReleaseMutex(); + } + + /// + /// Async version of Console.Write + /// + /// Message to write + public static void Write(string message) + { + ConsoleMutex.WaitOne(); + Console.Write(message); + ConsoleMutex.ReleaseMutex(); + } +} \ No newline at end of file diff --git a/C#/forSpbu/ConsoleNetChat/Chatter.cs b/C#/forSpbu/ConsoleNetChat/Chatter.cs new file mode 100644 index 0000000..acfbcc7 --- /dev/null +++ b/C#/forSpbu/ConsoleNetChat/Chatter.cs @@ -0,0 +1,95 @@ +using System.Net.Sockets; +using System.Net; +using System.Net.NetworkInformation; + +namespace ConsoleNetChat; + +/// +/// Class for chat member +/// +public class Chatter +{ + protected TcpClient Client = new (); + protected StreamWriter? Writer; + + /// + /// Sends message into chat + /// + /// Message to send + /// If connection is close + public async Task SendMessage(string message) + { + if (Writer == null) + { + throw new ChatterException("Not started"); + } + await Writer.WriteLineAsync(message); + } + + private static async Task HandleClient(TcpClient client) + { + using var reader = new StreamReader(client.GetStream()); + + while (IsConnected(client)) + { + try + { + var data = await reader.ReadLineAsync(); + if (string.IsNullOrEmpty(data)) + { + continue; + } + + AsyncConsole.Write($"External: {data}\n"); + } + catch (Exception e) when (e is ArgumentOutOfRangeException or ObjectDisposedException or InvalidOperationException) + { + } + } + + AsyncConsole.WriteLine("Disconnected"); + Environment.Exit(0); + } + + private static bool IsConnected(TcpClient client) + { + return GetState(client) == TcpState.Established; + } + + private static TcpState GetState(TcpClient tcpClient) + { + try + { + var connectionInfo = IPGlobalProperties.GetIPGlobalProperties() + .GetActiveTcpConnections() + .SingleOrDefault(x => x.LocalEndPoint.Equals(tcpClient.Client.LocalEndPoint)); + return connectionInfo?.State ?? TcpState.Unknown; + } + catch (ObjectDisposedException) + { + return TcpState.Closed; + } + } + + /// + /// Finishes the chat + /// + public void Disconnect() + { + Client.Close(); + AsyncConsole.WriteLine("Disconnected"); + } + + /// + /// Listens for external messages + /// + /// If connection is closed + public async Task Listen() + { + if (!IsConnected(Client)) + { + throw new ChatterException("Cant listen: no connection"); + } + await HandleClient(Client); + } +} \ No newline at end of file diff --git a/C#/forSpbu/ConsoleNetChat/ChatterException.cs b/C#/forSpbu/ConsoleNetChat/ChatterException.cs new file mode 100644 index 0000000..9a96a3d --- /dev/null +++ b/C#/forSpbu/ConsoleNetChat/ChatterException.cs @@ -0,0 +1,12 @@ +namespace ConsoleNetChat; + +public class ChatterException : Exception +{ + public ChatterException() + { + } + + public ChatterException(string message) : base(message) + { + } +} \ No newline at end of file diff --git a/C#/forSpbu/ConsoleNetChat/Client.cs b/C#/forSpbu/ConsoleNetChat/Client.cs new file mode 100644 index 0000000..c5356f4 --- /dev/null +++ b/C#/forSpbu/ConsoleNetChat/Client.cs @@ -0,0 +1,32 @@ +using System.Net; +using System.Net.Sockets; + +namespace ConsoleNetChat; + +/// +/// Represents chat client +/// +public class Client : Chatter +{ + /// + /// Connects to the server + /// + /// Server address + /// Server port + /// If error occured(e.g. no server on that address + public async Task Connect(IPAddress addr, int port) + { + try + { + await Client.ConnectAsync(addr, port); + } + catch (Exception e) when (e is ArgumentNullException or ArgumentOutOfRangeException or SocketException or ObjectDisposedException) + { + throw new ChatterException("Connection impossible"); + } + + Writer = new StreamWriter(Client.GetStream()); + Writer.AutoFlush = true; + AsyncConsole.WriteLine("Connected"); + } +} \ No newline at end of file diff --git a/C#/forSpbu/ConsoleNetChat/ConsoleNetChat.csproj b/C#/forSpbu/ConsoleNetChat/ConsoleNetChat.csproj new file mode 100644 index 0000000..b9de063 --- /dev/null +++ b/C#/forSpbu/ConsoleNetChat/ConsoleNetChat.csproj @@ -0,0 +1,10 @@ + + + + Exe + net6.0 + enable + enable + + + diff --git a/C#/forSpbu/ConsoleNetChat/Program.cs b/C#/forSpbu/ConsoleNetChat/Program.cs new file mode 100644 index 0000000..5c5f95e --- /dev/null +++ b/C#/forSpbu/ConsoleNetChat/Program.cs @@ -0,0 +1,110 @@ +using System.Net; +using ConsoleNetChat; + +switch (args.Length) +{ + case < 1 or > 2: + AsyncConsole.WriteLine("Incorrect arguments"); + return; + case 1: + { + if (!int.TryParse(args[0], out var port)) + { + AsyncConsole.WriteLine("Incorrect port"); + return; + } + + var server = new Server(); + try + { + await server.Connect(port); + } + catch (ChatterException e) + { + AsyncConsole.WriteLine(e.Message); + return; + } + + try + { +#pragma warning disable CS4014 // This is started in background thread + server.Listen(); +#pragma warning restore CS4014 // This is started in background thread + } + catch (ChatterException e) + { + AsyncConsole.WriteLine(e.Message); + return; + } + + while (true) + { + var message = Console.ReadLine(); + if (string.IsNullOrEmpty(message)) + { + continue; + } + await server.SendMessage(message); + if (message == "exit") + { + server.Disconnect(); + return; + } + } + + break; + } + default: + { + if (!int.TryParse(args[1], out var port)) + { + AsyncConsole.WriteLine("Incorrect port"); + return; + } + if (!IPAddress.TryParse(args[0], out var addr)) + { + AsyncConsole.WriteLine("Incorrect address"); + return; + } + + var client = new Client(); + try + { + await client.Connect(addr, port); + } + catch (ChatterException e) + { + AsyncConsole.WriteLine(e.Message); + return; + } + + try + { +#pragma warning disable CS4014 // This is started in background thread + client.Listen(); +#pragma warning restore CS4014 // This is started in background thread + } + catch (ChatterException e) + { + AsyncConsole.WriteLine(e.Message); + return; + } + + while (true) + { + var message = Console.ReadLine(); + if (string.IsNullOrEmpty(message)) + { + continue; + } + await client.SendMessage(message); + if (message == "exit") + { + client.Disconnect(); + return; + } + } + + break; + } +} \ No newline at end of file diff --git a/C#/forSpbu/ConsoleNetChat/Server.cs b/C#/forSpbu/ConsoleNetChat/Server.cs new file mode 100644 index 0000000..9df0bad --- /dev/null +++ b/C#/forSpbu/ConsoleNetChat/Server.cs @@ -0,0 +1,36 @@ +using System.Diagnostics; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; + +namespace ConsoleNetChat; + +/// +/// Represents chat server +/// +public class Server : Chatter +{ + private TcpListener? _listener; + + /// + /// Waits for chat client + /// + /// Port to open connection onto + /// If error occured(e.g. port was occupied) + public async Task Connect(int port) + { + try + { + _listener = new TcpListener(IPAddress.Any, port); + _listener.Start(); + Client = await _listener.AcceptTcpClientAsync(); + } + catch (Exception e) when (e is ArgumentNullException or ArgumentOutOfRangeException or SocketException or InvalidOperationException) + { + throw new ChatterException("Connection interrupted"); + } + Writer = new StreamWriter(Client.GetStream()); + Writer.AutoFlush = true; + AsyncConsole.WriteLine("Connected"); + } +} \ No newline at end of file diff --git a/C#/forSpbu/forSpbu.sln b/C#/forSpbu/forSpbu.sln index 527f400..9db52ed 100644 --- a/C#/forSpbu/forSpbu.sln +++ b/C#/forSpbu/forSpbu.sln @@ -10,6 +10,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "03.03", "03.03", "{882A9B9C EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "10.03", "10.03", "{EA6FC7D9-BDFB-49CD-AC00-FC5DDC5274B0}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2023", "2023", "{6EA2679E-EBFE-46CA-ACD5-0B9F756FF69C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "02.11", "02.11", "{1A79381A-96DE-408C-A93E-225A594FDB00}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleNetChat", "ConsoleNetChat\ConsoleNetChat.csproj", "{3689A376-E83D-4C8B-804B-876E9CE7C881}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,12 +27,18 @@ Global {E007586F-9760-4744-BB25-EDEFD6BA860C}.Release|Any CPU.ActiveCfg = Release|Any CPU {E007586F-9760-4744-BB25-EDEFD6BA860C}.Release|Any CPU.Build.0 = Release|Any CPU {A4F6ADD5-85FD-4F67-8B29-549DDDF6F82E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A4F6ADD5-85FD-4F67-8B29-549DDDF6F82E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A4F6ADD5-85FD-4F67-8B29-549DDDF6F82E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A4F6ADD5-85FD-4F67-8B29-549DDDF6F82E}.Release|Any CPU.Build.0 = Release|Any CPU + {A4F6ADD5-85FD-4F67-8B29-549DDDF6F82E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4F6ADD5-85FD-4F67-8B29-549DDDF6F82E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4F6ADD5-85FD-4F67-8B29-549DDDF6F82E}.Release|Any CPU.Build.0 = Release|Any CPU + {3689A376-E83D-4C8B-804B-876E9CE7C881}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3689A376-E83D-4C8B-804B-876E9CE7C881}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3689A376-E83D-4C8B-804B-876E9CE7C881}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3689A376-E83D-4C8B-804B-876E9CE7C881}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution - {E007586F-9760-4744-BB25-EDEFD6BA860C} = {D3FCB669-E93F-4F0B-B9C5-6592CE93AC7F} + {E007586F-9760-4744-BB25-EDEFD6BA860C} = {D3FCB669-E93F-4F0B-B9C5-6592CE93AC7F} {A4F6ADD5-85FD-4F67-8B29-549DDDF6F82E} = {D3FCB669-E93F-4F0B-B9C5-6592CE93AC7F} + {1A79381A-96DE-408C-A93E-225A594FDB00} = {6EA2679E-EBFE-46CA-ACD5-0B9F756FF69C} + {3689A376-E83D-4C8B-804B-876E9CE7C881} = {1A79381A-96DE-408C-A93E-225A594FDB00} EndGlobalSection EndGlobal