]> git.deb.at Git - rhonda/impostor.hazel.git/commitdiff
Got everything pretty stable now
authorForest <chocozilla@gmail.com>
Tue, 18 Dec 2018 09:56:45 +0000 (01:56 -0800)
committerForest <chocozilla@gmail.com>
Tue, 18 Dec 2018 09:56:45 +0000 (01:56 -0800)
12 files changed:
Hazel.UnitTests/TestHelper.cs
Hazel/ConnectionListener.cs
Hazel/DataReceivedEventArgs.cs
Hazel/DisconnectedEventArgs.cs
Hazel/MessageReader.cs
Hazel/NetworkConnectionListener.cs
Hazel/NewConnectionEventArgs.cs
Hazel/ObjectPool.cs
Hazel/Udp/UdpClientConnection.cs
Hazel/Udp/UdpConnection.Reliable.cs
Hazel/Udp/UdpConnection.cs
Hazel/Udp/UdpConnectionListener.cs

index 4fb4374ac592c3252dd4589e18805de2b9bbc90d..6cb83a81b92f82eaae82de98ebb14f4914a51331 100644 (file)
@@ -16,7 +16,7 @@ namespace Hazel.UnitTests
         /// </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);
@@ -66,7 +66,7 @@ namespace Hazel.UnitTests
         /// </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);
@@ -116,7 +116,7 @@ namespace Hazel.UnitTests
         /// </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);
 
@@ -142,7 +142,7 @@ namespace Hazel.UnitTests
         /// </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);
index 51fe187e6e728c5300aeac241cbbc7d34e731a7a..377e49b6583ca69ec7666463494e8286f9b8077b 100644 (file)
@@ -24,7 +24,7 @@ namespace Hazel
     ///     </para>
     /// </remarks>
     /// <threadsafety static="true" instance="true"/>
-    public abstract class ConnectionListener : IDisposable
+    public abstract class List : IDisposable
     {
         /// <summary>
         ///     Invoked when a new client connects.
index 69446e40193939b855655abf79f25c32b3910347..1d6ea513d58bbf2d3540871f0abdf5ee3ab980a0 100644 (file)
@@ -16,20 +16,15 @@ namespace Hazel
     ///     <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>
@@ -63,11 +58,5 @@ namespace Hazel
             this.SendOption = sendOption;
             this.ReliableId = reliableId;
         }
-
-        /// <inheritdoc />
-        public void Recycle()
-        {
-            objectPool.PutObject(this);
-        }
     }
 }
index 36ec2e5a604192e29578e746b96187cc8b1c5b36..4e950ade5067c0da0cce876f005e491e7060cc2a 100644 (file)
@@ -16,20 +16,15 @@ namespace Hazel
     ///     <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>
@@ -59,11 +54,5 @@ namespace Hazel
         {
             this.Exception = e;
         }
-
-        /// <inheritdoc />
-        public void Recycle()
-        {
-            objectPool.PutObject(this);
-        }
     }
 }
index 07677e27158cb79aa21b20fe5398793a943606af..6f7bd5975eb2596221a77d9b3b322506fb8d6bf4 100644 (file)
@@ -1,12 +1,18 @@
 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;
@@ -21,14 +27,22 @@ namespace Hazel
             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();
@@ -37,9 +51,6 @@ namespace Hazel
                 output.Buffer = new byte[minSize];
             }
 
-            output.Offset = 0;
-            output.Position = 0;
-            output.Length = minSize;
             output.Tag = byte.MaxValue;
             return output;
         }
@@ -47,6 +58,7 @@ namespace Hazel
         public static MessageReader GetRaw(byte[] bytes, int offset, int length)
         {
             var output = ReaderPool.GetObject();
+
             output.Buffer = bytes;
             output.Offset = offset;
             output.Position = 0;
@@ -58,6 +70,7 @@ namespace Hazel
         public static MessageReader Get(byte[] buffer)
         {
             var output = ReaderPool.GetObject();
+
             output.Buffer = buffer;
             output.Offset = 0;
             output.Position = 0;
@@ -67,14 +80,33 @@ namespace Hazel
             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();
 
@@ -87,8 +119,20 @@ namespace Hazel
         ///
         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;
@@ -97,7 +141,6 @@ namespace Hazel
         ///
         public void Recycle()
         {
-            this.Position = this.Length = 0;
             ReaderPool.PutObject(this);
         }
 
index 1e5e237aaa00e17b90abb9a6208bc47897205c1e..9e9bb72426edec26f4c6593120899977bccc9d0a 100644 (file)
@@ -8,10 +8,10 @@ using System.Text;
 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.
index b757284f2deeace2c332625d2ae1b191eabf27f7..5df102cf32cfdaa5a4766ba4b5caef3e9120f60b 100644 (file)
@@ -6,30 +6,25 @@ using System.Text;
 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>
@@ -60,11 +55,5 @@ namespace Hazel
             this.HandshakeData = msg;
             this.Connection = connection;
         }
-
-        /// <inheritdoc />
-        public void Recycle()
-        {
-            objectPool.PutObject(this);
-        }
     }
 }
index fdc1058328b233feb7f31742a48e6cb694b1a586..b97907cd935a410a1845cd482c5e35747a88b11b 100644 (file)
@@ -23,6 +23,8 @@ namespace Hazel
         /// </summary>
         ConcurrentBag<T> pool = new ConcurrentBag<T>();
 
+        private ConcurrentDictionary<T, bool> inuse = new ConcurrentDictionary<T, bool>();
+
         /// <summary>
         ///     The generator for creating new objects.
         /// </summary>
@@ -44,11 +46,18 @@ namespace Hazel
         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>
@@ -57,7 +66,14 @@ namespace Hazel
         /// <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");
+            }
         }
     }
 }
index 0616299453954d54b95713e22a3d0524087b56ae..e987643521ccaef06a4c27f861910a63863a77a0 100644 (file)
@@ -286,7 +286,7 @@ namespace Hazel.Udp
             }
 
             MessageReader msg = MessageReader.GetRaw(bytes, 0, bytesReceived);
-            HandleReceive(msg);
+            HandleReceive(msg, bytesReceived);
         }
 
         /// <inheritdoc />
index 74e282485662129d11e1a926f9c85292daaf501f..8145ce151674d19ea91d2dea255fd26d72b4d26b 100644 (file)
@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Linq;
@@ -30,12 +31,12 @@ namespace Hazel.Udp
         /// <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.
@@ -94,11 +95,11 @@ namespace Hazel.Udp
                 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();
             
@@ -107,11 +108,12 @@ namespace Hazel.Udp
 
             }
             
-            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,
@@ -120,7 +122,6 @@ namespace Hazel.Udp
 
                 LastTimeout = timeout;
                 AckCallback = ackCallback;
-                Acknowledged = false;
                 Retransmissions = 0;
 
                 Stopwatch.Reset();
@@ -132,8 +133,15 @@ namespace Hazel.Udp
             /// </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);
             }
@@ -151,8 +159,15 @@ namespace Hazel.Udp
             {
                 if (disposing)
                 {
-                    lock (Timer)
-                        Timer.Dispose();
+                    lock (this)
+                    {
+                        if (this.Timer != null)
+                        {
+                            this.Id = (ushort)(this.Id - 1);
+                            this.Timer.Dispose();
+                            this.Timer = null;
+                        }
+                    }
                 }
             }
         }
@@ -171,39 +186,45 @@ namespace Hazel.Udp
                 //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
@@ -222,9 +243,6 @@ namespace Hazel.Udp
                     resendTimeout > 0 ? resendTimeout : (int)Math.Max(40, Math.Min(AveragePingMs * 4, 750)),
                     ackCallback
                 );
-
-                //Remember packet
-                reliableDataPacketsSent.Add(id, packet);
             }
         }
 
@@ -287,11 +305,17 @@ namespace Hazel.Udp
         ///     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);
         }
@@ -304,11 +328,14 @@ namespace Hazel.Udp
         /// <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...)
@@ -384,30 +411,21 @@ namespace Hazel.Udp
             //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);
         }
 
@@ -436,16 +454,14 @@ namespace Hazel.Udp
 
         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();
             }
         }
     }
index 2e82f842863ca5addf2459bbb397ddf8b7f47e36..0e9823146bf75f93e7a7cdb6447a6bbad5464137 100644 (file)
@@ -144,37 +144,43 @@ namespace Hazel.Udp
         ///     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;
             }
@@ -219,10 +225,10 @@ namespace Hazel.Udp
         /// <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);
index acb47e668158f4400ae0fbaab9c6a425009a881a..86a32f9b60ce25b98e3aeace128d73d7a31b923d 100644 (file)
@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Linq;
 using System.Net;
@@ -14,6 +15,8 @@ namespace Hazel.Udp
     /// <inheritdoc />
     public class UdpConnectionListener : NetworkConnectionListener
     {
+        public const int BufferSize = ushort.MaxValue / 4;
+
         /// <summary>
         ///     The socket listening for connections.
         /// </summary>
@@ -22,8 +25,9 @@ namespace Hazel.Udp
         /// <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>
@@ -84,12 +88,14 @@ namespace Hazel.Udp
         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)
             {
@@ -99,6 +105,7 @@ namespace Hazel.Udp
             {
                 //Client no longer reachable, pretend it didn't happen
                 //TODO possibly able to disconnect client, see other TODO
+                message?.Recycle();
                 StartListeningForData();
                 return;
             }
@@ -109,17 +116,27 @@ namespace Hazel.Udp
         /// </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)
             {
@@ -128,44 +145,48 @@ namespace Hazel.Udp
             }
             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)
@@ -176,6 +197,12 @@ namespace Hazel.Udp
                 message.Position = 0;
                 InvokeNewConnection(message, connection);
             }
+            else if (isHello)
+            {
+                message.Recycle();
+            }
+
+            Interlocked.Decrement(ref ActiveCallbacks);
         }
 
         /// <summary>
@@ -197,7 +224,11 @@ namespace Hazel.Udp
                     endPoint,
                     delegate (IAsyncResult result)
                     {
-                        listener.EndSendTo(result);
+                        try
+                        {
+                            listener.EndSendTo(result);
+                        }
+                        catch { }
                     },
                     null
                 );
@@ -247,31 +278,20 @@ namespace Hazel.Udp
         /// <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)