using (TcpConnectionListener listener = new TcpConnectionListener(IPAddress.Any, 4296))
using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
- TestHelper.RunServerToClientTest(listener, connection, 4, 5, SendOption.FragmentedReliable);
+ TestHelper.RunServerToClientTest(listener, connection, 10, SendOption.FragmentedReliable);
}
}
using (TcpConnectionListener listener = new TcpConnectionListener(IPAddress.Any, 4296))
using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
- TestHelper.RunClientToServerTest(listener, connection, 4, 5, SendOption.FragmentedReliable);
+ TestHelper.RunClientToServerTest(listener, connection, 10, SendOption.FragmentedReliable);
}
}
/// </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 headerSize, int totalHandshakeSize, SendOption sendOption)
+ internal static void RunServerToClientTest(ConnectionListener listener, Connection connection, int dataSize, SendOption sendOption)
{
//Setup meta stuff
- byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+ byte[] data = BuildData(dataSize);
ManualResetEvent mutex = new ManualResetEvent(false);
//Setup listener
/// </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 totalHandshakeSize, SendOption sendOption)
+ internal static void RunClientToServerTest(ConnectionListener listener, Connection connection, int dataSize, SendOption sendOption)
{
//Setup meta stuff
- byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+ byte[] data = BuildData(dataSize);
ManualResetEvent mutex = new ManualResetEvent(false);
ManualResetEvent mutex2 = new ManualResetEvent(false);
mutex2.WaitOne();
}
+
+ /// <summary>
+ /// Builds new data of increaseing value bytes.
+ /// </summary>
+ /// <param name="dataSize">The number of bytes to generate.</param>
+ /// <returns>The data.</returns>
+ static byte[] BuildData(int dataSize)
+ {
+ byte[] data = new byte[dataSize];
+ for (int i = 0; i < dataSize; i++)
+ data[i] = (byte)i;
+ return data;
+ }
}
}
using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296)))
using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
- TestHelper.RunServerToClientTest(listener, connection, 1, 4, SendOption.None);
+ TestHelper.RunServerToClientTest(listener, connection, 10, SendOption.None);
}
}
using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296)))
using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
- TestHelper.RunServerToClientTest(listener, connection, 3, 4, SendOption.Reliable);
+ TestHelper.RunServerToClientTest(listener, connection, 10, SendOption.Reliable);
+ }
+ }
+
+ /// <summary>
+ /// Tests server to client reliable communication on the UdpConnection.
+ /// </summary>
+ [TestMethod]
+ public void UdpFragmentedServerToClientTest()
+ {
+ using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296)))
+ using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
+ {
+ TestHelper.RunServerToClientTest(listener, connection, (int)(connection.FragmentSize * 9.5), SendOption.FragmentedReliable);
}
}
using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296)))
using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
- TestHelper.RunClientToServerTest(listener, connection, 1, 4, SendOption.None);
+ TestHelper.RunClientToServerTest(listener, connection, 10, SendOption.None);
}
}
using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296)))
using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
- TestHelper.RunClientToServerTest(listener, connection, 3, 4, SendOption.Reliable);
+ TestHelper.RunClientToServerTest(listener, connection, 10, SendOption.Reliable);
+ }
+ }
+
+ /// <summary>
+ /// Tests server to client reliable communication on the UdpConnection.
+ /// </summary>
+ [TestMethod]
+ public void UdpFragmentedClientToServerTest()
+ {
+ using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296)))
+ using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
+ {
+ TestHelper.RunClientToServerTest(listener, connection, (int)(connection.FragmentSize * 9.5), SendOption.FragmentedReliable);
}
}
<Compile Include="ObjectPool.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SendOption.cs" />
- <Compile Include="SendOptionInternal.cs" />
+ <Compile Include="Udp\SendOptionInternal.cs" />
<Compile Include="Tcp\StateObject.cs" />
<Compile Include="ConnectionStatistics.cs" />
<Compile Include="Tcp\TcpConnection.cs" />
<Compile Include="Udp\UdpConnection.cs">
<SubType>Code</SubType>
</Compile>
+ <Compile Include="Udp\UdpConnection.Fragmented.cs" />
<Compile Include="Udp\UdpConnection.KeepAlive.cs" />
<Compile Include="Udp\UdpConnection.Reliable.cs" />
<Compile Include="Udp\UdpConnectionListener.cs" />
/// a larger number of protocol bytes and can be slower than unreliable delivery.
/// </remarks>
Reliable = 1,
-
- /// <summary>
- /// Requests data be sent so that large messages are fragmented into smaller chunks of
- /// data and reassembled when received.
- /// </summary>
- /// <remarks>
- /// Fragmented messages allow large amounts of data to be transmitted in smaller chunks when using connections
- /// that do not support the transmission of large messages. Without specifying reliable delivery there is no
- /// guarentee that the message will arrive but any incomplete messages will be simply be discarded.
- /// </remarks>
- Fragmented = 2,
-
+
/// <summary>
/// Requests data be sent so that large messages are fragmented into smaller chunks of
/// data and reassembled when received.
/// guaranteed to arrive and to arrive only once but the sending process may require more memory, processing,
/// a larger number protocol bytes and may be slower than sending unreliably.
/// </remarks>
- FragmentedReliable = 3
+ FragmentedReliable = 2
}
}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-
-namespace Hazel
-{
- /// <summary>
- /// Extra internal states for SendOption enumeration when using UDP.
- /// </summary>
- enum SendOptionInternal : byte
- {
- /// <summary>
- /// Hello message for initiating communication.
- /// </summary>
- Hello = 8,
-
- /// <summary>
- /// Message for discontinuing communication.
- /// </summary>
- Disconnect = 9,
-
- /// <summary>
- /// Message acknowledging the receipt of a message.
- /// </summary>
- Acknowledgement = 10
- }
-}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+
+namespace Hazel.Udp
+{
+ /// <summary>
+ /// Extra internal states for SendOption enumeration when using UDP.
+ /// </summary>
+ enum UdpSendOption : byte
+ {
+ /// <summary>
+ /// Hello message for initiating communication.
+ /// </summary>
+ Hello = 8,
+
+ /// <summary>
+ /// Message for discontinuing communication.
+ /// </summary>
+ Disconnect = 9,
+
+ /// <summary>
+ /// Message acknowledging the receipt of a message.
+ /// </summary>
+ Acknowledgement = 10,
+
+ /// <summary>
+ /// Message that is part of a larger, fragmented message.
+ /// </summary>
+ Fragment = 11
+ }
+}
lock (socket)
socket.EndSendTo(result);
}
+ catch (ObjectDisposedException e)
+ {
+ HandleDisconnect(new HazelException("Could not send as the socket was disposed of.", e));
+ }
catch (SocketException e)
{
HandleDisconnect(new HazelException("Could not send data as a SocketException occured.", e));
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Hazel.Udp
+{
+ partial class UdpConnection
+ {
+ /// <summary>
+ /// The amount of data that can be put into a fragment.
+ /// </summary>
+ public int FragmentSize { get; } = 65507 - 1 - 2 - 2 - 2;
+
+ /// <summary>
+ /// The last fragmented message ID that was written.
+ /// </summary>
+ volatile ushort lastFragmentIDAllocated;
+
+ Dictionary<ushort, FragmentedMessage> fragmentedMessagesReceived = new Dictionary<ushort, FragmentedMessage>();
+
+ /// <summary>
+ /// Sends a message fragmenting it as needed to pass over the network.
+ /// </summary>
+ /// <param name="sendOption">The send option the message was sent with.</param>
+ /// <param name="data">The data of the message to send.</param>
+ void FragmentedSend(byte[] data)
+ {
+ //Get an ID not used yet.
+ ushort id = ++lastFragmentIDAllocated; //TODO manage loop around
+
+ for (ushort i = 0; i < Math.Ceiling(data.Length / (double)FragmentSize); i++)
+ {
+ byte[] buffer = new byte[Math.Min(data.Length - (FragmentSize * i), FragmentSize) + 7];
+
+ //Add send option
+ buffer[0] = i == 0 ? (byte)SendOption.FragmentedReliable : (byte)UdpSendOption.Fragment;
+
+ //Add fragment message ID
+ buffer[1] = (byte)((id >> 8) & 0xFF);
+ buffer[2] = (byte)id;
+
+ //Add length or fragment id
+ if (i == 0)
+ {
+ ushort fragments = (ushort)Math.Ceiling(data.Length / (double)FragmentSize);
+ buffer[3] = (byte)((fragments >> 8) & 0xFF);
+ buffer[4] = (byte)fragments;
+ }
+ else
+ {
+ buffer[3] = (byte)((i >> 8) & 0xFF);
+ buffer[4] = (byte)i;
+ }
+
+ //Pass fragment to reliable send code to ensure it will arrive
+ AttachReliableID(buffer, 5);
+
+ //Copy data into fragment
+ Buffer.BlockCopy(data, FragmentSize * i, buffer, 7, buffer.Length - 7);
+
+ //Send
+ WriteBytesToConnection(buffer);
+ }
+ }
+
+ /// <summary>
+ /// Gets a message from those we've begun receiving or adds a new one.
+ /// </summary>
+ /// <param name="messageId">The Id of the message to find.</param>
+ /// <returns></returns>
+ FragmentedMessage GetFragmentedMessage(ushort messageId)
+ {
+ lock (fragmentedMessagesReceived)
+ {
+ FragmentedMessage message;
+ if (fragmentedMessagesReceived.ContainsKey(messageId))
+ {
+ message = fragmentedMessagesReceived[messageId];
+ }
+ else
+ {
+ message = new FragmentedMessage();
+
+ fragmentedMessagesReceived.Add(messageId, message);
+ }
+
+ return message;
+ }
+ }
+
+ /// <summary>
+ /// Handles a the start message of a fragmented message.
+ /// </summary>
+ /// <param name="buffer">The buffer received.</param>
+ void FragmentedStartMessageReceive(byte[] buffer)
+ {
+ //Send to reliable code to send the acknowledgement
+ if (!ProcessReliableReceive(buffer, 5))
+ return;
+
+ ushort id = (ushort)((buffer[1] << 8) + buffer[2]);
+
+ ushort length = (ushort)((buffer[3] << 8) + buffer[4]);
+
+ FragmentedMessage message;
+ bool messageComplete;
+ lock (fragmentedMessagesReceived)
+ {
+ message = GetFragmentedMessage(id);
+ message.received.Add(new FragmentedMessage.Fragment(0, buffer, 7));
+ message.noFragments = length;
+
+ messageComplete = message.noFragments == message.received.Count;
+ }
+
+ if (messageComplete)
+ FinalizeFragmentedMessage(message);
+ }
+
+ /// <summary>
+ /// Handles a fragment message of a fragmented message.
+ /// </summary>
+ /// <param name="buffer">The buffer received.</param>
+ void FragmentedMessageReceive(byte[] buffer)
+ {
+ //Send to reliable code to send the acknowledgement
+ if (!ProcessReliableReceive(buffer, 5))
+ return;
+
+ ushort id = (ushort)((buffer[1] << 8) + buffer[2]);
+
+ ushort fragmentID = (ushort)((buffer[3] << 8) + buffer[4]);
+
+ FragmentedMessage message;
+ bool messageComplete;
+ lock (fragmentedMessagesReceived)
+ {
+ message = GetFragmentedMessage(id);
+ message.received.Add(new FragmentedMessage.Fragment(fragmentID, buffer, 7));
+
+ messageComplete = message.noFragments == message.received.Count;
+ }
+
+ if (messageComplete)
+ FinalizeFragmentedMessage(message);
+ }
+
+ /// <summary>
+ /// Finalizes a completed fragmented message and invokes message received events.
+ /// </summary>
+ /// <param name="message">The message received.</param>
+ void FinalizeFragmentedMessage(FragmentedMessage message)
+ {
+ IEnumerable<FragmentedMessage.Fragment> orderedFragments = message.received.OrderBy((x) => x.fragmentID);
+
+ byte[] completeData = new byte[(orderedFragments.Count() - 1) * FragmentSize + orderedFragments.Last().data.Length];
+ int ptr = 0;
+ foreach (FragmentedMessage.Fragment fragment in orderedFragments)
+ {
+ Buffer.BlockCopy(fragment.data, fragment.offset, completeData, ptr, fragment.data.Length - fragment.offset);
+ ptr += fragment.data.Length - fragment.offset;
+ }
+
+ InvokeDataReceived(completeData, SendOption.FragmentedReliable);
+ }
+
+ /// <summary>
+ /// Holding class for the parts of a fragmented message so far received.
+ /// </summary>
+ private class FragmentedMessage
+ {
+ /// <summary>
+ /// The total number of fragments expected.
+ /// </summary>
+ public int noFragments = -1;
+
+ /// <summary>
+ /// The fragments received so far.
+ /// </summary>
+ public List<Fragment> received = new List<Fragment>();
+
+ public struct Fragment
+ {
+ public int fragmentID;
+ public byte[] data;
+ public int offset;
+
+ public Fragment(int fragmentID, byte[] data, int offset)
+ {
+ this.fragmentID = fragmentID;
+ this.data = data;
+ this.offset = offset;
+ }
+ }
+ }
+ }
+}
}
/// <summary>
- /// Sends the bytes reliably and stores the send.
+ /// Adds a 2 byte ID to the packet at offset and stores the packet reference for retransmission.
/// </summary>
- /// <param name="bytes">The byte array to write to.</param>
+ /// <param name="buffer">The buffer to attach to.</param>
+ /// <param name="offset">The offset to attach at.</param>
/// <param name="ackCallback">The callback to make once the packet has been acknowledged.</param>
- void ReliableSend(byte sendOption, byte[] data, Action ackCallback)
+ void AttachReliableID(byte[] buffer, int offset, Action ackCallback = null)
{
- byte[] bytes = new byte[data.Length + 3];
-
- //Add message type
- bytes[0] = sendOption;
-
//Find and reliable ID
lock (reliableDataPacketsSent)
{
while (reliableDataPacketsSent.ContainsKey(id));
//Write ID
- bytes[1] = (byte)((id >> 8) & 0xFF);
- bytes[2] = (byte)id;
+ buffer[offset] = (byte)((id >> 8) & 0xFF);
+ buffer[offset + 1] = (byte)id;
//Create packet object
Packet packet = Packet.GetObject();
packet.Set(
- bytes,
+ buffer,
(Packet p) =>
{
//Double packet timeout
if (++p.Retransmissions > ResendsBeforeDisconnect)
{
HandleDisconnect();
-
+
//Set acknowledged so we dont change the timer again
p.Acknowledged = true;
//Remember packet
reliableDataPacketsSent.Add(id, packet);
}
+ }
+
+ /// <summary>
+ /// Sends the bytes reliably and stores the send.
+ /// </summary>
+ /// <param name="bytes">The byte array to write to.</param>
+ /// <param name="ackCallback">The callback to make once the packet has been acknowledged.</param>
+ void ReliableSend(byte sendOption, byte[] data, Action ackCallback = null)
+ {
+ byte[] bytes = new byte[data.Length + 3];
+
+ //Add message type
+ bytes[0] = sendOption;
+
+ //Add reliable ID
+ AttachReliableID(bytes, 1, ackCallback);
//Copy data into new array
Buffer.BlockCopy(data, 0, bytes, bytes.Length - data.Length, data.Length);
/// Handles a reliable message being received and invokes the data event.
/// </summary>
/// <param name="buffer">The buffer received.</param>
- void ReliableReceive(byte[] buffer)
+ void ReliableMessageReceive(byte[] buffer)
{
- if (ProcessReliableReceive(buffer))
+ if (ProcessReliableReceive(buffer, 1))
InvokeDataReceived(SendOption.Reliable, buffer, 3);
Statistics.LogReliableReceive(buffer.Length - 3, buffer.Length);
/// Handles receives from reliable packets.
/// </summary>
/// <param name="bytes">The buffer containing the data.</param>
+ /// <param name="offset">The offset of the reliable header.</param>
/// <returns>Whether the packet was a new packet or not.</returns>
- bool ProcessReliableReceive(byte[] bytes)
+ bool ProcessReliableReceive(byte[] bytes, int offset)
{
//Get the ID form the packet
- ushort id = (ushort)((bytes[1] << 8) + bytes[2]);
+ ushort id = (ushort)((bytes[offset] << 8) + bytes[offset + 1]);
//Send an acknowledgement
- SendAck(bytes[1], bytes[2]);
+ SendAck(bytes[offset], bytes[offset + 1]);
/*
* It gets a little complicated here (note the fact I'm actually using a multiline comment for once...)
/// Handles acknowledgement packets to us.
/// </summary>
/// <param name="bytes">The buffer containing the data.</param>
- void AcknowledgementReceive(byte[] bytes)
+ void AcknowledgementMessageReceive(byte[] bytes)
{
//Get ID
ushort id = (ushort)((bytes[1] << 8) + bytes[2]);
WriteBytesToConnection( //TODO group acks together
new byte[]
{
- (byte)SendOptionInternal.Acknowledgement,
+ (byte)UdpSendOption.Acknowledgement,
byte1,
byte2
}
{
//Handle reliable header and hellos
case (byte)SendOption.Reliable:
- case (byte)SendOptionInternal.Hello:
+ case (byte)UdpSendOption.Hello:
ReliableSend(sendOption, data, ackCallback);
break;
+
+ case (byte)SendOption.FragmentedReliable:
+ FragmentedSend(data);
+ break;
//Treat all else as unreliable
default:
- UnreliableSend(sendOption, data);
+ UnreliableSend(data, sendOption);
break;
}
}
{
//Handle reliable receives
case (byte)SendOption.Reliable:
- ReliableReceive(buffer);
+ ReliableMessageReceive(buffer);
break;
//Handle acknowledgments
- case (byte)SendOptionInternal.Acknowledgement:
- AcknowledgementReceive(buffer);
+ case (byte)UdpSendOption.Acknowledgement:
+ AcknowledgementMessageReceive(buffer);
break;
//We need to acknowledge hello messages but dont want to invoke any events!
- case (byte)SendOptionInternal.Hello:
- ProcessReliableReceive(buffer);
+ case (byte)UdpSendOption.Hello:
+ ProcessReliableReceive(buffer, 1);
Statistics.LogHelloReceive(buffer.Length);
break;
- case (byte)SendOptionInternal.Disconnect:
+ case (byte)UdpSendOption.Disconnect:
HandleDisconnect();
break;
+ //Handle fragmented messages
+ case (byte)SendOption.FragmentedReliable:
+ FragmentedStartMessageReceive(buffer);
+ break;
+
+ case (byte)UdpSendOption.Fragment:
+ FragmentedMessageReceive(buffer);
+ break;
+
//Treat everything else as unreliable
default:
InvokeDataReceived(SendOption.None, buffer, 1);
}
}
- void UnreliableSend(byte sendOption, byte[] data)
+ /// <summary>
+ /// Sends bytes using the unreliable UDP protocol.
+ /// </summary>
+ /// <param name="data">The data.</param>
+ /// <param name="sendOption">The SendOption to attach.</param>
+ void UnreliableSend(byte[] data, byte sendOption)
{
byte[] bytes = new byte[data.Length + 1];
Buffer.BlockCopy(bytes, 0, actualBytes, 1, bytes.Length);
}
- HandleSend(actualBytes, (byte)SendOptionInternal.Hello, acknowledgeCallback);
+ HandleSend(actualBytes, (byte)UdpSendOption.Hello, acknowledgeCallback);
}
/// <summary>
/// </summary>
protected void SendDisconnect()
{
- HandleSend(new byte[0], (byte)SendOptionInternal.Disconnect); //TODO Should disconnect wait for an ack?
+ HandleSend(new byte[0], (byte)UdpSendOption.Disconnect); //TODO Should disconnect wait for an ack?
}
/// <inheritdoc/>
else
{
//Check for malformed connection attempts
- if (buffer[0] != (byte)SendOptionInternal.Hello)
+ if (buffer[0] != (byte)UdpSendOption.Hello)
return;
connection = new UdpServerConnection(this, remoteEndPoint, IPMode);