From 55d057463e2be85f940915ac2374ba276008bf84 Mon Sep 17 00:00:00 2001 From: JamJar00 Date: Sat, 14 May 2016 19:48:35 +0100 Subject: [PATCH] Added remote disconnections --- Hazel.UnitTests/TcpConnectionTests.cs | 27 ++++++++++++++ Hazel.UnitTests/TestHelper.cs | 52 +++++++++++++++++++++++++++ Hazel.UnitTests/UdpConnectionTests.cs | 26 ++++++++++++++ Hazel/Connection.cs | 2 +- Hazel/HazelException.cs | 3 +- Hazel/SendOptionInternal.cs | 7 +++- Hazel/TcpConnection.cs | 12 +++++-- Hazel/UdpClientConnection.cs | 10 ++++-- Hazel/UdpConnection.KeepAlive.cs | 11 +++++- Hazel/UdpConnection.cs | 21 +++++++++++ Hazel/UdpConnectionListener.cs | 6 +++- Hazel/UdpServerConnection.cs | 27 ++++++++++++++ 12 files changed, 193 insertions(+), 11 deletions(-) diff --git a/Hazel.UnitTests/TcpConnectionTests.cs b/Hazel.UnitTests/TcpConnectionTests.cs index 290eeae..e30ef29 100644 --- a/Hazel.UnitTests/TcpConnectionTests.cs +++ b/Hazel.UnitTests/TcpConnectionTests.cs @@ -1,6 +1,7 @@ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Net; +using System.Threading; namespace Hazel.UnitTests { @@ -56,5 +57,31 @@ namespace Hazel.UnitTests TestHelper.RunClientToServerTest(listener, connection, 4, 0, SendOption.OrderedFragmentedReliable); } } + + /// + /// Tests disconnection from the client. + /// + [TestMethod] + public void ClientDisconnectTest() + { + using (TcpConnectionListener listener = new TcpConnectionListener(IPAddress.Any, 4296)) + using (TcpConnection connection = new TcpConnection()) + { + TestHelper.RunClientDisconnectTest(listener, connection); + } + } + + /// + /// Tests disconnection from the server. + /// + [TestMethod] + public void ServerDisconnectTest() + { + using (TcpConnectionListener listener = new TcpConnectionListener(IPAddress.Any, 4296)) + using (TcpConnection connection = new TcpConnection()) + { + TestHelper.RunServerDisconnectTest(listener, connection); + } + } } } diff --git a/Hazel.UnitTests/TestHelper.cs b/Hazel.UnitTests/TestHelper.cs index 02cc113..5708a49 100644 --- a/Hazel.UnitTests/TestHelper.cs +++ b/Hazel.UnitTests/TestHelper.cs @@ -110,5 +110,57 @@ namespace Hazel.UnitTests Assert.AreEqual(totalHandshakeSize + data.Length + headerSize, connection.Statistics.TotalBytesSent); Assert.AreEqual(0, connection.Statistics.TotalBytesReceived); } + + /// + /// Runs a server disconnect test on the given listener and connection. + /// + /// The listener to test. + /// The connection to test. + internal static void RunServerDisconnectTest(ConnectionListener listener, Connection connection) + { + ManualResetEvent mutex = new ManualResetEvent(false); + + connection.Disconnected += delegate(object sender, DisconnectedEventArgs args) + { + mutex.Set(); + }; + + listener.NewConnection += delegate(object sender, NewConnectionEventArgs args) + { + args.Connection.Close(); + }; + + listener.Start(); + + connection.Connect(new NetworkEndPoint(IPAddress.Loopback, 4296)); + + mutex.WaitOne(); + } + + /// + /// Runs a client disconnect test on the given listener and connection. + /// + /// The listener to test. + /// The connection to test. + internal static void RunClientDisconnectTest(ConnectionListener listener, Connection connection) + { + ManualResetEvent mutex = new ManualResetEvent(false); + + listener.NewConnection += delegate(object sender, NewConnectionEventArgs args) + { + args.Connection.Disconnected += delegate(object sender2, DisconnectedEventArgs args2) + { + mutex.Set(); + }; + }; + + listener.Start(); + + connection.Connect(new NetworkEndPoint(IPAddress.Loopback, 4296)); + + connection.Close(); + + mutex.WaitOne(); + } } } diff --git a/Hazel.UnitTests/UdpConnectionTests.cs b/Hazel.UnitTests/UdpConnectionTests.cs index e2abd18..a6ce448 100644 --- a/Hazel.UnitTests/UdpConnectionTests.cs +++ b/Hazel.UnitTests/UdpConnectionTests.cs @@ -132,5 +132,31 @@ namespace Hazel.UnitTests mutex.WaitOne(); } } + + /// + /// Tests disconnection from the client. + /// + [TestMethod] + public void ClientDisconnectTest() + { + using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296)) + using (UdpConnection connection = new UdpClientConnection()) + { + TestHelper.RunClientDisconnectTest(listener, connection); + } + } + + /// + /// Tests disconnection from the server. + /// + [TestMethod] + public void ServerDisconnectTest() + { + using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296)) + using (UdpConnection connection = new UdpClientConnection()) + { + TestHelper.RunServerDisconnectTest(listener, connection); + } + } } } diff --git a/Hazel/Connection.cs b/Hazel/Connection.cs index 4c7c5ed..72cd245 100644 --- a/Hazel/Connection.cs +++ b/Hazel/Connection.cs @@ -130,7 +130,7 @@ namespace Hazel /// /// Closes this connection safely. /// - public void Close() + public virtual void Close() { Dispose(); } diff --git a/Hazel/HazelException.cs b/Hazel/HazelException.cs index 86f157b..f04a4b0 100644 --- a/Hazel/HazelException.cs +++ b/Hazel/HazelException.cs @@ -8,7 +8,8 @@ namespace Hazel /// /// Wrapper for exceptions thrown from Hazel. /// - class HazelException : Exception + [Serializable] + public class HazelException : Exception { internal HazelException(string msg) : base (msg) { diff --git a/Hazel/SendOptionInternal.cs b/Hazel/SendOptionInternal.cs index 5e4f621..16624f1 100644 --- a/Hazel/SendOptionInternal.cs +++ b/Hazel/SendOptionInternal.cs @@ -14,7 +14,12 @@ namespace Hazel /// /// Hello message for initiating communication. /// - Hello = 254, + Hello = 253, + + /// + /// Message for discontinuing communication. + /// + Disconnect = 254, /// /// Message acknowledging the receipt of a message. diff --git a/Hazel/TcpConnection.cs b/Hazel/TcpConnection.cs index f39514b..04e8598 100644 --- a/Hazel/TcpConnection.cs +++ b/Hazel/TcpConnection.cs @@ -47,9 +47,9 @@ namespace Hazel lock (this.Socket) { this.Socket.NoDelay = true; - } - State = ConnectionState.Connected; + State = ConnectionState.Connected; + } } /// @@ -223,7 +223,13 @@ namespace Hazel protected virtual void StartWaitingForChunk(StateObject state) { lock (Socket) - Socket.BeginReceive(state.buffer, state.totalBytesReceived, state.buffer.Length, SocketFlags.None, ChunkReadCallback, state); + { + //Double check we've not disconnected then begin receiving + if (State == ConnectionState.Connected || State == ConnectionState.Connecting) + Socket.BeginReceive(state.buffer, state.totalBytesReceived, state.buffer.Length, SocketFlags.None, ChunkReadCallback, state); + else + HandleDisconnect(); + } } /// diff --git a/Hazel/UdpClientConnection.cs b/Hazel/UdpClientConnection.cs index 000e803..5194cc5 100644 --- a/Hazel/UdpClientConnection.cs +++ b/Hazel/UdpClientConnection.cs @@ -116,7 +116,10 @@ namespace Hazel } catch (ObjectDisposedException) { - throw new HazelException("Could not begin read as the socket has been disposed of, did you disconnect?"); + //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... + State = ConnectionState.NotConnected; + return; } catch (SocketException e) { @@ -190,7 +193,8 @@ namespace Hazel } catch (ObjectDisposedException) { - throw new HazelException("Could not begin read as the socket has been disposed of."); + //If the socket's been disposed then we can just end there. + return; } if (buffer != null) @@ -201,7 +205,7 @@ namespace Hazel /// Called when the socket has been disconnected at the remote host. /// /// The exception if one was the cause. - void HandleDisconnect(HazelException e = null) + protected override void HandleDisconnect(HazelException e = null) { bool invoke = false; diff --git a/Hazel/UdpConnection.KeepAlive.cs b/Hazel/UdpConnection.KeepAlive.cs index 1531874..ee1b90a 100644 --- a/Hazel/UdpConnection.KeepAlive.cs +++ b/Hazel/UdpConnection.KeepAlive.cs @@ -46,6 +46,11 @@ namespace Hazel /// Object keepAliveTimerLock = new Object(); + /// + /// Has the keep alive timer been disposed already? + /// + bool keepAliveTimerDisposed; + /// /// Starts the keepalive timer. /// @@ -81,7 +86,11 @@ namespace Hazel void DisposeKeepAliveTimer() { lock(keepAliveTimerLock) - keepAliveTimer.Dispose(); + { + if (!keepAliveTimerDisposed) + keepAliveTimer.Dispose(); + keepAliveTimerDisposed = true; + } } } } diff --git a/Hazel/UdpConnection.cs b/Hazel/UdpConnection.cs index 557e7dc..69628ea 100644 --- a/Hazel/UdpConnection.cs +++ b/Hazel/UdpConnection.cs @@ -112,6 +112,11 @@ namespace Hazel case (byte)SendOptionInternal.Hello: HandleReliableReceive(buffer); + return null; + + case (byte)SendOptionInternal.Disconnect: + HandleDisconnect(); + return null; } @@ -132,6 +137,22 @@ namespace Hazel HandleSend(new byte[0], (byte)SendOptionInternal.Hello, acknowledgeCallback); } + /// + /// Closes this connection safely. + /// + public override void Close() + { + HandleSend(new byte[0], (byte)SendOptionInternal.Disconnect); //TODO Should disconnect wait for an ack? + + base.Close(); + } + + /// + /// Called when the socket has been disconnected at the remote host. + /// + /// The exception if one was the cause. + protected abstract void HandleDisconnect(HazelException e = null); + /// /// Called when things are being disposed of /// diff --git a/Hazel/UdpConnectionListener.cs b/Hazel/UdpConnectionListener.cs index cd362f7..b03f160 100644 --- a/Hazel/UdpConnectionListener.cs +++ b/Hazel/UdpConnectionListener.cs @@ -110,7 +110,7 @@ namespace Hazel //If the socket's been disposed then we can just end there. return; } - catch (SocketException e) + catch (SocketException) { //TODO Errr...; return; @@ -140,6 +140,10 @@ namespace Hazel //If this is a new client then connect with them! else { + //Check for malformed connection attempts + if (buffer[0] != (byte)SendOptionInternal.Hello || buffer.Length != 3) + return; + connection = new UdpServerConnection(this, remoteEndPoint); connections.Add(remoteEndPoint, connection); diff --git a/Hazel/UdpServerConnection.cs b/Hazel/UdpServerConnection.cs index 07735bf..5ea8a49 100644 --- a/Hazel/UdpServerConnection.cs +++ b/Hazel/UdpServerConnection.cs @@ -88,6 +88,33 @@ namespace Hazel InvokeDataReceived(new DataEventArgs(data, (SendOption)buffer[0])); } + /// + /// Called when the socket has been disconnected at the remote host. + /// + /// The exception if one was the cause. + protected override void HandleDisconnect(HazelException e = null) + { + 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) + { + InvokeDisconnected(new DisconnectedEventArgs(e)); + + Dispose(); + } + } + /// /// Safely closes this connection. /// -- 2.39.5