/// Tests the fields on TcpConnection.
/// </summary>
[TestMethod]
- public void TcpConnectionFieldTest()
+ public void TcpFieldTest()
{
using (TcpConnectionListener listener = new TcpConnectionListener(IPAddress.Any, 4296))
using (TcpConnection connection = new TcpConnection())
/// Tests sending and receiving on the TcpConnection.
/// </summary>
[TestMethod]
- public void TcpConnectionSendReceiveTest()
+ public void TcpServerToClientTest()
{
using (TcpConnectionListener listener = new TcpConnectionListener(IPAddress.Any, 4296))
using (TcpConnection connection = new TcpConnection())
{
- TestHelper.RunSendReceiveTest(listener, connection, 4, 0, 0);
+ TestHelper.RunServerToClientTest(listener, connection, 4, 0, 0, SendOption.OrderedFragmentedReliable);
}
}
}
/// </summary>
/// <param name="listener">The listener to test.</param>
/// <param name="connection">The connection to test.</param>
- internal static void RunSendReceiveTest(ConnectionListener listener, Connection connection, int headerSize, int handshakeSize, int totalHandshakeSize)
+ //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 };
//Setup listener
listener.NewConnection += delegate(object sender, NewConnectionEventArgs args)
{
- args.Connection.WriteBytes(data);
+ args.Connection.WriteBytes(data, sendOption);
Assert.AreEqual(data.Length, args.Connection.Statistics.DataBytesSent);
Assert.AreEqual(0, args.Connection.Statistics.DataBytesReceived);
Assert.AreEqual(data.Length + headerSize, args.Connection.Statistics.TotalBytesSent);
Assert.AreEqual(data[i], args.Bytes[i]);
}
+ Assert.AreEqual(sendOption, args.SendOption);
+
mutex.Set();
};
/// Tests the fields on UdpConnection.
/// </summary>
[TestMethod]
- public void UdpConnectionFieldTest()
+ public void UdpFieldTest()
{
using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296))
using (UdpConnection connection = new UdpClientConnection())
}
/// <summary>
- /// Tests sending and receiving on the UdpConnection.
+ /// Tests server to client unreliable communication on the UdpConnection.
/// </summary>
[TestMethod]
- public void UdpConnectionSendReceiveTest()
+ public void UdpUnreliableServerToClientTest()
{
using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296))
using (UdpConnection connection = new UdpClientConnection())
{
- TestHelper.RunSendReceiveTest(listener, connection, 1, 1, 2);
+ TestHelper.RunServerToClientTest(listener, connection, 1, 1, 2, SendOption.None);
+ }
+ }
+
+ /// <summary>
+ /// Tests server to client reliable communication on the UdpConnection.
+ /// </summary>
+ [TestMethod]
+ public void UdpReliableServerToClientTest()
+ {
+ using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296))
+ using (UdpConnection connection = new UdpClientConnection())
+ {
+ TestHelper.RunServerToClientTest(listener, connection, 3, 1, 2, SendOption.Reliable);
}
}
}
/// <summary>
/// The bytes received.
/// </summary>
- public byte[] Bytes;
+ public byte[] Bytes { get; private set; }
+
+ /// <summary>
+ /// The SendOption the data was sent with.
+ /// </summary>
+ public object SendOption { get; private set; }
/// <summary>
/// Creates DataEventArgs from bytes received.
/// </summary>
/// <param name="bytes"></param>
- public DataEventArgs(byte[] bytes)
+ public DataEventArgs(byte[] bytes, SendOption sendOption)
{
this.Bytes = bytes;
+ this.SendOption = sendOption;
}
}
}
<Compile Include="NetworkEndPoint.cs" />
<Compile Include="NewConnectionEventArgs.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
- <Compile Include="SendFlags.cs" />
+ <Compile Include="SendOption.cs" />
+ <Compile Include="SendOptionInternal.cs" />
<Compile Include="StateObject.cs" />
<Compile Include="ConnectionStatistics.cs" />
<Compile Include="TcpConnection.cs" />
<Compile Include="UdpConnection.cs">
<SubType>Code</SubType>
</Compile>
+ <Compile Include="UdpConnection.Reliable.cs" />
<Compile Include="UdpConnectionListener.cs" />
<Compile Include="UdpServerConnection.cs" />
<Compile Include="Utility.cs" />
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Hazel
-{
- /// <summary>
- /// Specifies how a message should be sent.
- /// </summary>
- [Flags]
- public enum SendOption : byte
- {
- /// <summary>
- /// Requests unreliable delivery with no framentation or ordering.
- /// </summary>
- None = 0,
-
- /// <summary>
- /// Requests data be sent reliably. Data is guaranteed to arrive at it's destination.
- /// </summary>
- Reliable = 1,
-
- /// <summary>
- /// Requests that data should be sent in order.
- /// </summary>
- /// <remarks>
- /// Any packets that are out of order in this option will be dropped.
- /// </remarks>
- Ordered = 2,
-
- /// <summary>
- /// Requests that data should be sent in order and reliably.
- /// </summary>
- /// <remarks>
- /// Only messages that are sent using OrderedReliable or OrderedFragmentedReliable will arrive
- /// in order, other messages
- /// may arrive in between.
- /// </remarks>
- OrderedReliable = 3,
-
- /// <summary>
- /// Requests data be sent so that large messages are fragmented into smaller chunks of
- /// data and reassembled when received.
- /// </summary>
- FragmentedReliable = 5,
-
- /// <summary>
- /// Requests data be sent so that large messages are fragmented into smaller chunks of data and
- /// reassembled when received and that the message arrives in order with other messages.
- /// </summary>
- OrderedFragmentedReliable = 7
- }
-}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hazel
+{
+ /// <summary>
+ /// Specifies how a message should be sent.
+ /// </summary>
+ [Flags]
+ public enum SendOption : byte
+ {
+ /// <summary>
+ /// Requests unreliable delivery with no framentation or ordering.
+ /// </summary>
+ None = 0,
+
+ /// <summary>
+ /// Requests data be sent reliably. Data is guaranteed to arrive at it's destination.
+ /// </summary>
+ Reliable = 1,
+
+ /// <summary>
+ /// Requests that data should be sent in order.
+ /// </summary>
+ /// <remarks>
+ /// Any packets that are out of order in this option will be dropped.
+ /// </remarks>
+ Ordered = 2,
+
+ /// <summary>
+ /// Requests that data should be sent in order and reliably.
+ /// </summary>
+ /// <remarks>
+ /// Only messages that are sent using OrderedReliable or OrderedFragmentedReliable will arrive
+ /// in order, other messages
+ /// may arrive in between.
+ /// </remarks>
+ OrderedReliable = 3,
+
+ /// <summary>
+ /// Requests data be sent so that large messages are fragmented into smaller chunks of
+ /// data and reassembled when received.
+ /// </summary>
+ FragmentedReliable = 5,
+
+ /// <summary>
+ /// Requests data be sent so that large messages are fragmented into smaller chunks of data and
+ /// reassembled when received and that the message arrives in order with other messages.
+ /// </summary>
+ OrderedFragmentedReliable = 7
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hazel
+{
+ /// <summary>
+ /// Extra internal states for SendOption enumeration.
+ /// </summary>
+ enum SendOptionInternal : byte
+ {
+ Acknowledgement = 255
+ }
+}
Statistics.LogReceive(bytes.Length, bytes.Length + 4);
//Fire DataReceived event
- InvokeDataReceived(new DataEventArgs(bytes));
+ InvokeDataReceived(new DataEventArgs(bytes, SendOption.OrderedFragmentedReliable));
}
/// <summary>
/// <param name="sendOption">The option this data is requested to send with.</param>
public override void WriteBytes(byte[] bytes, SendOption sendOption = SendOption.None)
{
- //Add sendflag byte to start
- byte[] fullBytes = new byte[bytes.Length + 1];
- fullBytes[0] = (byte)sendOption;
- Buffer.BlockCopy(bytes, 0, fullBytes, 1, bytes.Length);
+ //Add header information and send
+ HandleSend(bytes, sendOption);
+ }
+ /// <summary>
+ /// Writes bytes to the socket.
+ /// </summary>
+ /// <param name="bytes">The bytes to send.</param>
+ protected override void WriteBytesToConnection(byte[] bytes)
+ {
//Pack
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
- args.SetBuffer(fullBytes, 0, fullBytes.Length);
+ args.SetBuffer(bytes, 0, bytes.Length);
args.RemoteEndPoint = RemoteEndPoint;
lock (socket)
throw he;
}
}
-
- Statistics.LogSend(bytes.Length, fullBytes.Length);
}
/// <summary>
}
//Write bytes to the server to tell it hi (and to punch a hole in our NAT, if present).
- WriteBytes(new byte[] { 0 }, SendOption.Reliable);
+ WriteBytes(new byte[] { 0 }, SendOption.None); //TODO special hello message
}
/// <summary>
return;
}
- //Copy to new buffer
- byte[] buffer = new byte[bytesReceived];
- Buffer.BlockCopy((byte[])result.AsyncState, 1, buffer, 0, bytesReceived - 1);
+ //Decode the data received
+ byte[] buffer = HandleReceive(dataBuffer, bytesReceived);
+ SendOption sendOption = (SendOption)dataBuffer[0];
+
+ //TODO may possibly get better performance with above/below swapped and block copy added
//Begin receiving again
try
{
HandleDisconnect(new HazelException("A Socket exception occured while initiating a receive operation.", e));
}
-
- Statistics.LogReceive(buffer.Length - 1, buffer.Length);
-
- InvokeDataReceived(new DataEventArgs(buffer));
+
+ if (buffer != null)
+ InvokeDataReceived(new DataEventArgs(buffer, sendOption));
}
/// <summary>
/// <summary>
/// Represents a connection that uses the UDP protocol.
/// </summary>
- public abstract class UdpConnection : Connection
+ public abstract partial class UdpConnection : Connection
{
/// <summary>
- /// The packets of data that have been transmitted reliably and not acknowledged.
+ /// The remote end point of this connection.
/// </summary>
- Dictionary<uint, Packet> reliableDataPacketsSent = new Dictionary<uint, Packet>();
+ public EndPoint RemoteEndPoint { get; protected set; }
/// <summary>
- /// Holds the last ID allocated.
+ /// Writes the given bytes to the connection.
/// </summary>
- volatile uint lastIDAllocated;
+ /// <param name="bytes">The bytes to write.</param>
+ protected abstract void WriteBytesToConnection(byte[] bytes);
/// <summary>
- /// The remote end point of this connection.
+ /// Handles the reliable/fragmented/ordered sending from this connection.
/// </summary>
- public EndPoint RemoteEndPoint { get; protected set; }
-
- class Packet
+ /// <param name="data">The data being sent.</param>
+ /// <param name="sendOption">The send option.</param>
+ /// <returns>The bytes that should actually be sent.</returns>
+ protected void HandleSend(byte[] data, SendOption sendOption)
{
- public byte[] Data;
- public DateTime SentTime;
-
- public Packet(byte[] data, DateTime sentTime)
+ byte[] bytes;
+ switch (sendOption)
{
- Data = data;
- SentTime = sentTime;
+ case SendOption.Reliable:
+ bytes = new byte[data.Length + 3];
+ WriteReliableSendHeader(bytes);
+ break;
+
+ default:
+ bytes = new byte[data.Length + 1];
+ break;
}
+
+ //Add message type
+ bytes[0] = (byte)sendOption;
+
+ //Copy data into new array
+ Buffer.BlockCopy(data, 0, bytes, bytes.Length - data.Length, data.Length);
+
+ //Write to connection
+ WriteBytesToConnection(bytes);
+
+ Statistics.LogSend(data.Length, bytes.Length);
}
/// <summary>
- /// Handles the reliable/fragmented/ordered sending from this connection.
+ /// Handles the receiving of data.
/// </summary>
- /// <param name="data">The data being sent.</param>
- /// <param name="sendOption">The send option.</param>
- /// <returns>The bytes that should actually be sent.</returns>
- protected byte[] HandleSend(byte[] data, SendOption sendOption)
+ /// <param name="buffer">The array of the data received.</param>
+ /// <param name="bytesReceived">The number of bytes that were received.</param>
+ /// <returns>The bytes of data received.</returns>
+ protected byte[] HandleReceive(byte[] buffer, int bytesReceived)
{
- byte[] bytes = new byte[data.Length + 1];
- int offset = 1;
-
- if (sendOption == SendOption.Reliable)
+ int headerSize = 1;
+ switch (buffer[0])
{
- bytes = new byte[data.Length + 5];
- offset = 5;
-
- lock (reliableDataPacketsSent)
- {
- //Find an ID not used yet.
- uint id;
+ case (byte)SendOption.Reliable:
+ headerSize = 3;
- do
- id = ++lastIDAllocated;
- while (reliableDataPacketsSent.ContainsKey(id));
+ if (HandleReliableReceive(buffer) == false)
+ return null;
+ break;
- bytes[1] = (byte)(id & 0xFF);
- bytes[2] = (byte)((id >> 16) & 0xFF);
- bytes[3] = (byte)((id >> 8) & 0xFF);
- bytes[4] = (byte)id;
-
- //Remember packet
- reliableDataPacketsSent.Add(id, new Packet(data, DateTime.Now));
- }
+ case (byte)SendOptionInternal.Acknowledgement:
+ HandleAcknowledgement(buffer);
+
+ Statistics.LogReceive(0, bytesReceived);
+
+ return null;
}
- Buffer.BlockCopy(data, 0, bytes, offset, bytes.Length);
+ byte[] dataBytes = new byte[bytesReceived - headerSize];
+ Buffer.BlockCopy(buffer, headerSize, dataBytes, 0, dataBytes.Length);
+
+ Statistics.LogReceive(dataBytes.Length, bytesReceived);
- return bytes;
+ return dataBytes;
}
}
}
/// <param name="sendOption">The option this data is requested to send with.</param>
public override void WriteBytes(byte[] bytes, SendOption sendOption = SendOption.None)
{
- //Add sendflag byte to start
- byte[] fullBytes = new byte[bytes.Length + 1];
- fullBytes[0] = (byte)sendOption;
- Buffer.BlockCopy(bytes, 0, fullBytes, 1, bytes.Length);
+ HandleSend(bytes, sendOption);
+ }
+ /// <summary>
+ /// Writes bytes to the listener to send.
+ /// </summary>
+ /// <param name="bytes">bytes to send.</param>
+ protected override void WriteBytesToConnection(byte[] bytes)
+ {
lock (stateLock)
{
if (State != ConnectionState.Connected)
throw new InvalidOperationException("Could not send data as this Connection is not connected. Did you disconnect?");
- Listener.SendData(fullBytes, RemoteEndPoint);
+ Listener.SendData(bytes, RemoteEndPoint);
}
-
- Statistics.LogSend(bytes.Length, fullBytes.Length);
}
/// <summary>
/// <param name="buffer"></param>
internal void InvokeDataReceived(byte[] buffer)
{
- byte[] data = new byte[buffer.Length - 1];
- Buffer.BlockCopy(buffer, 1, data, 0, data.Length);
+ byte[] data = HandleReceive(buffer, buffer.Length);
- Statistics.LogReceive(data.Length, buffer.Length);
-
- InvokeDataReceived(new DataEventArgs(data));
+ if (data != null)
+ InvokeDataReceived(new DataEventArgs(data, (SendOption)data[0]));
}
/// <summary>