/// <summary>
/// Class to hold packet data
/// </summary>
- public class Packet : IRecyclable, IDisposable
+ public class Packet : IRecyclable
{
/// <summary>
/// Object pool for this event.
public ushort Id;
public byte[] Data;
- public Timer Timer;
- public volatile int LastTimeout;
+
+ public DateTime LastSend;
+ public int LastTimeout;
+ public volatile bool Acknowledged;
+
+ private Action<Packet> ResendAction;
public Action AckCallback;
+
public volatile int Retransmissions;
public Stopwatch Stopwatch = new Stopwatch();
Packet()
{
-
}
internal void Set(ushort id, byte[] data, Action<Packet> resendAction, int timeout, Action ackCallback)
{
this.Id = id;
this.Data = data;
-
- this.Timer = new Timer(
- (object obj) => resendAction(this),
- null,
- timeout,
- Timeout.Infinite
- );
-
- LastTimeout = timeout;
+
+ this.Acknowledged = false;
+ this.LastSend = DateTime.Now;
+ this.LastTimeout = timeout;
+ this.ResendAction = resendAction;
AckCallback = ackCallback;
Retransmissions = 0;
- Stopwatch.Reset();
- Stopwatch.Start();
+ Stopwatch.Restart();
}
- /// <summary>
- /// Returns this object back to the object pool from whence it came.
- /// </summary>
- public void Recycle()
+ public void Resend()
{
- lock (this)
+ var evt = this.ResendAction;
+ if (!this.Acknowledged)
{
- if (this.Timer != null)
+ if (evt != null)
{
- this.Id = (ushort)(this.Id - 1);
- this.Timer.Dispose();
- this.Timer = null;
+ evt(this);
}
}
-
- PacketPool.PutObject(this);
}
/// <summary>
- /// Disposes of this object.
+ /// Returns this object back to the object pool from whence it came.
/// </summary>
- public void Dispose()
+ public void Recycle()
{
- Dispose(true);
- GC.SuppressFinalize(this);
+ this.Acknowledged = true;
+
+ PacketPool.PutObject(this);
}
+ }
+
+ private Timer reliableTimer;
+ private int activePackets;
- protected void Dispose(bool disposing)
+ private void InitializeReliableTimer()
+ {
+ reliableTimer = new Timer(ManageReliablePackets, null, 100, 100);
+ }
+
+ private void ManageReliablePackets(object state)
+ {
+ if (this.reliableDataPacketsSent.Count > 0)
{
- if (disposing)
+ double minTimeout = int.MaxValue;
+ foreach (var kvp in this.reliableDataPacketsSent)
{
- lock (this)
+ Packet pkt = kvp.Value;
+ double timeSinceLast = (DateTime.Now - pkt.LastSend).TotalMilliseconds;
+ if (timeSinceLast >= pkt.LastTimeout)
{
- if (this.Timer != null)
+ try
{
- this.Id = (ushort)(this.Id - 1);
- this.Timer.Dispose();
- this.Timer = null;
+ pkt.Resend();
}
+ catch { }
}
+
+ minTimeout = Math.Min(pkt.LastTimeout, minTimeout);
}
}
}
/// <param name="ackCallback">The callback to make once the packet has been acknowledged.</param>
void AttachReliableID(byte[] buffer, int offset, int sendLength, Action ackCallback = null)
{
- //Find and reliable ID
- lock (reliableDataPacketsSent)
+ //Find an ID not used yet.
+ ushort id;
+
+ //Create packet object
+ Packet packet = Packet.GetObject();
+
+ do
{
- //Find an ID not used yet.
- ushort id;
+ id = (ushort)Interlocked.Increment(ref lastIDAllocated);
+ }
+ while (!reliableDataPacketsSent.TryAdd(id, packet));
+
+ int timeout = resendTimeout > 0 ? resendTimeout : (int)Math.Max(50, Math.Min(AveragePingMs * 2, 1000));
- //Create packet object
- Packet packet = Packet.GetObject();
+ //Write ID
+ buffer[offset] = (byte)((id >> 8) & 0xFF);
+ buffer[offset + 1] = (byte)id;
- do
+ packet.Set(
+ id,
+ buffer,
+ (Packet p) =>
{
- id = (ushort)Interlocked.Increment(ref lastIDAllocated);
- }
- while (!reliableDataPacketsSent.TryAdd(id, packet));
+ // Callback for a previous packet
+ if (p.Acknowledged) return;
- //Write ID
- buffer[offset] = (byte)((id >> 8) & 0xFF);
- buffer[offset + 1] = (byte)id;
+ p.LastSend = DateTime.Now;
- packet.Set(
- id,
- buffer,
- (Packet p) =>
+ Packet self;
+ if (p.Stopwatch.ElapsedMilliseconds > this.disconnectTimeout)
{
- Packet self;
- if (p.Stopwatch.ElapsedMilliseconds > this.disconnectTimeout)
+ if (reliableDataPacketsSent.TryRemove(p.Id, out self))
{
- if (reliableDataPacketsSent.TryRemove(p.Id, out self))
- {
- HandleDisconnect(new HazelException($"Reliable packet {self.Id} was not ack'd after {self.Retransmissions} resends"));
-
- self.Recycle();
- }
+ Interlocked.Decrement(ref this.activePackets);
+ HandleDisconnect(new HazelException($"Reliable packet {self.Id} was not ack'd after {self.Retransmissions} resends"));
- return;
+ self.Recycle();
}
- lock (p)
- {
- // Callback for a previous packet
- if (p.Id != id) return;
+ return;
+ }
- // Backoff retry frequency to avoid congestion
- p.LastTimeout = (int)Math.Min(p.LastTimeout * 1.25f, 2000);
- p.Timer.Change(p.LastTimeout, Timeout.Infinite);
- }
+ // Backoff retry frequency to avoid congestion
+ p.LastTimeout = (int)Math.Min(p.LastTimeout * 1.25f, 1000);
- try
- {
- WriteBytesToConnection(p.Data, sendLength);
- p.Retransmissions++;
- }
- catch (InvalidOperationException e)
- {
- //No longer connected
- HandleDisconnect(new HazelException("Could not resend data as connection is no longer connected", e));
- }
+ try
+ {
+ WriteBytesToConnection(p.Data, sendLength);
+ p.Retransmissions++;
+ }
+ catch (InvalidOperationException e)
+ {
+ //No longer connected
+ HandleDisconnect(new HazelException("Could not resend data as connection is no longer connected", e));
+ }
- Trace.WriteLine("Resend.");
- },
- resendTimeout > 0 ? resendTimeout : (int)Math.Max(500, Math.Min(AveragePingMs * 4, 2000)),
- ackCallback
- );
- }
+ Trace.WriteLine("Resend.");
+ },
+ timeout,
+ ackCallback
+ );
}
/// <summary>
Packet packet;
if (reliableDataPacketsSent.TryRemove(id, out packet))
{
+ Interlocked.Decrement(ref this.activePackets);
float rt = packet.Stopwatch.ElapsedMilliseconds;
packet.AckCallback?.Invoke();
void DisposeReliablePackets()
{
- var keys = this.reliableDataPacketsSent.Keys.ToArray();
- foreach (var k in keys)
+ this.reliableTimer.Dispose();
+
+ foreach (var kvp in reliableDataPacketsSent)
{
Packet pkt;
- if (this.reliableDataPacketsSent.TryRemove(k, out pkt))
+ if (this.reliableDataPacketsSent.TryRemove(kvp.Key, out pkt))
{
pkt.Recycle();
}
/// <inheritdoc />
public class UdpConnectionListener : NetworkConnectionListener
{
+ public long BytesReceived;
+ public long BytesSent;
+
public const int BufferSize = ushort.MaxValue / 4;
public int MinConnectionLength = 0;
ConcurrentDictionary<EndPoint, UdpServerConnection> allConnections = new ConcurrentDictionary<EndPoint, UdpServerConnection>();
public int ConnectionCount { get { return this.allConnections.Count; } }
- /// <summary>
- /// Creates a new UdpConnectionListener for the given <see cref="IPAddress"/>, port and <see cref="IPMode"/>.
- /// </summary>
- /// <param name="IPAddress">The IPAddress to listen on.</param>
- /// <param name="port">The port to listen on.</param>
- /// <param name="mode">The <see cref="IPMode"/> to listen with.</param>
- [Obsolete("Temporary constructor in beta only, use NetworkEndPoint constructor instead.")]
- public UdpConnectionListener(IPAddress IPAddress, int port, Action<string> logger, IPMode mode = IPMode.IPv4)
- : this (new NetworkEndPoint(IPAddress, port, mode))
- {
- this.Logger = logger;
- }
/// <summary>
/// Creates a new UdpConnectionListener for the given <see cref="IPAddress"/>, port and <see cref="IPMode"/>.
/// </summary>
/// <param name="endPoint">The endpoint to listen on.</param>
- public UdpConnectionListener(NetworkEndPoint endPoint)
+ public UdpConnectionListener(NetworkEndPoint endPoint, Action<string> logger = null)
{
+ this.Logger = logger;
this.EndPoint = endPoint.EndPoint;
this.IPMode = endPoint.IPMode;
{
Interlocked.Decrement(ref ActiveListeners);
bytesReceived = listener.EndReceiveFrom(result, ref remoteEndPoint);
+ Interlocked.Add(ref BytesReceived, bytesReceived);
+
message.Offset = 0;
message.Length = bytesReceived;
}
internal void SendData(byte[] bytes, int length, EndPoint endPoint)
{
if (length > bytes.Length) return;
+ Interlocked.Add(ref BytesSent, length);
try
{