protected set
{
this._state = value;
- if (this._state == ConnectionState.Connected)
- connectWaitLock.Set();
- else
- connectWaitLock.Reset();
+ this.SetState(value);
}
}
protected ConnectionState _state;
-
- /// <summary>
- /// Reset event that is triggered when the connection is marked Connected.
- /// </summary>
- private ManualResetEvent connectWaitLock = new ManualResetEvent(false);
-
+ protected virtual void SetState(ConnectionState state) { }
+
/// <summary>
/// Constructor that initializes the ConnecitonStatistics object.
/// </summary>
}
}
- /// <summary>
- /// Blocks until the Connection is connected.
- /// </summary>
- /// <param name="timeout">The number of milliseconds to wait before timing out.</param>
- protected bool WaitOnConnect(int timeout)
- {
- return connectWaitLock.WaitOne(timeout);
- }
-
/// <summary>
/// For times when you want to force the disconnect handler to fire as well as close it.
/// If you only want to close it, just use Dispose.
{
this.DataReceived = null;
this.Disconnected = null;
- this.connectWaitLock.Dispose();
}
}
}
}
/// <summary>
- /// The number of messages sent larger than 1400 bytes. This is smaller than most default MTUs.
+ /// The number of messages sent larger than 576 bytes. This is smaller than most default MTUs.
/// </summary>
/// <remarks>
/// This is the number of unreliable messages that were sent from the <see cref="Connection"/>, incremented
}
/// <summary>
- /// The number of messages sent larger than 1400 bytes.
+ /// The number of messages sent larger than 576 bytes.
/// </summary>
int fragmentableMessagesSent;
Interlocked.Add(ref dataBytesSent, dataLength);
Interlocked.Add(ref totalBytesSent, totalLength);
- if (totalLength > 1400)
+ if (totalLength > 576)
{
Interlocked.Increment(ref fragmentableMessagesSent);
}
<Compile Include="ConnectionStatistics.cs" />
<Compile Include="Udp\UdpBroadcaster.cs" />
<Compile Include="Udp\UdpBroadcastListener.cs" />
+ <Compile Include="Udp\UnityUdpClientConnection.cs" />
<Compile Include="Udp\UdpClientConnection.cs" />
<Compile Include="Udp\UdpConnection.cs">
<SubType>Code</SubType>
{
/// <summary>
/// The data received from the client in the handshake.
+ /// This data is yours. Remember to recycle it.
/// </summary>
public readonly MessageReader HandshakeData;
try
{
EndPoint endpt = new IPEndPoint(IPAddress.Any, 0);
- var result = this.socket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref endpt, this.HandleData, null);
- if (result.CompletedSynchronously)
- {
- ThreadPool.QueueUserWorkItem(_ => this.HandleData(result));
- }
+ this.socket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref endpt, this.HandleData, null);
}
catch (NullReferenceException) { }
catch (Exception e)
/// </summary>
private Socket socket;
+ /// <summary>
+ /// Reset event that is triggered when the connection is marked Connected.
+ /// </summary>
+ private ManualResetEvent connectWaitLock = new ManualResetEvent(false);
+
private Timer reliablePacketTimer;
#if DEBUG
this.RemoteEndPoint = remoteEndPoint;
this.IPMode = ipMode;
- if (this.IPMode == IPMode.IPv4)
- socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
- else
- {
- if (!Socket.OSSupportsIPv6)
- throw new InvalidOperationException("IPV6 not supported!");
-
- socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
- socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
- }
+ this.socket = CreateSocket(ipMode);
reliablePacketTimer = new Timer(ManageReliablePacketsInternal, null, 100, Timeout.Infinite);
}
catch
{
msg.Recycle();
+ this.Dispose();
}
}
+ protected override void SetState(ConnectionState state)
+ {
+ if (state == ConnectionState.Connected)
+ connectWaitLock.Set();
+ else
+ connectWaitLock.Reset();
+ }
+
+ /// <summary>
+ /// Blocks until the Connection is connected.
+ /// </summary>
+ /// <param name="timeout">The number of milliseconds to wait before timing out.</param>
+ public bool WaitOnConnect(int timeout)
+ {
+ return connectWaitLock.WaitOne(timeout);
+ }
+
/// <summary>
/// Called when data has been received by the socket.
/// </summary>
try { this.socket.Dispose(); } catch { }
this.reliablePacketTimer.Dispose();
+ this.connectWaitLock.Dispose();
base.Dispose(disposing);
}
using System;
+using System.Net.Sockets;
namespace Hazel.Udp
{
/// <inheritdoc />
public abstract partial class UdpConnection : NetworkConnection
{
+ private const int SioUdpConnectionReset = -1744830452;
+
public static readonly byte[] EmptyDisconnectBytes = new byte[] { (byte)UdpSendOption.Disconnect };
+ internal static Socket CreateSocket(IPMode ipMode)
+ {
+ Socket socket;
+ if (ipMode == IPMode.IPv4)
+ {
+ socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+ }
+ else
+ {
+ if (!Socket.OSSupportsIPv6)
+ throw new InvalidOperationException("IPV6 not supported!");
+
+ socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
+ socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
+ }
+
+ try
+ {
+ socket.DontFragment = false;
+ }
+ catch { }
+
+
+ try
+ {
+ const int SIO_UDP_CONNRESET = -1744830452;
+ socket.IOControl(SIO_UDP_CONNRESET, new byte[1], null);
+ }
+ catch { } // Only necessary on Windows
+
+ return socket;
+ }
+
/// <summary>
/// Writes the given bytes to the connection.
/// </summary>
/// <inheritdoc />
public class UdpConnectionListener : NetworkConnectionListener
{
- public const int BufferSize = ushort.MaxValue;
-
- public int MinConnectionLength = 0;
-
- public delegate bool AcceptConnectionCheck(IPEndPoint endPoint, byte[] input, out byte[] response);
- public AcceptConnectionCheck AcceptConnection;
+ private const int SendReceiveBufferSize = 1024 * 1024;
+ private const int BufferSize = ushort.MaxValue;
/// <summary>
- /// The socket listening for connections.
+ /// A callback for early connection rejection.
+ /// * Return false to reject connection.
+ /// * A null response is ok, we just won't send anything.
/// </summary>
- Socket socket;
+ public AcceptConnectionCheck AcceptConnection;
+ public delegate bool AcceptConnectionCheck(IPEndPoint endPoint, byte[] input, out byte[] response);
+ private Socket socket;
private Action<string> Logger;
+ private Timer reliablePacketTimer;
- Timer reliablePacketTimer;
-
- /// <summary>
- /// The connections we currently hold
- /// </summary>
private ConcurrentDictionary<EndPoint, UdpServerConnection> allConnections = new ConcurrentDictionary<EndPoint, UdpServerConnection>();
public int ConnectionCount { get { return this.allConnections.Count; } }
this.EndPoint = endPoint;
this.IPMode = ipMode;
- if (this.IPMode == IPMode.IPv4)
- this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
- else
- {
- if (!Socket.OSSupportsIPv6)
- throw new HazelException("IPV6 not supported!");
+ this.socket = UdpConnection.CreateSocket(this.IPMode);
- this.socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
- this.socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
- }
-
- socket.ReceiveBufferSize = BufferSize;
- socket.SendBufferSize = BufferSize;
+ socket.ReceiveBufferSize = SendReceiveBufferSize;
+ socket.SendBufferSize = SendReceiveBufferSize;
reliablePacketTimer = new Timer(ManageReliablePackets, null, 100, Timeout.Infinite);
}
{
message = MessageReader.GetSized(BufferSize);
- var result = socket.BeginReceiveFrom(message.Buffer, 0, message.Buffer.Length, SocketFlags.None, ref remoteEP, ReadCallback, message);
- if (result.CompletedSynchronously)
- {
- this.Logger("Operation completed synchronously");
- }
+ socket.BeginReceiveFrom(message.Buffer, 0, message.Buffer.Length, SocketFlags.None, ref remoteEP, ReadCallback, message);
+ }
+ catch (ObjectDisposedException)
+ {
+ message.Recycle();
+ return;
}
catch (SocketException sx)
{
}
catch (Exception ex)
{
- //If the socket's been disposed then we can just end there.
message.Recycle();
this.Logger?.Invoke("Stopped due to: " + ex.Message);
return;
}
}
- public volatile int ActiveCallbacks;
void ReadCallback(IAsyncResult result)
{
- Interlocked.Increment(ref this.ActiveCallbacks);
var message = (MessageReader)result.AsyncState;
int bytesReceived;
EndPoint remoteEndPoint = new IPEndPoint(IPMode == IPMode.IPv4 ? IPAddress.Any : IPAddress.IPv6Any, 0);
message.Offset = 0;
message.Length = bytesReceived;
}
+ catch (ObjectDisposedException)
+ {
+ message.Recycle();
+ return;
+ }
+ catch (InvalidOperationException) { return; } // Callback called twice, somehow...
catch (SocketException sx)
{
// Client no longer reachable, pretend it didn't happen
// 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();
- this.Logger?.Invoke("Socket Ex in ReadCallback: " + sx.Message);
+ this.Logger?.Invoke($"Socket Ex {sx.SocketErrorCode} in ReadCallback: {sx.Message}");
Thread.Sleep(10);
StartListeningForData();
- Interlocked.Decrement(ref this.ActiveCallbacks);
return;
}
catch (Exception ex)
//If the socket's been disposed then we can just end there.
message.Recycle();
this.Logger?.Invoke("Stopped due to: " + ex.Message);
- Interlocked.Decrement(ref this.ActiveCallbacks);
return;
}
this.Logger?.Invoke("Received 0 bytes");
Thread.Sleep(10);
StartListeningForData();
- Interlocked.Decrement(ref this.ActiveCallbacks);
return;
}
StartListeningForData();
bool aware = true;
- bool hasHelloByte = message.Buffer[0] == (byte)UdpSendOption.Hello;
- bool isHello = hasHelloByte && message.Length >= MinConnectionLength;
+ bool isHello = message.Buffer[0] == (byte)UdpSendOption.Hello;
- //If we're aware of this connection use the one already
- //If this is a new client then connect with them!
+ // If we're aware of this connection use the one already
+ // If this is a new client then connect with them!
UdpServerConnection connection;
if (!this.allConnections.TryGetValue(remoteEndPoint, out connection))
{
{
if (!this.allConnections.TryGetValue(remoteEndPoint, out connection))
{
- //Check for malformed connection attempts
+ // Check for malformed connection attempts
if (!isHello)
{
message.Recycle();
- Interlocked.Decrement(ref this.ActiveCallbacks);
return;
}
if (!AcceptConnection((IPEndPoint)remoteEndPoint, message.Buffer, out var response))
{
message.Recycle();
- SendData(response, response.Length, remoteEndPoint);
- Interlocked.Decrement(ref this.ActiveCallbacks);
+ if (response != null)
+ {
+ SendData(response, response.Length, remoteEndPoint);
+ }
+
return;
}
}
message.Position = 0;
InvokeNewConnection(message, connection);
}
- else if (isHello || (!isHello && hasHelloByte))
+ else if (isHello)
{
message.Recycle();
}
-
- Interlocked.Decrement(ref this.ActiveCallbacks);
}
#if DEBUG
SocketFlags.None,
endPoint,
SendCallback,
- null
- );
+ null);
}
catch (SocketException e)
{
--- /dev/null
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+
+
+namespace Hazel.Udp
+{
+ /// <summary>
+ /// Represents a client's connection to a server that uses the UDP protocol.
+ /// </summary>
+ /// <inheritdoc/>
+ public class UnityUdpClientConnection : UdpConnection
+ {
+ /// <summary>
+ /// The socket we're connected via.
+ /// </summary>
+ private Socket socket;
+
+ /// <summary>
+ /// Creates a new UdpClientConnection.
+ /// </summary>
+ /// <param name="remoteEndPoint">A <see cref="NetworkEndPoint"/> to connect to.</param>
+ public UnityUdpClientConnection(IPEndPoint remoteEndPoint, IPMode ipMode = IPMode.IPv4)
+ : base()
+ {
+ this.EndPoint = remoteEndPoint;
+ this.RemoteEndPoint = remoteEndPoint;
+ this.IPMode = ipMode;
+
+ this.socket = CreateSocket(ipMode);
+ }
+
+ ~UnityUdpClientConnection()
+ {
+ this.Dispose(false);
+ }
+
+ public void FixedUpdate()
+ {
+ base.ManageReliablePackets();
+ }
+
+ /// <inheritdoc />
+ protected override void WriteBytesToConnection(byte[] bytes, int length)
+ {
+ try
+ {
+ socket.BeginSendTo(
+ bytes,
+ 0,
+ length,
+ SocketFlags.None,
+ RemoteEndPoint,
+ HandleSendTo,
+ null);
+ }
+ catch (NullReferenceException) { }
+ catch (ObjectDisposedException)
+ {
+ // Already disposed and disconnected...
+ }
+ catch (SocketException ex)
+ {
+ Disconnect("Could not send data as a SocketException occurred: " + ex.Message);
+ }
+ }
+
+ private void HandleSendTo(IAsyncResult result)
+ {
+ try
+ {
+ socket.EndSendTo(result);
+ }
+ catch (NullReferenceException) { }
+ catch (ObjectDisposedException)
+ {
+ // Already disposed and disconnected...
+ }
+ catch (SocketException ex)
+ {
+ Disconnect("Could not send data as a SocketException occurred: " + ex.Message);
+ }
+ }
+
+ public override void Connect(byte[] bytes = null, int timeout = 5000)
+ {
+ throw new NotImplementedException("Use ConnectAsync and check State != ConnectionState.Connecting instead.");
+ }
+
+ /// <inheritdoc />
+ public override void ConnectAsync(byte[] bytes = null, int timeout = 5000)
+ {
+ this.State = ConnectionState.Connecting;
+
+ try
+ {
+ if (IPMode == IPMode.IPv4)
+ socket.Bind(new IPEndPoint(IPAddress.Any, 0));
+ else
+ socket.Bind(new IPEndPoint(IPAddress.IPv6Any, 0));
+ }
+ catch (SocketException e)
+ {
+ this.State = ConnectionState.NotConnected;
+ throw new HazelException("A SocketException occurred while binding to the port.", e);
+ }
+
+ try
+ {
+ StartListeningForData();
+ }
+ catch (ObjectDisposedException)
+ {
+ // 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...
+ this.State = ConnectionState.NotConnected;
+ return;
+ }
+ catch (SocketException e)
+ {
+ Dispose();
+ throw new HazelException("A SocketException occurred while initiating a receive operation.", 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, () =>
+ {
+ this.State = ConnectionState.Connected;
+ this.InitializeKeepAliveTimer();
+ });
+ }
+
+ /// <summary>
+ /// Instructs the listener to begin listening.
+ /// </summary>
+ void StartListeningForData()
+ {
+ var msg = MessageReader.GetSized(ushort.MaxValue);
+ try
+ {
+ socket.BeginReceive(msg.Buffer, 0, msg.Buffer.Length, SocketFlags.None, ReadCallback, msg);
+ }
+ catch
+ {
+ msg.Recycle();
+ this.Dispose();
+ }
+ }
+
+ /// <summary>
+ /// Called when data has been received by the socket.
+ /// </summary>
+ /// <param name="result">The asyncronous operation's result.</param>
+ void ReadCallback(IAsyncResult result)
+ {
+ var msg = (MessageReader)result.AsyncState;
+
+ try
+ {
+ msg.Length = socket.EndReceive(result);
+ }
+ catch (SocketException e)
+ {
+ msg.Recycle();
+ Disconnect("Socket exception while reading data: " + e.Message);
+ return;
+ }
+ catch (Exception)
+ {
+ msg.Recycle();
+ return;
+ }
+
+ //Exit if no bytes read, we've failed.
+ if (msg.Length == 0)
+ {
+ msg.Recycle();
+ Disconnect("Received 0 bytes");
+ return;
+ }
+
+ //Begin receiving again
+ try
+ {
+ StartListeningForData();
+ }
+ catch (SocketException e)
+ {
+ Disconnect("Socket exception during receive: " + e.Message);
+ }
+ catch (ObjectDisposedException)
+ {
+ //If the socket's been disposed then we can just end there.
+ return;
+ }
+
+ HandleReceive(msg, msg.Length);
+ }
+
+ /// <summary>
+ /// Sends a disconnect message to the end point.
+ /// You may include optional disconnect data. The SendOption must be unreliable.
+ /// </summary>
+ protected override bool SendDisconnect(MessageWriter data = null)
+ {
+ lock (this)
+ {
+ if (this._state == ConnectionState.NotConnected) return false;
+ this._state = ConnectionState.NotConnected;
+ }
+
+ var bytes = EmptyDisconnectBytes;
+ if (data != null && data.Length > 0)
+ {
+ if (data.SendOption != SendOption.None) throw new ArgumentException("Disconnect messages can only be unreliable.");
+
+ bytes = data.ToByteArray(true);
+ bytes[0] = (byte)UdpSendOption.Disconnect;
+ }
+
+ try
+ {
+ socket.SendTo(
+ bytes,
+ 0,
+ bytes.Length,
+ SocketFlags.None,
+ RemoteEndPoint);
+ }
+ catch { }
+
+ return true;
+ }
+
+ /// <inheritdoc />
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ SendDisconnect();
+ }
+
+ try { this.socket.Shutdown(SocketShutdown.Both); } catch { }
+ try { this.socket.Close(); } catch { }
+ try { this.socket.Dispose(); } catch { }
+
+ base.Dispose(disposing);
+ }
+ }
+}
\ No newline at end of file