/// <example>
/// <code language="C#" source="DocInclude/TcpClientExample.cs"/>
/// </example>
- public event EventHandler<DataReceivedEventArgs> DataReceived;
+ public Action<DataReceivedEventArgs> DataReceived;
public int TestLagMs = -1;
{
get
{
- return state;
+ return this.state;
}
protected set
{
state = value;
-
if (state == ConnectionState.Connected)
connectWaitLock.Set();
else
connectWaitLock.Reset();
}
}
- volatile ConnectionState state;
+
+ protected ConnectionState state;
/// <summary>
/// Reset event that is triggered when the connection is marked Connected.
protected void InvokeDataReceived(MessageReader msg, SendOption sendOption, ushort reliableId)
{
//Make a copy to avoid race condition between null check and invocation
- EventHandler<DataReceivedEventArgs> handler = DataReceived;
+ Action<DataReceivedEventArgs> handler = DataReceived;
if (handler != null)
{
DataReceivedEventArgs args = DataReceivedEventArgs.GetObject();
args.Set(msg, sendOption, reliableId);
- handler.Invoke(this, args);
+ handler.Invoke(args);
}
else
{
/// <example>
/// <code language="C#" source="DocInclude/TcpListenerExample.cs"/>
/// </example>
- public event EventHandler<NewConnectionEventArgs> NewConnection;
+ public Action<NewConnectionEventArgs> NewConnection;
/// <summary>
/// Makes this connection listener begin listening for connections.
protected void InvokeNewConnection(MessageReader msg, Connection connection)
{
//Make a copy to avoid race condition between null check and invocation
- EventHandler<NewConnectionEventArgs> handler = NewConnection;
+ Action<NewConnectionEventArgs> handler = NewConnection;
if (handler != null)
{
NewConnectionEventArgs args = NewConnectionEventArgs.GetObject();
args.Set(msg, connection);
- handler(this, args);
+ handler(args);
}
else
{
/// </summary>
Socket socket;
- /// <summary>
- /// Object for locking the state.
- /// </summary>
- Object stateLock = new Object();
-
/// <summary>
/// The buffer to store incomming data in.
/// </summary>
{
InvokeDataSentRaw(bytes, length);
- lock (stateLock)
- {
- if (State != ConnectionState.Connected && State != ConnectionState.Connecting)
- throw new InvalidOperationException("Could not send data as this Connection is not connected and is not connecting. Did you disconnect?");
- }
+ if (State != ConnectionState.Connected && State != ConnectionState.Connecting)
+ throw new InvalidOperationException("Could not send data as this Connection is not connected and is not connecting. Did you disconnect?");
try
{
HandleDisconnect(he);
}
}
-
- /// <inheritdoc />
- protected override void WriteBytesToConnectionSync(byte[] bytes, int length)
- {
- InvokeDataSentRaw(bytes, length);
-
- lock (stateLock)
- {
- if (State != ConnectionState.Connected && State != ConnectionState.Connecting)
- throw new InvalidOperationException("Could not send data as this Connection is not connected and is not connecting. Did you disconnect?");
- }
-
- try
- {
- socket.SendTo(
- bytes,
- 0,
- length,
- SocketFlags.None,
- RemoteEndPoint
- );
- }
- catch (ObjectDisposedException)
- {
- //User probably called Disconnect in between this method starting and here so report the issue
- throw new InvalidOperationException("Could not send data as this Connection is not connected. Did you disconnect?");
- }
- catch (SocketException e)
- {
- HazelException he = new HazelException("Could not send data as a SocketException occured.", e);
- HandleDisconnect(he);
- throw he;
- }
- }
-
+
/// <inheritdoc />
public override void Connect(byte[] bytes = null, int timeout = 5000)
{
/// <inheritdoc />
public override void ConnectAsync(byte[] bytes = null, int timeout = 5000)
{
- lock (stateLock)
- {
if (State != ConnectionState.NotConnected)
throw new InvalidOperationException("Cannot connect as the Connection is already connected.");
- State = ConnectionState.Connecting;
- }
+ State = ConnectionState.Connecting;
//Begin listening
try
{
//If the socket's been disposed then we can just end there but make sure we're in NotConnected state.
//If we end up here I'm really lost...
- lock (stateLock)
- State = ConnectionState.NotConnected;
+ State = ConnectionState.NotConnected;
return;
}
catch (SocketException e)
//Write bytes to the server to tell it hi (and to punch a hole in our NAT, if present)
//When acknowledged set the state to connected
- SendHello(bytes, () => { lock (stateLock) State = ConnectionState.Connected; });
+ SendHello(bytes, () => { State = ConnectionState.Connected; });
}
/// <summary>
/// <inheritdoc />
protected override void HandleDisconnect(HazelException e = null)
{
- bool invoke = false;
-
- lock (stateLock)
+ if (State == ConnectionState.Connected)
{
- //Only invoke the disconnected event if we're not already disconnecting
- if (State == ConnectionState.Connected)
- {
- State = ConnectionState.Disconnecting;
- invoke = true;
- }
- }
+ State = ConnectionState.Disconnecting;
- //Invoke event outide lock if need be
- if (invoke)
- {
try
{
InvokeDisconnected(e);
}
catch { }
-
- Dispose();
}
+
+ Dispose();
}
/// <inheritdoc />
if (disposing)
{
//Send disconnect message if we're not already disconnecting
- bool connected;
- lock (stateLock)
- connected = State == ConnectionState.Connected;
-
- if (connected)
- SendDisconnect();
-
- //Dispose of the socket
- lock (stateLock)
+ if (State == ConnectionState.Connected)
+ {
State = ConnectionState.NotConnected;
+ try
+ {
+ SendDisconnect();
+ }
+ catch { }
+ }
}
if (socket != null)
{
try
{
- ReliableSend((byte)UdpSendOption.Hello); // TODO: Change to ping after server can handle it, before clients update
+ ReliableSend((byte)UdpSendOption.Ping);
Trace.WriteLine("Keepalive packet sent.");
}
catch
PacketPool.PutObject(this);
}
}
-
- private Timer reliableTimer;
- private int activePackets;
-
- private void InitializeReliableTimer()
- {
- reliableTimer = new Timer(ManageReliablePackets, null, 100, 100);
- }
-
- private void ManageReliablePackets(object state)
+
+ internal void ManageReliablePackets(object state)
{
if (this.reliableDataPacketsSent.Count > 0)
{
//Create packet object
Packet packet = Packet.GetObject();
- do
+ id = (ushort)Interlocked.Increment(ref lastIDAllocated);
+
+ if (!reliableDataPacketsSent.TryAdd(id, packet))
{
- id = (ushort)Interlocked.Increment(ref lastIDAllocated);
+ throw new Exception("That shouldn't be possible");
}
- while (!reliableDataPacketsSent.TryAdd(id, packet));
int timeout = resendTimeout > 0 ? resendTimeout : (int)Math.Max(50, Math.Min(AveragePingMs * 2, 1000));
{
if (reliableDataPacketsSent.TryRemove(p.Id, out self))
{
- Interlocked.Decrement(ref this.activePackets);
HandleDisconnect(new HazelException($"Reliable packet {self.Id} was not ack'd after {self.Retransmissions} resends"));
self.Recycle();
Packet packet;
if (reliableDataPacketsSent.TryRemove(id, out packet))
{
- Interlocked.Decrement(ref this.activePackets);
float rt = packet.Stopwatch.ElapsedMilliseconds;
packet.AckCallback?.Invoke();
void DisposeReliablePackets()
{
- this.reliableTimer.Dispose();
-
foreach (var kvp in reliableDataPacketsSent)
{
Packet pkt;
protected UdpConnection()
{
InitializeKeepAliveTimer();
- InitializeReliableTimer();
}
/// <summary>
/// </summary>
/// <param name="bytes">The bytes to write.</param>
protected abstract void WriteBytesToConnection(byte[] bytes, int length);
-
- /// <summary>
- /// Writes the given bytes to the connection synchronously.
- /// </summary>
- /// <param name="bytes">The bytes to write.</param>
- protected abstract void WriteBytesToConnectionSync(byte[] bytes, int length);
-
+
/// <inheritdoc/>
public override void Send(MessageWriter msg)
{
/// </summary>
public override void SendDisconnect()
{
- WriteBytesToConnectionSync(new byte[] { (byte)UdpSendOption.Disconnect }, 1);
+ WriteBytesToConnection(new byte[] { (byte)UdpSendOption.Disconnect }, 1);
}
/// <inheritdoc/>
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
private Action<string> Logger;
+ Timer reliablePacketTimer;
+
/// <summary>
/// The connections we currently hold
/// </summary>
this.listener = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
this.listener.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false);
}
+
+ reliablePacketTimer = new Timer(ManageReliablePackets, null, 100, Timeout.Infinite);
}
~UdpConnectionListener()
this.Dispose(false);
}
+ public float AveragePacketsTime = 1;
+ Stopwatch stopwatch = new Stopwatch();
+ private void ManageReliablePackets(object state)
+ {
+ stopwatch.Restart();
+ foreach (var kvp in this.allConnections)
+ {
+ kvp.Value.ManageReliablePackets(state);
+ }
+
+ this.AveragePacketsTime = this.AveragePacketsTime * .7f + stopwatch.ElapsedMilliseconds * .3f;
+
+ this.reliablePacketTimer.Change(100, Timeout.Infinite);
+ }
+
/// <inheritdoc />
public override void Start()
{
/// Called when data has been received by the listener.
/// </summary>
/// <param name="result">The asyncronous operation's result.</param>
-
+
public int ActiveListeners;
public int ActiveCallbacks;
void ReadCallback(IAsyncResult result)
// This thread suggests the IP is not passed out from WinSoc so maybe not possible
// http://stackoverflow.com/questions/2576926/python-socket-error-on-udp-data-receive-10054
message.Recycle();
+
+ UdpServerConnection dead;
+ if (this.allConnections.TryRemove(remoteEndPoint, out dead))
+ {
+ dead.Dispose();
+ }
+
StartListeningForData();
return;
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
- var keys = this.allConnections.Keys.ToArray();
- foreach (var k in keys)
+ foreach (var kvp in this.allConnections)
{
- UdpServerConnection conn;
- if (this.allConnections.TryGetValue(k, out conn))
- {
- conn.Dispose();
- }
+ kvp.Value.Dispose();
}
if (listener != null)
this.listener = null;
}
+ this.reliablePacketTimer.Dispose();
+
base.Dispose(disposing);
}
}
using System.Linq;
using System.Net;
using System.Text;
-
+using System.Threading;
namespace Hazel.Udp
{
Listener.SendData(bytes, length, RemoteEndPoint);
}
- /// <inheritdoc />
- protected override void WriteBytesToConnectionSync(byte[] bytes, int length)
- {
- InvokeDataSentRaw(bytes, length);
-
- lock (stateLock)
- {
- if (State != ConnectionState.Connected)
- throw new InvalidOperationException("Could not send data as this Connection is not connected. Did you disconnect?");
- }
-
- Listener.SendDataSync(bytes, length, RemoteEndPoint);
- }
-
/// <inheritdoc />
/// <remarks>
/// This will always throw a HazelException.
//Here we just need to inform the listener we no longer need data.
if (disposing)
{
- //Send disconnect message if we're not already disconnecting
- bool connected;
-
- lock (stateLock)
- connected = State == ConnectionState.Connected;
+ // Send disconnect message if we're not already disconnecting
+ if (this.state == ConnectionState.Connected)
+ {
+ try
+ {
+ SendDisconnect();
+ }
+ catch { }
+ this.state = ConnectionState.Disconnecting;
+ }
- if (connected)
- SendDisconnect();
-
Listener.RemoveConnectionTo(RemoteEndPoint);
- lock (stateLock)
- State = ConnectionState.NotConnected;
}
base.Dispose(disposing);