From dc3a00f671d3f4789c224671df50f4e06a8c6560 Mon Sep 17 00:00:00 2001 From: Forest Date: Thu, 25 Jul 2019 14:47:08 -0700 Subject: [PATCH] Lots of code clean up, fixed an issue were a latent connection could cause packet spamming --- Hazel/Connection.cs | 41 +++------------ Hazel/ConnectionListener.cs | 4 +- Hazel/DisconnectedEventArgs.cs | 26 ++-------- Hazel/MessageWriter.cs | 33 ++++++++++++ Hazel/NetworkConnection.cs | 4 +- Hazel/ObjectPool.cs | 8 ++- Hazel/Udp/SendOptionInternal.cs | 2 +- Hazel/Udp/UdpClientConnection.cs | 38 ++++++-------- Hazel/Udp/UdpConnection.KeepAlive.cs | 26 ++++------ Hazel/Udp/UdpConnection.Reliable.cs | 20 ++++---- Hazel/Udp/UdpConnection.cs | 10 +--- Hazel/Udp/UdpConnectionListener.cs | 75 +++++++++++++++------------- Hazel/Udp/UdpServerConnection.cs | 2 +- 13 files changed, 133 insertions(+), 156 deletions(-) diff --git a/Hazel/Connection.cs b/Hazel/Connection.cs index 3615c51..50daf44 100644 --- a/Hazel/Connection.cs +++ b/Hazel/Connection.cs @@ -94,22 +94,8 @@ namespace Hazel /// The state of this connection. /// /// - /// - /// 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 then the sequence repeats back to - /// once disconnection is complete. - /// - /// - /// Data can only be sent while in and all attempts to send data when - /// in any other state will throw an InvalidOperationException. - /// - /// - /// All implementers should be aware that when this is set to it will - /// release all threads that are blocked on . - /// + /// All implementers should be aware that when this is set to ConnectionState.Connected it will + /// release all threads that are blocked on . /// public ConnectionState State { @@ -179,27 +165,19 @@ namespace Hazel /// /// Connects the connection to a server and begins listening. + /// This method blocks and may thrown if there is a problem connecting. /// /// The bytes of data to send in the handshake. /// The number of milliseconds to wait before giving up on the connect attempt. - /// - /// Calling Connect makes the connection attempt to connect to the end point that's specified in the - /// constructor. This method will block until the connection attempt completes and will throw a - /// if there is a problem connecting. - /// public abstract void Connect(byte[] bytes = null, int timeout = 5000); /// /// Connects the connection to a server and begins listening. + /// This method does not block. /// /// The bytes of data to send in the handshake. /// The number of milliseconds to wait before giving up on the connect attempt. - /// - /// Calling Connect makes the connection attempt to connect to the end point that's specified in the - /// constructor. This method will block until the connection attempt completes and will throw a - /// if there is a problem connecting. - /// public abstract void ConnectAsync(byte[] bytes = null, int timeout = 5000); /// @@ -229,11 +207,11 @@ namespace Hazel /// /// Invokes the Disconnected event. /// - /// The exception, if any, that occured to cause this. + /// The exception, if any, that occurred to cause this. /// Extra disconnect data /// /// Invokes the event to alert subscribres this connection has been disconnected either - /// by the end point or because an error occured. If an error occured the error should be passed in in order to + /// by the end point or because an error occurred. If an error occurred the error should be passed in in order to /// pass to the subscribers, otherwise null can be passed in. /// protected void InvokeDisconnected(string e, MessageReader reader) @@ -251,11 +229,6 @@ namespace Hazel /// Blocks until the Connection is connected. /// /// The number of milliseconds to wait before timing out. - /// - /// This is a helper method for waiting until the connection is connected. It will block until the - /// property is set to allowing the main thread to - /// wait until specific data is received etc. before returning to the user's code. - /// protected bool WaitOnConnect(int timeout) { return connectWaitLock.WaitOne(timeout); @@ -265,7 +238,7 @@ namespace Hazel /// 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. /// - public abstract void Disconnect(string reason, MessageWriter writer = null, bool fireEvent = true); + public abstract void Disconnect(string reason, MessageWriter writer = null); /// /// Disposes of this NetworkConnection. diff --git a/Hazel/ConnectionListener.cs b/Hazel/ConnectionListener.cs index 9d9c664..b4b852f 100644 --- a/Hazel/ConnectionListener.cs +++ b/Hazel/ConnectionListener.cs @@ -8,9 +8,7 @@ namespace Hazel /// /// /// ConnectionListeners are server side objects that listen for clients and create matching server side connections - /// for each client in a similar way to TCP does. These connections should already have a - /// State of and so should be ready for - /// comunication immediately. + /// for each client in a similar way to TCP does. These connections should be ready for communication immediately. /// /// /// Each time a client connects the event will be invoked to alert all subscribers to diff --git a/Hazel/DisconnectedEventArgs.cs b/Hazel/DisconnectedEventArgs.cs index bbecf81..a7fb05c 100644 --- a/Hazel/DisconnectedEventArgs.cs +++ b/Hazel/DisconnectedEventArgs.cs @@ -1,34 +1,18 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace Hazel { - /// - /// Event arguments for the event. - /// - /// - /// - /// This contains information about the cause of a disconnection and is passed to subscribers of the - /// event. - /// - /// - /// - /// public class DisconnectedEventArgs : EventArgs { /// - /// The exception, if any, that caused the disconnect. + /// Optional disconnect reason. May be null. /// - /// - /// If the disconnection was caused because of an exception occuring (for exemple a - /// on network based connections) this will contain the error - /// that caused it or a with the details of the exception, if the disconnection - /// wasn't caused by an error then this will contain null. - /// public readonly string Reason; + /// + /// Optional data sent with a disconnect message. May be null. + /// You must not recycle this. If you need the message outside of a callback, you should copy it. + /// public readonly MessageReader Message; public DisconnectedEventArgs(string reason, MessageReader message) diff --git a/Hazel/MessageWriter.cs b/Hazel/MessageWriter.cs index 95b0143..73a6efd 100644 --- a/Hazel/MessageWriter.cs +++ b/Hazel/MessageWriter.cs @@ -112,6 +112,7 @@ namespace Hazel this.Buffer[0] = (byte)sendOption; switch (sendOption) { + default: case SendOption.None: this.Length = this.Position = 1; break; @@ -234,6 +235,12 @@ namespace Hazel this.Write(bytes, length); } + public void WriteBytesAndSize(byte[] bytes, int offset, int length) + { + this.WritePacked((uint)length); + this.Write(bytes, offset, length); + } + public void Write(byte[] bytes) { Array.Copy(bytes, 0, this.Buffer, this.Position, bytes.Length); @@ -241,6 +248,13 @@ namespace Hazel if (this.Position > this.Length) this.Length = this.Position; } + public void Write(byte[] bytes, int offset, int length) + { + Array.Copy(bytes, offset, this.Buffer, this.Position, length); + this.Position += length; + if (this.Position > this.Length) this.Length = this.Position; + } + public void Write(byte[] bytes, int length) { Array.Copy(bytes, 0, this.Buffer, this.Position, length); @@ -271,6 +285,25 @@ namespace Hazel } #endregion + public void Write(MessageWriter msg, bool includeHeader) + { + int offset = 0; + if (!includeHeader) + { + switch (msg.SendOption) + { + case SendOption.None: + offset = 1; + break; + case SendOption.Reliable: + offset = 3; + break; + } + } + + this.Write(msg.Buffer, offset, msg.Length - offset); + } + public unsafe static bool IsLittleEndian() { byte b; diff --git a/Hazel/NetworkConnection.cs b/Hazel/NetworkConnection.cs index f670d11..eb63fd2 100644 --- a/Hazel/NetworkConnection.cs +++ b/Hazel/NetworkConnection.cs @@ -60,9 +60,9 @@ namespace Hazel /// /// Called when the socket has been disconnected locally. /// - public override void Disconnect(string reason, MessageWriter writer = null, bool fireEvent = true) + public override void Disconnect(string reason, MessageWriter writer = null) { - if (this.SendDisconnect(writer) && fireEvent) + if (this.SendDisconnect(writer)) { try { diff --git a/Hazel/ObjectPool.cs b/Hazel/ObjectPool.cs index d508fba..8899699 100644 --- a/Hazel/ObjectPool.cs +++ b/Hazel/ObjectPool.cs @@ -43,8 +43,7 @@ namespace Hazel /// An instance of T. internal T GetObject() { - T item; - if (!pool.TryTake(out item)) + if (!pool.TryTake(out T item)) { Interlocked.Increment(ref numberCreated); item = objectFactory.Invoke(); @@ -73,5 +72,10 @@ namespace Hazel throw new Exception("Duplicate add " + typeof(T).Name); } } + + public bool IsObjectInUse(T item) + { + return inuse.ContainsKey(item); + } } } diff --git a/Hazel/Udp/SendOptionInternal.cs b/Hazel/Udp/SendOptionInternal.cs index 397334f..74786d8 100644 --- a/Hazel/Udp/SendOptionInternal.cs +++ b/Hazel/Udp/SendOptionInternal.cs @@ -9,7 +9,7 @@ namespace Hazel.Udp /// /// Extra internal states for SendOption enumeration when using UDP. /// - enum UdpSendOption : byte + public enum UdpSendOption : byte { /// /// Hello message for initiating communication. diff --git a/Hazel/Udp/UdpClientConnection.cs b/Hazel/Udp/UdpClientConnection.cs index fd4145b..c02cd31 100644 --- a/Hazel/Udp/UdpClientConnection.cs +++ b/Hazel/Udp/UdpClientConnection.cs @@ -97,7 +97,7 @@ namespace Hazel.Udp } catch (SocketException ex) { - Disconnect("Could not send data as a SocketException occured: " + ex.Message); + Disconnect("Could not send data as a SocketException occurred: " + ex.Message); } } @@ -114,7 +114,7 @@ namespace Hazel.Udp } catch (SocketException ex) { - Disconnect("Could not send data as a SocketException occured: " + ex.Message); + Disconnect("Could not send data as a SocketException occurred: " + ex.Message); } } @@ -149,7 +149,7 @@ namespace Hazel.Udp catch (SocketException e) { this.State = ConnectionState.NotConnected; - throw new HazelException("A socket exception occured while binding to the port.", e); + throw new HazelException("A SocketException occurred while binding to the port.", e); } try @@ -166,12 +166,16 @@ namespace Hazel.Udp catch (SocketException e) { Dispose(); - throw new HazelException("A Socket exception occured while initiating a receive operation.", e); + 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; }); + SendHello(bytes, () => + { + this.State = ConnectionState.Connected; + this.InitializeKeepAliveTimer(); + }); } /// @@ -202,20 +206,15 @@ namespace Hazel.Udp { msg.Length = socket.EndReceive(result); } - catch (NullReferenceException) - { - msg.Recycle(); - return; - } - catch (ObjectDisposedException) + catch (SocketException e) { msg.Recycle(); + Disconnect("Socket exception while reading data: " + e.Message); return; } - catch (SocketException e) + catch (Exception) { msg.Recycle(); - Disconnect("Socket exception while reading data: " + e.Message); return; } @@ -267,7 +266,7 @@ namespace Hazel.Udp { lock (this) { - if (this._state != ConnectionState.Connected) return false; + if (this._state == ConnectionState.NotConnected) return false; this._state = ConnectionState.NotConnected; } @@ -302,14 +301,9 @@ namespace Hazel.Udp SendDisconnect(); } - if (this.socket != null) - { - try { this.socket.Shutdown(SocketShutdown.Both); } catch { } - try { this.socket.Close(); } catch { } - try { this.socket.Dispose(); } catch { } - - this.socket = null; - } + try { this.socket.Shutdown(SocketShutdown.Both); } catch { } + try { this.socket.Close(); } catch { } + try { this.socket.Dispose(); } catch { } this.reliablePacketTimer.Dispose(); diff --git a/Hazel/Udp/UdpConnection.KeepAlive.cs b/Hazel/Udp/UdpConnection.KeepAlive.cs index 20d1fac..71babf0 100644 --- a/Hazel/Udp/UdpConnection.KeepAlive.cs +++ b/Hazel/Udp/UdpConnection.KeepAlive.cs @@ -55,43 +55,41 @@ namespace Hazel.Udp set { keepAliveInterval = value; - - //Update timer ResetKeepAliveTimer(); } } - int keepAliveInterval = 1500; + private int keepAliveInterval = 1500; public int MissingPingsUntilDisconnect { get; set; } = 6; - int pingsSinceAck = 0; + private volatile int pingsSinceAck = 0; /// /// The timer creating keepalive pulses. /// - Timer keepAliveTimer; + private Timer keepAliveTimer; /// /// Starts the keepalive timer. /// - void InitializeKeepAliveTimer() + protected void InitializeKeepAliveTimer() { keepAliveTimer = new Timer( (o) => { if (this.pingsSinceAck >= this.MissingPingsUntilDisconnect) { + this.DisposeKeepAliveTimer(); this.Disconnect($"Sent {this.pingsSinceAck} pings that remote has not responded to."); return; } try { - SendPing(); this.pingsSinceAck++; + SendPing(); } catch { - DisposeKeepAliveTimer(); } }, null, @@ -105,7 +103,7 @@ namespace Hazel.Udp // An unacked ping should never be the sole cause of a disconnect. // Rather, the responses will reset our pingsSinceAck, enough unacked // pings should cause a disconnect. - void SendPing() + private void SendPing() { ushort id = (ushort)Interlocked.Increment(ref lastIDAllocated); @@ -134,7 +132,7 @@ namespace Hazel.Udp /// /// Resets the keepalive timer to zero. /// - void ResetKeepAliveTimer() + private void ResetKeepAliveTimer() { try { @@ -146,13 +144,11 @@ namespace Hazel.Udp /// /// Disposes of the keep alive timer. /// - void DisposeKeepAliveTimer() + private void DisposeKeepAliveTimer() { - var timer = this.keepAliveTimer; - if (timer != null) + if (this.keepAliveTimer != null) { - this.keepAliveTimer = null; - timer.Dispose(); + this.keepAliveTimer.Dispose(); } foreach (var kvp in activePingPackets) diff --git a/Hazel/Udp/UdpConnection.Reliable.cs b/Hazel/Udp/UdpConnection.Reliable.cs index b18c8c8..6f7e54e 100644 --- a/Hazel/Udp/UdpConnection.Reliable.cs +++ b/Hazel/Udp/UdpConnection.Reliable.cs @@ -72,7 +72,7 @@ namespace Hazel.Udp /// This returns the average ping for a one-way trip as calculated from the reliable packets that have been sent /// and acknowledged by the endpoint. /// - public float AveragePingMs = 200; + public float AveragePingMs = 500; /// /// The maximum times a message should be resent before marking the endpoint as disconnected. @@ -171,7 +171,7 @@ namespace Hazel.Udp return 0; } - this.NextTimeout = (int)Math.Min(this.NextTimeout * connection.ResendPingMultiplier, 1500); + this.NextTimeout += (int)Math.Min(this.NextTimeout * connection.ResendPingMultiplier, 500); try { connection.WriteBytesToConnection(this.Data, this.Length); @@ -225,7 +225,7 @@ namespace Hazel.Udp /// The buffer to attach to. /// The offset to attach at. /// The callback to make once the packet has been acknowledged. - void AttachReliableID(byte[] buffer, int offset, int sendLength, Action ackCallback = null) + private void AttachReliableID(byte[] buffer, int offset, int sendLength, Action ackCallback = null) { ushort id = (ushort)Interlocked.Increment(ref lastIDAllocated); @@ -260,7 +260,7 @@ namespace Hazel.Udp /// /// The byte array to write to. /// The callback to make once the packet has been acknowledged. - void ReliableSend(byte sendOption, byte[] data, Action ackCallback = null) + private void ReliableSend(byte sendOption, byte[] data, Action ackCallback = null) { this.ReliableSend(sendOption, data, 0, data.Length, ackCallback); } @@ -273,7 +273,7 @@ namespace Hazel.Udp /// /// /// The callback to make once the packet has been acknowledged. - void ReliableSend(byte sendOption, byte[] data, int offset, int length, Action ackCallback = null) + private void ReliableSend(byte sendOption, byte[] data, int offset, int length, Action ackCallback = null) { //Inform keepalive not to send for a while ResetKeepAliveTimer(); @@ -299,7 +299,7 @@ namespace Hazel.Udp /// Handles a reliable message being received and invokes the data event. /// /// The buffer received. - void ReliableMessageReceive(MessageReader message, int bytesReceived) + private void ReliableMessageReceive(MessageReader message, int bytesReceived) { ushort id; if (ProcessReliableReceive(message.Buffer, 1, out id)) @@ -320,7 +320,7 @@ namespace Hazel.Udp /// The buffer containing the data. /// The offset of the reliable header. /// Whether the packet was a new packet or not. - bool ProcessReliableReceive(byte[] bytes, int offset, out ushort id) + private bool ProcessReliableReceive(byte[] bytes, int offset, out ushort id) { byte b1 = bytes[offset]; byte b2 = bytes[offset + 1]; @@ -400,7 +400,7 @@ namespace Hazel.Udp /// Handles acknowledgement packets to us. /// /// The buffer containing the data. - void AcknowledgementMessageReceive(byte[] bytes) + private void AcknowledgementMessageReceive(byte[] bytes) { this.pingsSinceAck = 0; @@ -440,7 +440,7 @@ namespace Hazel.Udp /// /// The first identification byte. /// The second identification byte. - internal void SendAck(byte byte1, byte byte2) + private void SendAck(byte byte1, byte byte2) { byte[] bytes = new byte[] { @@ -458,7 +458,7 @@ namespace Hazel.Udp catch (InvalidOperationException) { } } - void DisposeReliablePackets() + private void DisposeReliablePackets() { foreach (var kvp in reliableDataPacketsSent) { diff --git a/Hazel/Udp/UdpConnection.cs b/Hazel/Udp/UdpConnection.cs index 5cc56eb..014a61e 100644 --- a/Hazel/Udp/UdpConnection.cs +++ b/Hazel/Udp/UdpConnection.cs @@ -10,14 +10,6 @@ namespace Hazel.Udp { protected static readonly byte[] EmptyDisconnectBytes = new byte[] { (byte)UdpSendOption.Disconnect }; - /// - /// Creates a new UdpConnection and initializes the keep alive timer. - /// - protected UdpConnection() - { - InitializeKeepAliveTimer(); - } - /// /// Writes the given bytes to the connection. /// @@ -36,8 +28,8 @@ namespace Hazel.Udp switch (msg.SendOption) { case SendOption.Reliable: - // Inform keepalive not to send for a while ResetKeepAliveTimer(); + AttachReliableID(buffer, 1, buffer.Length); WriteBytesToConnection(buffer, buffer.Length); Statistics.LogReliableSend(buffer.Length - 3, buffer.Length); diff --git a/Hazel/Udp/UdpConnectionListener.cs b/Hazel/Udp/UdpConnectionListener.cs index e1d6f46..09ee2ac 100644 --- a/Hazel/Udp/UdpConnectionListener.cs +++ b/Hazel/Udp/UdpConnectionListener.cs @@ -1,11 +1,7 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; using System.Net; using System.Net.Sockets; -using System.Text; using System.Threading; namespace Hazel.Udp @@ -20,6 +16,9 @@ namespace Hazel.Udp public int MinConnectionLength = 0; + public delegate bool AcceptConnectionCheck(out byte[] response); + public AcceptConnectionCheck AcceptConnection; + /// /// The socket listening for connections. /// @@ -92,7 +91,7 @@ namespace Hazel.Udp } catch (SocketException e) { - throw new HazelException("Could not start listening as a SocketException occured", e); + throw new HazelException("Could not start listening as a SocketException occurred", e); } StartListeningForData(); @@ -111,11 +110,14 @@ namespace Hazel.Udp message = MessageReader.GetSized(BufferSize); socket.BeginReceiveFrom(message.Buffer, 0, message.Buffer.Length, SocketFlags.None, ref remoteEP, ReadCallback, message); - Interlocked.Increment(ref ActiveListeners); } - catch (SocketException) + catch (SocketException sx) { message?.Recycle(); + + this.Logger?.Invoke("Socket Ex in StartListening: " + sx.Message); + + Thread.Sleep(10); StartListeningForData(); return; } @@ -127,20 +129,11 @@ namespace Hazel.Udp return; } } - - /// - /// Called when data has been received by the listener. - /// - /// The asyncronous operation's result. - - public int ActiveListeners; - public int PacketsReceived; + public volatile int ActiveCallbacks; void ReadCallback(IAsyncResult result) { - Interlocked.Decrement(ref ActiveListeners); - Interlocked.Increment(ref PacketsReceived); - + Interlocked.Increment(ref this.ActiveCallbacks); var message = (MessageReader)result.AsyncState; int bytesReceived; EndPoint remoteEndPoint = new IPEndPoint(IPMode == IPMode.IPv4 ? IPAddress.Any : IPAddress.IPv6Any, 0); @@ -153,7 +146,7 @@ namespace Hazel.Udp message.Offset = 0; message.Length = bytesReceived; } - catch (SocketException) + catch (SocketException sx) { // Client no longer reachable, pretend it didn't happen // TODO should this not inform the connection this client is lost??? @@ -161,8 +154,11 @@ namespace Hazel.Udp // 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); + + Thread.Sleep(10); StartListeningForData(); + Interlocked.Decrement(ref this.ActiveCallbacks); return; } catch (Exception ex) @@ -170,6 +166,7 @@ namespace Hazel.Udp //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; } @@ -178,7 +175,10 @@ namespace Hazel.Udp if (bytesReceived == 0) { message.Recycle(); + this.Logger?.Invoke("Received 0 bytes"); + Thread.Sleep(10); StartListeningForData(); + Interlocked.Decrement(ref this.ActiveCallbacks); return; } @@ -198,21 +198,26 @@ namespace Hazel.Udp if (!isHello) { message.Recycle(); + Interlocked.Decrement(ref this.ActiveCallbacks); return; } - lock (this.allConnections) + if (AcceptConnection != null) { - aware = this.allConnections.TryGetValue(remoteEndPoint, out connection); - if (!aware) + if (!AcceptConnection(out var response)) { - connection = new UdpServerConnection(this, (IPEndPoint)remoteEndPoint, this.IPMode); - if (!this.allConnections.TryAdd(remoteEndPoint, connection)) - { - throw new Exception(); - } + message.Recycle(); + SendData(response, response.Length, remoteEndPoint); + Interlocked.Decrement(ref this.ActiveCallbacks); + return; } } + + connection = this.allConnections.GetOrAdd(remoteEndPoint, (ep) => + { + aware = false; + return new UdpServerConnection(this, (IPEndPoint)ep, this.IPMode); + }); } //Inform the connection of the buffer (new connections need to send an ack back to client) @@ -231,6 +236,8 @@ namespace Hazel.Udp { message.Recycle(); } + + Interlocked.Decrement(ref this.ActiveCallbacks); } #if DEBUG @@ -278,7 +285,7 @@ namespace Hazel.Udp } catch (SocketException e) { - throw new HazelException("Could not send data as a SocketException occured.", e); + throw new HazelException("Could not send data as a SocketException occurred.", e); } catch (ObjectDisposedException) { @@ -324,13 +331,9 @@ namespace Hazel.Udp kvp.Value.Dispose(); } - if (this.socket != null) - { - try { this.socket.Shutdown(SocketShutdown.Both); } catch { } - try { this.socket.Close(); } catch { } - try { this.socket.Dispose(); } catch { } - this.socket = null; - } + try { this.socket.Shutdown(SocketShutdown.Both); } catch { } + try { this.socket.Close(); } catch { } + try { this.socket.Dispose(); } catch { } this.reliablePacketTimer.Dispose(); diff --git a/Hazel/Udp/UdpServerConnection.cs b/Hazel/Udp/UdpServerConnection.cs index 9dad8c7..530920d 100644 --- a/Hazel/Udp/UdpServerConnection.cs +++ b/Hazel/Udp/UdpServerConnection.cs @@ -33,6 +33,7 @@ namespace Hazel.Udp this.IPMode = IPMode; State = ConnectionState.Connected; + this.InitializeKeepAliveTimer(); } /// @@ -97,7 +98,6 @@ namespace Hazel.Udp SendDisconnect(); } - base.Dispose(disposing); } } -- 2.39.5