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
31 changes: 31 additions & 0 deletions C#/forSpbu/ConsoleNetChat/AsyncConsole.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace ConsoleNetChat;

/// <summary>
/// Represents asynchronous console
/// </summary>
public static class AsyncConsole
{
private static readonly Mutex ConsoleMutex = new ();

/// <summary>
/// Async version of Console.WriteLine
/// </summary>
/// <param name="message">Message to write</param>
public static void WriteLine(string message)
{
ConsoleMutex.WaitOne();
Console.WriteLine(message);
ConsoleMutex.ReleaseMutex();
Comment on lines +16 to +18

Choose a reason for hiding this comment

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

Консоль сама буквально это и делает :) AsyncConsole не нужен

}

/// <summary>
/// Async version of Console.Write
/// </summary>
/// <param name="message">Message to write</param>
public static void Write(string message)
{
ConsoleMutex.WaitOne();
Console.Write(message);
ConsoleMutex.ReleaseMutex();
}
}
95 changes: 95 additions & 0 deletions C#/forSpbu/ConsoleNetChat/Chatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System.Net.Sockets;
using System.Net;
using System.Net.NetworkInformation;

namespace ConsoleNetChat;

/// <summary>
/// Class for chat member
/// </summary>
public class Chatter
{
protected TcpClient Client = new ();
protected StreamWriter? Writer;
Comment on lines +12 to +13

Choose a reason for hiding this comment

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

  • protected-полями лучше вовсе не пользоваться, потомкам нельзя доверять.
  • Раз это не-public-поля, они именуются со строчной.
  • TcpClient реализует IDisposable, так что и весь класс должен реализовывать IDisposable, и в своём методе Dispose() вызывать Dispose у TcpClient


/// <summary>
/// Sends message into chat
/// </summary>
/// <param name="message">Message to send</param>
/// <exception cref="ChatterException">If connection is close</exception>
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);

Choose a reason for hiding this comment

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

Не используйте Environment.Exit, это делает код непереиспользуемым, если надо выйти из чата, не убивая весь процесс, в котором он находится

}

private static bool IsConnected(TcpClient client)
{
return GetState(client) == TcpState.Established;
}
Comment on lines +54 to +57

Choose a reason for hiding this comment

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

Suggested change
private static bool IsConnected(TcpClient client)
{
return GetState(client) == TcpState.Established;
}
private static bool IsConnected(TcpClient client)
=> 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;
}
}

/// <summary>
/// Finishes the chat
/// </summary>
public void Disconnect()
{
Client.Close();
AsyncConsole.WriteLine("Disconnected");
}

/// <summary>
/// Listens for external messages
/// </summary>
/// <exception cref="ChatterException">If connection is closed</exception>
public async Task Listen()
{
if (!IsConnected(Client))
{
throw new ChatterException("Cant listen: no connection");
}
await HandleClient(Client);
}
}
12 changes: 12 additions & 0 deletions C#/forSpbu/ConsoleNetChat/ChatterException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace ConsoleNetChat;

public class ChatterException : Exception

Choose a reason for hiding this comment

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

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

{
public ChatterException()
{
}

public ChatterException(string message) : base(message)
{
}
}
32 changes: 32 additions & 0 deletions C#/forSpbu/ConsoleNetChat/Client.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Net;
using System.Net.Sockets;

namespace ConsoleNetChat;

/// <summary>
/// Represents chat client
/// </summary>
public class Client : Chatter
{
/// <summary>
/// Connects to the server
/// </summary>
/// <param name="addr">Server address</param>
/// <param name="port">Server port</param>
/// <exception cref="ChatterException">If error occured(e.g. no server on that address</exception>
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");
}
}
10 changes: 10 additions & 0 deletions C#/forSpbu/ConsoleNetChat/ConsoleNetChat.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>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
110 changes: 110 additions & 0 deletions C#/forSpbu/ConsoleNetChat/Program.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}

Choose a reason for hiding this comment

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

Архитектурно ужасно, что этот код в Main-е :)


break;

Choose a reason for hiding this comment

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

Компилятор выдаёт предупреждение, что выше while (true) с return, так что управление сюда никогда не попадёт

}
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;
}
}
36 changes: 36 additions & 0 deletions C#/forSpbu/ConsoleNetChat/Server.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Diagnostics;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;

namespace ConsoleNetChat;

/// <summary>
/// Represents chat server
/// </summary>
public class Server : Chatter
{
private TcpListener? _listener;

/// <summary>
/// Waits for chat client
/// </summary>
/// <param name="port">Port to open connection onto</param>
/// <exception cref="ChatterException">If error occured(e.g. port was occupied)</exception>
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");
}
}
20 changes: 16 additions & 4 deletions C#/forSpbu/forSpbu.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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