Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions Test2.Tests/ClientAndServerTests.cs
Original file line number Diff line number Diff line change
@@ -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));
}
}
23 changes: 23 additions & 0 deletions Test2.Tests/Test2.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.NET 7 умер. .NET 9 Preview 2 даже давно доступен. Переходите на .NET 8

<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2" />
<PackageReference Include="NUnit.Analyzers" Version="3.6.1" />
<PackageReference Include="coverlet.collector" Version="3.2.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Test2\Test2.csproj" />
</ItemGroup>
</Project>
1 change: 1 addition & 0 deletions Test2.Tests/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using NUnit.Framework;
86 changes: 86 additions & 0 deletions Test2/Client.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
namespace Test2;

using System.Net;
using System.Net.Sockets;

public class Client
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Надо комментарии

{
public IPAddress Ip { get; private set; }
public int Port { get; private set; }
public bool IsInterrupted { get; private set; } = false;

/// <summary>
/// Creates new instance of server class.
/// </summary>
public Client(IPAddress ip, int port)
{
Ip = ip;
Port = port;
}

/// <summary>
/// Starts client, that writes messages from concole, communicates witn server and stops when get "exit" from server or from comsole.
/// </summary>
/// <returns></returns>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Пустые тэги не нужны

public async Task Start()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

По соглашению, принятому в .NET, все async-методы должны иметь суффикс "Async"

{
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);
Comment on lines +31 to +33
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кто-то из них должен быть объявлен с using, чтобы корректно закрыть поток


async void actionWriteEntered() => await WriteEntered(writer, lockObject);
async void actionReadPrintResponse() => await ReadPrintResponse(reader, lockObject);
Comment on lines +35 to +36
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Нет нужды именованные функции объявлять. Так можно, но можно просто лямбды.


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);
}
Comment on lines +53 to +56
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Зачем, мы ведь его только что ввели. Вообще, Console.WriteLine внутри синхронизируется, lockObject не нужен

}
}
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;
}
}
}
}




Comment on lines +83 to +86
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Много лишних пустых строчек

32 changes: 32 additions & 0 deletions Test2/Program.cs
Original file line number Diff line number Diff line change
@@ -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)");
}
93 changes: 93 additions & 0 deletions Test2/Server.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
namespace Test2;

using System.Net;
using System.Net.Sockets;

/// <summary>
/// Server class
/// </summary>
public class Server
{
private TcpListener listener;
public int Port { get; private set; }
public bool IsInterrupted { get; private set; }

/// <summary>
/// Creates new instance of Server class
/// </summary>
public Server(int port)
{
listener = new TcpListener(IPAddress.Any, port);
Port = port;
IsInterrupted = false;
}

/// <summary>
/// Starts server, that writes messages from concole, communicates with client and stops when get "exit" from client or from comsole.
/// </summary>
/// <returns></returns>
public async Task Start()
{
object lockObject = new();
listener.Start();
var tcpClient = await listener.AcceptTcpClientAsync();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вызывать длительную операцию без способа её отменить может быть плохой идеей, потому что можно задедлочиться. Тут, например, если к нам так и не подключится никакой клиент, AcceptTcpClientAsync никогда не вернёт управление и остановить сервер будет нельзя. Чтобы так не было, у AcceptTcpClientAsync есть перегрузка, принимающая CancellationToken.

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;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут тоже, если IsInterrupted true, то ReadPrintResponse не закончит свою работу, пока клиент нам что-нибудь не пришлёт. Потенциальный дедлок.

}

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;
}
}
}
}



10 changes: 10 additions & 0 deletions Test2/Test2.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
31 changes: 31 additions & 0 deletions Test2/Test2.sln
Original file line number Diff line number Diff line change
@@ -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