using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Net;
+using System.Threading;
namespace Hazel.UnitTests
{
TestHelper.RunClientToServerTest(listener, connection, 4, 0, SendOption.OrderedFragmentedReliable);
}
}
+
+ /// <summary>
+ /// Tests disconnection from the client.
+ /// </summary>
+ [TestMethod]
+ public void ClientDisconnectTest()
+ {
+ using (TcpConnectionListener listener = new TcpConnectionListener(IPAddress.Any, 4296))
+ using (TcpConnection connection = new TcpConnection())
+ {
+ TestHelper.RunClientDisconnectTest(listener, connection);
+ }
+ }
+
+ /// <summary>
+ /// Tests disconnection from the server.
+ /// </summary>
+ [TestMethod]
+ public void ServerDisconnectTest()
+ {
+ using (TcpConnectionListener listener = new TcpConnectionListener(IPAddress.Any, 4296))
+ using (TcpConnection connection = new TcpConnection())
+ {
+ TestHelper.RunServerDisconnectTest(listener, connection);
+ }
+ }
}
}
Assert.AreEqual(totalHandshakeSize + data.Length + headerSize, connection.Statistics.TotalBytesSent);
Assert.AreEqual(0, connection.Statistics.TotalBytesReceived);
}
+
+ /// <summary>
+ /// Runs a server disconnect test on the given listener and connection.
+ /// </summary>
+ /// <param name="listener">The listener to test.</param>
+ /// <param name="connection">The connection to test.</param>
+ 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();
+ }
+
+ /// <summary>
+ /// Runs a client disconnect test on the given listener and connection.
+ /// </summary>
+ /// <param name="listener">The listener to test.</param>
+ /// <param name="connection">The connection to test.</param>
+ 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();
+ }
}
}
mutex.WaitOne();
}
}
+
+ /// <summary>
+ /// Tests disconnection from the client.
+ /// </summary>
+ [TestMethod]
+ public void ClientDisconnectTest()
+ {
+ using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296))
+ using (UdpConnection connection = new UdpClientConnection())
+ {
+ TestHelper.RunClientDisconnectTest(listener, connection);
+ }
+ }
+
+ /// <summary>
+ /// Tests disconnection from the server.
+ /// </summary>
+ [TestMethod]
+ public void ServerDisconnectTest()
+ {
+ using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296))
+ using (UdpConnection connection = new UdpClientConnection())
+ {
+ TestHelper.RunServerDisconnectTest(listener, connection);
+ }
+ }
}
}
/// <summary>
/// Closes this connection safely.
/// </summary>
- public void Close()
+ public virtual void Close()
{
Dispose();
}
/// <summary>
/// Wrapper for exceptions thrown from Hazel.
/// </summary>
- class HazelException : Exception
+ [Serializable]
+ public class HazelException : Exception
{
internal HazelException(string msg) : base (msg)
{
/// <summary>
/// Hello message for initiating communication.
/// </summary>
- Hello = 254,
+ Hello = 253,
+
+ /// <summary>
+ /// Message for discontinuing communication.
+ /// </summary>
+ Disconnect = 254,
/// <summary>
/// Message acknowledging the receipt of a message.
lock (this.Socket)
{
this.Socket.NoDelay = true;
- }
- State = ConnectionState.Connected;
+ State = ConnectionState.Connected;
+ }
}
/// <summary>
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();
+ }
}
/// <summary>
}
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)
{
}
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)
/// Called when the socket has been disconnected at the remote host.
/// </summary>
/// <param name="e">The exception if one was the cause.</param>
- void HandleDisconnect(HazelException e = null)
+ protected override void HandleDisconnect(HazelException e = null)
{
bool invoke = false;
/// </summary>
Object keepAliveTimerLock = new Object();
+ /// <summary>
+ /// Has the keep alive timer been disposed already?
+ /// </summary>
+ bool keepAliveTimerDisposed;
+
/// <summary>
/// Starts the keepalive timer.
/// </summary>
void DisposeKeepAliveTimer()
{
lock(keepAliveTimerLock)
- keepAliveTimer.Dispose();
+ {
+ if (!keepAliveTimerDisposed)
+ keepAliveTimer.Dispose();
+ keepAliveTimerDisposed = true;
+ }
}
}
}
case (byte)SendOptionInternal.Hello:
HandleReliableReceive(buffer);
+ return null;
+
+ case (byte)SendOptionInternal.Disconnect:
+ HandleDisconnect();
+
return null;
}
HandleSend(new byte[0], (byte)SendOptionInternal.Hello, acknowledgeCallback);
}
+ /// <summary>
+ /// Closes this connection safely.
+ /// </summary>
+ public override void Close()
+ {
+ HandleSend(new byte[0], (byte)SendOptionInternal.Disconnect); //TODO Should disconnect wait for an ack?
+
+ base.Close();
+ }
+
+ /// <summary>
+ /// Called when the socket has been disconnected at the remote host.
+ /// </summary>
+ /// <param name="e">The exception if one was the cause.</param>
+ protected abstract void HandleDisconnect(HazelException e = null);
+
/// <summary>
/// Called when things are being disposed of
/// </summary>
//If the socket's been disposed then we can just end there.
return;
}
- catch (SocketException e)
+ catch (SocketException)
{
//TODO Errr...;
return;
//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);
InvokeDataReceived(new DataEventArgs(data, (SendOption)buffer[0]));
}
+ /// <summary>
+ /// Called when the socket has been disconnected at the remote host.
+ /// </summary>
+ /// <param name="e">The exception if one was the cause.</param>
+ 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();
+ }
+ }
+
/// <summary>
/// Safely closes this connection.
/// </summary>