From: JamJar00 Date: Sun, 17 Apr 2016 21:38:58 +0000 (+0100) Subject: Fixes and adds X-Git-Tag: 1.0.0~166 X-Git-Url: https://git.deb.at/?a=commitdiff_plain;h=18a63a844c490f858775eabf0d47120e2dad2d11;p=rhonda%2Fimpostor.hazel.git Fixes and adds --- diff --git a/Hazel.UnitTests/TcpConnectionTests.cs b/Hazel.UnitTests/TcpConnectionTests.cs index d0f50b5..cde4049 100644 --- a/Hazel.UnitTests/TcpConnectionTests.cs +++ b/Hazel.UnitTests/TcpConnectionTests.cs @@ -43,5 +43,18 @@ namespace Hazel.UnitTests TestHelper.RunServerToClientTest(listener, connection, 4, 0, 0, SendOption.OrderedFragmentedReliable); } } + + /// + /// Tests sending and receiving on the TcpConnection. + /// + [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); + } + } } } diff --git a/Hazel.UnitTests/TestHelper.cs b/Hazel.UnitTests/TestHelper.cs index d5b190b..1c4214f 100644 --- a/Hazel.UnitTests/TestHelper.cs +++ b/Hazel.UnitTests/TestHelper.cs @@ -16,12 +16,11 @@ namespace Hazel.UnitTests /// /// The listener to test. /// The connection to test. - //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); } + + /// + /// Runs a general test on the given listener and connection. + /// + /// The listener to test. + /// The connection to test. + 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); + } } } diff --git a/Hazel.UnitTests/UdpConnectionTests.cs b/Hazel.UnitTests/UdpConnectionTests.cs index b2dbcd5..0134cdc 100644 --- a/Hazel.UnitTests/UdpConnectionTests.cs +++ b/Hazel.UnitTests/UdpConnectionTests.cs @@ -56,5 +56,31 @@ namespace Hazel.UnitTests TestHelper.RunServerToClientTest(listener, connection, 3, 1, 2, SendOption.Reliable); } } + + /// + /// Tests server to client unreliable communication on the UdpConnection. + /// + [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); + } + } + + /// + /// Tests server to client reliable communication on the UdpConnection. + /// + [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 index 0000000..36baf3c --- /dev/null +++ b/Hazel/UdpConnection.Reliable.cs @@ -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? + /// + /// The starting timeout, in miliseconds, at which data will be resent. + /// + /// + /// On each resend this is doubled for that packet. + /// + public int ResendTimeout { get { return resendTimeout; } set { resendTimeout = value; } } + private int resendTimeout = 200; //TODO this based of average ping? + + /// + /// Holds the last ID allocated. + /// + volatile ushort lastIDAllocated; + + /// + /// The number of items to remember we have received before overwriting. + /// + private readonly int receiveCapacity = 4096; + + /// + /// The packets of data that have been transmitted reliably and not acknowledged. + /// + Dictionary reliableDataPacketsSent = new Dictionary(); + + /// + /// The last packets that were received. + /// + HashSet reliableDataPacketsMissing = new HashSet(); + + /// + /// The packet id that was received last. + /// + volatile ushort reliableReceiveLast = 0; + + /// + /// Has the connection received anything yet + /// + volatile bool hasReceivedSomething = false; + + /// + /// Class to hold packet data + /// + class Packet + { + public byte[] Data; + public Timer Timer; + public int LastTimeout; + + public Packet(byte[] data, Action resendAction, int timeout) + { + Data = data; + + Timer = new Timer( + (object obj) => resendAction(this), + null, + timeout, + timeout + ); + + LastTimeout = timeout; + } + } + + /// + /// Writes the bytes neccessary for a reliable send and stores the send. + /// + /// The byte array to write to. + 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); + } + } + + /// + /// Handles receives from reliable packets. + /// + /// The buffer containing the data. + /// Whether the bytes were valid or not. + 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; + } + + /// + /// Handles acknowledgement packets to us. + /// + /// The buffer containing the data. + 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); + } + } + } + } +} diff --git a/Hazel/UdpConnectionListener.cs b/Hazel/UdpConnectionListener.cs index a0151f2..f820e76 100644 --- a/Hazel/UdpConnectionListener.cs +++ b/Hazel/UdpConnectionListener.cs @@ -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)); } /// diff --git a/Hazel/UdpServerConnection.cs b/Hazel/UdpServerConnection.cs index 40dc48d..ee9f95f 100644 --- a/Hazel/UdpServerConnection.cs +++ b/Hazel/UdpServerConnection.cs @@ -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])); } ///