diff --git a/Hazel.UnitTests/TcpConnectionTests.cs b/Hazel.UnitTests/TcpConnectionTests.cs
index 9ef0772..92317d0 100644
--- a/Hazel.UnitTests/TcpConnectionTests.cs
+++ b/Hazel.UnitTests/TcpConnectionTests.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Net;
using System.Threading;
@@ -136,5 +137,66 @@ public void ServerDisconnectTest()
TestHelper.RunServerDisconnectTest(listener, connection);
}
}
+
+ ///
+ /// Tests the keepalive functionality from the client,
+ ///
+ [TestMethod]
+ public void KeepAliveClientTest()
+ {
+ using (TcpConnectionListener listener = new TcpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296)))
+ using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
+ {
+ listener.Start();
+
+ connection.Connect();
+ connection.KeepAliveInterval = 100;
+
+ System.Threading.Thread.Sleep(1050); //Enough time for ~10 keep alive packets
+
+ Assert.IsTrue(
+ connection.Statistics.TotalBytesSent >= 30 &&
+ connection.Statistics.TotalBytesSent <= 50,
+ "Sent: " + connection.Statistics.TotalBytesSent
+ );
+ }
+ }
+
+ ///
+ /// Tests the keepalive functionality from the client,
+ ///
+ [TestMethod]
+ public void KeepAliveServerTest()
+ {
+ ManualResetEvent mutex = new ManualResetEvent(false);
+ TcpConnection listenerConnectionToClient = null;
+
+ using (TcpConnectionListener listener = new TcpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296)))
+ using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
+ {
+ listener.NewConnection += delegate (object sender, NewConnectionEventArgs args)
+ {
+ listenerConnectionToClient = (TcpConnection)args.Connection;
+ listenerConnectionToClient.KeepAliveInterval = 100;
+
+ Thread.Sleep(1050); //Enough time for ~10 keep alive packets
+
+ mutex.Set();
+ };
+
+ listener.Start();
+
+ connection.Connect();
+
+ mutex.WaitOne();
+
+ Assert.IsNotNull(listenerConnectionToClient);
+ Assert.IsTrue(
+ listenerConnectionToClient.Statistics.TotalBytesSent >= 30 &&
+ listenerConnectionToClient.Statistics.TotalBytesSent <= 50,
+ "Sent: " + listenerConnectionToClient.Statistics.TotalBytesSent
+ );
+ }
+ }
}
}
diff --git a/Hazel.UnitTests/UdpConnectionTests.cs b/Hazel.UnitTests/UdpConnectionTests.cs
index 7216fb4..ead4800 100644
--- a/Hazel.UnitTests/UdpConnectionTests.cs
+++ b/Hazel.UnitTests/UdpConnectionTests.cs
@@ -194,22 +194,18 @@ public void KeepAliveClientTest()
public void KeepAliveServerTest()
{
ManualResetEvent mutex = new ManualResetEvent(false);
+ UdpConnection listenerConnectionToClient = null;
using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296)))
using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
listener.NewConnection += delegate(object sender, NewConnectionEventArgs args)
{
- ((UdpConnection)args.Connection).KeepAliveInterval = 100;
+ listenerConnectionToClient = (UdpConnection) args.Connection;
+ listenerConnectionToClient.KeepAliveInterval = 100;
Thread.Sleep(1050); //Enough time for ~10 keep alive packets
- Assert.IsTrue(
- args.Connection.Statistics.TotalBytesSent >= 30 &&
- args.Connection.Statistics.TotalBytesSent <= 50,
- "Sent: " + args.Connection.Statistics.TotalBytesSent
- );
-
mutex.Set();
};
@@ -218,6 +214,13 @@ public void KeepAliveServerTest()
connection.Connect();
mutex.WaitOne();
+
+ Assert.IsNotNull(listenerConnectionToClient);
+ Assert.IsTrue(
+ listenerConnectionToClient.Statistics.TotalBytesSent >= 30 &&
+ listenerConnectionToClient.Statistics.TotalBytesSent <= 50,
+ "Sent: " + listenerConnectionToClient.Statistics.TotalBytesSent
+ );
}
}
diff --git a/Hazel/Udp/UdpConnection.KeepAlive.cs b/Hazel/Connection.KeepAlive.cs
similarity index 79%
rename from Hazel/Udp/UdpConnection.KeepAlive.cs
rename to Hazel/Connection.KeepAlive.cs
index f1d6f3c..7126c51 100644
--- a/Hazel/Udp/UdpConnection.KeepAlive.cs
+++ b/Hazel/Connection.KeepAlive.cs
@@ -3,12 +3,13 @@
using System.Diagnostics;
using System.Linq;
using System.Text;
+using System.Net.Sockets;
+using System.Net;
using System.Threading;
-
-namespace Hazel.Udp
+namespace Hazel
{
- partial class UdpConnection
+ partial class Connection
{
///
/// The interval from data being received or transmitted to a keepalive packet being sent in milliseconds.
@@ -33,7 +34,7 @@ public int KeepAliveInterval
set
{
keepAliveInterval = value;
-
+
//Update timer
ResetKeepAliveTimer();
}
@@ -55,10 +56,12 @@ public int KeepAliveInterval
///
bool keepAliveTimerDisposed;
+ readonly byte[] keepAlivePacket = new byte[1];
+
///
/// Starts the keepalive timer.
///
- void InitializeKeepAliveTimer()
+ protected void InitializeKeepAliveTimer()
{
lock (keepAliveTimerLock)
{
@@ -66,7 +69,7 @@ void InitializeKeepAliveTimer()
(o) =>
{
Trace.WriteLine("Keepalive packet sent.");
- SendHello(null, null);
+ SendBytes(keepAlivePacket, SendOption.KeepAlive);
},
null,
keepAliveInterval,
@@ -78,20 +81,23 @@ void InitializeKeepAliveTimer()
///
/// Resets the keepalive timer to zero.
///
- void ResetKeepAliveTimer()
+ protected void ResetKeepAliveTimer()
{
lock (keepAliveTimerLock)
- keepAliveTimer.Change(keepAliveInterval, keepAliveInterval);
+ {
+ if(keepAliveTimer != null)
+ keepAliveTimer.Change(keepAliveInterval, keepAliveInterval);
+ }
}
///
/// Disposes of the keep alive timer.
///
- void DisposeKeepAliveTimer()
+ protected void DisposeKeepAliveTimer()
{
- lock(keepAliveTimerLock)
+ lock (keepAliveTimerLock)
{
- if (!keepAliveTimerDisposed)
+ if (!keepAliveTimerDisposed && keepAliveTimer != null)
keepAliveTimer.Dispose();
keepAliveTimerDisposed = true;
}
diff --git a/Hazel/Connection.cs b/Hazel/Connection.cs
index 7bc9d57..c2e8029 100644
--- a/Hazel/Connection.cs
+++ b/Hazel/Connection.cs
@@ -32,7 +32,7 @@ namespace Hazel
///
///
///
- public abstract class Connection : IDisposable
+ public abstract partial class Connection : IDisposable
{
///
/// Called when a message has been received.
@@ -261,6 +261,7 @@ protected virtual void Dispose(bool disposing)
{
if (disposing)
{
+ DisposeKeepAliveTimer();
}
}
}
diff --git a/Hazel/Hazel.csproj b/Hazel/Hazel.csproj
index 5cf54e2..2b86c87 100644
--- a/Hazel/Hazel.csproj
+++ b/Hazel/Hazel.csproj
@@ -49,6 +49,7 @@
+
@@ -75,7 +76,6 @@
Code
-
diff --git a/Hazel/SendOption.cs b/Hazel/SendOption.cs
index e7ebec2..20d2287 100644
--- a/Hazel/SendOption.cs
+++ b/Hazel/SendOption.cs
@@ -42,6 +42,15 @@ public enum SendOption : byte
/// guaranteed to arrive and to arrive only once but the sending process may require more memory, processing,
/// a larger number protocol bytes and may be slower than sending unreliably.
///
- FragmentedReliable = 2
+ FragmentedReliable = 2,
+
+ ///
+ /// Requests data be treated as keep alive packet.
+ ///
+ ///
+ /// This packet type is used soly to keep connection working.
+ /// Data inside keep alive packet will be ignored.
+ ///
+ KeepAlive = 3
}
}
diff --git a/Hazel/Tcp/TcpConnection.cs b/Hazel/Tcp/TcpConnection.cs
index 99d60c1..148ec54 100644
--- a/Hazel/Tcp/TcpConnection.cs
+++ b/Hazel/Tcp/TcpConnection.cs
@@ -42,6 +42,8 @@ internal TcpConnection(Socket socket)
this.socket.NoDelay = true;
State = ConnectionState.Connected;
+
+ InitializeKeepAliveTimer();
}
}
@@ -69,7 +71,7 @@ public TcpConnection(NetworkEndPoint remoteEndPoint)
throw new HazelException("IPV6 not supported!");
socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
- socket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false);
+ socket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false);
}
socket.NoDelay = true;
@@ -123,6 +125,8 @@ public override void Connect(byte[] bytes = null, int timeout = 5000)
State = ConnectionState.Connected;
SendBytes(actualBytes);
+
+ InitializeKeepAliveTimer();
}
}
@@ -130,14 +134,15 @@ public override void Connect(byte[] bytes = null, int timeout = 5000)
///
///
///
- /// The sendOption parameter is ignored by the TcpConnection as TCP only supports FragmentedReliable
- /// communication, specifying anything else will have no effect.
+ /// The sendOption parameter can only be set as FragmentedReliable or KeepAlive.
+ /// TCP only supports FragmentedReliable communication, specifying anything else will have no effect.
+ /// KeepAlive packets are still send as FragmentedReliable.
///
///
public override void SendBytes(byte[] bytes, SendOption sendOption = SendOption.FragmentedReliable)
{
//Get bytes for length
- byte[] fullBytes = AppendLengthHeader(bytes);
+ byte[] fullBytes = AppendLengthHeader(bytes, sendOption);
//Write the bytes to the socket
lock (socketLock)
@@ -145,6 +150,8 @@ public override void SendBytes(byte[] bytes, SendOption sendOption = SendOption.
if (State != ConnectionState.Connected)
throw new InvalidOperationException("Could not send data as this Connection is not connected. Did you disconnect?");
+ ResetKeepAliveTimer();
+
try
{
socket.BeginSend(fullBytes, 0, fullBytes.Length, SocketFlags.None, null, null);
@@ -170,6 +177,22 @@ void HeaderReadCallback(byte[] bytes, Action callback)
//Get length
int length = GetLengthFromBytes(bytes);
+ //Check if there is no body -> [KeepAlive] packet, if so then log it and wait for next message
+ if (length == 0)
+ {
+ try
+ {
+ StartWaitingForHeader(callback);
+ }
+ catch (Exception e)
+ {
+ HandleDisconnect(new HazelException("An exception occured while initiating a header receive operation.", e));
+ }
+
+ Statistics.LogHelloReceive(bytes.Length + 4);
+ return;
+ }
+
//Begin receiving the body
try
{
@@ -303,6 +326,8 @@ void ChunkReadCallback(IAsyncResult result)
return;
}
+ ResetKeepAliveTimer();
+
StateObject state = (StateObject)result.AsyncState;
state.totalBytesReceived += bytesReceived; //TODO threading issues on state?
@@ -362,16 +387,28 @@ void HandleDisconnect(HazelException e = null)
/// Appends the length header to the bytes.
///
/// The source bytes.
+ /// Allows for marking packet as KeepAlive. (Zero body length)
/// The new bytes.
- static byte[] AppendLengthHeader(byte[] bytes)
+ static byte[] AppendLengthHeader(byte[] bytes, SendOption sendOption)
{
byte[] fullBytes = new byte[bytes.Length + 4];
- //Append length
- fullBytes[0] = (byte)(((uint)bytes.Length >> 24) & 0xFF);
- fullBytes[1] = (byte)(((uint)bytes.Length >> 16) & 0xFF);
- fullBytes[2] = (byte)(((uint)bytes.Length >> 8) & 0xFF);
- fullBytes[3] = (byte)(uint)bytes.Length;
+ if (sendOption == SendOption.KeepAlive)
+ {
+ //Append length as 0
+ fullBytes[0] = 0;
+ fullBytes[1] = 0;
+ fullBytes[2] = 0;
+ fullBytes[3] = 0;
+ }
+ else
+ {
+ //Append length
+ fullBytes[0] = (byte) (((uint) bytes.Length >> 24) & 0xFF);
+ fullBytes[1] = (byte) (((uint) bytes.Length >> 16) & 0xFF);
+ fullBytes[2] = (byte) (((uint) bytes.Length >> 8) & 0xFF);
+ fullBytes[3] = (byte) (uint) bytes.Length;
+ }
//Add rest of bytes
Buffer.BlockCopy(bytes, 0, fullBytes, 4, bytes.Length);
diff --git a/Hazel/Udp/UdpClientConnection.cs b/Hazel/Udp/UdpClientConnection.cs
index 4ffe424..e74645a 100644
--- a/Hazel/Udp/UdpClientConnection.cs
+++ b/Hazel/Udp/UdpClientConnection.cs
@@ -160,6 +160,8 @@ public override void Connect(byte[] bytes = null, int timeout = 5000)
Dispose();
throw new HazelException("Connection attempt timed out.");
}
+
+ InitializeKeepAliveTimer();
}
///
diff --git a/Hazel/Udp/UdpConnection.Fragmented.cs b/Hazel/Udp/UdpConnection.Fragmented.cs
index abea8c4..96f74de 100644
--- a/Hazel/Udp/UdpConnection.Fragmented.cs
+++ b/Hazel/Udp/UdpConnection.Fragmented.cs
@@ -10,7 +10,7 @@ partial class UdpConnection
///
/// The amount of data that can be put into a fragment.
///
- public int FragmentSize { get { return _fragmentSize; } }
+ public int FragmentSize { get { return fragmentSize; } }
int fragmentSize = 65507 - 1 - 2 - 2 - 2;
///
diff --git a/Hazel/Udp/UdpConnection.cs b/Hazel/Udp/UdpConnection.cs
index 6f97d2d..97475d9 100644
--- a/Hazel/Udp/UdpConnection.cs
+++ b/Hazel/Udp/UdpConnection.cs
@@ -14,14 +14,6 @@ namespace Hazel.Udp
///
public abstract partial class UdpConnection : NetworkConnection
{
- ///
- /// Creates a new UdpConnection and initializes the keep alive timer.
- ///
- protected UdpConnection()
- {
- InitializeKeepAliveTimer();
- }
-
///
/// Writes the given bytes to the connection.
///
@@ -63,6 +55,7 @@ protected void HandleSend(byte[] data, byte sendOption, Action ackCallback = nul
{
//Handle reliable header and hellos
case (byte)SendOption.Reliable:
+ case (byte)SendOption.KeepAlive:
case (byte)UdpSendOption.Hello:
ReliableSend(sendOption, data, ackCallback);
break;
@@ -99,7 +92,8 @@ protected internal void HandleReceive(byte[] buffer)
AcknowledgementMessageReceive(buffer);
break;
- //We need to acknowledge hello messages but dont want to invoke any events!
+ //We need to acknowledge hello/keep alive messages but dont want to invoke any events!
+ case (byte)SendOption.KeepAlive:
case (byte)UdpSendOption.Hello:
ProcessReliableReceive(buffer, 1);
Statistics.LogHelloReceive(buffer.Length);
@@ -195,16 +189,5 @@ protected void SendDisconnect()
{
HandleSend(new byte[0], (byte)UdpSendOption.Disconnect); //TODO Should disconnect wait for an ack?
}
-
- ///
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- DisposeKeepAliveTimer();
- }
-
- base.Dispose(disposing);
- }
}
}
diff --git a/Hazel/Udp/UdpServerConnection.cs b/Hazel/Udp/UdpServerConnection.cs
index 566bf1e..415f706 100644
--- a/Hazel/Udp/UdpServerConnection.cs
+++ b/Hazel/Udp/UdpServerConnection.cs
@@ -42,6 +42,8 @@ internal UdpServerConnection(UdpConnectionListener listener, EndPoint endPoint,
this.IPMode = IPMode;
State = ConnectionState.Connected;
+
+ InitializeKeepAliveTimer();
}
///