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);
+ }
+ }
}
}
/// </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)
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);
+ }
}
}
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);
+ }
+ }
}
}
--- /dev/null
+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);
+ }
+ }
+ }
+ }
+}
//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>
byte[] data = HandleReceive(buffer, buffer.Length);
if (data != null)
- InvokeDataReceived(new DataEventArgs(data, (SendOption)data[0]));
+ InvokeDataReceived(new DataEventArgs(data, (SendOption)buffer[0]));
}
/// <summary>