From 533f560f9cc5e19a847a2e01e74f02690eae0c02 Mon Sep 17 00:00:00 2001 From: Forest Date: Mon, 24 Dec 2018 23:46:06 -0800 Subject: [PATCH] Have I been making mistakes this whole time? --- Hazel/Connection.cs | 36 ++--- Hazel/Udp/UdpClientConnection.cs | 77 +++++----- Hazel/Udp/UdpConnection.KeepAlive.cs | 55 +++---- Hazel/Udp/UdpConnection.Reliable.cs | 133 ++++++++-------- Hazel/Udp/UdpConnection.cs | 42 ++++- Hazel/Udp/UdpConnectionListener.cs | 219 +++++++++++++++------------ Hazel/Udp/UdpServerConnection.cs | 68 +++------ 7 files changed, 324 insertions(+), 306 deletions(-) diff --git a/Hazel/Connection.cs b/Hazel/Connection.cs index 50014ba..d300db1 100644 --- a/Hazel/Connection.cs +++ b/Hazel/Connection.cs @@ -51,7 +51,7 @@ namespace Hazel public Action DataReceived; public int TestLagMs = -1; - + public event Action DataSentRaw; protected void InvokeDataSentRaw(byte[] data, int length) { @@ -106,7 +106,7 @@ namespace Hazel /// Connections go round 4 states in their lifetime: they start as to /// indicate they have no endpoint, calling takes them into /// , once they have received confirmation they are connected they enter - /// and finally calling sets them to + /// and finally calling sets them to /// and then the sequence repeats back to /// once disconnection is complete. /// @@ -128,8 +128,8 @@ namespace Hazel protected set { - state = value; - if (state == ConnectionState.Connected) + this.state = value; + if (this.state == ConnectionState.Connected) connectWaitLock.Set(); else connectWaitLock.Reset(); @@ -152,9 +152,8 @@ namespace Hazel /// protected Connection() { - Statistics = new ConnectionStatistics(); - - State = ConnectionState.NotConnected; + this.Statistics = new ConnectionStatistics(); + this.State = ConnectionState.NotConnected; } /// @@ -231,7 +230,7 @@ namespace Hazel /// /// Sends a disconnect message to the end point. /// - public abstract void SendDisconnect(); + protected abstract void SendDisconnect(); /// /// Invokes the DataReceived event. @@ -295,24 +294,11 @@ namespace Hazel } /// - /// Closes this connection safely. + /// 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. /// - /// - /// - /// Informs the end point of the connection that we are disconnecting from them and disposes of this - /// connection. - /// - /// - /// This calls and therefore sets straight to - /// . Once you call Close you will not be able to send any more - /// data using this connection and no more data will be received. - /// - /// - public virtual void Close() - { - Dispose(); - } - + public abstract void Disconnect(string reason); + /// /// Disposes of this NetworkConnection. /// diff --git a/Hazel/Udp/UdpClientConnection.cs b/Hazel/Udp/UdpClientConnection.cs index 0752d2c..fb3a86f 100644 --- a/Hazel/Udp/UdpClientConnection.cs +++ b/Hazel/Udp/UdpClientConnection.cs @@ -93,11 +93,11 @@ namespace Hazel.Udp } catch (ObjectDisposedException) { - HandleDisconnect("Could not send as the socket was disposed of."); + Disconnect("Could not send as the socket was disposed of."); } catch (SocketException) { - HandleDisconnect("Could not send data as a SocketException occured."); + Disconnect("Could not send data as a SocketException occured."); } }, null @@ -110,7 +110,35 @@ namespace Hazel.Udp } catch (SocketException) { - HandleDisconnect("Could not send data as a SocketException occured."); + Disconnect("Could not send data as a SocketException occured."); + throw; + } + } + + protected override void WriteBytesToConnectionSync(byte[] bytes, int length) + { + InvokeDataSentRaw(bytes, length); + + 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) + { + Disconnect("Could not send data as a SocketException occured."); throw; } } @@ -203,14 +231,14 @@ namespace Hazel.Udp } catch (SocketException e) { - HandleDisconnect("A socket exception occured while reading data."); + Disconnect("Socket exception while reading data: " + e.Message); return; } //Exit if no bytes read, we've failed. if (bytesReceived == 0) { - HandleDisconnect("Recieved 0 bytes"); + Disconnect("Received 0 bytes"); return; } @@ -225,7 +253,7 @@ namespace Hazel.Udp } catch (SocketException e) { - HandleDisconnect("A Socket exception occured while initiating a receive operation."); + Disconnect("Socket exception during receive: " + e.Message); } catch (ObjectDisposedException) { @@ -242,45 +270,24 @@ namespace Hazel.Udp HandleReceive(msg, bytesReceived); } - /// - protected override void HandleDisconnect(string e) - { - if (State == ConnectionState.Connected) - { - State = ConnectionState.Disconnecting; - - try - { - InvokeDisconnected(e); - } - catch { } - } - - Dispose(); - } - /// protected override void Dispose(bool disposing) { if (disposing) { - //Send disconnect message if we're not already disconnecting - if (State == ConnectionState.Connected) + if (this.state == ConnectionState.Connected + || this.state == ConnectionState.Disconnecting) { - State = ConnectionState.NotConnected; - try - { - SendDisconnect(); - } - catch { } + SendDisconnect(); + this.state = ConnectionState.NotConnected; } } - if (socket != null) + if (this.socket != null) { - socket.Close(); - socket.Dispose(); - socket = null; + this.socket.Close(); + this.socket.Dispose(); + this.socket = null; } this.reliablePacketTimer.Dispose(); diff --git a/Hazel/Udp/UdpConnection.KeepAlive.cs b/Hazel/Udp/UdpConnection.KeepAlive.cs index 8227a6f..a0d5f5a 100644 --- a/Hazel/Udp/UdpConnection.KeepAlive.cs +++ b/Hazel/Udp/UdpConnection.KeepAlive.cs @@ -47,37 +47,29 @@ namespace Hazel.Udp /// Timer keepAliveTimer; - /// - /// Lock for keep alive timer. - /// - Object keepAliveTimerLock = new Object(); - /// /// Starts the keepalive timer. /// void InitializeKeepAliveTimer() { - lock (keepAliveTimerLock) - { - keepAliveTimer = new Timer( - (o) => + keepAliveTimer = new Timer( + (o) => + { + try { - try - { - ReliableSend((byte)UdpSendOption.Ping); - Interlocked.Increment(ref KeepAlivesSent); - } - catch - { - Trace.WriteLine("Keepalive packet failed to send."); - DisposeKeepAliveTimer(); - } - }, - null, - keepAliveInterval, - keepAliveInterval - ); - } + ReliableSend((byte)UdpSendOption.Ping); + Interlocked.Increment(ref KeepAlivesSent); + } + catch + { + Trace.WriteLine("Keepalive packet failed to send."); + DisposeKeepAliveTimer(); + } + }, + null, + keepAliveInterval, + keepAliveInterval + ); } /// @@ -85,10 +77,11 @@ namespace Hazel.Udp /// void ResetKeepAliveTimer() { - lock (keepAliveTimerLock) + try { keepAliveTimer.Change(keepAliveInterval, keepAliveInterval); } + catch { } } /// @@ -96,13 +89,11 @@ namespace Hazel.Udp /// void DisposeKeepAliveTimer() { - lock (keepAliveTimerLock) + var timer = this.keepAliveTimer; + if (timer != null) { - if (keepAliveTimer != null) - { - keepAliveTimer.Dispose(); - keepAliveTimer = null; - } + this.keepAliveTimer = null; + timer.Dispose(); } } } diff --git a/Hazel/Udp/UdpConnection.Reliable.cs b/Hazel/Udp/UdpConnection.Reliable.cs index 23be344..f5e8968 100644 --- a/Hazel/Udp/UdpConnection.Reliable.cs +++ b/Hazel/Udp/UdpConnection.Reliable.cs @@ -28,6 +28,10 @@ namespace Hazel.Udp public int ResendTimeout { get { return resendTimeout; } set { resendTimeout = value; } } private volatile int resendTimeout = 0; + public volatile int ResendLimit = 0; + + public volatile float ResendPingMultiplier = 3; + /// /// Holds the last ID allocated. /// @@ -75,8 +79,7 @@ namespace Hazel.Udp /// connection will be marked as disconnected and the Disconnected event /// will be invoked. /// - public int DisconnectTimeout { get { return disconnectTimeout; } set { disconnectTimeout = value; } } - private volatile int disconnectTimeout = 2500; + public volatile int DisconnectTimeout = 2500; /// /// Class to hold packet data @@ -98,13 +101,13 @@ namespace Hazel.Udp } public ushort Id; - public byte[] Data; + private byte[] Data; + private UdpConnection Connection; + private int Length; - public DateTime LastSend; - public int LastTimeout; + public volatile int NextTimeout; public volatile bool Acknowledged; - private Func ResendAction; public Action AckCallback; public volatile int Retransmissions; @@ -114,30 +117,65 @@ namespace Hazel.Udp { } - internal void Set(ushort id, byte[] data, Func resendAction, int timeout, Action ackCallback) + internal void Set(ushort id, UdpConnection connection, byte[] data, int length, int timeout, Action ackCallback) { this.Id = id; this.Data = data; + this.Connection = connection; + this.Length = length; this.Acknowledged = false; - this.LastSend = DateTime.Now; - this.LastTimeout = timeout; - this.ResendAction = resendAction; - AckCallback = ackCallback; - Retransmissions = 0; + this.NextTimeout = timeout; + this.AckCallback = ackCallback; + this.Retransmissions = 0; - Stopwatch.Restart(); + this.Stopwatch.Restart(); } // Packets resent public int Resend() { - var evt = this.ResendAction; - if (!this.Acknowledged) + var connection = this.Connection; + if (!this.Acknowledged && connection != null) { - if (evt != null) + long lifetime = this.Stopwatch.ElapsedMilliseconds; + if (lifetime >= connection.DisconnectTimeout) { - return evt(this); + if (connection.reliableDataPacketsSent.TryRemove(this.Id, out Packet self)) + { + connection.Disconnect($"Reliable packet {self.Id} was not ack'd after {lifetime}ms"); + + self.Recycle(); + } + + return 0; + } + + if (lifetime >= this.NextTimeout) + { + if (++this.Retransmissions > connection.ResendLimit) + { + if (connection.reliableDataPacketsSent.TryRemove(this.Id, out Packet self)) + { + connection.Disconnect($"Reliable packet {self.Id} was not ack'd after {self.Retransmissions} resends"); + + self.Recycle(); + } + + return 0; + } + + this.NextTimeout = (int)Math.Min(this.NextTimeout * 3f, connection.DisconnectTimeout); + try + { + connection.WriteBytesToConnection(this.Data, this.Length); + return 1; + } + catch (InvalidOperationException) + { + //No longer connected + connection.Disconnect("Could not resend data as connection is no longer connected"); + } } } @@ -150,6 +188,7 @@ namespace Hazel.Udp public void Recycle() { this.Acknowledged = true; + this.Connection = null; PacketPool.PutObject(this); } @@ -164,17 +203,14 @@ namespace Hazel.Udp foreach (var kvp in this.reliableDataPacketsSent) { Packet pkt = kvp.Value; - double timeSinceLast = (DateTime.Now - pkt.LastSend).TotalMilliseconds; - if (timeSinceLast >= pkt.LastTimeout) - { + try { output += pkt.Resend(); } catch { } - } - minTimeout = Math.Min(pkt.LastTimeout, minTimeout); + minTimeout = Math.Min(pkt.NextTimeout, minTimeout); } } @@ -197,59 +233,22 @@ namespace Hazel.Udp id = (ushort)Interlocked.Increment(ref lastIDAllocated); - if (!reliableDataPacketsSent.TryAdd(id, packet)) - { - throw new Exception("That shouldn't be possible"); - } - - int timeout = resendTimeout > 0 ? resendTimeout : (int)Math.Max(50, Math.Min(AveragePingMs * 2, 1000)); - - //Write ID buffer[offset] = (byte)((id >> 8) & 0xFF); buffer[offset + 1] = (byte)id; packet.Set( id, + this, buffer, - (Packet p) => - { - // Callback for a previous packet - if (p.Acknowledged) return 0; - - p.LastSend = DateTime.Now; - p.LastTimeout = (int)Math.Min(p.LastTimeout * 1.5f, 3000); - - Packet self; - if (p.Stopwatch.ElapsedMilliseconds > this.disconnectTimeout) - { - if (reliableDataPacketsSent.TryRemove(p.Id, out self)) - { - HandleDisconnect($"Reliable packet {self.Id} was not ack'd after {self.Retransmissions} resends"); - - self.Recycle(); - } - - return 0; - } - - - try - { - WriteBytesToConnection(p.Data, sendLength); - p.Retransmissions++; - return 1; - } - catch (InvalidOperationException) - { - //No longer connected - HandleDisconnect("Could not resend data as connection is no longer connected"); - } - - return 0; - }, - timeout, + sendLength, + resendTimeout > 0 ? resendTimeout : (int)Math.Max(300, Math.Min(AveragePingMs * this.ResendPingMultiplier, 2000)), ackCallback ); + + if (!reliableDataPacketsSent.TryAdd(id, packet)) + { + throw new Exception("That shouldn't be possible"); + } } /// diff --git a/Hazel/Udp/UdpConnection.cs b/Hazel/Udp/UdpConnection.cs index 3d6c720..89e80e5 100644 --- a/Hazel/Udp/UdpConnection.cs +++ b/Hazel/Udp/UdpConnection.cs @@ -28,7 +28,13 @@ namespace Hazel.Udp /// /// The bytes to write. protected abstract void WriteBytesToConnection(byte[] bytes, int length); - + + /// + /// Writes the given bytes to the connection synchronously. + /// + /// The bytes to write. + protected abstract void WriteBytesToConnectionSync(byte[] bytes, int length); + /// public override void Send(MessageWriter msg) { @@ -168,7 +174,7 @@ namespace Hazel.Udp break; case (byte)UdpSendOption.Disconnect: - HandleDisconnect("The remote sent a disconnect request"); + Disconnect("The remote sent a disconnect request"); message.Recycle(); break; @@ -253,14 +259,40 @@ namespace Hazel.Udp /// Called when the socket has been disconnected at the remote host. /// /// The exception if one was the cause. - protected abstract void HandleDisconnect(string reason); + public override void Disconnect(string reason) + { + bool invoke = false; + lock (this) + { + if (this.state == ConnectionState.Connected) + { + this.state = ConnectionState.Disconnecting; + invoke = true; + } + } + + if (invoke) + { + try + { + InvokeDisconnected(reason); + } + catch { } + } + + this.Dispose(); + } /// /// Sends a disconnect message to the end point. /// - public override void SendDisconnect() + protected override void SendDisconnect() { - WriteBytesToConnection(new byte[] { (byte)UdpSendOption.Disconnect }, 1); + try + { + WriteBytesToConnectionSync(new byte[] { (byte)UdpSendOption.Disconnect }, 1); + } + catch { } } /// diff --git a/Hazel/Udp/UdpConnectionListener.cs b/Hazel/Udp/UdpConnectionListener.cs index 0c2239a..4c2584d 100644 --- a/Hazel/Udp/UdpConnectionListener.cs +++ b/Hazel/Udp/UdpConnectionListener.cs @@ -26,8 +26,10 @@ namespace Hazel.Udp /// /// The socket listening for connections. /// - Socket listener; - + Socket socket; + + private Action Logger; + Timer reliablePacketTimer; /// @@ -41,20 +43,21 @@ namespace Hazel.Udp /// Creates a new UdpConnectionListener for the given , port and . /// /// The endpoint to listen on. - public UdpConnectionListener(NetworkEndPoint endPoint) + public UdpConnectionListener(NetworkEndPoint endPoint, Action logger = null) { + this.Logger = logger; this.EndPoint = endPoint.EndPoint; this.IPMode = endPoint.IPMode; if (endPoint.IPMode == IPMode.IPv4) - this.listener = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); else { if (!Socket.OSSupportsIPv6) throw new HazelException("IPV6 not supported!"); - this.listener = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); - this.listener.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false); + this.socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); + this.socket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false); } reliablePacketTimer = new Timer(ManageReliablePackets, null, 100, Timeout.Infinite); @@ -77,7 +80,7 @@ namespace Hazel.Udp foreach (var kvp in this.allConnections) { var sock = kvp.Value; - PacketsResent += sock.ManageReliablePackets(state); + Interlocked.Add(ref PacketsResent, sock.ManageReliablePackets(state)); KeepAlives += Interlocked.Exchange(ref sock.KeepAlivesSent, 0); DuplicateRecieves += Interlocked.Exchange(ref sock.DuplicateRecieves, 0); } @@ -92,7 +95,7 @@ namespace Hazel.Udp { try { - listener.Bind(EndPoint); + socket.Bind(EndPoint); } catch (SocketException e) { @@ -114,7 +117,7 @@ namespace Hazel.Udp { message = MessageReader.GetSized(BufferSize); - listener.BeginReceiveFrom(message.Buffer, 0, message.Buffer.Length, SocketFlags.None, ref remoteEP, ReadCallback, message); + socket.BeginReceiveFrom(message.Buffer, 0, message.Buffer.Length, SocketFlags.None, ref remoteEP, ReadCallback, message); Interlocked.Increment(ref ActiveListeners); } catch (ObjectDisposedException) @@ -138,113 +141,133 @@ namespace Hazel.Udp public int ActiveListeners; public int ActiveCallbacks; + void ReadCallback(IAsyncResult result) { - var message = (MessageReader)result.AsyncState; Interlocked.Increment(ref ActiveCallbacks); + Interlocked.Decrement(ref ActiveListeners); - int bytesReceived; - EndPoint remoteEndPoint = new IPEndPoint(IPMode == IPMode.IPv4 ? IPAddress.Any : IPAddress.IPv6Any, 0); - - //End the receive operation try { - Interlocked.Decrement(ref ActiveListeners); - bytesReceived = listener.EndReceiveFrom(result, ref remoteEndPoint); - Interlocked.Add(ref BytesReceived, bytesReceived); - - message.Offset = 0; - message.Length = bytesReceived; - } - catch (NullReferenceException) - { - return; - } - catch (ObjectDisposedException) - { - //If the socket's been disposed then we can just end there. - return; - } - catch (SocketException) - { - // Client no longer reachable, pretend it didn't happen - // TODO should this not inform the connection this client is lost??? + var message = (MessageReader)result.AsyncState; + int bytesReceived; + EndPoint remoteEndPoint = new IPEndPoint(IPMode == IPMode.IPv4 ? IPAddress.Any : IPAddress.IPv6Any, 0); - // 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(); + //End the receive operation + try + { + bytesReceived = socket.EndReceiveFrom(result, ref remoteEndPoint); + Interlocked.Add(ref BytesReceived, bytesReceived); - UdpServerConnection dead; - if (this.allConnections.TryRemove(remoteEndPoint, out dead)) + message.Offset = 0; + message.Length = bytesReceived; + } + catch (NullReferenceException) + { + return; + } + catch (ObjectDisposedException) { - dead.Dispose(); + //If the socket's been disposed then we can just end there. + return; } + catch (SocketException) + { + // Client no longer reachable, pretend it didn't happen + // TODO should this not inform the connection this client is lost??? - StartListeningForData(); - return; - } + // 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(); - // Exit if no bytes read, we've closed. - if (bytesReceived == 0) - { - message.Recycle(); - return; - } + UdpServerConnection dead; + if (this.allConnections.TryRemove(remoteEndPoint, out dead)) + { + dead.Dispose(); + } - //Begin receiving again - StartListeningForData(); + StartListeningForData(); + return; + } - bool aware = true; - bool hasHelloByte = message.Buffer[0] == (byte)UdpSendOption.Hello; - bool isHello = hasHelloByte && message.Length >= MinConnectionLength; + // Exit if no bytes read, we've closed. + if (bytesReceived == 0) + { + message.Recycle(); + return; + } - //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)) - { - lock (this.allConnections) + //Begin receiving again + StartListeningForData(); + + bool aware = true; + bool hasHelloByte = message.Buffer[0] == (byte)UdpSendOption.Hello; + bool isHello = hasHelloByte && message.Length >= MinConnectionLength; + + //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)) { - aware = this.allConnections.TryGetValue(remoteEndPoint, out connection); - if (!aware) + //Check for malformed connection attempts + if (!isHello) { - //Check for malformed connection attempts - if (!isHello) - { - Interlocked.Decrement(ref ActiveCallbacks); - message.Recycle(); - return; - } + message.Recycle(); + return; + } - connection = new UdpServerConnection(this, remoteEndPoint, this.IPMode); - if (!this.allConnections.TryAdd(remoteEndPoint, connection)) + lock (this.allConnections) + { + aware = this.allConnections.TryGetValue(remoteEndPoint, out connection); + if (!aware) { - throw new Exception(); + connection = new UdpServerConnection(this, remoteEndPoint, this.IPMode); + if (!this.allConnections.TryAdd(remoteEndPoint, connection)) + { + throw new Exception(); + } } } } - } - //Inform the connection of the buffer (new connections need to send an ack back to client) - connection.HandleReceive(message, bytesReceived); + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + try + { + //Inform the connection of the buffer (new connections need to send an ack back to client) + connection.HandleReceive(message, bytesReceived); + } + finally + { + var el = stopwatch.ElapsedMilliseconds; + if (el > 5) + { + this.Logger?.Invoke($"Long Packet {el}ms = {string.Join(" ", message.Buffer.Take(bytesReceived))}"); + } + } - //If it's a new connection invoke the NewConnection event. - if (!aware) - { - // Skip header and hello byte; - message.Offset = 4; - message.Length = bytesReceived - 4; - message.Position = 0; - InvokeNewConnection(message, connection); + //If it's a new connection invoke the NewConnection event. + if (!aware) + { + // Skip header and hello byte; + message.Offset = 4; + message.Length = bytesReceived - 4; + message.Position = 0; + InvokeNewConnection(message, connection); + } + else if (isHello || (!isHello && hasHelloByte)) + { + message.Recycle(); + } } - else if (isHello || (!isHello && hasHelloByte)) + finally { - message.Recycle(); + Interlocked.Decrement(ref ActiveCallbacks); } - - Interlocked.Decrement(ref ActiveCallbacks); } + public int TestDropRate = -1; + private int dropCounter = 0; + /// /// Sends data from the listener socket. /// @@ -255,9 +278,17 @@ namespace Hazel.Udp if (length > bytes.Length) return; Interlocked.Add(ref BytesSent, length); + if (TestDropRate > 0) + { + if (Interlocked.Increment(ref dropCounter) % TestDropRate == 0) + { + return; + } + } + try { - listener.BeginSendTo( + socket.BeginSendTo( bytes, 0, length, @@ -267,7 +298,7 @@ namespace Hazel.Udp { try { - listener.EndSendTo(result); + socket.EndSendTo(result); } catch { } }, @@ -294,7 +325,7 @@ namespace Hazel.Udp { try { - listener.SendTo( + socket.SendTo( bytes, 0, length, @@ -330,11 +361,11 @@ namespace Hazel.Udp kvp.Value.Dispose(); } - if (listener != null) + if (this.socket != null) { - listener.Close(); - this.listener.Dispose(); - this.listener = null; + this.socket.Close(); + this.socket.Dispose(); + this.socket = null; } this.reliablePacketTimer.Dispose(); diff --git a/Hazel/Udp/UdpServerConnection.cs b/Hazel/Udp/UdpServerConnection.cs index 27f593c..1b8847e 100644 --- a/Hazel/Udp/UdpServerConnection.cs +++ b/Hazel/Udp/UdpServerConnection.cs @@ -25,7 +25,7 @@ namespace Hazel.Udp /// /// Lock object for the writing to the state of the connection. /// - Object stateLock = new Object(); + private ReaderWriterLockSlim stateLock = new ReaderWriterLockSlim(); /// /// Creates a UdpConnection for the virtual connection to the endpoint. @@ -49,15 +49,22 @@ namespace Hazel.Udp { 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?"); - } + if (State != ConnectionState.Connected) + throw new InvalidOperationException("Could not send data: Not connected."); Listener.SendData(bytes, length, RemoteEndPoint); } + /// + protected override void WriteBytesToConnectionSync(byte[] bytes, int length) + { + InvokeDataSentRaw(bytes, length); + + // No throw: As an internal interface, I want to try sending bytes whenever the I feel like it. + + Listener.SendDataSync(bytes, length, RemoteEndPoint); + } + /// /// /// This will always throw a HazelException. @@ -75,54 +82,19 @@ namespace Hazel.Udp { throw new HazelException("Cannot manually connect a UdpServerConnection, did you mean to use UdpClientConnection?"); } - - /// - protected override void HandleDisconnect(string reason) - { - bool invoke = false; - - lock (stateLock) - { - //Only invoke the disconnected event if we're not already disconnecting - if (State == ConnectionState.Connected) - { - State = ConnectionState.Disconnecting; - invoke = true; - } - } - - //Invoke event outide lock if need be - if (invoke) - { - try - { - InvokeDisconnected(reason); - } - catch { } - - Dispose(); - } - } - - /// + protected override void Dispose(bool disposing) { - //Here we just need to inform the listener we no longer need data. + Listener.RemoveConnectionTo(RemoteEndPoint); + if (disposing) { - // Send disconnect message if we're not already disconnecting - if (this.state == ConnectionState.Connected) + if (this.state == ConnectionState.Connected + || this.state == ConnectionState.Disconnecting) { - try - { - SendDisconnect(); - } - catch { } - this.state = ConnectionState.Disconnecting; + SendDisconnect(); + this.state = ConnectionState.NotConnected; } - - Listener.RemoveConnectionTo(RemoteEndPoint); - } base.Dispose(disposing); -- 2.39.5