/// </summary>
/// <param name="listener">The listener to test.</param>
/// <param name="connection">The connection to test.</param>
- internal static void RunServerToClientTest(ConnectionListener listener, Connection connection, int dataSize, SendOption sendOption)
+ internal static void RunServerToClientTest(NetworkConnectionListener listener, Connection connection, int dataSize, SendOption sendOption)
{
//Setup meta stuff
byte[] data = BuildData(dataSize);
/// </summary>
/// <param name="listener">The listener to test.</param>
/// <param name="connection">The connection to test.</param>
- internal static void RunClientToServerTest(ConnectionListener listener, Connection connection, int dataSize, SendOption sendOption)
+ internal static void RunClientToServerTest(NetworkConnectionListener listener, Connection connection, int dataSize, SendOption sendOption)
{
//Setup meta stuff
byte[] data = BuildData(dataSize);
/// </summary>
/// <param name="listener">The listener to test.</param>
/// <param name="connection">The connection to test.</param>
- internal static void RunServerDisconnectTest(ConnectionListener listener, Connection connection)
+ internal static void RunServerDisconnectTest(NetworkConnectionListener listener, Connection connection)
{
ManualResetEvent mutex = new ManualResetEvent(false);
/// </summary>
/// <param name="listener">The listener to test.</param>
/// <param name="connection">The connection to test.</param>
- internal static void RunClientDisconnectTest(ConnectionListener listener, Connection connection)
+ internal static void RunClientDisconnectTest(NetworkConnectionListener listener, Connection connection)
{
ManualResetEvent mutex = new ManualResetEvent(false);
ManualResetEvent mutex2 = new ManualResetEvent(false);
/// </para>
/// </remarks>
/// <threadsafety static="true" instance="true"/>
- public abstract class ConnectionListener : IDisposable
+ public abstract class List : IDisposable
{
/// <summary>
/// Invoked when a new client connects.
/// <include file="DocInclude/common.xml" path="docs/item[@name='Recyclable']/*" />
/// </remarks>
/// <threadsafety static="true" instance="true"/>
- public class DataReceivedEventArgs : EventArgs, IRecyclable
+ public class DataReceivedEventArgs : EventArgs
{
- /// <summary>
- /// Object pool for this event.
- /// </summary>
- static readonly ObjectPool<DataReceivedEventArgs> objectPool = new ObjectPool<DataReceivedEventArgs>(() => new DataReceivedEventArgs());
-
/// <summary>
/// Returns an instance of this object from the pool.
/// </summary>
/// <returns>A new or recycled DataEventArgs object.</returns>
internal static DataReceivedEventArgs GetObject()
{
- return objectPool.GetObject();
+ return new DataReceivedEventArgs();
}
/// <summary>
this.SendOption = sendOption;
this.ReliableId = reliableId;
}
-
- /// <inheritdoc />
- public void Recycle()
- {
- objectPool.PutObject(this);
- }
}
}
/// <include file="DocInclude/common.xml" path="docs/item[@name='Recyclable']/*" />
/// </remarks>
/// <threadsafety static="true" instance="true"/>
- public class DisconnectedEventArgs : EventArgs, IRecyclable
+ public class DisconnectedEventArgs : EventArgs
{
- /// <summary>
- /// Object pool for this event.
- /// </summary>
- static readonly ObjectPool<DisconnectedEventArgs> objectPool = new ObjectPool<DisconnectedEventArgs>(() => new DisconnectedEventArgs());
-
/// <summary>
/// Returns an instance of this object from the pool.
/// </summary>
/// <returns>A new or recycled DisconnectedEventArgs object.</returns>
internal static DisconnectedEventArgs GetObject()
{
- return objectPool.GetObject();
+ return new DisconnectedEventArgs();
}
/// <summary>
{
this.Exception = e;
}
-
- /// <inheritdoc />
- public void Recycle()
- {
- objectPool.PutObject(this);
- }
}
}
using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
+using System.Threading;
namespace Hazel
{
- ///
public class MessageReader : IRecyclable
{
+ public static int Readers = 0;
+ public int ReaderId = 0;
+
public static readonly ObjectPool<MessageReader> ReaderPool = new ObjectPool<MessageReader>(() => new MessageReader());
public byte[] Buffer;
set
{
this._position = value;
- this.readHead = this._position + Offset;
+ this.readHead = value + Offset;
}
}
-
+
private int _position;
-
private int readHead;
+ public MessageReader()
+ {
+ this.ReaderId = Interlocked.Increment(ref Readers);
+ }
+ public override string ToString()
+ {
+ return $"{ReaderId}: BL:{Buffer.Length} O:{Offset} L:{Length}";
+ }
+
public static MessageReader GetSized(int minSize)
{
var output = ReaderPool.GetObject();
output.Buffer = new byte[minSize];
}
- output.Offset = 0;
- output.Position = 0;
- output.Length = minSize;
output.Tag = byte.MaxValue;
return output;
}
public static MessageReader GetRaw(byte[] bytes, int offset, int length)
{
var output = ReaderPool.GetObject();
+
output.Buffer = bytes;
output.Offset = offset;
output.Position = 0;
public static MessageReader Get(byte[] buffer)
{
var output = ReaderPool.GetObject();
+
output.Buffer = buffer;
output.Offset = 0;
output.Position = 0;
return output;
}
+ public static MessageReader Get(MessageReader source)
+ {
+ var output = GetSized(source.Buffer.Length);
+ System.Buffer.BlockCopy(source.Buffer, 0, output.Buffer, 0, source.Buffer.Length);
+
+ output.Offset = source.Offset;
+
+ output._position = source._position;
+ output.readHead = source.readHead;
+
+ output.Length = source.Length;
+ output.Tag = source.Tag;
+
+ return output;
+ }
+
public static MessageReader Get(byte[] buffer, int offset)
{
+ // Ensure there is at least a header
+ if (offset + 3 > buffer.Length) return null;
+
var output = ReaderPool.GetObject();
+
output.Buffer = buffer;
output.Offset = offset;
output.Position = 0;
- if (output.readHead + 3 > output.Buffer.Length) return null;
output.Length = output.ReadUInt16();
output.Tag = output.ReadByte();
///
public MessageReader ReadMessage()
{
- var output = MessageReader.Get(this.Buffer, this.readHead);
- if (output == null) return null;
+ // Ensure there is at least a header
+ if (this.readHead + 3 > this.Buffer.Length) return null;
+
+ var output = new MessageReader();
+
+ output.Buffer = this.Buffer;
+ output.Offset = this.readHead;
+ output.Position = 0;
+
+ output.Length = output.ReadUInt16();
+ output.Tag = output.ReadByte();
+
+ output.Offset += 3;
+ output.Position = 0;
this.Position += output.Length + 3;
return output;
///
public void Recycle()
{
- this.Position = this.Length = 0;
ReaderPool.PutObject(this);
}
namespace Hazel
{
/// <summary>
- /// Abstract base class for a <see cref="ConnectionListener"/> for network based connections.
+ /// Abstract base class for a <see cref="List"/> for network based connections.
/// </summary>
/// <threadsafety static="true" instance="true"/>
- public abstract class NetworkConnectionListener : ConnectionListener
+ public abstract class NetworkConnectionListener : List
{
/// <summary>
/// The local end point the listener is listening for new clients on.
namespace Hazel
{
/// <summary>
- /// Event arguments for the <see cref="ConnectionListener.NewConnection"/> event.
+ /// Event arguments for the <see cref="List.NewConnection"/> event.
/// </summary>
/// <remarks>
/// <para>
/// This contains the new connection for the client that connection and is passed to subscribers of the
- /// <see cref="ConnectionListener.NewConnection"/> event.
+ /// <see cref="List.NewConnection"/> event.
/// </para>
/// <include file="DocInclude/common.xml" path="docs/item[@name='Recyclable']/*" />
/// </remarks>
/// <threadsafety static="true" instance="true"/>
- public class NewConnectionEventArgs : EventArgs, IRecyclable
+ public class NewConnectionEventArgs : EventArgs
{
- /// <summary>
- /// Object pool for this event.
- /// </summary>
- static readonly ObjectPool<NewConnectionEventArgs> objectPool = new ObjectPool<NewConnectionEventArgs>(() => new NewConnectionEventArgs());
-
/// <summary>
/// Returns an instance of this object from the pool.
/// </summary>
/// <returns>A new or recycled NewConnectionEventArgs object.</returns>
internal static NewConnectionEventArgs GetObject()
{
- return objectPool.GetObject();
+ return new NewConnectionEventArgs();
}
/// <summary>
this.HandshakeData = msg;
this.Connection = connection;
}
-
- /// <inheritdoc />
- public void Recycle()
- {
- objectPool.PutObject(this);
- }
}
}
/// </summary>
ConcurrentBag<T> pool = new ConcurrentBag<T>();
+ private ConcurrentDictionary<T, bool> inuse = new ConcurrentDictionary<T, bool>();
+
/// <summary>
/// The generator for creating new objects.
/// </summary>
internal T GetObject()
{
T item;
- if (pool.TryTake(out item))
- return item;
+ if (!pool.TryTake(out item))
+ {
+ Interlocked.Increment(ref numberCreated);
+ item = objectFactory.Invoke();
+ }
+
+ if (!inuse.TryAdd(item, true))
+ {
+ throw new Exception("Duplicate pull");
+ }
- Interlocked.Increment(ref numberCreated);
- return objectFactory.Invoke();
+ return item;
}
/// <summary>
/// <param name="item">The item to return.</param>
internal void PutObject(T item)
{
- pool.Add(item);
+ if (inuse.TryRemove(item, out bool b))
+ {
+ pool.Add(item);
+ }
+ else
+ {
+ throw new Exception("Duplicate add");
+ }
}
}
}
}
MessageReader msg = MessageReader.GetRaw(bytes, 0, bytesReceived);
- HandleReceive(msg);
+ HandleReceive(msg, bytesReceived);
}
/// <inheritdoc />
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
/// <summary>
/// Holds the last ID allocated.
/// </summary>
- volatile ushort lastIDAllocated;
+ volatile int lastIDAllocated = ushort.MaxValue + 1;
/// <summary>
/// The packets of data that have been transmitted reliably and not acknowledged.
/// </summary>
- Dictionary<ushort, Packet> reliableDataPacketsSent = new Dictionary<ushort, Packet>();
+ ConcurrentDictionary<ushort, Packet> reliableDataPacketsSent = new ConcurrentDictionary<ushort, Packet>();
/// <summary>
/// The last packets that were received.
return objectPool.GetObject();
}
+ public ushort Id;
public byte[] Data;
public Timer Timer;
public volatile int LastTimeout;
public Action AckCallback;
- public volatile bool Acknowledged;
public volatile int Retransmissions;
public Stopwatch Stopwatch = new Stopwatch();
}
- internal void Set(byte[] data, Action<Packet> resendAction, int timeout, Action ackCallback)
+ internal void Set(ushort id, byte[] data, Action<Packet> resendAction, int timeout, Action ackCallback)
{
- Data = data;
+ this.Id = id;
+ this.Data = data;
- Timer = new Timer(
+ this.Timer = new Timer(
(object obj) => resendAction(this),
null,
timeout,
LastTimeout = timeout;
AckCallback = ackCallback;
- Acknowledged = false;
Retransmissions = 0;
Stopwatch.Reset();
/// </summary>
public void Recycle()
{
- lock (Timer)
- Timer.Dispose();
+ lock (this)
+ {
+ if (this.Timer != null)
+ {
+ this.Id = (ushort)(this.Id - 1);
+ this.Timer.Dispose();
+ this.Timer = null;
+ }
+ }
objectPool.PutObject(this);
}
{
if (disposing)
{
- lock (Timer)
- Timer.Dispose();
+ lock (this)
+ {
+ if (this.Timer != null)
+ {
+ this.Id = (ushort)(this.Id - 1);
+ this.Timer.Dispose();
+ this.Timer = null;
+ }
+ }
}
}
}
//Find an ID not used yet.
ushort id;
+ //Create packet object
+ Packet packet = Packet.GetObject();
+
do
- id = ++lastIDAllocated;
- while (reliableDataPacketsSent.ContainsKey(id));
+ {
+ id = (ushort)Interlocked.Increment(ref lastIDAllocated);
+ }
+ while (!reliableDataPacketsSent.TryAdd(id, packet));
//Write ID
buffer[offset] = (byte)((id >> 8) & 0xFF);
buffer[offset + 1] = (byte)id;
- //Create packet object
- Packet packet = Packet.GetObject();
packet.Set(
+ id,
buffer,
(Packet p) =>
{
- lock (p.Timer)
+ Packet self;
+ if (p.Stopwatch.ElapsedMilliseconds > this.disconnectTimeout)
{
- if (!p.Acknowledged)
+ if (reliableDataPacketsSent.TryRemove(p.Id, out self))
{
- if (p.Stopwatch.ElapsedMilliseconds > this.disconnectTimeout)
- {
- HandleDisconnect(new HazelException($"Reliable packet {id} was not ack'd after {p.Retransmissions} resends"));
+ HandleDisconnect(new HazelException($"Reliable packet {self.Id} was not ack'd after {self.Retransmissions} resends"));
- //Set acknowledged so we dont change the timer again
- p.Acknowledged = true;
+ self.Recycle();
+ }
- p.Recycle();
- return;
- }
+ return;
+ }
- // Backoff retry frequency to avoid congestion
- p.LastTimeout = (int)Math.Min(p.LastTimeout * 1.5f, this.disconnectTimeout / 2f);
- p.Timer.Change(p.LastTimeout, Timeout.Infinite);
- }
+ lock (p)
+ {
+ // Callback for a previous packet
+ if (p.Id != id) return;
+
+ // Backoff retry frequency to avoid congestion
+ p.LastTimeout = (int)Math.Min(p.LastTimeout * 1.5f, this.disconnectTimeout / 2f);
+ p.Timer.Change(p.LastTimeout, Timeout.Infinite);
}
try
resendTimeout > 0 ? resendTimeout : (int)Math.Max(40, Math.Min(AveragePingMs * 4, 750)),
ackCallback
);
-
- //Remember packet
- reliableDataPacketsSent.Add(id, packet);
}
}
/// Handles a reliable message being received and invokes the data event.
/// </summary>
/// <param name="message">The buffer received.</param>
- void ReliableMessageReceive(MessageReader message)
+ void ReliableMessageReceive(MessageReader message, int bytesReceived)
{
ushort id;
if (ProcessReliableReceive(message.Buffer, 1, out id))
- InvokeDataReceived(SendOption.Reliable, message, 3, id);
+ {
+ InvokeDataReceived(SendOption.Reliable, message, 3, bytesReceived, id);
+ }
+ else
+ {
+ message.Recycle();
+ }
Statistics.LogReliableReceive(message.Length - 3, message.Length);
}
/// <returns>Whether the packet was a new packet or not.</returns>
bool ProcessReliableReceive(byte[] bytes, int offset, out ushort id)
{
+ byte b1 = bytes[offset];
+ byte b2 = bytes[offset + 1];
+
//Get the ID form the packet
- id = (ushort)((bytes[offset] << 8) + bytes[offset + 1]);
+ id = (ushort)((b1 << 8) + b2);
//Send an acknowledgement
- SendAck(bytes[offset], bytes[offset + 1]);
+ SendAck(b1, b2);
/*
* It gets a little complicated here (note the fact I'm actually using a multiline comment for once...)
//Get ID
ushort id = (ushort)((bytes[1] << 8) + bytes[2]);
- lock (reliableDataPacketsSent)
+ //Dispose of timer and remove from dictionary
+ Packet packet;
+ if (reliableDataPacketsSent.TryRemove(id, out packet))
{
- //Dispose of timer and remove from dictionary
- Packet packet;
- if (reliableDataPacketsSent.TryGetValue(id, out packet))
- {
- packet.Acknowledged = true;
-
- if (packet.AckCallback != null)
- packet.AckCallback.Invoke();
-
- //Add to average ping
- packet.Stopwatch.Stop();
- lock (PingLock)
- {
- this.AveragePingMs = Math.Max(10, this.AveragePingMs * .7f + (float)packet.Stopwatch.Elapsed.TotalMilliseconds * .3f);
- }
+ float rt = packet.Stopwatch.ElapsedMilliseconds;
- packet.Recycle();
+ packet.AckCallback?.Invoke();
+ packet.Recycle();
- reliableDataPacketsSent.Remove(id);
+ lock (PingLock)
+ {
+ this.AveragePingMs = Math.Max(10, this.AveragePingMs * .7f + rt * .3f);
}
}
-
+
Statistics.LogReliableReceive(0, bytes.Length);
}
void DisposeReliablePackets()
{
- lock (this.reliableDataPacketsSent)
+ var keys = this.reliableDataPacketsSent.Keys.ToArray();
+ foreach (var k in keys)
{
- foreach (var kvp in this.reliableDataPacketsSent)
+ Packet pkt;
+ if (this.reliableDataPacketsSent.TryRemove(k, out pkt))
{
- Packet pkt = kvp.Value;
- pkt.Acknowledged = true;
pkt.Recycle();
}
-
- this.reliableDataPacketsSent.Clear();
}
}
}
/// Handles the receiving of data.
/// </summary>
/// <param name="message">The buffer containing the bytes received.</param>
- protected internal void HandleReceive(MessageReader message)
+ protected internal void HandleReceive(MessageReader message, int bytesReceived)
{
InvokeDataReceivedRaw(message.Buffer);
-
+
+ ushort id;
switch (message.Buffer[0])
{
//Handle reliable receives
case (byte)SendOption.Reliable:
- ReliableMessageReceive(message);
+ ReliableMessageReceive(message, bytesReceived);
break;
//Handle acknowledgments
case (byte)UdpSendOption.Acknowledgement:
AcknowledgementMessageReceive(message.Buffer);
+ message.Recycle();
break;
//We need to acknowledge hello and ping messages but dont want to invoke any events!
case (byte)UdpSendOption.Ping:
+ ProcessReliableReceive(message.Buffer, 1, out id);
+ Statistics.LogHelloReceive(message.Length);
+ message.Recycle();
+ break;
case (byte)UdpSendOption.Hello:
- ushort id;
ProcessReliableReceive(message.Buffer, 1, out id);
Statistics.LogHelloReceive(message.Length);
break;
case (byte)UdpSendOption.Disconnect:
- HandleDisconnect(new HazelException("The remote sent a disconnect request"));
+ HandleDisconnect(new HazelException("The remote sent a disconnect request"));
+ message.Recycle();
break;
//Treat everything else as unreliable
default:
- InvokeDataReceived(SendOption.None, message, 1, 0);
+ InvokeDataReceived(SendOption.None, message, 1, bytesReceived, 0);
Statistics.LogUnreliableReceive(message.Length - 1, message.Length);
break;
}
/// <param name="sendOption">The send option the message was received with.</param>
/// <param name="buffer">The buffer received.</param>
/// <param name="dataOffset">The offset of data in the buffer.</param>
- void InvokeDataReceived(SendOption sendOption, MessageReader buffer, int dataOffset, ushort reliableId)
+ void InvokeDataReceived(SendOption sendOption, MessageReader buffer, int dataOffset, int bytesReceived, ushort reliableId)
{
buffer.Offset = dataOffset;
- buffer.Length = buffer.Length - dataOffset;
+ buffer.Length = bytesReceived - dataOffset;
buffer.Position = 0;
InvokeDataReceived(buffer, sendOption, reliableId);
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
/// <inheritdoc />
public class UdpConnectionListener : NetworkConnectionListener
{
+ public const int BufferSize = ushort.MaxValue / 4;
+
/// <summary>
/// The socket listening for connections.
/// </summary>
/// <summary>
/// The connections we currently hold
/// </summary>
- Dictionary<EndPoint, UdpServerConnection> connections = new Dictionary<EndPoint, UdpServerConnection>();
+ ConcurrentDictionary<EndPoint, UdpServerConnection> allConnections = new ConcurrentDictionary<EndPoint, UdpServerConnection>();
+ public int ConnectionCount { get { return this.allConnections.Count; } }
/// <summary>
/// Creates a new UdpConnectionListener for the given <see cref="IPAddress"/>, port and <see cref="IPMode"/>.
/// </summary>
void StartListeningForData()
{
EndPoint remoteEP = EndPoint;
-
+
+ MessageReader message = null;
try
{
- var message = MessageReader.GetSized(ushort.MaxValue);
+ message = MessageReader.GetSized(BufferSize);
+
listener.BeginReceiveFrom(message.Buffer, 0, message.Buffer.Length, SocketFlags.None, ref remoteEP, ReadCallback, message);
- Interlocked.Increment(ref ActiveThreads);
+ Interlocked.Increment(ref ActiveListeners);
}
catch (ObjectDisposedException)
{
{
//Client no longer reachable, pretend it didn't happen
//TODO possibly able to disconnect client, see other TODO
+ message?.Recycle();
StartListeningForData();
return;
}
/// </summary>
/// <param name="result">The asyncronous operation's result.</param>
- private int ActiveThreads;
+ public int ActiveListeners;
+ public int ActiveCallbacks;
void ReadCallback(IAsyncResult result)
{
+ var message = (MessageReader)result.AsyncState;
+ Interlocked.Increment(ref ActiveCallbacks);
+
int bytesReceived;
EndPoint remoteEndPoint = new IPEndPoint(IPMode == IPMode.IPv4 ? IPAddress.Any : IPAddress.IPv6Any, 0);
//End the receive operation
try
{
- Interlocked.Decrement(ref ActiveThreads);
+ Interlocked.Decrement(ref ActiveListeners);
bytesReceived = listener.EndReceiveFrom(result, ref remoteEndPoint);
+ message.Offset = 0;
+ message.Length = bytesReceived;
+ }
+ catch (NullReferenceException)
+ {
+ return;
}
catch (ObjectDisposedException)
{
}
catch (SocketException)
{
- //Client no longer reachable, pretend it didn't happen
- //TODO should this not inform the connection this client is lost???
+ // Client no longer reachable, pretend it didn't happen
+ // TODO should this not inform the connection this client is lost???
- //This thread suggests the IP is not passed out from WinSoc so maybe not possible
- //http://stackoverflow.com/questions/2576926/python-socket-error-on-udp-data-receive-10054
+ // This thread suggests the IP is not passed out from WinSoc so maybe not possible
+ // http://stackoverflow.com/questions/2576926/python-socket-error-on-udp-data-receive-10054
+ message.Recycle();
StartListeningForData();
return;
}
- //Exit if no bytes read, we've closed.
+ // Exit if no bytes read, we've closed.
if (bytesReceived == 0)
+ {
+ message.Recycle();
return;
+ }
//Begin receiving again
StartListeningForData();
- var message = (MessageReader)result.AsyncState;
- message.Length = bytesReceived;
+ bool aware = true;
+ bool isHello = message.Buffer[0] == (byte)UdpSendOption.Hello;
+
+ //If we're aware of this connection use the one already
+ //If this is a new client then connect with them!
+ UdpServerConnection connection = this.allConnections.GetOrAdd(
+ remoteEndPoint,
+ key => { aware = false; return new UdpServerConnection(this, key, IPMode); });
- bool aware;
- UdpServerConnection connection;
- lock (connections)
+ if (!aware)
{
- //If we're aware of this connection use the one already
- //If this is a new client then connect with them!
- if (!(aware = connections.TryGetValue(remoteEndPoint, out connection)))
+ //Check for malformed connection attempts
+ if (!isHello)
{
- //Check for malformed connection attempts
- if (message.Buffer[0] != (byte)UdpSendOption.Hello)
- return;
-
- connection = new UdpServerConnection(this, remoteEndPoint, IPMode);
- connections.Add(remoteEndPoint, connection);
+ Interlocked.Decrement(ref ActiveCallbacks);
+ message.Recycle();
+ return;
}
}
//Inform the connection of the buffer (new connections need to send an ack back to client)
- connection.HandleReceive(message);
+ connection.HandleReceive(message, bytesReceived);
//If it's a new connection invoke the NewConnection event.
if (!aware)
message.Position = 0;
InvokeNewConnection(message, connection);
}
+ else if (isHello)
+ {
+ message.Recycle();
+ }
+
+ Interlocked.Decrement(ref ActiveCallbacks);
}
/// <summary>
endPoint,
delegate (IAsyncResult result)
{
- listener.EndSendTo(result);
+ try
+ {
+ listener.EndSendTo(result);
+ }
+ catch { }
},
null
);
/// <param name="endPoint">The endpoint of the virtual connection.</param>
internal void RemoveConnectionTo(EndPoint endPoint)
{
- lock (connections)
- connections.Remove(endPoint);
+ this.allConnections.TryRemove(endPoint, out var conn);
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
- lock (connections)
+ var keys = this.allConnections.Keys.ToArray();
+ foreach (var k in keys)
{
- var connects = this.connections.ToArray();
- foreach (var kvp in connects)
+ UdpServerConnection conn;
+ if (this.allConnections.TryGetValue(k, out conn))
{
- if (kvp.Value.State == ConnectionState.Connected)
- {
- try
- {
- kvp.Value.SendDisconnect();
- }
- catch { }
- }
-
- kvp.Value.Dispose();
+ conn.Dispose();
}
-
- connections.Clear();
}
if (listener != null)