using System.Collections.Concurrent; using System.Net.Sockets; internal class Connections : IDisposable { private record struct User(TcpClient Tcp, Thread Thread); private readonly ConcurrentDictionary _aliasUsages = []; private readonly ConcurrentDictionary _users = []; public void Accept(TcpListener tcp) { try { var client = tcp.AcceptTcpClient(); new Thread(() => Listen(client)).Start(); } catch (SocketException) { return; } } private void Broadcast(TcpClient tcp, string alias, string action) { bool NotMe(User aliasUser) => aliasUser.Tcp != tcp; var message = $"{alias} {action}"; Console.WriteLine(message); foreach (var user in _users.Values.Where(NotMe)) { var writer = new BinaryWriter(user.Tcp.GetStream()); writer.Write(message); writer.Flush(); } } public void Dispose() { foreach (var user in _users.Values) { user.Tcp.Close(); user.Thread.Join(); user.Tcp.Dispose(); } } private void Listen(TcpClient tcp) { var reader = new BinaryReader(tcp.GetStream()); var requestedAlias = reader.ReadString(); var usages = _aliasUsages.GetOrAdd(requestedAlias, 0); var alias = (usages == 0) ? requestedAlias : $"{requestedAlias} ({usages})"; _aliasUsages[requestedAlias] += 1; if (!_users.TryAdd(alias, new User(tcp, Thread.CurrentThread))) { throw new InvalidOperationException("User with alias already exists"); } Broadcast(tcp, alias, "has connected."); while (true) { const int checkDelayMilliseconds = 100; try { var message = reader.ReadString(); Broadcast(tcp, alias, $": {message}"); } catch (EndOfStreamException) { break; } Thread.Sleep(checkDelayMilliseconds); } if (!_users.Remove(alias, out var _)) { throw new InvalidOperationException("User with alias does not exist"); } Broadcast(tcp, alias, "has disconnected."); } }