]> git.deb.at Git - rhonda/impostor.hazel.git/commitdiff
Fixes and adds
authorJamJar00 <jamster.30@btinternet.com>
Sun, 17 Apr 2016 21:38:58 +0000 (22:38 +0100)
committerJamJar00 <jamster.30@btinternet.com>
Sun, 17 Apr 2016 21:38:58 +0000 (22:38 +0100)
Hazel.UnitTests/TcpConnectionTests.cs
Hazel.UnitTests/TestHelper.cs
Hazel.UnitTests/UdpConnectionTests.cs
Hazel/UdpConnection.Reliable.cs [new file with mode: 0644]
Hazel/UdpConnectionListener.cs
Hazel/UdpServerConnection.cs

index d0f50b5a18f9993a3815ad9010d669d4e09c64bc..cde4049b870c22b7d853df4bbb913f621c46c1f7 100644 (file)
@@ -43,5 +43,18 @@ namespace Hazel.UnitTests
                 TestHelper.RunServerToClientTest(listener, connection, 4, 0, 0, SendOption.OrderedFragmentedReliable);
             }
         }
+
+        /// <summary>
+        ///     Tests sending and receiving on the TcpConnection.
+        /// </summary>
+        [TestMethod]
+        public void TcpClientToServerTest()
+        {
+            using (TcpConnectionListener listener = new TcpConnectionListener(IPAddress.Any, 4296))
+            using (TcpConnection connection = new TcpConnection())
+            {
+                TestHelper.RunClientToServerTest(listener, connection, 4, 0, 0, SendOption.OrderedFragmentedReliable);
+            }
+        }
     }
 }
index d5b190b6411677213492f917cb74a435d467680d..1c4214febbff720bc8d1ee2773071119e18db5c6 100644 (file)
@@ -16,12 +16,11 @@ namespace Hazel.UnitTests
         /// </summary>
         /// <param name="listener">The listener to test.</param>
         /// <param name="connection">The connection to test.</param>
-        //TODO both directions?
         internal static void RunServerToClientTest(ConnectionListener listener, Connection connection, int headerSize, int handshakeSize, int totalHandshakeSize, SendOption sendOption)
         {
             //Setup meta stuff 
             byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
-            AutoResetEvent mutex = new AutoResetEvent(false);
+            ManualResetEvent mutex = new ManualResetEvent(false);
 
             //Setup listener
             listener.NewConnection += delegate(object sender, NewConnectionEventArgs args)
@@ -60,5 +59,54 @@ namespace Hazel.UnitTests
             Assert.AreEqual(totalHandshakeSize, connection.Statistics.TotalBytesSent);
             Assert.AreEqual(data.Length + headerSize, connection.Statistics.TotalBytesReceived);
         }
+
+        /// <summary>
+        ///     Runs a general test on the given listener and connection.
+        /// </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 headerSize, int handshakeSize, int totalHandshakeSize, SendOption sendOption)
+        {
+            //Setup meta stuff 
+            byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+            ManualResetEvent mutex = new ManualResetEvent(false);
+
+            //Setup listener
+            listener.NewConnection += delegate(object sender, NewConnectionEventArgs args)
+            {
+                args.Connection.DataReceived += delegate(object innerSender, DataEventArgs innerArgs)
+                {
+                    Trace.WriteLine("Data was received correctly.");
+
+                    for (int i = 0; i < data.Length; i++)
+                    {
+                        Assert.AreEqual(data[i], innerArgs.Bytes[i]);
+                    }
+
+                    Assert.AreEqual(sendOption, innerArgs.SendOption);
+
+                    Assert.AreEqual(0, args.Connection.Statistics.DataBytesSent);
+                    Assert.AreEqual(data.Length, args.Connection.Statistics.DataBytesReceived);
+                    Assert.AreEqual(0, args.Connection.Statistics.TotalBytesSent);
+                    Assert.AreEqual(data.Length + headerSize, args.Connection.Statistics.TotalBytesReceived);
+
+                    mutex.Set();
+                };
+            };
+
+            listener.Start();
+
+            //Connect
+            connection.Connect(new NetworkEndPoint(IPAddress.Loopback, 4296));
+            connection.WriteBytes(data, sendOption);
+
+            //Wait until data is received
+            mutex.WaitOne();
+
+            Assert.AreEqual(data.Length + handshakeSize, connection.Statistics.DataBytesSent);
+            Assert.AreEqual(0, connection.Statistics.DataBytesReceived);
+            Assert.AreEqual(totalHandshakeSize + data.Length + headerSize, connection.Statistics.TotalBytesSent);
+            Assert.AreEqual(sendOption == SendOption.Reliable ? 3 : 0, connection.Statistics.TotalBytesReceived);
+        }
     }
 }
index b2dbcd5830c2fee9bb98bd6174a789408d70ddf4..0134cdc0a6ab0bddf00cc792a3030323ccc17a5c 100644 (file)
@@ -56,5 +56,31 @@ namespace Hazel.UnitTests
                 TestHelper.RunServerToClientTest(listener, connection, 3, 1, 2, SendOption.Reliable);
             }
         }
+
+        /// <summary>
+        ///     Tests server to client unreliable communication on the UdpConnection.
+        /// </summary>
+        [TestMethod]
+        public void UdpUnreliableClientToServerTest()
+        {
+            using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296))
+            using (UdpConnection connection = new UdpClientConnection())
+            {
+                TestHelper.RunClientToServerTest(listener, connection, 1, 1, 2, SendOption.None);
+            }
+        }
+
+        /// <summary>
+        ///     Tests server to client reliable communication on the UdpConnection.
+        /// </summary>
+        [TestMethod]
+        public void UdpReliableClientToServerTest()
+        {
+            using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296))
+            using (UdpConnection connection = new UdpClientConnection())
+            {
+                TestHelper.RunClientToServerTest(listener, connection, 3, 1, 2, SendOption.Reliable);
+            }
+        }
     }
 }
diff --git a/Hazel/UdpConnection.Reliable.cs b/Hazel/UdpConnection.Reliable.cs
new file mode 100644 (file)
index 0000000..36baf3c
--- /dev/null
@@ -0,0 +1,183 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Hazel
+{
+    partial class UdpConnection
+    {//TODO recycle dataevents and things?
+        /// <summary>
+        ///     The starting timeout, in miliseconds, at which data will be resent.
+        /// </summary>
+        /// <remarks>
+        ///     On each resend this is doubled for that packet.
+        /// </remarks>
+        public int ResendTimeout { get { return resendTimeout; } set { resendTimeout = value; } }
+        private int resendTimeout = 200;        //TODO this based of average ping?
+
+        /// <summary>
+        ///     Holds the last ID allocated.
+        /// </summary>
+        volatile ushort lastIDAllocated;
+
+        /// <summary>
+        ///     The number of items to remember we have received before overwriting.
+        /// </summary>
+        private readonly int receiveCapacity = 4096;
+
+        /// <summary>
+        ///     The packets of data that have been transmitted reliably and not acknowledged.
+        /// </summary>
+        Dictionary<ushort, Packet> reliableDataPacketsSent = new Dictionary<ushort, Packet>();
+
+        /// <summary>
+        ///     The last packets that were received.
+        /// </summary>
+        HashSet<ushort> reliableDataPacketsMissing = new HashSet<ushort>();
+
+        /// <summary>
+        ///     The packet id that was received last.
+        /// </summary>
+        volatile ushort reliableReceiveLast = 0;
+
+        /// <summary>
+        ///     Has the connection received anything yet
+        /// </summary>
+        volatile bool hasReceivedSomething = false;
+
+        /// <summary>
+        ///     Class to hold packet data
+        /// </summary>
+        class Packet
+        {
+            public byte[] Data;
+            public Timer Timer;
+            public int LastTimeout;
+
+            public Packet(byte[] data, Action<Packet> resendAction, int timeout)
+            {
+                Data = data;
+                
+                Timer = new Timer(
+                    (object obj) => resendAction(this),
+                    null, 
+                    timeout,
+                    timeout
+                );
+
+                LastTimeout = timeout;
+            }
+        }
+
+        /// <summary>
+        ///     Writes the bytes neccessary for a reliable send and stores the send.
+        /// </summary>
+        /// <param name="bytes">The byte array to write to.</param>
+        void WriteReliableSendHeader(byte[] bytes)
+        {
+            lock (reliableDataPacketsSent)
+            {
+                //Find an ID not used yet.
+                ushort id;
+
+                do
+                    id = ++lastIDAllocated;
+                while (reliableDataPacketsSent.ContainsKey(id));
+
+                //Write ID
+                bytes[1] = (byte)((id >> 8) & 0xFF);
+                bytes[2] = (byte)id;
+
+                //Create packet object
+                Packet packet = new Packet(
+                    bytes,
+                    (Packet p) =>
+                    {
+                        WriteBytesToConnection(p.Data);
+
+                        //Double packet timeout
+                        p.Timer.Change(0, p.LastTimeout *= 2);
+                    },
+                    resendTimeout
+                );
+
+                //Remember packet
+                reliableDataPacketsSent.Add(id, packet);
+            }
+        }
+
+        /// <summary>
+        ///     Handles receives from reliable packets.
+        /// </summary>
+        /// <param name="bytes">The buffer containing the data.</param>
+        /// <returns>Whether the bytes were valid or not.</returns>
+        bool HandleReliableReceive(byte[] bytes)
+        {
+            //Get the ID form the packet
+            ushort id = (ushort)((bytes[1] << 8) + bytes[2]);
+
+            //Always reply with acknowledgement in order to stop the sender repeatedly sending it
+            WriteBytesToConnection(     //TODO group acks together
+                new byte[]
+                {
+                    (byte)SendOptionInternal.Acknowledgement,
+                    bytes[1],
+                    bytes[2]
+                }
+            );
+
+            //Handle reliableness!
+            lock (reliableDataPacketsMissing)
+            {
+                //If the ID <= reliableReceiveLast it might be something we're missing
+                //HasReceivedSomething handles the edge case of reliableReceiveLast = 0 & ID = 0
+                //TODO Looping of IDs
+                if (id <= reliableReceiveLast && hasReceivedSomething)
+                {
+                    //See if we're missing it, else this packet is a duplicate
+                    if (reliableDataPacketsMissing.Contains(id))
+                        reliableDataPacketsMissing.Remove(id);
+                    else
+                        return false;
+                }
+                
+                //If ID > reliableReceiveLast then it's something new
+                else
+                {
+                    //Mark items between the most recent receive and the id received as missing
+                    for (ushort i = (ushort)(reliableReceiveLast + 1); i < id; i++)
+                        reliableDataPacketsMissing.Add(i);
+
+                    //Update the most recently received
+                    reliableReceiveLast = id;
+                    hasReceivedSomething = true;
+                }
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        ///     Handles acknowledgement packets to us.
+        /// </summary>
+        /// <param name="bytes">The buffer containing the data.</param>
+        void HandleAcknowledgement(byte[] bytes)
+        {
+            //Get ID
+            ushort id = (ushort)((bytes[1] << 8) + bytes[2]);
+
+            lock (reliableDataPacketsSent)
+            {
+                //Dispose of timer and remove from dictionary
+                if (reliableDataPacketsSent.ContainsKey(id))
+                {
+                    reliableDataPacketsSent[id].Timer.Dispose();
+                    reliableDataPacketsSent.Remove(id);
+                }
+            }
+        }
+    }
+}
index a0151f24c5d45c1f444b78f095576c67eb04cb93..f820e761c592bca672e63b1700ee1981c96ca59f 100644 (file)
@@ -127,26 +127,29 @@ namespace Hazel
             //Begin receiving again
             StartListeningForData();
 
-            //If we're aware of this connection pass the data to the neccesary UdpConnection
-            bool exists;
+            bool aware;
+            UdpServerConnection connection;
             lock (connections)
-                exists = connections.ContainsKey(remoteEndPoint);
-            
-            if (exists)
             {
-                lock (connections)
-                    connections[remoteEndPoint].InvokeDataReceived(buffer);
+                aware = connections.ContainsKey(remoteEndPoint);
+
+                //If we're aware of this connection use the one already
+                if (aware)
+                    connection = connections[remoteEndPoint];
+                
+                //If this is a new client then connect with them!
+                else
+                {
+                    connection = new UdpServerConnection(this, remoteEndPoint);
+                    connections.Add(remoteEndPoint, connection);
+                }
             }
-            //If this is a new client then connect with them!
-            else
-            {
-                UdpServerConnection newConnection = new UdpServerConnection(this, remoteEndPoint);
-                lock (connections)
-                    connections.Add(remoteEndPoint, newConnection);
 
-                //And tell everyone about it!
-                FireNewConnectionEvent(new NewConnectionEventArgs(newConnection));
-            }
+            //And fire the corresponding event
+            if (aware)
+                connection.InvokeDataReceived(buffer);
+            else
+                FireNewConnectionEvent(new NewConnectionEventArgs(connection));
         }
 
         /// <summary>
index 40dc48d3abc7c0eb4f3581c56c37bebb260fcebd..ee9f95f03ee92a886842773d42bec1de3fe57674 100644 (file)
@@ -84,7 +84,7 @@ namespace Hazel
            byte[] data = HandleReceive(buffer, buffer.Length);
 
            if (data != null)
-                InvokeDataReceived(new DataEventArgs(data, (SendOption)data[0]));
+                InvokeDataReceived(new DataEventArgs(data, (SendOption)buffer[0]));
         }
 
         /// <summary>