/// <code language="C#" source="DocInclude/TcpListenerExample.cs"/>
/// </example>
public event EventHandler<NewConnectionEventArgs> NewConnection;
- //TODO add threadsafe markers on all xmldocs
+
/// <summary>
/// Makes this connection listener begin listening for connections.
/// </summary>
/// </para>
/// <include file="DocInclude/common.xml" path="docs/item[@name='Recyclable']/*" />
/// </remarks>
+ /// <threadsafety static="true" instance="true"/>
public class DataEventArgs : EventArgs, IRecyclable
{
/// <summary>
/// </para>
/// <include file="DocInclude/common.xml" path="docs/item[@name='Recyclable']/*" />
/// </remarks>
+ /// <threadsafety static="true" instance="true"/>
public class DisconnectedEventArgs : IRecyclable
{
/// <summary>
/// <summary>
/// Interface for all items that can be returned to an object pool.
/// </summary>
+ /// <threadsafety static="true" instance="true"/>
interface IRecyclable
{
/// <summary>
/// <summary>
/// Abstract base class for a <see cref="Connection"/> to a remote end point via a network protocol like TCP or UDP.
/// </summary>
+ /// <threadsafety static="true" instance="true"/>
public abstract class NetworkConnection : Connection
{
/// <summary>
/// <summary>
/// Abstract base class for a <see cref="ConnectionListener"/> for network based connections.
/// </summary>
+ /// <threadsafety static="true" instance="true"/>
public abstract class NetworkConnectionListener : ConnectionListener
{
/// <summary>
/// <remarks>
/// This wraps a <see cref="System.Net.EndPoint"/> for connecting across a network using protocols like TCP or UDP.
/// </remarks>
+ /// <threadsafety static="true" instance="true"/>
public sealed class NetworkEndPoint : ConnectionEndPoint
{
/// <summary>
/// </para>
/// <include file="DocInclude/common.xml" path="docs/item[@name='Recyclable']/*" />
/// </remarks>
+ /// <threadsafety static="true" instance="true"/>
public class NewConnectionEventArgs : EventArgs, IRecyclable
{
/// <summary>
/// A fairly simple object pool for items that will be created a lot.
/// </summary>
/// <typeparam name="T">The type that is pooled.</typeparam>
+ /// <threadsafety static="true" instance="true"/>
sealed class ObjectPool<T> where T : IRecyclable
{
/// <summary>
//If the socket's been disposed then we can just end there.
return;
}
+ catch (SocketException e)
+ {
+ HandleDisconnect(new HazelException("A Socket exception occured while initiating a receive operation.", e));
+ return;
+ }
StateObject state = (StateObject)result.AsyncState;
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
- //Dispose of the socket
if (disposing)
{
+ //Send disconnect message if we're not already disconnecting
+ if (State == ConnectionState.Connected)
+ SendDisconnect();
+
+ //Dispose of the socket
lock (socketLock)
{
State = ConnectionState.NotConnected;
/// Handles receives from reliable packets.
/// </summary>
/// <param name="bytes">The buffer containing the data.</param>
- /// <returns>Whether the bytes were valid or not.</returns>
+ /// <returns>Whether the packet was a new packet or not.</returns>
bool HandleReliableReceive(byte[] bytes)
{
//Get the ID form the packet
//Send an acknowledgement
SendAck(bytes[1], bytes[2]);
- //Handle reliableness!
+ /*
+ * It gets a little complicated here (note the fact I'm actually using a multiline comment for once...)
+ *
+ * In a simple world if our data is greater than the last reliable packet received (reliableReceiveLast)
+ * then it is guaranteed to be a new packet, if it's not we can see if we are missing that packet (lookup
+ * in reliableDataPacketsMissing).
+ *
+ * --------rrl############# (1)
+ *
+ * (where --- are packets received already and #### are packets that will be counted as new)
+ *
+ * Unfortunately if id becomes greater than 65535 it will loop back to zero so we will add a pointer that
+ * specifies any packets with an id behind it are also new (overwritePointer).
+ *
+ * ####op----------rrl##### (2)
+ *
+ * ------rll#########op---- (3)
+ *
+ * Anything behind than the reliableReceiveLast pointer (but greater than the overwritePointer is either a
+ * missing packet or something we've already received so when we change the pointers we need to make sure
+ * we keep note of what hasn't been received yet (reliableDataPacketsMissing).
+ *
+ * So...
+ */
+
lock (reliableDataPacketsMissing)
{
- //TODO Looping of IDs
- // Currently when ID loops all packets will be discarded as ID will be less than reliableReceiveLast
- // And wont be in reliableDataPacketsMissing.
+ //Calculate overwritePointer
+ ushort overwritePointer = (ushort)(reliableReceiveLast - 32768);
- //If the ID <= reliableReceiveLast it might be something we're missing
- //HasReceivedSomething handles the edge case of reliableReceiveLast = 0 & ID = 0
- 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
+ //Calculate if it is a new packet by examining if it is within the range
+ bool isNew;
+ if (overwritePointer < reliableReceiveLast)
+ isNew = id > reliableReceiveLast || id <= overwritePointer; //Figure (2)
else
+ isNew = id > reliableReceiveLast && id <= overwritePointer; //Figure (3)
+
+ //If it's new or we've not received anything yet
+ if (isNew || !hasReceivedSomething)
{
//Mark items between the most recent receive and the id received as missing
for (ushort i = (ushort)(reliableReceiveLast + 1); i < id; i++)
reliableReceiveLast = id;
hasReceivedSomething = true;
}
+
+ //Else it could be a missing packet
+ else
+ {
+ //See if we're missing it, else this packet is a duplicate as so we return false
+ if (reliableDataPacketsMissing.Contains(id))
+ reliableDataPacketsMissing.Remove(id);
+ else
+ return false;
+ }
}
return true;
HandleSend(new byte[0], (byte)SendOptionInternal.Hello, acknowledgeCallback);
}
- /// <inheritdoc/>
- public override void Close()
- {
- HandleSend(new byte[0], (byte)SendOptionInternal.Disconnect); //TODO Should disconnect wait for an ack?
-
- base.Close();
- }
-
/// <summary>
/// Called when the socket has been disconnected at the remote host.
/// </summary>
/// <param name="e">The exception if one was the cause.</param>
protected abstract void HandleDisconnect(HazelException e = null);
+ /// <summary>
+ /// Sends a disconnect message to the end point.
+ /// </summary>
+ protected void SendDisconnect()
+ {
+ HandleSend(new byte[0], (byte)SendOptionInternal.Disconnect); //TODO Should disconnect wait for an ack?
+ }
+
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
/// </summary>
Socket listener;
+ /// <summary>
+ /// Buffer to store incoming data in.
+ /// </summary>
+ byte[] dataBuffer = new byte[ushort.MaxValue];
+
/// <summary>
/// The connections we currently hold
/// </summary>
void StartListeningForData()
{
EndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
- byte[] dataBuffer = new byte[ushort.MaxValue];
-
+
try
{
lock (listener)
//End the receive operation
try
{
- lock (listener) //TODO how does this stop when the client disconnects?
+ lock (listener)
bytesReceived = listener.EndReceiveFrom(result, ref remoteEndPoint);
}
catch (ObjectDisposedException)
//If the socket's been disposed then we can just end there.
return;
}
- catch (SocketException)
+ catch (SocketException e)
{
- //TODO Errr...;
- return;
+ //Errrr... shit...
+ //Not exactly much we can do if we've got here
+ throw e;
}
//Exit if no bytes read, we've closed.
//Here we just need to inform the listener we no longer need data.
if (disposing)
{
+ //Send disconnect message if we're not already disconnecting
+ if (State == ConnectionState.Connected)
+ SendDisconnect();
+
lock (stateLock)
{
Listener.RemoveConnectionTo(RemoteEndPoint);