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