diff --git a/Test2.Tests/ClientAndServerTests.cs b/Test2.Tests/ClientAndServerTests.cs new file mode 100644 index 0000000..c9955e0 --- /dev/null +++ b/Test2.Tests/ClientAndServerTests.cs @@ -0,0 +1,54 @@ +namespace Test2.Tests; + +using System.Net; +using System.Net.Sockets; + +public class Tests +{ + private int port = 12346; + private Server? server = null; + private Client? client = null; + + [Test] + public async Task ServerInterruptionTest() + { + server = new(port); + async void actionStartServer() => await server.Start(); + var actionStartFakeClient = (Action)(async () => + { + Thread.Sleep(1000); + var client = new TcpClient(); + await client.ConnectAsync("localhost", port); + var stream = client.GetStream(); + var writer = new StreamWriter(stream) { AutoFlush = true }; + var reader = new StreamReader(stream); + + await writer.WriteLineAsync("exit"); + }); + + await Task.WhenAll(Task.Run(actionStartServer), Task.Run(actionStartFakeClient)); + + Assert.That(server.IsInterrupted, Is.EqualTo(true)); + } + + [Test] + public async Task ClientInterruptionTest() + { + client = new(Dns.GetHostAddresses("localhost")[1], port); + async void actionStartClient() => await client.Start(); + var actionStartFakeServer = (Action)(async () => + { + var listener = new TcpListener(IPAddress.Any, port); + listener.Start(); + var tcpClient = await listener.AcceptTcpClientAsync(); + var stream = tcpClient.GetStream(); + var writer = new StreamWriter(stream) { AutoFlush = true }; + + await writer.WriteLineAsync("exit"); + listener.Stop(); + }); + + await Task.WhenAll(Task.Run(actionStartFakeServer), Task.Run(actionStartClient)); + Assert.That(client.IsInterrupted, Is.EqualTo(true)); + } +} diff --git a/Test2.Tests/Test2.Tests.csproj b/Test2.Tests/Test2.Tests.csproj new file mode 100644 index 0000000..b3b7b55 --- /dev/null +++ b/Test2.Tests/Test2.Tests.csproj @@ -0,0 +1,23 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + + + + + + + + diff --git a/Test2.Tests/Usings.cs b/Test2.Tests/Usings.cs new file mode 100644 index 0000000..9a28bd8 --- /dev/null +++ b/Test2.Tests/Usings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; diff --git a/Test2/Client.cs b/Test2/Client.cs new file mode 100644 index 0000000..ffa1182 --- /dev/null +++ b/Test2/Client.cs @@ -0,0 +1,86 @@ +namespace Test2; + +using System.Net; +using System.Net.Sockets; + +public class Client +{ + public IPAddress Ip { get; private set; } + public int Port { get; private set; } + public bool IsInterrupted { get; private set; } = false; + + /// + /// Creates new instance of server class. + /// + public Client(IPAddress ip, int port) + { + Ip = ip; + Port = port; + } + + /// + /// Starts client, that writes messages from concole, communicates witn server and stops when get "exit" from server or from comsole. + /// + /// + public async Task Start() + { + var lockObject = new object(); + var client = new TcpClient(); + await client.ConnectAsync(Ip, Port); + Console.WriteLine("Connected"); + var stream = client.GetStream(); + var writer = new StreamWriter(stream) { AutoFlush = true }; + var reader = new StreamReader(stream); + + async void actionWriteEntered() => await WriteEntered(writer, lockObject); + async void actionReadPrintResponse() => await ReadPrintResponse(reader, lockObject); + + await Task.WhenAny(Task.Run(actionWriteEntered), Task.Run(actionReadPrintResponse)); + } + + private async Task WriteEntered(StreamWriter writer, object lockObject) + { + while (!IsInterrupted) + { + string? message = Console.ReadLine(); + if (message != null) + { + if (message == "exit") + { + IsInterrupted = true; + } + await writer.WriteLineAsync(message); + lock (lockObject) + { + Console.WriteLine(message); + } + } + } + Console.WriteLine("Connection interrupted"); + } + + private async Task ReadPrintResponse(StreamReader reader, object lockObject) + { + while (!IsInterrupted) + { + var response = await reader.ReadLineAsync(); + lock (lockObject) + { + Console.WriteLine(response); + } + if (response == "exit") + { + IsInterrupted = true; + lock (lockObject) + { + Console.WriteLine("Connection interrupted"); + } + return; + } + } + } +} + + + + diff --git a/Test2/Program.cs b/Test2/Program.cs new file mode 100644 index 0000000..db69684 --- /dev/null +++ b/Test2/Program.cs @@ -0,0 +1,32 @@ +using Test2; + +using System.Net; + +if (args.Length == 0 || args.Length > 2) +{ + Console.WriteLine("Expected port (number from 1025 to 65535) if you want to run the application as a server."); + Console.WriteLine("Expected IP and port (number from 1025 to 65535) if you want to run the application as a client."); + return; +} + +if (args.Length == 1) +{ + bool isNumber = int.TryParse(args[0], out int port); + if (isNumber && port > 1024 && port < 65536) + { + var server = new Server(port); + await server.Start(); + } + throw new InvalidDataException("Expected port (number from 1025 to 65535)"); +} +else +{ + bool isNumber = int.TryParse(args[1], out int port); + bool isIP = IPAddress.TryParse(args[0], out IPAddress? ip); + if (isNumber && port > 1024 && port < 65536 && isIP && ip != null) + { + var client = new Client(ip, port); + await client.Start(); + } + throw new InvalidDataException("Expected IP and port (number from 1025 to 65535)"); +} \ No newline at end of file diff --git a/Test2/Server.cs b/Test2/Server.cs new file mode 100644 index 0000000..788c4a7 --- /dev/null +++ b/Test2/Server.cs @@ -0,0 +1,93 @@ +namespace Test2; + +using System.Net; +using System.Net.Sockets; + +/// +/// Server class +/// +public class Server +{ + private TcpListener listener; + public int Port { get; private set; } + public bool IsInterrupted { get; private set; } + + /// + /// Creates new instance of Server class + /// + public Server(int port) + { + listener = new TcpListener(IPAddress.Any, port); + Port = port; + IsInterrupted = false; + } + + /// + /// Starts server, that writes messages from concole, communicates with client and stops when get "exit" from client or from comsole. + /// + /// + public async Task Start() + { + object lockObject = new(); + listener.Start(); + var tcpClient = await listener.AcceptTcpClientAsync(); + Console.WriteLine("Connected"); + var stream = tcpClient.GetStream(); + var reader = new StreamReader(stream); + var writer = new StreamWriter(stream) { AutoFlush = true }; + + async void actionWriteEntered() => await WriteEntered(writer, lockObject); + async void actionReadPrintResponse() => await ReadPrintResponse(reader, lockObject); + + await Task.WhenAny(Task.Run(actionWriteEntered), Task.Run(actionReadPrintResponse)); + listener.Stop(); + } + + private async Task WriteEntered(StreamWriter writer, object lockObject) + { + while (!IsInterrupted) + { + string? message = Console.ReadLine(); + + if (message != null) + { + if (message == "exit") + { + IsInterrupted = true; + } + + await writer.WriteLineAsync(message); + + lock (lockObject) + { + Console.WriteLine(message); + } + } + } + Console.WriteLine("Connection interrupted"); + } + + private async Task ReadPrintResponse(StreamReader reader, object lockObject) + { + while (!IsInterrupted) + { + var response = await reader.ReadLineAsync(); + lock(lockObject) + { + Console.WriteLine(response); + } + if (response == "exit") + { + IsInterrupted = true; + lock (lockObject) + { + Console.WriteLine("Connection interrupted"); + } + return; + } + } + } +} + + + diff --git a/Test2/Test2.csproj b/Test2/Test2.csproj new file mode 100644 index 0000000..d439800 --- /dev/null +++ b/Test2/Test2.csproj @@ -0,0 +1,10 @@ + + + + Exe + net7.0 + enable + enable + + + diff --git a/Test2/Test2.sln b/Test2/Test2.sln new file mode 100644 index 0000000..a22c19f --- /dev/null +++ b/Test2/Test2.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 25.0.1706.10 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test2", "Test2.csproj", "{A9821BAB-9ABD-4EB7-AD0D-EFDEAA6F5766}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test2.Tests", "..\Test2.Tests\Test2.Tests.csproj", "{6FCF5F0F-F709-43C2-808A-127718A579AF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A9821BAB-9ABD-4EB7-AD0D-EFDEAA6F5766}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A9821BAB-9ABD-4EB7-AD0D-EFDEAA6F5766}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A9821BAB-9ABD-4EB7-AD0D-EFDEAA6F5766}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A9821BAB-9ABD-4EB7-AD0D-EFDEAA6F5766}.Release|Any CPU.Build.0 = Release|Any CPU + {6FCF5F0F-F709-43C2-808A-127718A579AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6FCF5F0F-F709-43C2-808A-127718A579AF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6FCF5F0F-F709-43C2-808A-127718A579AF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6FCF5F0F-F709-43C2-808A-127718A579AF}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {48782106-A8DE-406E-B748-8A2FC02BBF9A} + EndGlobalSection +EndGlobal