diff --git a/src/Syroot.OnlineWorms.Server/Client.cs b/src/Syroot.OnlineWorms.Server/Client.cs
index 8eef7d7..7164de3 100644
--- a/src/Syroot.OnlineWorms.Server/Client.cs
+++ b/src/Syroot.OnlineWorms.Server/Client.cs
@@ -6,7 +6,7 @@ namespace Syroot.Worms.OnlineWorms.Server
///
/// Represents a connection with an Online Worms client which replies to received packets appropriately.
///
- internal class Client : PacketHandler
+ internal class Client : GameConnection
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
diff --git a/src/Syroot.OnlineWorms.Server/Core/MathFuncs.cs b/src/Syroot.OnlineWorms.Server/Core/MathFuncs.cs
deleted file mode 100644
index 72ac934..0000000
--- a/src/Syroot.OnlineWorms.Server/Core/MathFuncs.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-namespace Syroot.Worms.OnlineWorms.Server.Core
-{
- ///
- /// Represents common mathematical operations.
- ///
- internal static class MathFuncs
- {
- // ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
-
- ///
- /// Returns the nearest power of two bigger than the given .
- ///
- /// The value to get the nearest, bigger power of two for.
- /// The nearest, bigger power of two.
- internal static int NextPowerOfTwo(int value)
- {
- value--;
- value |= value >> 1;
- value |= value >> 2;
- value |= value >> 4;
- value |= value >> 8;
- value |= value >> 16;
- return ++value;
- }
- }
-}
diff --git a/src/Syroot.OnlineWorms.Server/Net/GameConnection.cs b/src/Syroot.OnlineWorms.Server/Net/GameConnection.cs
index 414841a..ef96c4f 100644
--- a/src/Syroot.OnlineWorms.Server/Net/GameConnection.cs
+++ b/src/Syroot.OnlineWorms.Server/Net/GameConnection.cs
@@ -1,128 +1,161 @@
using System;
+using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Net.Sockets;
-using System.Text;
+using System.Reflection;
using Syroot.BinaryData;
namespace Syroot.Worms.OnlineWorms.Server.Net
{
///
- /// Represents a stream of instances send between a game client and server.
+ /// Represents a class capable of dispatching received instances to a corresponding method.
///
- internal class GameConnection
+ internal class GameConnection : IDisposable
{
- // A complete packet consists of the following:
- // - ushort id
- // - ushort dataSize
- // - byte[dataSize] data
-
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _maxDataSize = 2048;
// ---- FIELDS -------------------------------------------------------------------------------------------------
- private readonly Stream _tcpStream;
- private readonly byte[] _receiveBuffer = new byte[_maxDataSize];
- private readonly byte[] _sendDataBuffer = new byte[_maxDataSize];
- private readonly BinaryStream _writeStream;
+ private static readonly Dictionary> _connectionClassesCache
+ = new Dictionary>();
+
+ private readonly Dictionary _handlers;
+ private readonly TcpClient _tcpClient;
+ private readonly byte[] _receiveBuffer;
+ private readonly byte[] _sendDataBuffer;
+ private readonly PacketStream _receiveStream;
+ private readonly PacketStream _sendStream;
+ private bool _disposed;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
///
- /// Initializes a new instance of the class, receiving and sending packets from and
- /// to the given .
+ /// Initializes a new instance of the class, handling the given
+ /// .
///
/// The to communicate with.
internal GameConnection(TcpClient tcpClient)
{
- _tcpStream = tcpClient.GetStream();
- _writeStream = new BinaryStream(new MemoryStream(_sendDataBuffer, 0, _maxDataSize, true),
- encoding: Encoding.GetEncoding(949), stringCoding: StringCoding.Int16CharCount);
+ _tcpClient = tcpClient;
+ Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} connected");
+
+ _handlers = GetHandlers();
+ _receiveBuffer = new byte[_maxDataSize];
+ _receiveBuffer = new byte[_maxDataSize];
+ _receiveStream = new PacketStream(_tcpClient.GetStream());
+ _sendStream = new PacketStream(new MemoryStream(_sendDataBuffer, 0, _maxDataSize, true));
}
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing).
+ Dispose(true);
+ }
+
+ // ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
+
///
- /// Returns a read from the stream. If the packet could not be read,
- /// is returned.
+ /// Starts handling packets incoming from this connection and dispatches them to corresponding methods.
///
- /// The read instance or if the packet could not be read.
- ///
- internal Packet ReceivePacket()
+ internal void Listen()
+ {
+ Packet packet;
+ while ((packet = ReceivePacket()) != null)
+ HandlePacket(packet);
+ }
+
+ // ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ _tcpClient.Dispose();
+ _sendStream.Dispose();
+ }
+
+ _disposed = true;
+ }
+ }
+
+ // ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
+
+ private Dictionary GetHandlers()
+ {
+ Type classType = GetType();
+ if (!_connectionClassesCache.TryGetValue(classType, out Dictionary handlerMethods))
+ {
+ handlerMethods = new Dictionary();
+ // Find all packet handling methods which are methods accepting and returning a specific packet.
+ foreach (MethodInfo method in classType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance))
+ {
+ Type inPacket = method.GetParameters().FirstOrDefault()?.ParameterType;
+ if (typeof(Packet).IsAssignableFrom(inPacket) && typeof(Packet).IsAssignableFrom(method.ReturnType))
+ handlerMethods.Add(inPacket, method);
+ }
+ _connectionClassesCache.Add(classType, handlerMethods);
+ }
+ return handlerMethods;
+ }
+
+ private void HandlePacket(Packet inPacket)
+ {
+ Type packetType = inPacket.GetType();
+ Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} >> {packetType.Name}");
+
+ // Invoke the handler and send back any packet resulting from it.
+ if (_handlers[packetType].Invoke(this, new[] { inPacket }) is Packet outPacket)
+ {
+ Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} << {outPacket.GetType().Name}");
+ SendPacket(outPacket);
+ }
+ }
+
+ private Packet ReceivePacket()
{
// Receive the raw packet data.
ushort id;
ushort dataSize;
try
{
- id = _tcpStream.ReadUInt16();
- dataSize = _tcpStream.ReadUInt16();
- ReadComplete(_receiveBuffer, 0, dataSize);
+ id = _receiveStream.ReadUInt16();
+ dataSize = _receiveStream.ReadUInt16();
+ _receiveStream.ReadAll(_receiveBuffer, 0, dataSize);
}
catch (IOException) { return null; } // The underlying socket closed.
catch (ObjectDisposedException) { return null; } // The underlying stream closed.
// Deserialize and return the packet.
Packet packet = PacketFactory.Create(id);
- using (BinaryStream readStream = new BinaryStream(new MemoryStream(_receiveBuffer, 0, dataSize, false),
- encoding: Encoding.GetEncoding(949), stringCoding: StringCoding.Int16CharCount))
- {
+ using (PacketStream readStream = new PacketStream(new MemoryStream(_receiveBuffer, 0, dataSize, false)))
packet.Deserialize(readStream);
- }
return packet;
}
- ///
- /// Sends the given and returns if the packet was sent
- /// successfully.
- ///
- /// The to send.
- /// when the packet was sent.
- internal bool SendPacket(Packet packet)
+ private bool SendPacket(Packet packet)
{
// Serialize the raw packet data.
- _writeStream.Position = 0;
- packet.Serialize(_writeStream);
- ushort dataSize = (ushort)_writeStream.Position;
+ _sendStream.Position = 0;
+ packet.Serialize(_sendStream);
+ ushort dataSize = (ushort)_sendStream.Position;
// Send the data and return success.
try
{
- _tcpStream.WriteUInt16(PacketFactory.GetID(packet));
- _tcpStream.WriteUInt16(dataSize);
- _tcpStream.Write(_sendDataBuffer, 0, dataSize);
+ _receiveStream.WriteUInt16(PacketFactory.GetID(packet));
+ _receiveStream.WriteUInt16(dataSize);
+ _receiveStream.Write(_sendDataBuffer, 0, dataSize);
return true;
}
catch (IOException) { return false; } // A network error appeared, and communication should end.
catch (ObjectDisposedException) { return false; } // The underlying stream was most apparently closed.
}
-
- // ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
-
- ///
- /// Reads number of bytes to the given , starting at the
- /// specified , and returns the number of bytes read. Since this method ensures that
- /// the requested number of bytes is always read, it always returns , and otherwise
- /// throws an if the required bytes could not be read.
- ///
- /// The byte array to write the read data to.
- /// The offset into at which to start writing data.
- /// The number of bytes to read into .
- /// The cannot store the requested number of
- /// bytes.
- /// The requested number of bytes could not be read.
- private void ReadComplete(byte[] buffer, int offset, int count)
- {
- int totalRead = 0;
- while (totalRead < count)
- {
- // Read returns 0 only when the underlying socket is closed, otherwise it blocks.
- int read = _tcpStream.Read(buffer, offset + totalRead, count - totalRead);
- if (read == 0)
- throw new IOException("The underlying stream has closed, 0 bytes were retrieved.");
- totalRead += read;
- }
- }
}
}
diff --git a/src/Syroot.OnlineWorms.Server/Net/Packets/Packet.cs b/src/Syroot.OnlineWorms.Server/Net/Packet.cs
similarity index 66%
rename from src/Syroot.OnlineWorms.Server/Net/Packets/Packet.cs
rename to src/Syroot.OnlineWorms.Server/Net/Packet.cs
index f9cef3a..ad648d1 100644
--- a/src/Syroot.OnlineWorms.Server/Net/Packets/Packet.cs
+++ b/src/Syroot.OnlineWorms.Server/Net/Packet.cs
@@ -1,6 +1,4 @@
-using Syroot.BinaryData;
-
-namespace Syroot.Worms.OnlineWorms.Server.Net
+namespace Syroot.Worms.OnlineWorms.Server.Net
{
///
/// Represents a packet with an ID specifying its contents. To allow the server to instantiate child packet classes,
@@ -10,8 +8,8 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
{
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
- internal abstract void Deserialize(BinaryStream stream);
+ internal abstract void Deserialize(PacketStream stream);
- internal abstract void Serialize(BinaryStream stream);
+ internal abstract void Serialize(PacketStream stream);
}
}
diff --git a/src/Syroot.OnlineWorms.Server/Net/PacketHandler.cs b/src/Syroot.OnlineWorms.Server/Net/PacketHandler.cs
deleted file mode 100644
index c85c75f..0000000
--- a/src/Syroot.OnlineWorms.Server/Net/PacketHandler.cs
+++ /dev/null
@@ -1,100 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net.Sockets;
-using System.Reflection;
-
-namespace Syroot.Worms.OnlineWorms.Server.Net
-{
- ///
- /// Represents a class capable of dispatching received instances to a corresponding method.
- ///
- internal class PacketHandler : IDisposable
- {
- // ---- FIELDS -------------------------------------------------------------------------------------------------
-
- private static readonly Dictionary> _packetHandlers
- = new Dictionary>();
-
- private readonly TcpClient _tcpClient;
- private readonly Dictionary _handlerMethods;
- private readonly GameConnection _connection;
- private bool _disposed;
-
- // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
-
- internal PacketHandler(TcpClient tcpClient)
- {
- Console.WriteLine($"{tcpClient.Client.RemoteEndPoint} connected");
- _tcpClient = tcpClient;
- _handlerMethods = GetHandlerMethods();
- _connection = new GameConnection(_tcpClient);
- }
-
- // ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
-
- public void Dispose()
- {
- // Do not change this code. Put cleanup code in Dispose(bool disposing).
- Dispose(true);
- }
-
- // ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
-
- ///
- /// Starts handling packets incoming from this client and calls corresponding methods.
- ///
- internal void Accept()
- {
- Packet packet;
- while ((packet = _connection.ReceivePacket()) != null)
- HandlePacket(packet);
- }
-
- // ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------
-
- protected virtual void Dispose(bool disposing)
- {
- if (!_disposed)
- {
- if (disposing)
- _tcpClient.Dispose();
-
- _disposed = true;
- }
- }
-
- // ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
-
- private Dictionary GetHandlerMethods()
- {
- Type classType = GetType();
- if (!_packetHandlers.TryGetValue(classType, out Dictionary handlerMethods))
- {
- handlerMethods = new Dictionary();
- // Find all packet handling methods which are methods accepting and returning a specific packet.
- foreach (MethodInfo method in classType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance))
- {
- Type inPacket = method.GetParameters().FirstOrDefault()?.ParameterType;
- if (typeof(Packet).IsAssignableFrom(inPacket) && typeof(Packet).IsAssignableFrom(method.ReturnType))
- handlerMethods.Add(inPacket, method);
- }
- _packetHandlers.Add(classType, handlerMethods);
- }
- return handlerMethods;
- }
-
- private void HandlePacket(Packet inPacket)
- {
- Type packetType = inPacket.GetType();
- Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} >> {packetType.Name}");
-
- // Invoke the handler and send back any packet resulting from it.
- if (_handlerMethods[packetType].Invoke(this, new[] { inPacket }) is Packet outPacket)
- {
- Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} << {outPacket.GetType().Name}");
- _connection.SendPacket(outPacket);
- }
- }
- }
-}
diff --git a/src/Syroot.OnlineWorms.Server/Net/PacketStream.cs b/src/Syroot.OnlineWorms.Server/Net/PacketStream.cs
new file mode 100644
index 0000000..fd7e0d7
--- /dev/null
+++ b/src/Syroot.OnlineWorms.Server/Net/PacketStream.cs
@@ -0,0 +1,106 @@
+using System.IO;
+using System.Text;
+using Syroot.BinaryData;
+
+namespace Syroot.Worms.OnlineWorms.Server.Net
+{
+ ///
+ /// Represents a stream formatting data for being sent or received from instances.
+ ///
+ internal class PacketStream : Stream
+ {
+ // ---- FIELDS -------------------------------------------------------------------------------------------------
+
+ private static readonly Encoding _win949Encoding;
+
+ private readonly Stream _baseStream;
+ private bool _disposed;
+
+ // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
+
+ static PacketStream()
+ {
+ Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+ _win949Encoding = Encoding.GetEncoding(949);
+ }
+
+ internal PacketStream(Stream baseStream)
+ {
+ _baseStream = baseStream;
+ }
+
+ // ---- PROPERTIES ---------------------------------------------------------------------------------------------
+
+ public override bool CanRead => _baseStream.CanRead;
+ public override bool CanSeek => _baseStream.CanSeek;
+ public override bool CanWrite => _baseStream.CanWrite;
+ public override long Length => _baseStream.Length;
+ public override long Position
+ {
+ get => _baseStream.Position;
+ set => _baseStream.Position = value;
+ }
+
+ // ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
+
+ public override void Flush() => _baseStream.Flush();
+ public override int Read(byte[] buffer, int offset, int count) => _baseStream.Read(buffer, offset, count);
+ public override long Seek(long offset, SeekOrigin origin) => _baseStream.Seek(offset, origin);
+ public override void SetLength(long value) => _baseStream.SetLength(value);
+ public override void Write(byte[] buffer, int offset, int count) => _baseStream.Write(buffer, offset, count);
+
+ // ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_disposed)
+ return;
+
+ if (disposing)
+ _baseStream.Dispose();
+
+ _disposed = true;
+ base.Dispose(disposing);
+ }
+
+ // ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
+
+ ///
+ /// Reads number of bytes to the given , starting at the
+ /// specified , and returns the number of bytes read. Since this method ensures that
+ /// the requested number of bytes is always read, it always returns , and otherwise
+ /// throws an if the required bytes could not be read.
+ ///
+ /// The byte array to write the read data to.
+ /// The offset into at which to start writing data.
+ /// The number of bytes to read into .
+ /// The requested number of bytes could not be read.
+ internal void ReadAll(byte[] buffer, int offset, int count)
+ {
+ int totalRead = 0;
+ while (totalRead < count)
+ {
+ // Read returns 0 only when the underlying socket is closed, otherwise it blocks.
+ int read = _baseStream.Read(buffer, offset + totalRead, count - totalRead);
+ if (read == 0)
+ throw new IOException("The underlying stream has closed, 0 bytes were retrieved.");
+ totalRead += read;
+ }
+ }
+
+ internal string ReadString()
+ {
+ // Strings are word prefixed and 0 termianted.
+ string value = _baseStream.ReadString(StringCoding.Int16CharCount);
+ _baseStream.Seek(1);
+ return value;
+ }
+
+ internal void WriteString(string value)
+ {
+ // Strings are word prefixed and 0 termianted.
+ _baseStream.WriteString(value, StringCoding.Int16CharCount, _win949Encoding);
+ _baseStream.WriteByte(0);
+ }
+ }
+}
diff --git a/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectQueryPacket.cs b/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectQueryPacket.cs
index 71edf94..65bbd14 100644
--- a/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectQueryPacket.cs
+++ b/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectQueryPacket.cs
@@ -1,5 +1,4 @@
using System;
-using Syroot.BinaryData;
namespace Syroot.Worms.OnlineWorms.Server.Net
{
@@ -11,8 +10,8 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
{
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
- internal override void Deserialize(BinaryStream stream) { }
+ internal override void Deserialize(PacketStream stream) { }
- internal override void Serialize(BinaryStream stream) => throw new NotImplementedException();
+ internal override void Serialize(PacketStream stream) => throw new NotImplementedException();
}
}
diff --git a/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectReplyPacket.cs b/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectReplyPacket.cs
index 9224c21..066d6a4 100644
--- a/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectReplyPacket.cs
+++ b/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectReplyPacket.cs
@@ -1,5 +1,4 @@
using System;
-using Syroot.BinaryData;
namespace Syroot.Worms.OnlineWorms.Server.Net
{
@@ -17,9 +16,9 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
- internal override void Deserialize(BinaryStream stream) => throw new NotImplementedException();
+ internal override void Deserialize(PacketStream stream) => throw new NotImplementedException();
- internal override void Serialize(BinaryStream stream)
+ internal override void Serialize(PacketStream stream)
{
stream.WriteString(Unknown);
stream.WriteString(Version);
diff --git a/src/Syroot.OnlineWorms.Server/Net/Packets/LoginReplyPacket.cs b/src/Syroot.OnlineWorms.Server/Net/Packets/LoginReplyPacket.cs
index 02f33a8..84c4a52 100644
--- a/src/Syroot.OnlineWorms.Server/Net/Packets/LoginReplyPacket.cs
+++ b/src/Syroot.OnlineWorms.Server/Net/Packets/LoginReplyPacket.cs
@@ -15,9 +15,9 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
- internal override void Deserialize(BinaryStream stream) => throw new NotImplementedException();
+ internal override void Deserialize(PacketStream stream) => throw new NotImplementedException();
- internal override void Serialize(BinaryStream stream)
+ internal override void Serialize(PacketStream stream)
{
stream.WriteBoolean(LoginResult == LoginResult.Success);
stream.WriteEnum(LoginResult);
diff --git a/src/Syroot.OnlineWorms.Server/Net/Packets/RawQueryPacket.cs b/src/Syroot.OnlineWorms.Server/Net/Packets/RawQueryPacket.cs
index 676e386..f3081bf 100644
--- a/src/Syroot.OnlineWorms.Server/Net/Packets/RawQueryPacket.cs
+++ b/src/Syroot.OnlineWorms.Server/Net/Packets/RawQueryPacket.cs
@@ -1,5 +1,4 @@
#if DEBUG
-using Syroot.BinaryData;
namespace Syroot.Worms.OnlineWorms.Server.Net
{
@@ -23,13 +22,13 @@ namespace Syroot.Worms.OnlineWorms.Server.Net
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
- internal override void Deserialize(BinaryStream stream)
+ internal override void Deserialize(PacketStream stream)
{
Data = new byte[stream.Length];
stream.Read(Data, 0, Data.Length);
}
- internal override void Serialize(BinaryStream stream)
+ internal override void Serialize(PacketStream stream)
{
stream.Write(Data, 0, Data.Length);
}
diff --git a/src/Syroot.OnlineWorms.Server/Program.cs b/src/Syroot.OnlineWorms.Server/Program.cs
index f1e1c73..768ef7f 100644
--- a/src/Syroot.OnlineWorms.Server/Program.cs
+++ b/src/Syroot.OnlineWorms.Server/Program.cs
@@ -1,5 +1,4 @@
using System;
-using System.Text;
namespace Syroot.Worms.OnlineWorms.Server
{
@@ -8,13 +7,6 @@ namespace Syroot.Worms.OnlineWorms.Server
///
internal class Program
{
- // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
-
- static Program()
- {
- Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
- }
-
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private static void Main(string[] args)
diff --git a/src/Syroot.OnlineWorms.Server/Server.cs b/src/Syroot.OnlineWorms.Server/Server.cs
index c97351d..5e70c17 100644
--- a/src/Syroot.OnlineWorms.Server/Server.cs
+++ b/src/Syroot.OnlineWorms.Server/Server.cs
@@ -29,12 +29,13 @@ namespace Syroot.Worms.OnlineWorms.Server
tcpListener.Start();
Console.WriteLine($"Listening on port {port}...");
+ // Continually accept clients and dispatch them to their listening thread.
while (true)
{
Client client = new Client(tcpListener.AcceptTcpClient(), this);
_clients.Add(client);
- Task.Run(client.Accept).ContinueWith(_ =>
+ Task.Run(client.Listen).ContinueWith(_ =>
{
_clients.Remove(client);
client.Dispose();