using System.Net;
using System.Threading;
+using Hazel.Tcp;
+
namespace Hazel.UnitTests
{
[TestClass]
[TestMethod]
public void TcpFieldTest()
{
+ NetworkEndPoint ep = new NetworkEndPoint(IPAddress.Loopback, 4296);
+
using (TcpConnectionListener listener = new TcpConnectionListener(IPAddress.Any, 4296))
- using (TcpConnection connection = new TcpConnection())
+ using (TcpConnection connection = new TcpConnection(ep))
{
listener.Start();
- NetworkEndPoint ep = new NetworkEndPoint(IPAddress.Loopback, 4296);
- connection.Connect(ep);
+ connection.Connect();
//Connection fields
Assert.AreEqual(ep, connection.EndPoint);
public void TcpIPv4ConnectionTest()
{
using (TcpConnectionListener listener = new TcpConnectionListener(IPAddress.Any, 4296, IPMode.IPv4))
- using (TcpConnection connection = new TcpConnection())
+ using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.Loopback, 4296, IPMode.IPv4)))
{
listener.Start();
- connection.Connect(new NetworkEndPoint(IPAddress.Loopback, 4296, IPMode.IPv4));
+ connection.Connect();
}
}
{
listener.Start();
- using (TcpConnection connection = new TcpConnection())
+ using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.Loopback, 4296, IPMode.IPv4)))
{
- connection.Connect(new NetworkEndPoint(IPAddress.Loopback, 4296, IPMode.IPv4));
+ connection.Connect();
}
- using (TcpConnection connection = new TcpConnection())
+ using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.Loopback, 4296, IPMode.IPv4AndIPv6)))
{
- connection.Connect(new NetworkEndPoint(IPAddress.Loopback, 4296, IPMode.IPv4AndIPv6));
+ connection.Connect();
}
}
}
public void TcpServerToClientTest()
{
using (TcpConnectionListener listener = new TcpConnectionListener(IPAddress.Any, 4296))
- using (TcpConnection connection = new TcpConnection())
+ using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
TestHelper.RunServerToClientTest(listener, connection, 4, 0, SendOption.FragmentedReliable);
}
public void TcpClientToServerTest()
{
using (TcpConnectionListener listener = new TcpConnectionListener(IPAddress.Any, 4296))
- using (TcpConnection connection = new TcpConnection())
+ using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
TestHelper.RunClientToServerTest(listener, connection, 4, 0, SendOption.FragmentedReliable);
}
public void ClientDisconnectTest()
{
using (TcpConnectionListener listener = new TcpConnectionListener(IPAddress.Any, 4296))
- using (TcpConnection connection = new TcpConnection())
+ using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
TestHelper.RunClientDisconnectTest(listener, connection);
}
public void ServerDisconnectTest()
{
using (TcpConnectionListener listener = new TcpConnectionListener(IPAddress.Any, 4296))
- using (TcpConnection connection = new TcpConnection())
+ using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
TestHelper.RunServerDisconnectTest(listener, connection);
}
listener.Start();
//Setup conneciton
- connection.DataReceived += delegate(object sender, DataEventArgs args)
+ connection.DataReceived += delegate(object sender, DataReceivedEventArgs args)
{
Trace.WriteLine("Data was received correctly.");
mutex.Set();
};
- connection.Connect(new NetworkEndPoint(IPAddress.Loopback, 4296));
+ connection.Connect();
//Wait until data is received
mutex.WaitOne();
//Setup listener
listener.NewConnection += delegate(object sender, NewConnectionEventArgs args)
{
- args.Connection.DataReceived += delegate(object innerSender, DataEventArgs innerArgs)
+ args.Connection.DataReceived += delegate(object innerSender, DataReceivedEventArgs innerArgs)
{
Trace.WriteLine("Data was received correctly.");
listener.Start();
//Connect
- connection.Connect(new NetworkEndPoint(IPAddress.Loopback, 4296));
+ connection.Connect();
connection.SendBytes(data, sendOption);
//Wait until data is received
listener.Start();
- connection.Connect(new NetworkEndPoint(IPAddress.Loopback, 4296));
+ connection.Connect();
mutex.WaitOne();
}
listener.Start();
- connection.Connect(new NetworkEndPoint(IPAddress.Loopback, 4296));
+ connection.Connect();
connection.Close();
using System.Net;
using System.Threading;
+using Hazel.Udp;
+
namespace Hazel.UnitTests
{
[TestClass]
[TestMethod]
public void UdpFieldTest()
{
+ NetworkEndPoint ep = new NetworkEndPoint(IPAddress.Loopback, 4296);
+
using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296))
- using (UdpConnection connection = new UdpClientConnection())
+ using (UdpConnection connection = new UdpClientConnection(ep))
{
listener.Start();
- NetworkEndPoint ep = new NetworkEndPoint(IPAddress.Loopback, 4296);
- connection.Connect(ep);
+ connection.Connect();
//Connection fields
Assert.AreEqual(ep, connection.EndPoint);
public void UdpIPv4ConnectionTest()
{
using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296, IPMode.IPv4))
- using (UdpConnection connection = new UdpClientConnection())
+ using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296, IPMode.IPv4)))
{
listener.Start();
- connection.Connect(new NetworkEndPoint(IPAddress.Loopback, 4296, IPMode.IPv4));
+ connection.Connect();
}
}
{
listener.Start();
- using (UdpConnection connection = new UdpClientConnection())
+ using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296, IPMode.IPv4)))
{
- connection.Connect(new NetworkEndPoint(IPAddress.Loopback, 4296, IPMode.IPv4));
+ connection.Connect();
}
- using (UdpConnection connection = new UdpClientConnection())
+ using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296, IPMode.IPv4AndIPv6)))
{
- connection.Connect(new NetworkEndPoint(IPAddress.Loopback, 4296, IPMode.IPv4AndIPv6));
+ connection.Connect();
}
}
}
public void UdpUnreliableServerToClientTest()
{
using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296))
- using (UdpConnection connection = new UdpClientConnection())
+ using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
TestHelper.RunServerToClientTest(listener, connection, 1, 3, SendOption.None);
}
public void UdpReliableServerToClientTest()
{
using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296))
- using (UdpConnection connection = new UdpClientConnection())
+ using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
TestHelper.RunServerToClientTest(listener, connection, 3, 3, SendOption.Reliable);
}
public void UdpUnreliableClientToServerTest()
{
using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296))
- using (UdpConnection connection = new UdpClientConnection())
+ using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
TestHelper.RunClientToServerTest(listener, connection, 1, 3, SendOption.None);
}
public void UdpReliableClientToServerTest()
{
using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296))
- using (UdpConnection connection = new UdpClientConnection())
+ using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
TestHelper.RunClientToServerTest(listener, connection, 3, 3, SendOption.Reliable);
}
public void KeepAliveClientTest()
{
using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296))
- using (UdpConnection connection = new UdpClientConnection())
+ using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
listener.Start();
- connection.Connect(new NetworkEndPoint(IPAddress.Loopback, 4296));
+ connection.Connect();
connection.KeepAliveInterval = 100;
System.Threading.Thread.Sleep(1100); //Enough time for ~10 keep alive packets
ManualResetEvent mutex = new ManualResetEvent(false);
using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296))
- using (UdpConnection connection = new UdpClientConnection())
+ using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
listener.NewConnection += delegate(object sender, NewConnectionEventArgs args)
{
listener.Start();
- connection.Connect(new NetworkEndPoint(IPAddress.Loopback, 4296));
+ connection.Connect();
mutex.WaitOne();
}
public void ClientDisconnectTest()
{
using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296))
- using (UdpConnection connection = new UdpClientConnection())
+ using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
TestHelper.RunClientDisconnectTest(listener, connection);
}
public void ServerDisconnectTest()
{
using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296))
- using (UdpConnection connection = new UdpClientConnection())
+ using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
TestHelper.RunServerDisconnectTest(listener, connection);
}
/// <remarks>
/// <para>
/// DataReceived is invoked everytime a message is received from the end point of this connection, the message
- /// that was received can be found in the <see cref="DataEventArgs"/> alongside other information from the
+ /// that was received can be found in the <see cref="DataReceivedEventArgs"/> alongside other information from the
/// event.
/// </para>
/// <include file="DocInclude/common.xml" path="docs/item[@name='Event_Thread_Safety_Warning']/*" />
/// </remarks>
/// <example>
- /// <code language="C#" source="DocInclude/TcpClientExample.cs"/>
+ /// <code language="C#" source="../DocInclude/TcpClientExample.cs"/>
/// </example>
- public event EventHandler<DataEventArgs> DataReceived;
+ public event EventHandler<DataReceivedEventArgs> DataReceived;
/// <summary>
/// Called when the end point disconnects or an error occurs.
/// <include file="DocInclude/common.xml" path="docs/item[@name='Event_Thread_Safety_Warning']/*" />
/// </remarks>
/// <example>
- /// <code language="C#" source="DocInclude/TcpClientExample.cs"/>
+ /// <code language="C#" source="../DocInclude/TcpClientExample.cs"/>
/// </example>
public event EventHandler<DisconnectedEventArgs> Disconnected;
/// </summary>
/// <remarks>
/// Calling Connect makes the connection attempt to connect to the end point that's specified in the
- /// <see cref="ConnectionEndPoint"/> passed. This method will block until the connection attempt completes and
- /// will throw a <see cref="HazelException"/> if there is a problem connecting.
+ /// constructor. This method will block until the connection attempt completes and will throw a
+ /// <see cref="HazelException"/> if there is a problem connecting.
/// </remarks>
- public abstract void Connect(ConnectionEndPoint remoteEndPoint);
+ public abstract void Connect();
/// <summary>
/// Invokes the DataReceived event.
/// </remarks>
protected void InvokeDataReceived(byte[] bytes, SendOption sendOption)
{
- DataEventArgs args = DataEventArgs.GetObject();
+ DataReceivedEventArgs args = DataReceivedEventArgs.GetObject();
args.Set(bytes, sendOption);
//Make a copy to avoid race condition between null check and invocation
- EventHandler<DataEventArgs> handler = DataReceived;
+ EventHandler<DataReceivedEventArgs> handler = DataReceived;
if (handler != null)
handler(this, args);
}
/// connection.
/// </para>
/// <para>
- /// This calls <see cref="Dispose"/> and therefore sets <see cref="State"/> straight to
+ /// This calls <see cref="Dispose()"/> and therefore sets <see cref="State"/> straight to
/// <see cref="ConnectionState.NotConnected"/>. Once you call Close you will not be able to send any more
/// data using this connection and no more data will be received.
/// </para>
/// <para>
/// Hazel doesn't store connections so it is your responsibility to keep track of the connections to your
/// server. Note that as <see cref="Connection"/> implements <see cref="IDisposable"/> if you are not storing
- /// a connection then as a bare minimum you should call <see cref="Connection.Dispose"/> here in order to
+ /// a connection then as a bare minimum you should call <see cref="Connection.Dispose()"/> here in order to
/// release the connection correctly.
/// </para>
/// <include file="DocInclude/common.xml" path="docs/item[@name='Event_Thread_Safety_Warning']/*" />
/// </remarks>
/// <example>
- /// <code language="C#" source="DocInclude/TcpListenerExample.cs"/>
+ /// <code language="C#" source="../DocInclude/TcpListenerExample.cs"/>
/// </example>
public event EventHandler<NewConnectionEventArgs> NewConnection;
/// connects the <see cref="NewConnection"/> event will be invoked containing the connection to the new client.
/// </para>
/// <para>
- /// To stop listening you should call <see cref="Dispose"/>.
+ /// To stop listening you should call <see cref="Dispose()"/>.
/// </para>
/// </remarks>
/// <example>
- /// <code language="C#" source="DocInclude/TcpListenerExample.cs"/>
+ /// <code language="C#" source="../DocInclude/TcpListenerExample.cs"/>
/// </example>
public abstract void Start();
/// <summary>
/// Invokes the NewConnection event with the supplied connection.
/// </summary>
- /// <param name="args">The connection to pass to subscribers.</param>
+ /// <param name="connection">The connection to pass in the arguments.</param>
/// <remarks>
/// Implementers should call this to invoke the <see cref="NewConnection"/> event before data is received so that
/// subscribers do not miss any data that may have been sent immediately after connecting.
handler(this, args);
}
+ /// <summary>
+ /// Closes the connection listener safely.
+ /// </summary>
+ /// <remarks>
+ /// Internally this simply calls Dispose therefore trying to reuse the ConnectionListener after calling Close will
+ /// cause ObjectDisposedExceptions.
+ /// </remarks>
+ public virtual void Close()
+ {
+ Dispose();
+ }
+
/// <summary>
/// Call to dispose of the connection listener.
/// </summary>
namespace Hazel
{
/// <summary>
- /// Event arguments for the <see cref="Connection.DataEvent"/> event.
+ /// Event arguments for the <see cref="Connection.DataReceived"/> event.
/// </summary>
/// <remarks>
/// <para>
/// This contains information about messages received by a connection and is passed to subscribers of the
- /// <see cref="Connection.DataEvent">DataEvent</see>.
+ /// <see cref="Connection.DataReceived">DataEvent</see>.
/// </para>
/// <include file="DocInclude/common.xml" path="docs/item[@name='Recyclable']/*" />
/// </remarks>
/// <threadsafety static="true" instance="true"/>
- public class DataEventArgs : EventArgs, IRecyclable
+ public class DataReceivedEventArgs : EventArgs, IRecyclable
{
/// <summary>
/// Object pool for this event.
/// </summary>
- static readonly ObjectPool<DataEventArgs> objectPool = new ObjectPool<DataEventArgs>(() => new DataEventArgs());
+ static readonly ObjectPool<DataReceivedEventArgs> objectPool = new ObjectPool<DataReceivedEventArgs>(() => new DataReceivedEventArgs());
/// <summary>
/// Returns an instance of this object from the pool.
/// </summary>
/// <returns>A new or recycled DataEventArgs object.</returns>
- internal static DataEventArgs GetObject()
+ internal static DataReceivedEventArgs GetObject()
{
return objectPool.GetObject();
}
/// <summary>
/// Private constructor for object pool.
/// </summary>
- DataEventArgs()
+ DataReceivedEventArgs()
{
}
/// The exception, if any, that caused the disconnect.
/// </summary>
/// <remarks>
- /// If the disconnection was caused because of an exception occuring (for exemple a <see cref="SocketException"/>
- /// on network based connections) this will contain the error that caused it or a <see cref="HazelException"/>
- /// with the details of the exception, if the disconnection wasn't caused by an error then this will contain null.
+ /// If the disconnection was caused because of an exception occuring (for exemple a
+ /// <see cref="System.Net.Sockets.SocketException"/> on network based connections) this will contain the error
+ /// that caused it or a <see cref="HazelException"/> with the details of the exception, if the disconnection
+ /// wasn't caused by an error then this will contain null.
/// </remarks>
public Exception Exception { get; private set; }
<docs>
<item name="Event_Thread_Safety_Warning">
<para>
- As with all Hazel events it is invoked on a thread from the .NET <see cref="ThreadPool"/> and hence any
- subscribers should ensure their handling code is thread safe. Implementing connections are not bound to
- invoking this event in the sequence messages are received, in fact implementers are only required to
- ensure this method is always and only invoked for a user sent message, therefore subscribers should be
- aware that this event may be called out of order and may be called whilst another thread is still handling
- an invocation of the event.
+ As with all Hazel events it is invoked on a thread from the .NET
+ <see cref="System.Threading.ThreadPool">ThreadPool</see> and hence any subscribers should ensure their
+ handling code is thread safe. Implementing connections are not bound to invoking this event in the sequence
+ messages are received, in fact implementers are only required to ensure this method is always and only invoked
+ for a user sent message, therefore subscribers should be aware that this event may be called out of order and
+ may be called whilst another thread is still handling an invocation of the event.
</para>
</item>
<item name="Recyclable">
This method sends a number of bytes in a message to the end point of this client using the given
<see cref="SendOption"/> to describe how the data should be sent. Sending messages requires that the
this connection is connected to a remote end point and SendBytes will throw an exception if that is not
- the case. See the <see cref="State"/> property for information on whether a connection is connected or not.
+ the case. See the <see cref="Connection.State"/> property for information on whether a connection is connected or not.
</para>
</item>
</docs>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<Topics>
+ <Topic id="a9f1cfac-8d7d-4668-af2f-60b588018519" visible="True" isSelected="true" title="Introduction" />
+ <Topic id="4e89497f-70d3-4fb3-9c92-cfd8981ccfa9" visible="True" title="Quickstart" />
+ <Topic id="852623d7-5d68-47e2-b5db-76498da44f95" visible="True" isExpanded="true" title="Technical Details">
+ <Topic id="0ffa7468-4813-4fd0-ba65-2fa5436bc5ce" visible="True" isExpanded="true" title="Protocols">
+ <Topic id="a3d0767d-2da3-45d2-80d7-b0c05698992f" visible="True" />
+ <Topic id="490c9f89-38c8-4ccf-8af1-683ba7fb0888" visible="True" />
+ </Topic>
+ </Topic>
+</Topics>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<topic id="a9f1cfac-8d7d-4668-af2f-60b588018519" revisionNumber="1">
+ <developerConceptualDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <introduction>
+ <para>
+ Welcome to the Hazel documentation! Here you will find technical
+ details, API references and tutorials for getting started with Hazel.
+ </para>
+ <para>
+ Hazel Networking is an open source low level networking library for C# providing
+ connection orientated, message bassed communication via TCP, UDP and
+ RUDP. You can download it from Github
+ <externalLink>
+ <linkText>here</linkText>
+ <linkUri>https://github.com/DarkRiftNetworking/Hazel-Networking</linkUri>
+ <linkTarget>_blank</linkTarget>
+ </externalLink>
+ .
+ </para>
+ </introduction>
+
+ </developerConceptualDocument>
+</topic>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<topic id="4e89497f-70d3-4fb3-9c92-cfd8981ccfa9" revisionNumber="1">
+ <developerWalkthroughDocument
+ xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <introduction>
+ <para>
+ Hazel is a low level networking library that takes away a lot of the pain of writing sockets. Hazel provides the guarantee of connection orientated, message based communication across TCP, UDP and RUDP.
+ </para>
+ <para>
+ This guide will take you through the stages of writing a console based server and connecting to it from a console based client.
+ </para>
+ </introduction>
+
+ <!-- <prerequisites><content>Optional prerequisites info</content></prerequisites> -->
+
+ <section>
+ <title>Creating a Server</title>
+ <content>
+ <procedure>
+ <title>Creating a Listener</title>
+ <steps class="ordered">
+ <step>
+ <content>
+ <para>Create a new solution containing a Console project named "Server" (or at least something obvious).</para>
+ </content>
+ </step>
+ <step>
+ <content>
+ <para>Add a reference to Hazel.dll in the project.</para>
+ </content>
+ </step>
+ <step>
+ <content>
+ <para>Modify the default file to look like this, we'll go through each part individually.</para>
+ <code language="C#">
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Net;
+
+using Hazel;
+using Hazel.Tcp;
+
+namespace HazelExample
+{
+ class ServerExample
+ {
+ static ConnectionListener listener;
+
+ public static void Main(string[] args)
+ {
+ listener = new TcpConnectionListener(IPAddress.Any, 4296);
+
+ listener.NewConnection += NewConnectionHandler;
+
+ Console.WriteLine("Starting server!");
+
+ listener.Start();
+
+ Console.WriteLine("Press any key to continue...");
+
+ Console.ReadKey();
+
+ listener.Close();
+ }
+ }
+}
+ </code>
+ <para>
+ Firstly we need to tell the compiler that we are <codeInline>using Hazel;</codeInline> and <codeInline>using Hazel.Tcp;</codeInline> so we have access to Hazel's general and TCP specific types. Secondly we create a <codeEntityReference qualifyHint="false" linkText="ConnectionListener">T:Hazel.ConnectionListener</codeEntityReference>, these wait on a specified port and accept new clients to the server invoking the <codeEntityReference qualifyHint="false" linkText="NewConnection">E:Hazel.ConnectionListener.NewConnection</codeEntityReference> event each time a new client connects.
+ </para>
+ <para>
+ We then instruct the ConnectionListener to begin listening for new clients by calling <codeEntityReference qualifyHint="false" linkText="Start">M:ConnectionListener.Start</codeEntityReference> and then finally we close the listener using <codeEntityReference qualifyHint="false" linkText="Close">M:ConnectionListener.Close</codeEntityReference>.
+ </para>
+ <para>
+ In this circumstance it would be much better if we enclosed the listener within a <codeInline>using</codeInline> block as it implements IDisposable, however for the majority of use cases you are more likely to use the listener in this way. If you want to see it used in a <codeInline>using</codeInline> block then look at the unit tests.
+ </para>
+ <para>
+ If you want to use UDP instead of TCP then it is as simple as including the <codeInline>Hazel.Udp</codeInline> namespace and creating a <codeEntityReference qualifyHint="false" linkText="UdpConnectionListener">T:UdpConnectionListener</codeEntityReference> instead.
+ </para>
+ </content>
+ </step>
+ </steps>
+ </procedure>
+
+ <procedure>
+ <title>Handling Events</title>
+ <steps class="ordered">
+ <step>
+ <content>
+ <para>In the last example we subscribed to the <codeEntityReference qualifyHint="false" linkText="NewConnection">E:ConnectionListener.NewConnection</codeEntityReference> event but we never spsecified what to do in that event. Add the following method to your code.</para>
+ <code language="C#">
+static void NewConnectionHandler(object sender, NewConnectionEventArgs args)
+{
+ Console.WriteLine("New connection from " + args.Connection.EndPoint.ToString();
+
+ args.Connection.DataReceived += DataReceivedHandler;
+}
+ </code>
+ <para>This method is fairly simple, it follows the standard event handler delegate and take a sender (in this case it will be the ConnectionListener that called the event) and some args. The args contain a Connection which is the main object we use for communication with clients.</para>
+ <para>You can imagine this process in a similar way to TCP. You create a listener which creates sockets on the server side for each client socket that connects to it.</para>
+ <para>In this method we simply print out the IP of the client that just connected and then we subscribe to any data that this client sends us. You may also want to store the connection here for later reference as Hazel doesn't maintain a list of connection for you.</para>
+ </content>
+ </step>
+ <step>
+ <content>
+ <para>Once again we've got an undeclared event handler so lets fill that in now.</para>
+ <code language="C#">
+private static void DataReceivedHandler(object sender, DataEventArgs args)
+{
+ Connection connection = (Connection)sender;
+
+ Console.WriteLine("Received (" + string.Join<byte>(", ", args.Bytes) + ") from " + connection.EndPoint.ToString());
+
+ connection.SendBytes(args.Bytes, args.SendOption);
+
+ args.Recycle();
+}
+ </code>
+ <para>Again this method follows the standard event handler delegate and this time we make use of the sender parameter to get the connection that received the data. We then go on to print out the data received and then we send it back to the client using <codeEntityReference qualifyHint="false" linkText="SendBytes">M:ConnectionListener.SendBytes</codeEntityReference>. When we send we specify that the send option should be the same as the data that was received, we'll talk more about send options later.</para>
+ <para>You have also probably noticed that I didn't mention the <codeEntityReference qualifyHint="false" linkText="Recycle">M:DataEventArgs.Recycle</codeEntityReference> call. Recycle is an optional call that tells Hazel that it is now safe to use the object again rather than creating a new one and having to wait for the GC to collect the old object. If you are sending a lot of data then you will get less GC runs if you call Recycle but it is not necerssary to call it and if you dont the GC will collect it as normal. Also note that you should only call Recycle once you are done using the object otherwise the data inside it may change!</para>
+ </content>
+ </step>
+ </steps>
+ </procedure>
+
+ <para>In total, you should have something like this.</para>
+ <code language="C#">
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Net;
+
+using Hazel;
+using Hazel.Tcp;
+
+namespace HazelExample
+{
+ class ServerExample
+ {
+ static ConnectionListener listener;
+
+ public static void Main(string[] args)
+ {
+ listener = new TcpConnectionListener(IPAddress.Any, 4296);
+
+ listener.NewConnection += NewConnectionHandler;
+
+ Console.WriteLine("Starting server!");
+
+ listener.Start();
+
+ Console.WriteLine("Press any key to continue...");
+
+ Console.ReadKey();
+
+ listener.Close();
+ }
+
+ static void NewConnectionHandler(object sender, NewConnectionEventArgs args)
+ {
+ Console.WriteLine("New connection from " + args.Connection.EndPoint.ToString();
+
+ args.Connection.DataReceived += DataReceivedHandler;
+
+ args.Recycle();
+ }
+
+ private static void DataReceivedHandler(object sender, DataEventArgs args)
+ {
+ Connection connection = (Connection)sender;
+
+ Console.WriteLine("Received (" + string.Join<byte>(", ", args.Bytes) + ") from " + connection.EndPoint.ToString());
+
+ connection.SendBytes(args.Bytes, args.SendOption);
+
+ args.Recycle();
+ }
+ }
+}
+ </code>
+ </content>
+ </section>
+
+ <section>
+ <title>Creating a Client</title>
+ <content>
+ <procedure>
+ <title>Connecting to a Server</title>
+ <steps class="ordered">
+ <step>
+ <content>
+ <para>Create a new project in your solution for th client and add a reference to Hazel.dll.</para>
+ </content>
+ </step>
+
+ <step>
+ <content>
+ <para>Modify the default file to contain the following.</para>
+ <code language="C#">
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using Hazel;
+using Hazel.Tcp;
+
+namespace HazelExample
+{
+ class ClientExample
+ {
+ static Connection connection;
+
+ public static void Main(string[] args)
+ {
+ NetworkEndPoint endPoint = new NetworkEndPoint("127.0.0.1", 4296);
+
+ connection = new TcpConnection(endPoint);
+
+ connection.DataReceived += DataReceived;
+
+ Console.WriteLine("Connecting!");
+
+ connection.Connect();
+
+ Console.WriteLine("Press any key to continue...");
+
+ Console.ReadKey();
+
+ connection.Close();
+ }
+ }
+}
+ </code>
+ <para>As you can see you simply create a <codeEntityReference qualifyHint="false" linkText="NetworkEndPoint">T:NetworkEndPoint</codeEntityReference> for the remote server and then pass it into a new <codeEntityReference qualifyHint="false" linkText="Connection">T:Connection</codeEntityReference>. Then you can setup any events needed and finally call <codeEntityReference qualifyHint="false" linkText="Connect">M:Connection.Connect</codeEntityReference> to begin the connection.</para>
+ <para>Finally we close the connection using <codeEntityReference qualifyHint="false" linkText="Close">M:Connection.Close</codeEntityReference>. Again, Connection implements IDisposable and so must be closed, if it is easier you could wrap it in a <codeInline>using</codeInline> block.</para>
+ <para>
+ If you are using UDP instead of TCP then include the <codeInline>Hazel.Udp</codeInline> namespace and create a <codeEntityReference qualifyHint="false" linkText="UdpClientConnection">T:UdpClientConnection</codeEntityReference> instead.
+ </para>
+ </content>
+ </step>
+ </steps>
+ </procedure>
+
+ <procedure>
+ <title>Handling Events</title>
+ <steps class="ordered">
+ <step>
+ <content>
+ <para>Add the following event handler to receive data, you'll notice that this is the same event we used in the server and so it will not be explained.</para>
+ <code language="C#">
+private static void DataReceived(object sender, DataEventArgs args)
+{
+ Console.WriteLine("Received (" + string.Join<byte>(", ", args.Bytes) + ") from " + connection.EndPoint.ToString());
+
+ args.Recycle();
+}
+ </code>
+ </content>
+ </step>
+ </steps>
+ </procedure>
+
+ <procedure>
+ <title>Sending Messages</title>
+ <steps class="ordered">
+ <step>
+ <content>
+ <para>Sending a message is the same for both the server and client, you simply call <codeEntityReference qualifyHint="false" linkText="SendBytes">M:Connection.SendBytes</codeEntityReference> on your connection.</para>
+ <para>Add the following line after the call to Connect</para>
+ <code>
+connection.SendBytes(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
+ </code>
+ </content>
+ </step>
+ </steps>
+ </procedure>
+
+ <para>You should have something like this.</para>
+ <code language="C#">
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using Hazel;
+using Hazel.Tcp;
+
+namespace HazelExample
+{
+ class ClientExample
+ {
+ static Connection connection;
+
+ public static void Main(string[] args)
+ {
+ NetworkEndPoint endPoint = new NetworkEndPoint("127.0.0.1", 4296);
+
+ connection = new TcpConnection(endPoint);
+
+ connection.DataReceived += DataReceived;
+
+ Console.WriteLine("Connecting!");
+
+ connection.Connect();
+
+ connection.SendBytes(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
+
+ Console.WriteLine("Press any key to continue...");
+
+ Console.ReadKey();
+
+ connection.Close();
+ }
+
+ private static void DataReceived(object sender, DataEventArgs args)
+ {
+ Console.WriteLine("Received (" + string.Join<byte>(", ", args.Bytes) + ") from " + connection.EndPoint.ToString());
+
+ args.Recycle();
+ }
+ }
+}
+ </code>
+ </content>
+ </section>
+ </developerWalkthroughDocument>
+</topic>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<topic id="0ffa7468-4813-4fd0-ba65-2fa5436bc5ce" revisionNumber="1">
+ <developerConceptualDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <introduction>
+ <para>
+ In order to provide the guarantees that it does, Hazel augments each
+ transport protocol with its own meta data (or header data) that is
+ hidden from users.
+ </para>
+ </introduction>
+
+ </developerConceptualDocument>
+</topic>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<topic id="a3d0767d-2da3-45d2-80d7-b0c05698992f" revisionNumber="1">
+ <developerSDKTechnologyOverviewArchitectureDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <introduction>
+ <para>
+ Hazel's TCP protocol is fairly simple as TCP already provides connection
+ based communication and thus only needs a message system implementing.
+ </para>
+ </introduction>
+
+ <section>
+ <title>Header Data</title>
+ <content>
+ <para>
+ The TCP protocol is fairly simple interms of header. 4 bytes are
+ added to mark the number of bytes in each message.
+ </para>
+ <table>
+ <row>
+ <entry><para>Length (MSB)</para></entry>
+ <entry><para>Length</para></entry>
+ <entry><para>Length</para></entry>
+ <entry><para>Length (LSB)</para></entry>
+ <entry><para>Data...</para></entry>
+ </row>
+ </table>
+ <para>All header data in Hazel is encoded in big endian format.</para>
+ </content>
+ </section>
+
+ </developerSDKTechnologyOverviewArchitectureDocument>
+</topic>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<topic id="490c9f89-38c8-4ccf-8af1-683ba7fb0888" revisionNumber="1">
+ <developerSDKTechnologyOverviewArchitectureDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <introduction>
+ <para>
+ UDP provides message based communication however it is not connection
+ oriented nor does it have any built in functionality for reliable or
+ fragmented delivery of messages hence the implementation to cover this
+ if fairly complicated.
+ </para>
+ </introduction>
+
+ <section>
+ <title>Header Data</title>
+ <content>
+ <para>
+ The UDP protocol has multiple layers of header data depending on the
+ send options that are requested with the message.
+ </para>
+ <table>
+ <row>
+ <entry><para><legacyBold>Unreliable</legacyBold></para></entry>
+ <entry><para>Type</para></entry>
+ <entry><para>Data...</para></entry>
+ <entry><para> </para></entry>
+ <entry><para> </para></entry>
+ </row>
+ <row>
+ <entry><para><legacyBold>Reliable</legacyBold></para></entry>
+ <entry><para>Type</para></entry>
+ <entry><para>ID (MSB)</para></entry>
+ <entry><para>ID (LSB)</para></entry>
+ <entry><para>Data...</para></entry>
+ </row>
+ </table>
+ <para>All header data in Hazel is encoded in big endian format.</para>
+ <para>
+ In both of these, <quoteInline>Type</quoteInline> is an identifier
+ that holds the SendOption or another value indicating a control
+ packet of data. The most significant 4 bits of
+ <quoteInline>Type</quoteInline> are reserved for future use and the
+ least significant bits specify the send option flags for the message.
+ </para>
+ <table>
+ <tableHeader>
+ <row>
+ <entry><para>128</para></entry>
+ <entry><para>64</para></entry>
+ <entry><para>32</para></entry>
+ <entry><para>16</para></entry>
+ <entry><para>8</para></entry>
+ <entry><para>4</para></entry>
+ <entry><para>2</para></entry>
+ <entry><para>1</para></entry>
+ </row>
+ </tableHeader>
+ <row>
+ <entry><para>Reserved</para></entry>
+ <entry><para>Reserved</para></entry>
+ <entry><para>Reserved</para></entry>
+ <entry><para>Reserved</para></entry>
+ <entry><para>Control</para></entry>
+ <entry><para>Reserved</para></entry>
+ <entry><para>Fragmented</para></entry>
+ <entry><para>Reliable</para></entry>
+ </row>
+ </table>
+ <para>
+ <quoteInline>Reliable</quoteInline> and
+ <quoteInline>Fragmented</quoteInline> are both flags for their
+ respective send option, <quoteInline>Control</quoteInline>, if set,
+ specifies that the 3 least significant bits refer to a control code:
+ </para>
+ <table>
+ <row>
+ <entry><para>0</para></entry>
+ <entry><para>Hello</para></entry>
+ </row>
+ <row>
+ <entry><para>1</para></entry>
+ <entry><para>Disconnect</para></entry>
+ </row>
+ <row>
+ <entry><para>2</para></entry>
+ <entry><para>Acknowledgment</para></entry>
+ </row>
+ </table>
+ </content>
+ </section>
+
+ <section>
+ <title>Reliable Delivery</title>
+ <content>
+ <para>
+ To implement reliable delivery 2 ID bytes are sent which identify the
+ packet. When the receiver receives the data is replys with an
+ acknowledgement packet as follows:
+ </para>
+ <table>
+ <row>
+ <entry><para><legacyBold>Acknowledgement</legacyBold></para></entry>
+ <entry><para>Type</para></entry>
+ <entry><para>ID (MSB)</para></entry>
+ <entry><para>ID (LSB)</para></entry>
+ </row>
+ </table>
+ <para>
+ Where type is specifying an acknowledgement packet as laid out above.
+ </para>
+ <para>
+ If the sending client does not receive an acknowledgement after a
+ specific amount of time elapses from the pakcet being sent then it
+ resends that packet and thetime before the next resend of that packet
+ is doubled. When the sending client receives the acknowledgement is
+ should not resend the data again.
+ </para>
+ <para>
+ The receiving client must also ensure that it does not present the
+ same packet to the user twice but must acknowledge any packets it
+ receives, even if it has already received that packet, in case an
+ acknowledgement is lost.
+ </para>
+ <para>
+ After a specific number of resends without acknowledgement a sending
+ client may mark assume that communication has been interrupted and
+ thus mark the connection as disconnected.
+ </para>
+ </content>
+ </section>
+
+ <section>
+ <title>Keepalive packets</title>
+ <content>
+ <para>
+ Keepalive packets should be sent after a specific time has elapsed
+ since the last packet (either keepalive, acknowledgement or user sent)
+ was transmitted and should be sent using reliable delivery so that an
+ acknowledgement can be received to indicate communication has not been
+ lost. As packets that are not acknowledged should cause a
+ disconnection no additional logic is required for keepalives.
+ </para>
+ </content>
+ </section>
+ </developerSDKTechnologyOverviewArchitectureDocument>
+</topic>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<topic id="852623d7-5d68-47e2-b5db-76498da44f95" revisionNumber="1">
+ <developerConceptualDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <introduction>
+ <para>
+ Underneath Hazel there are a lot of technicalities, this section will
+ help outline those details so you understand Hazel's implementations
+ better.
+ </para>
+ </introduction>
+
+ </developerConceptualDocument>
+</topic>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <!-- The configuration and platform will be used to determine which assemblies to include from solution and
+ project documentation sources -->
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{359995e0-bef3-42dd-800f-20368ff5fabb}</ProjectGuid>
+ <SHFBSchemaVersion>2015.6.5.0</SHFBSchemaVersion>
+ <!-- AssemblyName, Name, and RootNamespace are not used by SHFB but Visual Studio adds them anyway -->
+ <AssemblyName>Documentation</AssemblyName>
+ <RootNamespace>Documentation</RootNamespace>
+ <Name>Documentation</Name>
+ <!-- SHFB properties -->
+ <FrameworkVersion>.NET Framework 4.5</FrameworkVersion>
+ <OutputPath>.\Help\</OutputPath>
+ <HtmlHelpName>Documentation</HtmlHelpName>
+ <Language>en-US</Language>
+ <SaveComponentCacheCapacity>100</SaveComponentCacheCapacity>
+ <BuildAssemblerVerbosity>OnlyWarningsAndErrors</BuildAssemblerVerbosity>
+ <HelpFileFormat>Website</HelpFileFormat>
+ <IndentHtml>False</IndentHtml>
+ <KeepLogFile>True</KeepLogFile>
+ <DisableCodeBlockComponent>False</DisableCodeBlockComponent>
+ <CleanIntermediates>True</CleanIntermediates>
+ <DocumentationSources>
+ <DocumentationSource sourceFile="..\bin\Release\Hazel.dll" />
+<DocumentationSource sourceFile="..\bin\Release\Hazel.xml" /></DocumentationSources>
+ <HelpFileVersion>1.0.0.0</HelpFileVersion>
+ <MaximumGroupParts>2</MaximumGroupParts>
+ <NamespaceGrouping>False</NamespaceGrouping>
+ <SyntaxFilters>C#</SyntaxFilters>
+ <SdkLinkTarget>Blank</SdkLinkTarget>
+ <RootNamespaceContainer>False</RootNamespaceContainer>
+ <PresentationStyle>VS2013</PresentationStyle>
+ <Preliminary>False</Preliminary>
+ <NamingMethod>Guid</NamingMethod>
+ <HelpTitle>Hazel Networking</HelpTitle>
+ <ContentPlacement>AboveNamespaces</ContentPlacement>
+ <NamespaceSummaries>
+ <NamespaceSummaryItem name="Hazel" isDocumented="True" xmlns="">The Hazel namespace contains various classes used across the different communication methods implemented.</NamespaceSummaryItem>
+<NamespaceSummaryItem name="Hazel.Tcp" isDocumented="True" xmlns="">Namespace for classes relating to communication via TCP.</NamespaceSummaryItem>
+<NamespaceSummaryItem name="Hazel.Udp" isDocumented="True" xmlns="">Namespace for classes relating to communication via TCP.</NamespaceSummaryItem></NamespaceSummaries>
+ </PropertyGroup>
+ <!-- There are no properties for these groups. AnyCPU needs to appear in order for Visual Studio to perform
+ the build. The others are optional common platform types that may appear. -->
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|Win32' ">
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|Win32' ">
+ </PropertyGroup>
+ <!-- Import the SHFB build targets -->
+ <Import Project="$(SHFBROOT)\SandcastleHelpFileBuilder.targets" />
+ <!-- The pre-build and post-build event properties must appear *after* the targets file import in order to be
+ evaluated correctly. -->
+ <PropertyGroup>
+ <PreBuildEvent>
+ </PreBuildEvent>
+ <PostBuildEvent>
+ </PostBuildEvent>
+ <RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
+ </PropertyGroup>
+ <ItemGroup>
+ <None Include="Documentation\Introduction.aml" />
+ <None Include="Documentation\Technical Details\Protocols\TCP.aml" />
+ <None Include="Documentation\Technical Details\Protocols\UDP-RUDP.aml" />
+ <None Include="Documentation\Technical Details\Protocols\TCP.aml" />
+ <None Include="Documentation\Technical Details\Technical Details.aml" />
+ <None Include="Documentation\Technical Details\Protocols\Protocols.aml" />
+ <None Include="Documentation\Quickstart.aml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Folder Include="Documentation\Technical Details\Protocols\" />
+ <Folder Include="Documentation\Technical Details\" />
+ <Folder Include="Documentation\" />
+ </ItemGroup>
+ <ItemGroup>
+ <ContentLayout Include="Documentation\Content Layout.content" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\Hazel.csproj">
+ <Name>Hazel</Name>
+ <Project>{02CFBD30-D77D-400F-94B2-700F60EFDD7F}</Project>
+ </ProjectReference>
+ </ItemGroup>
+</Project>
\ No newline at end of file
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SendOption.cs" />
<Compile Include="SendOptionInternal.cs" />
- <Compile Include="StateObject.cs" />
+ <Compile Include="Tcp\StateObject.cs" />
<Compile Include="ConnectionStatistics.cs" />
- <Compile Include="TcpConnection.cs" />
- <Compile Include="TcpConnectionListener.cs" />
- <Compile Include="UdpClientConnection.cs" />
- <Compile Include="UdpConnection.cs">
+ <Compile Include="Tcp\TcpConnection.cs" />
+ <Compile Include="Tcp\TcpConnectionListener.cs" />
+ <Compile Include="Udp\UdpClientConnection.cs" />
+ <Compile Include="Udp\UdpConnection.cs">
<SubType>Code</SubType>
</Compile>
- <Compile Include="UdpConnection.KeepAlive.cs" />
- <Compile Include="UdpConnection.Reliable.cs" />
- <Compile Include="UdpConnectionListener.cs" />
- <Compile Include="UdpServerConnection.cs" />
+ <Compile Include="Udp\UdpConnection.KeepAlive.cs" />
+ <Compile Include="Udp\UdpConnection.Reliable.cs" />
+ <Compile Include="Udp\UdpConnectionListener.cs" />
+ <Compile Include="Udp\UdpServerConnection.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="DocInclude\common.xml">
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <PropertyGroup>
- <!-- The configuration and platform will be used to determine which assemblies to include from solution and
- project documentation sources -->
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
- <SchemaVersion>2.0</SchemaVersion>
- <ProjectGuid>{359995e0-bef3-42dd-800f-20368ff5fabb}</ProjectGuid>
- <SHFBSchemaVersion>2015.6.5.0</SHFBSchemaVersion>
- <!-- AssemblyName, Name, and RootNamespace are not used by SHFB but Visual Studio adds them anyway -->
- <AssemblyName>Documentation</AssemblyName>
- <RootNamespace>Documentation</RootNamespace>
- <Name>Documentation</Name>
- <!-- SHFB properties -->
- <FrameworkVersion>.NET Framework 4.5</FrameworkVersion>
- <OutputPath>.\Help\</OutputPath>
- <HtmlHelpName>Documentation</HtmlHelpName>
- <Language>en-US</Language>
- <SaveComponentCacheCapacity>100</SaveComponentCacheCapacity>
- <BuildAssemblerVerbosity>OnlyWarningsAndErrors</BuildAssemblerVerbosity>
- <HelpFileFormat>Website</HelpFileFormat>
- <IndentHtml>False</IndentHtml>
- <KeepLogFile>True</KeepLogFile>
- <DisableCodeBlockComponent>False</DisableCodeBlockComponent>
- <CleanIntermediates>True</CleanIntermediates>
- <DocumentationSources>
- <DocumentationSource sourceFile="bin\Release\Hazel.dll" />
-<DocumentationSource sourceFile="bin\Release\Hazel.XML" /></DocumentationSources>
- <HelpFileVersion>1.0.0.0</HelpFileVersion>
- <MaximumGroupParts>2</MaximumGroupParts>
- <NamespaceGrouping>False</NamespaceGrouping>
- <SyntaxFilters>C#</SyntaxFilters>
- <SdkLinkTarget>Blank</SdkLinkTarget>
- <RootNamespaceContainer>False</RootNamespaceContainer>
- <PresentationStyle>VS2013</PresentationStyle>
- <Preliminary>False</Preliminary>
- <NamingMethod>Guid</NamingMethod>
- <HelpTitle>Hazel Networking</HelpTitle>
- <ContentPlacement>AboveNamespaces</ContentPlacement>
- </PropertyGroup>
- <!-- There are no properties for these groups. AnyCPU needs to appear in order for Visual Studio to perform
- the build. The others are optional common platform types that may appear. -->
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|Win32' ">
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|Win32' ">
- </PropertyGroup>
- <!-- Import the SHFB build targets -->
- <Import Project="$(SHFBROOT)\SandcastleHelpFileBuilder.targets" />
- <!-- The pre-build and post-build event properties must appear *after* the targets file import in order to be
- evaluated correctly. -->
- <PropertyGroup>
- <PreBuildEvent>
- </PreBuildEvent>
- <PostBuildEvent>
- </PostBuildEvent>
- <RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
- </PropertyGroup>
- <ItemGroup>
- <ProjectReference Include="Hazel.csproj">
- <Name>Hazel</Name>
- <Project>{02CFBD30-D77D-400F-94B2-700F60EFDD7F}</Project>
- </ProjectReference>
- </ItemGroup>
-</Project>
\ No newline at end of file
/// <summary>
/// Creates a NetworkEndPoint from a given <see cref="System.Net.EndPoint">EndPoint</see>.
/// </summary>
- /// <param name="endPoint">The end point to wrap./param>
+ /// <param name="endPoint">The end point to wrap.</param>
+ /// <param name="mode">The IP mode to use.</param>
public NetworkEndPoint(EndPoint endPoint, IPMode mode = IPMode.IPv4AndIPv6)
{
this.EndPoint = endPoint;
/// </summary>
/// <param name="address">The IP address of the server.</param>
/// <param name="port">The port the server is listening on.</param>
+ /// <param name="mode">The IP mode to use.</param>
/// <remarks>
/// When using this constructor <see cref="EndPoint"/> will contain an <see cref="IPEndPoint"/>.
/// </remarks>
/// </summary>
/// <param name="IP">A valid IP address of the server.</param>
/// <param name="port">The port the server is listening on.</param>
+ /// <param name="mode">The IP mode to use.</param>
/// <remarks>
/// When using this constructor <see cref="EndPoint"/> will contain an <see cref="IPEndPoint"/>.
/// </remarks>
public NetworkEndPoint(string IP, int port, IPMode mode = IPMode.IPv4AndIPv6)
: this(IPAddress.Parse(IP), port)
{
+
+ }
+ /// <inheritdoc />
+ public override string ToString()
+ {
+ return EndPoint.ToString();
}
}
}
/// typically requires more processing, more memory (as packets need to be stored in case they need resending),
/// a larger number of protocol bytes and can be slower than unreliable delivery.
/// </remarks>
- Reliable = 16,
+ Reliable = 1,
/// <summary>
/// Requests data be sent so that large messages are fragmented into smaller chunks of
/// that do not support the transmission of large messages. Without specifying reliable delivery there is no
/// guarentee that the message will arrive but any incomplete messages will be simply be discarded.
/// </remarks>
- Fragmented = 32,
+ Fragmented = 2,
/// <summary>
/// Requests data be sent so that large messages are fragmented into smaller chunks of
/// 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.
/// </remarks>
- FragmentedReliable = 48
+ FragmentedReliable = 3
}
}
/// <summary>
/// Hello message for initiating communication.
/// </summary>
- Hello = 128,
+ Hello = 8,
/// <summary>
/// Message for discontinuing communication.
/// </summary>
- Disconnect = 129,
+ Disconnect = 9,
/// <summary>
/// Message acknowledging the receipt of a message.
/// </summary>
- Acknowledgement = 130
+ Acknowledgement = 10
}
}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Hazel
-{
- /// <summary>
- /// Represents the state of the current receive operation for TCP connections.
- /// </summary>
- struct StateObject
- {
- /// <summary>
- /// The buffer we're receiving.
- /// </summary>
- internal byte[] buffer;
-
- /// <summary>
- /// The total number of bytes received so far.
- /// </summary>
- internal int totalBytesReceived;
-
- /// <summary>
- /// The callback to invoke once the buffer has been filled.
- /// </summary>
- internal Action<byte[]> callback;
-
- /// <summary>
- /// Creates a StateObject with the specified length.
- /// </summary>
- /// <param name="length">The number of bytes expected to be received.</param>
- internal StateObject(int length, Action<byte[]> callback)
- {
- this.buffer = new byte[length];
- this.totalBytesReceived = 0;
- this.callback = callback;
- }
- }
-}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hazel.Tcp
+{
+ /// <summary>
+ /// Represents the state of the current receive operation for TCP connections.
+ /// </summary>
+ struct StateObject
+ {
+ /// <summary>
+ /// The buffer we're receiving.
+ /// </summary>
+ internal byte[] buffer;
+
+ /// <summary>
+ /// The total number of bytes received so far.
+ /// </summary>
+ internal int totalBytesReceived;
+
+ /// <summary>
+ /// The callback to invoke once the buffer has been filled.
+ /// </summary>
+ internal Action<byte[]> callback;
+
+ /// <summary>
+ /// Creates a StateObject with the specified length.
+ /// </summary>
+ /// <param name="length">The number of bytes expected to be received.</param>
+ /// <param name="callback">The callback to invoke once data has been received.</param>
+ internal StateObject(int length, Action<byte[]> callback)
+ {
+ this.buffer = new byte[length];
+ this.totalBytesReceived = 0;
+ this.callback = callback;
+ }
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Hazel.Tcp
+{
+ /// <summary>
+ /// Represents a connection that uses the TCP protocol.
+ /// </summary>
+ /// <inheritdoc />
+ public sealed class TcpConnection : NetworkConnection
+ {
+ /// <summary>
+ /// The socket we're managing.
+ /// </summary>
+ Socket socket;
+
+ /// <summary>
+ /// Lock for the socket.
+ /// </summary>
+ Object socketLock = new Object();
+
+ /// <summary>
+ /// Creates a TcpConnection from a given TCP Socket.
+ /// </summary>
+ /// <param name="socket">The TCP socket to wrap.</param>
+ internal TcpConnection(Socket socket)
+ {
+ //Check it's a TCP socket
+ if (socket.ProtocolType != System.Net.Sockets.ProtocolType.Tcp)
+ throw new ArgumentException("A TcpConnection requires a TCP socket.");
+
+ lock (this.socketLock)
+ {
+ this.EndPoint = new NetworkEndPoint(socket.RemoteEndPoint);
+ this.RemoteEndPoint = socket.RemoteEndPoint;
+
+ this.socket = socket;
+ this.socket.NoDelay = true;
+
+ State = ConnectionState.Connected;
+ }
+ }
+
+ /// <summary>
+ /// Creates a new TCP connection.
+ /// </summary>
+ /// <param name="remoteEndPoint">A <see cref="NetworkEndPoint"/> to connect to.</param>
+ public TcpConnection(NetworkEndPoint remoteEndPoint)
+ {
+ lock (socketLock)
+ {
+ if (State != ConnectionState.NotConnected)
+ throw new InvalidOperationException("Cannot connect as the Connection is already connected.");
+
+ this.EndPoint = remoteEndPoint;
+ this.RemoteEndPoint = remoteEndPoint.EndPoint;
+
+ //Create a socket
+ if (remoteEndPoint.IPMode == IPMode.IPv4)
+ socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
+ else
+ {
+ if (!Socket.OSSupportsIPv6)
+ throw new HazelException("IPV6 not supported!");
+
+ socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
+ }
+
+ //Set parameters of socket
+ if (remoteEndPoint.IPMode == IPMode.IPv4AndIPv6)
+ socket.DualMode = true;
+
+ socket.NoDelay = true;
+ }
+ }
+
+ /// <summary>
+ /// Internal call to start listening once this socket has been constructed and is ready.
+ /// </summary>
+ internal void StartListening()
+ {
+ //Start receiving data
+ try
+ {
+ StartWaitingForHeader();
+ }
+ catch (SocketException e)
+ {
+ throw new HazelException("A Socket exception occured while initiating a receive operation.", e);
+ }
+ }
+
+ /// <inheritdoc />
+ public override void Connect()
+ {
+ lock(socketLock)
+ {
+ //Connect
+ State = ConnectionState.Connecting;
+
+ try
+ {
+ socket.Connect(RemoteEndPoint);
+ }
+ catch (SocketException e)
+ {
+ throw new HazelException("Could not connect as a socket exception occured.", e);
+ }
+
+ //Start receiving data
+ StartListening();
+
+ //Set connected
+ State = ConnectionState.Connected;
+ }
+ }
+
+ /// <inheritdoc/>
+ /// <remarks>
+ /// <include file="DocInclude/common.xml" path="docs/item[@name='Connection_SendBytes_General']/*" />
+ /// <para>
+ /// The sendOption parameter is ignored by the TcpConnection as TCP only supports FragmentedReliable
+ /// communication, specifying anything else will have no effect.
+ /// </para>
+ /// </remarks>
+ public override void SendBytes(byte[] bytes, SendOption sendOption = SendOption.FragmentedReliable)
+ {
+ //Get bytes for length
+ byte[] fullBytes = AppendLengthHeader(bytes);
+
+ //Write the bytes to the socket
+ lock (socketLock)
+ {
+ if (State != ConnectionState.Connected)
+ throw new InvalidOperationException("Could not send data as this Connection is not connected. Did you disconnect?");
+
+ try
+ {
+ socket.BeginSend(fullBytes, 0, fullBytes.Length, SocketFlags.None, null, null);
+ }
+ catch (SocketException e)
+ {
+ HazelException he = new HazelException("Could not send data as a SocketException occured.", e);
+ HandleDisconnect(he);
+ throw he;
+ }
+ }
+
+ Statistics.LogSend(bytes.Length, fullBytes.Length);
+ }
+
+ /// <summary>
+ /// Called when a 4 byte header has been received.
+ /// </summary>
+ /// <param name="bytes">The 4 header bytes read.</param>
+ void HeaderReadCallback(byte[] bytes)
+ {
+ //Get length
+ int length = GetLengthFromBytes(bytes);
+
+ //Begin receiving the body
+ try
+ {
+ StartWaitingForBytes(length, BodyReadCallback);
+ }
+ catch (SocketException e)
+ {
+ HandleDisconnect(new HazelException("A Socket exception occured while initiating a receive operation.", e));
+ }
+ }
+
+ /// <summary>
+ /// Callback for when a body has been read.
+ /// </summary>
+ /// <param name="bytes">The data bytes received by the connection.</param>
+ void BodyReadCallback(byte[] bytes)
+ {
+ //Begin receiving from the start
+ StartWaitingForHeader();
+
+ Statistics.LogReceive(bytes.Length, bytes.Length + 4);
+
+ //Fire DataReceived event
+ InvokeDataReceived(bytes, SendOption.FragmentedReliable);
+ }
+
+ /// <summary>
+ /// Starts this connections waiting for the header.
+ /// </summary>
+ void StartWaitingForHeader()
+ {
+ StartWaitingForBytes(4, HeaderReadCallback);
+ }
+
+ /// <summary>
+ /// Waits for the specified amount of bytes to be received.
+ /// </summary>
+ /// <param name="length">The number of bytes to receive.</param>
+ /// <param name="callback">The callback </param>
+ void StartWaitingForBytes(int length, Action<byte[]> callback)
+ {
+ StateObject state = new StateObject(length, callback);
+
+ StartWaitingForChunk(state);
+ }
+
+ /// <summary>
+ /// Waits for the next chunk of data from this socket.
+ /// </summary>
+ /// <param name="state">The StateObject for the receive operation.</param>
+ void StartWaitingForChunk(StateObject state)
+ {
+ lock (socketLock)
+ {
+ //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>
+ /// Called when a chunk has been read.
+ /// </summary>
+ /// <param name="result"></param>
+ void ChunkReadCallback(IAsyncResult result)
+ {
+ int bytesReceived;
+
+ //End the receive operation
+ try
+ {
+ lock (socketLock)
+ bytesReceived = socket.EndReceive(result);
+ }
+ catch (ObjectDisposedException)
+ {
+ //If the socket's been disposed then we can just end there.
+ return;
+ }
+ catch (SocketException e)
+ {
+ HandleDisconnect(new HazelException("A Socket exception occured while initiating a receive operation.", e));
+ return;
+ }
+
+ StateObject state = (StateObject)result.AsyncState;
+
+ state.totalBytesReceived += bytesReceived;
+
+ //Exit if receive nothing
+ if (bytesReceived == 0)
+ {
+ HandleDisconnect();
+ return;
+ }
+
+ //If we need to receive more then wait for more, else process it.
+ if (state.totalBytesReceived < state.buffer.Length)
+ {
+ try
+ {
+ StartWaitingForChunk(state);
+ }
+ catch (SocketException e)
+ {
+ HandleDisconnect(new HazelException("A Socket exception occured while initiating a receive operation.", e));
+ return;
+ }
+ }
+ else
+ state.callback.Invoke(state.buffer);
+ }
+
+ /// <summary>
+ /// 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)
+ {
+ bool invoke = false;
+
+ lock (socketLock)
+ {
+ //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(e);
+
+ Dispose();
+ }
+ }
+
+ /// <summary>
+ /// Appends the length header to the bytes.
+ /// </summary>
+ /// <param name="bytes">The source bytes.</param>
+ /// <returns>The new bytes.</returns>
+ static byte[] AppendLengthHeader(byte[] bytes)
+ {
+ 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;
+
+ //Add rest of bytes
+ Buffer.BlockCopy(bytes, 0, fullBytes, 4, bytes.Length);
+
+ return fullBytes;
+ }
+
+ /// <summary>
+ /// Returns the length from a length header.
+ /// </summary>
+ /// <param name="bytes">The bytes received.</param>
+ /// <returns>The number of bytes.</returns>
+ static int GetLengthFromBytes(byte[] bytes)
+ {
+ if (bytes.Length < 4)
+ throw new IndexOutOfRangeException("Not enough bytes passed to calculate length.");
+
+ return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];
+ }
+
+ /// <inheritdoc />
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ lock (socketLock)
+ {
+ State = ConnectionState.NotConnected;
+
+ if (socket.Connected)
+ socket.Shutdown(SocketShutdown.Send);
+ socket.Dispose();
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hazel.Tcp
+{
+ /// <summary>
+ /// Listens for new TCP connections and creates TCPConnections for them.
+ /// </summary>
+ /// <inheritdoc />
+ public sealed class TcpConnectionListener : NetworkConnectionListener
+ {
+ /// <summary>
+ /// The socket listening for connections.
+ /// </summary>
+ Socket listener;
+
+ /// <summary>
+ /// Creates a new TcpConnectionListener for the given <see cref="IPAddress"/>, port and <see cref="IPMode"/>.
+ /// </summary>
+ /// <param name="IPAddress">The IPAddress to listen on.</param>
+ /// <param name="port">The port to listen on.</param>
+ /// <param name="mode">The <see cref="IPMode"/> to listen with.</param>
+ public TcpConnectionListener(IPAddress IPAddress, int port, IPMode mode = IPMode.IPv4AndIPv6)
+ {
+ this.IPAddress = IPAddress;
+ this.Port = port;
+
+ if (mode == IPMode.IPv4)
+ this.listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ else
+ {
+ if (!Socket.OSSupportsIPv6)
+ throw new HazelException("IPV6 not supported!");
+
+ this.listener = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
+ }
+
+ if (mode == IPMode.IPv4AndIPv6)
+ this.listener.DualMode = true;
+ }
+
+ /// <inheritdoc />
+ public override void Start()
+ {
+ try
+ {
+ lock (listener)
+ {
+ listener.Bind(new IPEndPoint(IPAddress, Port));
+ listener.Listen(1000);
+
+ listener.BeginAccept(AcceptConnection, null);
+ }
+ }
+ catch (SocketException e)
+ {
+ throw new HazelException("Could not start listening as a SocketException occured", e);
+ }
+ }
+
+ /// <summary>
+ /// Called when a new connection has been accepted by the listener.
+ /// </summary>
+ /// <param name="result">The asyncronous operation's result.</param>
+ void AcceptConnection(IAsyncResult result)
+ {
+ lock (listener)
+ {
+ //Accept Tcp socket
+ Socket tcpSocket;
+ try
+ {
+ tcpSocket = listener.EndAccept(result);
+ }
+ catch (ObjectDisposedException)
+ {
+ //If the socket's been disposed then we can just end there.
+ return;
+ }
+
+ //Start listening for the next connection
+ listener.BeginAccept(new AsyncCallback(AcceptConnection), null);
+
+ //Sort the event out
+ TcpConnection tcpConnection = new TcpConnection(tcpSocket);
+
+ //Invoke
+ InvokeNewConnection(tcpConnection);
+
+ tcpConnection.StartListening();
+ }
+ }
+
+ /// <inheritdoc/>
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ lock (listener)
+ listener.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+ }
+}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Net.Sockets;
-using System.Text;
-
-namespace Hazel
-{
- /// <summary>
- /// Represents a connection that uses the TCP protocol.
- /// </summary>
- /// <inheritdoc />
- public sealed class TcpConnection : NetworkConnection
- {
- /// <summary>
- /// The socket we're managing.
- /// </summary>
- Socket socket;
-
- /// <summary>
- /// Lock for the socket.
- /// </summary>
- Object socketLock = new Object();
-
- /// <summary>
- /// Creates a TcpConnection from a given TCP Socket.
- /// </summary>
- /// <param name="socket">The TCP socket to wrap.</param>
- internal TcpConnection(Socket socket)
- {
- //Check it's a TCP socket
- if (socket.ProtocolType != System.Net.Sockets.ProtocolType.Tcp)
- throw new ArgumentException("A TcpConnection requires a TCP socket.");
-
- lock (this.socketLock)
- {
- this.EndPoint = new NetworkEndPoint(socket.RemoteEndPoint);
- this.RemoteEndPoint = socket.RemoteEndPoint;
-
- this.socket = socket;
- this.socket.NoDelay = true;
-
- State = ConnectionState.Connected;
- }
- }
-
- /// <summary>
- /// Creates a new TCP connection.
- /// </summary>
- public TcpConnection()
- {
-
- }
-
- /// <summary>
- /// Internal call to start listening once this socket has been constructed and is ready.
- /// </summary>
- internal void StartListening()
- {
- //Start receiving data
- try
- {
- StartWaitingForHeader();
- }
- catch (SocketException e)
- {
- throw new HazelException("A Socket exception occured while initiating a receive operation.", e);
- }
- }
-
- /// <inheritdoc />
- /// <param name="remoteEndPoint">A <see cref="NetworkEndPoint"/> to connect to.</param>
- public override void Connect(ConnectionEndPoint remoteEndPoint)
- {
- NetworkEndPoint nep = remoteEndPoint as NetworkEndPoint;
- if (nep == null)
- {
- throw new ArgumentException("The remote end point of a TCP connection must be a NetworkEndPoint.");
- }
-
- lock (socketLock)
- {
- if (State != ConnectionState.NotConnected)
- throw new InvalidOperationException("Cannot connect as the Connection is already connected.");
-
- this.EndPoint = remoteEndPoint;
- this.RemoteEndPoint = nep.EndPoint;
-
- //Create a socket
- if (nep.IPMode == IPMode.IPv4)
- socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
- else
- {
- if (!Socket.OSSupportsIPv6)
- throw new HazelException("IPV6 not supported!");
-
- socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
- }
-
- //Set parameters of socket
- if (nep.IPMode == IPMode.IPv4AndIPv6)
- socket.DualMode = true;
-
- socket.NoDelay = true;
-
- //Connect
- State = ConnectionState.Connecting;
-
- try
- {
- socket.Connect(nep.EndPoint);
- }
- catch (SocketException e)
- {
- throw new HazelException("Could not connect as a socket exception occured.", e);
- }
-
- //Start receiving data
- StartListening();
-
- //Set connected
- State = ConnectionState.Connected;
- }
- }
-
- /// <inheritdoc/>
- /// <remarks>
- /// <include file="DocInclude/common.xml" path="docs/item[@name='Connection_SendBytes_General']/*" />
- /// <para>
- /// The sendOption parameter is ignored by the TcpConnection as TCP only supports FragmentedReliable
- /// communication, specifying anything else will have no effect.
- /// </para>
- /// </remarks>
- public override void SendBytes(byte[] bytes, SendOption sendOption = SendOption.FragmentedReliable)
- {
- //Get bytes for length
- byte[] fullBytes = AppendLengthHeader(bytes);
-
- //Write the bytes to the socket
- lock (socketLock)
- {
- if (State != ConnectionState.Connected)
- throw new InvalidOperationException("Could not send data as this Connection is not connected. Did you disconnect?");
-
- try
- {
- socket.BeginSend(fullBytes, 0, fullBytes.Length, SocketFlags.None, null, null);
- }
- catch (SocketException e)
- {
- HazelException he = new HazelException("Could not send data as a SocketException occured.", e);
- HandleDisconnect(he);
- throw he;
- }
- }
-
- Statistics.LogSend(bytes.Length, fullBytes.Length);
- }
-
- /// <summary>
- /// Called when a 4 byte header has been received.
- /// </summary>
- /// <param name="bytes">The 4 header bytes read.</param>
- void HeaderReadCallback(byte[] bytes)
- {
- //Get length
- int length = GetLengthFromBytes(bytes);
-
- //Begin receiving the body
- try
- {
- StartWaitingForBytes(length, BodyReadCallback);
- }
- catch (SocketException e)
- {
- HandleDisconnect(new HazelException("A Socket exception occured while initiating a receive operation.", e));
- }
- }
-
- /// <summary>
- /// Callback for when a body has been read.
- /// </summary>
- /// <param name="bytes">The data bytes received by the connection.</param>
- void BodyReadCallback(byte[] bytes)
- {
- //Begin receiving from the start
- StartWaitingForHeader();
-
- Statistics.LogReceive(bytes.Length, bytes.Length + 4);
-
- //Fire DataReceived event
- InvokeDataReceived(bytes, SendOption.FragmentedReliable);
- }
-
- /// <summary>
- /// Starts this connections waiting for the header.
- /// </summary>
- void StartWaitingForHeader()
- {
- StartWaitingForBytes(4, HeaderReadCallback);
- }
-
- /// <summary>
- /// Waits for the specified amount of bytes to be received.
- /// </summary>
- /// <param name="length">The number of bytes to receive.</param>
- /// <param name="callback">The callback </param>
- void StartWaitingForBytes(int length, Action<byte[]> callback)
- {
- StateObject state = new StateObject(length, callback);
-
- StartWaitingForChunk(state);
- }
-
- /// <summary>
- /// Waits for the next chunk of data from this socket.
- /// </summary>
- /// <param name="state">The StateObject for the receive operation.</param>
- void StartWaitingForChunk(StateObject state)
- {
- lock (socketLock)
- {
- //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>
- /// Called when a chunk has been read.
- /// </summary>
- /// <param name="result"></param>
- void ChunkReadCallback(IAsyncResult result)
- {
- int bytesReceived;
-
- //End the receive operation
- try
- {
- lock (socketLock)
- bytesReceived = socket.EndReceive(result);
- }
- catch (ObjectDisposedException)
- {
- //If the socket's been disposed then we can just end there.
- return;
- }
- catch (SocketException e)
- {
- HandleDisconnect(new HazelException("A Socket exception occured while initiating a receive operation.", e));
- return;
- }
-
- StateObject state = (StateObject)result.AsyncState;
-
- state.totalBytesReceived += bytesReceived;
-
- //Exit if receive nothing
- if (bytesReceived == 0)
- {
- HandleDisconnect();
- return;
- }
-
- //If we need to receive more then wait for more, else process it.
- if (state.totalBytesReceived < state.buffer.Length)
- {
- try
- {
- StartWaitingForChunk(state);
- }
- catch (SocketException e)
- {
- HandleDisconnect(new HazelException("A Socket exception occured while initiating a receive operation.", e));
- return;
- }
- }
- else
- state.callback.Invoke(state.buffer);
- }
-
- /// <summary>
- /// 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)
- {
- bool invoke = false;
-
- lock (socketLock)
- {
- //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(e);
-
- Dispose();
- }
- }
-
- /// <summary>
- /// Appends the length header to the bytes.
- /// </summary>
- /// <param name="bytes">The source bytes.</param>
- /// <returns>The new bytes.</returns>
- static byte[] AppendLengthHeader(byte[] bytes)
- {
- 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;
-
- //Add rest of bytes
- Buffer.BlockCopy(bytes, 0, fullBytes, 4, bytes.Length);
-
- return fullBytes;
- }
-
- /// <summary>
- /// Returns the length from a length header.
- /// </summary>
- /// <param name="bytes">The bytes received.</param>
- /// <returns>The number of bytes.</returns>
- static int GetLengthFromBytes(byte[] bytes)
- {
- if (bytes.Length < 4)
- throw new IndexOutOfRangeException("Not enough bytes passed to calculate length.");
-
- return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];
- }
-
- /// <inheritdoc />
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- lock (socketLock)
- {
- State = ConnectionState.NotConnected;
-
- if (socket.Connected)
- socket.Shutdown(SocketShutdown.Send);
- socket.Dispose();
- }
- }
-
- base.Dispose(disposing);
- }
- }
-}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Net.Sockets;
-using System.Text;
-using System.Threading.Tasks;
-
-//TODO replace copyright notices with MIT licenses
-
-namespace Hazel
-{
- /// <summary>
- /// Listens for new TCP connections and creates TCPConnections for them.
- /// </summary>
- /// <inheritdoc />
- public sealed class TcpConnectionListener : NetworkConnectionListener
- {
- /// <summary>
- /// The socket listening for connections.
- /// </summary>
- Socket listener;
-
- /// <summary>
- /// Creates a new TcpConnectionListener for the given <see cref="IPAddress"/>, port and <see cref="IPMode"/>.
- /// </summary>
- /// <param name="IPAddress">The IPAddress to listen on.</param>
- /// <param name="port">The port to listen on.</param>
- /// <param name="mode">The <see cref="IPMode"/> to listen with.</param>
- public TcpConnectionListener(IPAddress IPAddress, int port, IPMode mode = IPMode.IPv4AndIPv6)
- {
- this.IPAddress = IPAddress;
- this.Port = port;
-
- if (mode == IPMode.IPv4)
- this.listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- else
- {
- if (!Socket.OSSupportsIPv6)
- throw new HazelException("IPV6 not supported!");
-
- this.listener = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
- }
-
- if (mode == IPMode.IPv4AndIPv6)
- this.listener.DualMode = true;
- }
-
- /// <inheritdoc />
- public override void Start()
- {
- try
- {
- lock (listener)
- {
- listener.Bind(new IPEndPoint(IPAddress, Port));
- listener.Listen(1000);
-
- listener.BeginAccept(AcceptConnection, null);
- }
- }
- catch (SocketException e)
- {
- throw new HazelException("Could not start listening as a SocketException occured", e);
- }
- }
-
- /// <summary>
- /// Called when a new connection has been accepted by the listener.
- /// </summary>
- /// <param name="result">The asyncronous operation's result.</param>
- void AcceptConnection(IAsyncResult result)
- {
- lock (listener)
- {
- //Accept Tcp socket
- Socket tcpSocket;
- try
- {
- tcpSocket = listener.EndAccept(result);
- }
- catch (ObjectDisposedException)
- {
- //If the socket's been disposed then we can just end there.
- return;
- }
-
- //Start listening for the next connection
- listener.BeginAccept(new AsyncCallback(AcceptConnection), null);
-
- //Sort the event out
- TcpConnection tcpConnection = new TcpConnection(tcpSocket);
-
- //Invoke
- InvokeNewConnection(tcpConnection);
-
- tcpConnection.StartListening();
- }
- }
-
- /// <inheritdoc/>
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- lock (listener)
- listener.Dispose();
- }
-
- base.Dispose(disposing);
- }
- }
-}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Hazel.Udp
+{
+ /// <summary>
+ /// Represents a client's connection to a server that uses the UDP protocol.
+ /// </summary>
+ /// <inheritdoc/>
+ public sealed class UdpClientConnection : UdpConnection
+ {
+ /// <summary>
+ /// The socket we're connected via.
+ /// </summary>
+ Socket socket;
+
+ /// <summary>
+ /// The lock for the socket.
+ /// </summary>
+ Object socketLock = new Object();
+
+ /// <summary>
+ /// The buffer to store incomming data in.
+ /// </summary>
+ byte[] dataBuffer = new byte[ushort.MaxValue];
+
+ /// <summary>
+ /// Creates a new UdpClientConnection.
+ /// </summary>
+ /// <param name="remoteEndPoint">A <see cref="NetworkEndPoint"/> to connect to.</param>
+ public UdpClientConnection(NetworkEndPoint remoteEndPoint)
+ : base()
+ {
+ lock (socketLock)
+ {
+ this.EndPoint = remoteEndPoint;
+ this.RemoteEndPoint = remoteEndPoint.EndPoint;
+
+ if (remoteEndPoint.IPMode == IPMode.IPv4)
+ socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+ else
+ {
+ socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
+ socket.DualMode = true;
+ }
+ }
+ }
+
+ /// <inheritdoc />
+ protected override void WriteBytesToConnection(byte[] bytes)
+ {
+ //Pack
+ SocketAsyncEventArgs args = new SocketAsyncEventArgs();
+ args.SetBuffer(bytes, 0, bytes.Length);
+ args.RemoteEndPoint = RemoteEndPoint;
+
+ lock (socketLock)
+ {
+ 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.SendToAsync(args);
+ }
+ 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 e)
+ {
+ HazelException he = new HazelException("Could not send data as a SocketException occured.", e);
+ HandleDisconnect(he);
+ throw he;
+ }
+ }
+ }
+
+ /// <inheritdoc />
+ public override void Connect()
+ {
+ lock(socketLock)
+ {
+ if (State != ConnectionState.NotConnected)
+ throw new InvalidOperationException("Cannot connect as the Connection is already connected.");
+
+ State = ConnectionState.Connecting;
+
+ //Begin listening
+ try
+ {
+ socket.Bind(new IPEndPoint(IPAddress.Any, 0));
+ }
+ catch (SocketException e)
+ {
+ throw new HazelException("A socket exception occured while binding to the port.", e);
+ }
+
+ try
+ {
+ StartListeningForData();
+ }
+ catch (ObjectDisposedException)
+ {
+ //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)
+ {
+ throw new HazelException("A Socket exception occured 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(() => { lock (socketLock) State = ConnectionState.Connected; });
+
+ //Wait till hello packet is acknowledged and the state is set to Connected
+ WaitOnConnect();
+ }
+
+ /// <summary>
+ /// Instructs the listener to begin listening.
+ /// </summary>
+ void StartListeningForData()
+ {
+ lock (socketLock)
+ socket.BeginReceive(dataBuffer, 0, dataBuffer.Length, SocketFlags.None, ReadCallback, dataBuffer);
+ }
+
+ /// <summary>
+ /// Called when data has been received by the socket.
+ /// </summary>
+ /// <param name="result">The asyncronous operation's result.</param>
+ void ReadCallback(IAsyncResult result)
+ {
+ int bytesReceived;
+
+ //End the receive operation
+ try
+ {
+ lock (socketLock)
+ bytesReceived = socket.EndReceive(result);
+ }
+ catch (ObjectDisposedException)
+ {
+ //If the socket's been disposed then we can just end there.
+ return;
+ }
+ catch (SocketException e)
+ {
+ HandleDisconnect(new HazelException("A socket exception occured while reading data.", e));
+ return;
+ }
+
+ //Exit if no bytes read, we've failed.
+ if (bytesReceived == 0)
+ {
+ HandleDisconnect();
+ return;
+ }
+
+ //Decode the data received
+ byte[] buffer = HandleReceive(dataBuffer, bytesReceived);
+ SendOption sendOption = (SendOption)dataBuffer[0];
+
+ //TODO may get better performance with Handle receive after and block copy call added
+
+ //Begin receiving again
+ try
+ {
+ StartListeningForData();
+ }
+ catch (SocketException e)
+ {
+ HandleDisconnect(new HazelException("A Socket exception occured while initiating a receive operation.", e));
+ }
+ catch (ObjectDisposedException)
+ {
+ //If the socket's been disposed then we can just end there.
+ return;
+ }
+
+ if (buffer != null)
+ InvokeDataReceived(buffer, sendOption);
+ }
+
+ /// <inheritdoc />
+ protected override void HandleDisconnect(HazelException e = null)
+ {
+ bool invoke = false;
+
+ lock (socketLock)
+ {
+ //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(e);
+
+ Dispose();
+ }
+ }
+
+ /// <inheritdoc />
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ //Send disconnect message if we're not already disconnecting
+ if (State == ConnectionState.Connected)
+ SendDisconnect();
+
+ //Dispose of the socket
+ lock (socketLock)
+ {
+ State = ConnectionState.NotConnected;
+
+ socket.Dispose();
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Hazel.Udp
+{
+ partial class UdpConnection
+ {
+ /// <summary>
+ /// The interval from data being received or transmitted to a keepalive packet being sent in milliseconds.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// Keepalive packets serve to close connections when an endpoint abruptly disconnects and to ensure than any
+ /// NAT devices do not close their translation for our argument. By ensuring there is regular contact the
+ /// connection can detect and prevent these issues.
+ /// </para>
+ /// <para>
+ /// The default value is 10 seconds, set to System.Threading.Timeout.Infinite to disable keepalive packets.
+ /// </para>
+ /// </remarks>
+ public int KeepAliveInterval
+ {
+ get
+ {
+ return keepAliveInterval;
+ }
+
+ set
+ {
+ keepAliveInterval = value;
+
+ //Update timer
+ ResetKeepAliveTimer();
+ }
+ }
+ int keepAliveInterval = 10000;
+
+ /// <summary>
+ /// The timer creating keepalive pulses.
+ /// </summary>
+ Timer keepAliveTimer;
+
+ /// <summary>
+ /// Lock for keep alive timer.
+ /// </summary>
+ Object keepAliveTimerLock = new Object();
+
+ /// <summary>
+ /// Has the keep alive timer been disposed already?
+ /// </summary>
+ bool keepAliveTimerDisposed;
+
+ /// <summary>
+ /// Starts the keepalive timer.
+ /// </summary>
+ void InitializeKeepAliveTimer()
+ {
+ lock (keepAliveTimerLock)
+ {
+ keepAliveTimer = new Timer(
+ (o) =>
+ {
+ Trace.WriteLine("Keepalive packet sent.");
+ SendHello(null);
+ },
+ null,
+ keepAliveInterval,
+ keepAliveInterval
+ );
+ }
+ }
+
+ /// <summary>
+ /// Resets the keepalive timer to zero.
+ /// </summary>
+ void ResetKeepAliveTimer()
+ {
+ lock (keepAliveTimerLock)
+ keepAliveTimer.Change(keepAliveInterval, keepAliveInterval);
+ }
+
+ /// <summary>
+ /// Disposes of the keep alive timer.
+ /// </summary>
+ void DisposeKeepAliveTimer()
+ {
+ lock(keepAliveTimerLock)
+ {
+ if (!keepAliveTimerDisposed)
+ keepAliveTimer.Dispose();
+ keepAliveTimerDisposed = true;
+ }
+ }
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Hazel.Udp
+{
+ partial class UdpConnection
+ {
+ /// <summary>
+ /// The starting timeout, in miliseconds, at which data will be resent.
+ /// </summary>
+ /// <remarks>
+ /// For reliable delivery data is resent at specified intervals unless an acknowledgement is received from the
+ /// receiving device. The ResendTimeout specifies the interval between the packets being resent, each time a packet
+ /// is resent the interval is doubled for that packet until the number of resends exceeds the
+ /// <see cref="ResendsBeforeDisconnect"/> value.
+ /// </remarks>
+ public int ResendTimeout { get { return resendTimeout; } set { resendTimeout = value; } }
+ private volatile int resendTimeout = 200; //TODO this based of average ping?
+
+ /// <summary>
+ /// Holds the last ID allocated.
+ /// </summary>
+ volatile ushort lastIDAllocated;
+
+ /// <summary>
+ /// The packets of data that have been transmitted reliably and not acknowledged.
+ /// </summary>
+ Dictionary<ushort, Packet> reliableDataPacketsSent = new Dictionary<ushort, Packet>();
+
+ /// <summary>
+ /// The last packets that were received.
+ /// </summary>
+ HashSet<ushort> reliableDataPacketsMissing = new HashSet<ushort>();
+
+ /// <summary>
+ /// The packet id that was received last.
+ /// </summary>
+ volatile ushort reliableReceiveLast = 0;
+
+ /// <summary>
+ /// Has the connection received anything yet
+ /// </summary>
+ volatile bool hasReceivedSomething = false;
+
+ /// <summary>
+ /// The maximum times a message should be resent before marking the endpoint as disconnected.
+ /// </summary>
+ /// <remarks>
+ /// Reliable packets will be resent at an interval defined in <see cref="ResendTimeout"/> for the number of times
+ /// specified here. Once a packet has been retransmitted this number of times and has not been acknowledged the
+ /// connection will be marked as disconnected and the <see cref="Connection.Disconnected">Disconnected</see> event
+ /// will be invoked.
+ /// </remarks>
+ public int ResendsBeforeDisconnect { get { return resendsBeforeDisconnect; } set { resendsBeforeDisconnect = value; } }
+ private volatile int resendsBeforeDisconnect = 3;
+
+ /// <summary>
+ /// Class to hold packet data
+ /// </summary>
+ class Packet : IRecyclable, IDisposable
+ {
+ /// <summary>
+ /// Object pool for this event.
+ /// </summary>
+ static readonly ObjectPool<Packet> objectPool = new ObjectPool<Packet>(() => new Packet());
+
+ /// <summary>
+ /// Returns an instance of this object from the pool.
+ /// </summary>
+ /// <returns></returns>
+ internal static Packet GetObject()
+ {
+ return objectPool.GetObject();
+ }
+
+ public byte[] Data;
+ public Timer Timer;
+ public volatile int LastTimeout;
+ public Action AckCallback;
+ public volatile bool Acknowledged;
+ public volatile int Retransmissions;
+
+ Packet()
+ {
+
+ }
+
+ internal void Set(byte[] data, Action<Packet> resendAction, int timeout, Action ackCallback)
+ {
+ Data = data;
+
+ Timer = new Timer(
+ (object obj) => resendAction(this),
+ null,
+ timeout,
+ Timeout.Infinite
+ );
+
+ LastTimeout = timeout;
+ AckCallback = ackCallback;
+ Acknowledged = false;
+ Retransmissions = 0;
+ }
+
+ /// <summary>
+ /// Returns this object back to the object pool from whence it came.
+ /// </summary>
+ public void Recycle()
+ {
+ lock (Timer)
+ Timer.Dispose();
+
+ objectPool.PutObject(this);
+ }
+
+ /// <summary>
+ /// Disposes of this object.
+ /// </summary>
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ lock (Timer)
+ Timer.Dispose();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Writes the bytes neccessary for a reliable send and stores the send.
+ /// </summary>
+ /// <param name="bytes">The byte array to write to.</param>
+ /// <param name="ackCallback">The callback to make once the packet has been acknowledged.</param>
+ void WriteReliableSendHeader(byte[] bytes, Action ackCallback)
+ {
+ lock (reliableDataPacketsSent)
+ {
+ //Find an ID not used yet.
+ ushort id;
+
+ do
+ id = ++lastIDAllocated;
+ while (reliableDataPacketsSent.ContainsKey(id));
+
+ //Write ID
+ bytes[1] = (byte)((id >> 8) & 0xFF);
+ bytes[2] = (byte)id;
+
+ //Create packet object
+ Packet packet = Packet.GetObject();
+ packet.Set(
+ bytes,
+ (Packet p) =>
+ {
+ //Double packet timeout
+ lock (p.Timer)
+ {
+ if (!p.Acknowledged)
+ {
+ p.Timer.Change(p.LastTimeout *= 2, Timeout.Infinite);
+ if (++p.Retransmissions > ResendsBeforeDisconnect)
+ {
+ HandleDisconnect();
+ p.Recycle();
+ return;
+ }
+ }
+ }
+
+ WriteBytesToConnection(p.Data);
+
+ Trace.WriteLine("Resend.");
+ },
+ resendTimeout,
+ ackCallback
+ );
+
+ //Remember packet
+ reliableDataPacketsSent.Add(id, packet);
+ }
+ }
+
+ /// <summary>
+ /// Handles receives from reliable packets.
+ /// </summary>
+ /// <param name="bytes">The buffer containing the data.</param>
+ /// <returns>Whether the packet was a new packet or not.</returns>
+ bool HandleReliableReceive(byte[] bytes)
+ {
+ //Get the ID form the packet
+ ushort id = (ushort)((bytes[1] << 8) + bytes[2]);
+
+ //Send an acknowledgement
+ SendAck(bytes[1], bytes[2]);
+
+ /*
+ * It gets a little complicated here (note the fact I'm actually using a multiline comment for once...)
+ *
+ * In a simple world if our data is greater than the last reliable packet received (reliableReceiveLast)
+ * then it is guaranteed to be a new packet, if it's not we can see if we are missing that packet (lookup
+ * in reliableDataPacketsMissing).
+ *
+ * --------rrl############# (1)
+ *
+ * (where --- are packets received already and #### are packets that will be counted as new)
+ *
+ * Unfortunately if id becomes greater than 65535 it will loop back to zero so we will add a pointer that
+ * specifies any packets with an id behind it are also new (overwritePointer).
+ *
+ * ####op----------rrl##### (2)
+ *
+ * ------rll#########op---- (3)
+ *
+ * Anything behind than the reliableReceiveLast pointer (but greater than the overwritePointer is either a
+ * missing packet or something we've already received so when we change the pointers we need to make sure
+ * we keep note of what hasn't been received yet (reliableDataPacketsMissing).
+ *
+ * So...
+ */
+
+ lock (reliableDataPacketsMissing)
+ {
+ //Calculate overwritePointer
+ ushort overwritePointer = (ushort)(reliableReceiveLast - 32768);
+
+ //Calculate if it is a new packet by examining if it is within the range
+ bool isNew;
+ if (overwritePointer < reliableReceiveLast)
+ isNew = id > reliableReceiveLast || id <= overwritePointer; //Figure (2)
+ else
+ isNew = id > reliableReceiveLast && id <= overwritePointer; //Figure (3)
+
+ //If it's new or we've not received anything yet
+ if (isNew || !hasReceivedSomething)
+ {
+ //Mark items between the most recent receive and the id received as missing
+ for (ushort i = (ushort)(reliableReceiveLast + 1); i < id; i++)
+ reliableDataPacketsMissing.Add(i);
+
+ //Update the most recently received
+ reliableReceiveLast = id;
+ hasReceivedSomething = true;
+ }
+
+ //Else it could be a missing packet
+ else
+ {
+ //See if we're missing it, else this packet is a duplicate as so we return false
+ if (reliableDataPacketsMissing.Contains(id))
+ reliableDataPacketsMissing.Remove(id);
+ else
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Handles acknowledgement packets to us.
+ /// </summary>
+ /// <param name="bytes">The buffer containing the data.</param>
+ void HandleAcknowledgement(byte[] bytes)
+ {
+ //Get ID
+ ushort id = (ushort)((bytes[1] << 8) + bytes[2]);
+
+ lock (reliableDataPacketsSent)
+ {
+ //Dispose of timer and remove from dictionary
+ if (reliableDataPacketsSent.ContainsKey(id))
+ {
+ Packet packet = reliableDataPacketsSent[id];
+
+ packet.Acknowledged = true;
+
+ if (packet.AckCallback != null)
+ packet.AckCallback.Invoke();
+
+ packet.Recycle();
+
+ reliableDataPacketsSent.Remove(id);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Sends an acknowledgement for a packet given its identification bytes.
+ /// </summary>
+ /// <param name="byte1">The first identification byte.</param>
+ /// <param name="byte2">The second identification byte.</param>
+ internal void SendAck(byte byte1, byte byte2)
+ {
+ //Always reply with acknowledgement in order to stop the sender repeatedly sending it
+ WriteBytesToConnection( //TODO group acks together
+ new byte[]
+ {
+ (byte)SendOptionInternal.Acknowledgement,
+ byte1,
+ byte2
+ }
+ );
+ }
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+
+namespace Hazel.Udp
+{
+ /// <summary>
+ /// Represents a connection that uses the UDP protocol.
+ /// </summary>
+ /// <inheritdoc />
+ public abstract partial class UdpConnection : NetworkConnection
+ {
+ /// <summary>
+ /// Creates a new UdpConnection and initializes the keep alive timer.
+ /// </summary>
+ protected UdpConnection()
+ {
+ InitializeKeepAliveTimer();
+ }
+
+ /// <summary>
+ /// Writes the given bytes to the connection.
+ /// </summary>
+ /// <param name="bytes">The bytes to write.</param>
+ protected abstract void WriteBytesToConnection(byte[] bytes);
+
+ /// <inheritdoc/>
+ /// <remarks>
+ /// <include file="DocInclude/common.xml" path="docs/item[@name='Connection_SendBytes_General']/*" />
+ /// <para>
+ /// Udp connections can currently send messages using <see cref="SendOption.None"/> and
+ /// <see cref="SendOption.Reliable"/>. Fragmented messages are not currently supported and will default to
+ /// <see cref="SendOption.None"/> until implemented.
+ /// </para>
+ /// </remarks>
+ public override void SendBytes(byte[] bytes, SendOption sendOption = SendOption.None)
+ {
+ //Early check
+ if (State != ConnectionState.Connected)
+ throw new InvalidOperationException("Could not send data as this Connection is not connected. Did you disconnect?");
+
+ //Add header information and send
+ HandleSend(bytes, (byte)sendOption);
+ }
+
+ /// <summary>
+ /// Handles the reliable/fragmented sending from this connection.
+ /// </summary>
+ /// <param name="data">The data being sent.</param>
+ /// <param name="sendOption">The <see cref="SendOption"/> specified as its byte value.</param>
+ /// <param name="ackCallback">The callback to invoke when this packet is acknowledged.</param>
+ /// <returns>The bytes that should actually be sent.</returns>
+ protected void HandleSend(byte[] data, byte sendOption, Action ackCallback = null)
+ {
+ byte[] bytes;
+ switch (sendOption)
+ {
+ //Handle reliable header
+ case (byte)SendOption.Reliable:
+ bytes = new byte[data.Length + 3];
+ WriteReliableSendHeader(bytes, ackCallback);
+ break;
+
+ //Handle hellos (ignore data)
+ case (byte)SendOptionInternal.Hello:
+ bytes = new byte[3];
+ WriteReliableSendHeader(bytes, ackCallback);
+ break;
+
+ default:
+ bytes = new byte[data.Length + 1];
+ break;
+ }
+
+ //Add message type
+ bytes[0] = sendOption;
+
+ //Copy data into new array
+ Buffer.BlockCopy(data, 0, bytes, bytes.Length - data.Length, data.Length);
+
+ //Inform keepalive not to send for a while
+ ResetKeepAliveTimer();
+
+ //Write to connection
+ WriteBytesToConnection(bytes);
+
+ Statistics.LogSend(data.Length, bytes.Length);
+ }
+
+ /// <summary>
+ /// Handles the receiving of data.
+ /// </summary>
+ /// <param name="buffer">The buffer containing the bytes received.</param>
+ /// <param name="bytesReceived">The number of bytes that were received.</param>
+ /// <returns>The bytes of data received.</returns>
+ protected byte[] HandleReceive(byte[] buffer, int bytesReceived)
+ {
+ //Inform keepalive not to send for a while
+ ResetKeepAliveTimer();
+
+ int headerSize = 1;
+ switch (buffer[0])
+ {
+ //Handle reliable receives
+ case (byte)SendOption.Reliable:
+ headerSize = 3;
+
+ if (HandleReliableReceive(buffer) == false)
+ return null;
+ break;
+
+ //Handle acknowledgments
+ case (byte)SendOptionInternal.Acknowledgement:
+ HandleAcknowledgement(buffer);
+
+ return null;
+
+ //We need to acknowledge hello messages so just use the same reliable receive
+ //method
+ case (byte)SendOptionInternal.Hello:
+ HandleReliableReceive(buffer);
+
+ return null;
+
+ case (byte)SendOptionInternal.Disconnect:
+ HandleDisconnect();
+
+ return null;
+ }
+
+ byte[] dataBytes = new byte[bytesReceived - headerSize];
+ Buffer.BlockCopy(buffer, headerSize, dataBytes, 0, dataBytes.Length);
+
+ Statistics.LogReceive(dataBytes.Length, bytesReceived);
+
+ return dataBytes;
+ }
+
+ /// <summary>
+ /// Sends a hello packet to the remote endpoint.
+ /// </summary>
+ /// <param name="acknowledgeCallback">The callback to invoke when the hello packet is acknowledged.</param>
+ protected void SendHello(Action acknowledgeCallback)
+ {
+ HandleSend(new byte[0], (byte)SendOptionInternal.Hello, acknowledgeCallback);
+ }
+
+ /// <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>
+ /// Sends a disconnect message to the end point.
+ /// </summary>
+ protected void SendDisconnect()
+ {
+ HandleSend(new byte[0], (byte)SendOptionInternal.Disconnect); //TODO Should disconnect wait for an ack?
+ }
+
+ /// <inheritdoc/>
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ DisposeKeepAliveTimer();
+ }
+
+ base.Dispose(disposing);
+ }
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hazel.Udp
+{
+ /// <summary>
+ /// Listens for new UDP connections and creates UdpConnections for them.
+ /// </summary>
+ /// <inheritdoc />
+ public class UdpConnectionListener : NetworkConnectionListener
+ {
+ /// <summary>
+ /// The socket listening for connections.
+ /// </summary>
+ Socket listener;
+
+ /// <summary>
+ /// Buffer to store incoming data in.
+ /// </summary>
+ byte[] dataBuffer = new byte[ushort.MaxValue];
+
+ /// <summary>
+ /// The connections we currently hold
+ /// </summary>
+ Dictionary<EndPoint, UdpServerConnection> connections = new Dictionary<EndPoint, UdpServerConnection>();
+
+ /// <summary>
+ /// Creates a new ConnectionListener for the given <see cref="IPAddress"/>, port and <see cref="IPMode"/>.
+ /// </summary>
+ /// <param name="IPAddress">The IPAddress to listen on.</param>
+ /// <param name="port">The port to listen on.</param>
+ /// <param name="mode">The <see cref="IPMode"/> to listen with.</param>
+ public UdpConnectionListener(IPAddress IPAddress, int port, IPMode mode = IPMode.IPv4AndIPv6)
+ {
+ this.IPAddress = IPAddress;
+ this.Port = port;
+
+ if (mode == IPMode.IPv4)
+ this.listener = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+ else
+ {
+ this.listener = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
+ this.listener.DualMode = true;
+ }
+ }
+
+ /// <inheritdoc />
+ public override void Start()
+ {
+ try
+ {
+ lock (listener)
+ listener.Bind(new IPEndPoint(IPAddress, Port));
+ }
+ catch (SocketException e)
+ {
+ throw new HazelException("Could not start listening as a SocketException occured", e);
+ }
+
+ StartListeningForData();
+ }
+
+ /// <summary>
+ /// Instructs the listener to begin listening.
+ /// </summary>
+ void StartListeningForData()
+ {
+ EndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
+
+ try
+ {
+ lock (listener)
+ listener.BeginReceiveFrom(dataBuffer, 0, dataBuffer.Length, SocketFlags.None, ref remoteEP, ReadCallback, dataBuffer);
+ }
+ catch (ObjectDisposedException)
+ {
+ return;
+ }
+ }
+
+ /// <summary>
+ /// Called when data has been received by the listener.
+ /// </summary>
+ /// <param name="result">The asyncronous operation's result.</param>
+ void ReadCallback(IAsyncResult result)
+ {
+ int bytesReceived;
+ EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
+
+ //End the receive operation
+ try
+ {
+ lock (listener)
+ bytesReceived = listener.EndReceiveFrom(result, ref remoteEndPoint);
+ }
+ catch (ObjectDisposedException)
+ {
+ //If the socket's been disposed then we can just end there.
+ return;
+ }
+ catch (SocketException e)
+ {
+ //Errrr... shit...
+ //Not exactly much we can do if we've got here
+ throw e;
+ }
+
+ //Exit if no bytes read, we've closed.
+ if (bytesReceived == 0)
+ return;
+
+ //Copy to new buffer
+ byte[] buffer = new byte[bytesReceived];
+ Buffer.BlockCopy((byte[])result.AsyncState, 0, buffer, 0, bytesReceived);
+
+ //Begin receiving again
+ StartListeningForData();
+
+ bool aware;
+ UdpServerConnection connection;
+ lock (connections)
+ {
+ aware = connections.ContainsKey(remoteEndPoint);
+
+ //If we're aware of this connection use the one already
+ if (aware)
+ connection = connections[remoteEndPoint];
+
+ //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);
+
+ //Then ping back an ack to make sure they're happy
+ connection.SendAck(buffer[1], buffer[2]);
+ }
+ }
+
+ //And fire the corresponding event
+ if (aware)
+ connection.InvokeDataReceived(buffer);
+ else
+ InvokeNewConnection(connection);
+ }
+
+ /// <summary>
+ /// Sends data from the listener socket.
+ /// </summary>
+ /// <param name="bytes">The bytes to send.</param>
+ /// <param name="endPoint">The endpoint to send to.</param>
+ internal void SendData(byte[] bytes, EndPoint endPoint)
+ {
+ SocketAsyncEventArgs args = new SocketAsyncEventArgs();
+ args.SetBuffer(bytes, 0, bytes.Length);
+ args.RemoteEndPoint = endPoint;
+
+ try
+ {
+ lock (listener)
+ listener.SendToAsync(args);
+ }
+ catch (SocketException e)
+ {
+ throw new HazelException("Could not send data as a SocketException occured.", e);
+ }
+ catch (ObjectDisposedException)
+ {
+ //Keep alive timer probably ran, ignore
+ return;
+ }
+ }
+
+ /// <summary>
+ /// Removes a virtual connection from the list.
+ /// </summary>
+ /// <param name="endPoint">The endpoint of the virtual connection.</param>
+ internal void RemoveConnectionTo(EndPoint endPoint)
+ {
+ lock (connections)
+ connections.Remove(endPoint);
+ }
+
+ /// <inheritdoc />
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ lock (listener)
+ listener.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hazel.Udp
+{
+ /// <summary>
+ /// Represents a servers's connection to a client that uses the UDP protocol.
+ /// </summary>
+ /// <inheritdoc/>
+ sealed class UdpServerConnection : UdpConnection
+ {
+ /// <summary>
+ /// The connection listener that we use the socket of.
+ /// </summary>
+ /// <remarks>
+ /// Udp server connections utilize the same socket in the listener for sends/receives, this is the listener that
+ /// created this connection and is hence the listener this conenction sends and receives via.
+ /// </remarks>
+ public UdpConnectionListener Listener { get; private set; }
+
+ /// <summary>
+ /// Lock object for the writing to the state of the connection.
+ /// </summary>
+ Object stateLock = new Object();
+
+ /// <summary>
+ /// Creates a UdpConnection for the virtual connection to the endpoint.
+ /// </summary>
+ /// <param name="listener">The listener that created this connection.</param>
+ /// <param name="endPoint">The endpoint that we are connected to.</param>
+ internal UdpServerConnection(UdpConnectionListener listener, EndPoint endPoint)
+ : base()
+ {
+ this.Listener = listener;
+ this.RemoteEndPoint = endPoint;
+ this.EndPoint = new NetworkEndPoint(endPoint);
+
+ State = ConnectionState.Connected;
+ }
+
+ /// <inheritdoc />
+ protected override void WriteBytesToConnection(byte[] bytes)
+ {
+ lock (stateLock)
+ {
+ if (State != ConnectionState.Connected)
+ throw new InvalidOperationException("Could not send data as this Connection is not connected. Did you disconnect?");
+
+ Listener.SendData(bytes, RemoteEndPoint);
+ }
+ }
+
+ /// <inheritdoc />
+ /// <remarks>
+ /// This will always throw a HazelException.
+ /// </remarks>
+ public override void Connect()
+ {
+ throw new HazelException("Cannot manually connect a UdpServerConnection, did you mean to use UdpClientConnection?");
+ }
+
+ /// <summary>
+ /// Called by the listener when we have data.
+ /// </summary>
+ /// <param name="buffer"></param>
+ internal void InvokeDataReceived(byte[] buffer)
+ {
+ byte[] data = HandleReceive(buffer, buffer.Length);
+
+ if (data != null)
+ InvokeDataReceived(data, (SendOption)buffer[0]);
+ }
+
+ /// <inheritdoc />
+ 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(e);
+
+ Dispose();
+ }
+ }
+
+ /// <inheritdoc />
+ protected override void Dispose(bool disposing)
+ {
+ //Here we just need to inform the listener we no longer need data.
+ if (disposing)
+ {
+ //Send disconnect message if we're not already disconnecting
+ if (State == ConnectionState.Connected)
+ SendDisconnect();
+
+ lock (stateLock)
+ {
+ Listener.RemoveConnectionTo(RemoteEndPoint);
+
+ State = ConnectionState.NotConnected;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+ }
+}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Net.Sockets;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Hazel
-{
- /// <summary>
- /// Represents a client's connection to a server that uses the UDP protocol.
- /// </summary>
- /// <inheritdoc/>
- public sealed class UdpClientConnection : UdpConnection
- {
- /// <summary>
- /// The socket we're connected via.
- /// </summary>
- Socket socket;
-
- /// <summary>
- /// The lock for the socket.
- /// </summary>
- Object socketLock = new Object();
-
- /// <summary>
- /// The buffer to store incomming data in.
- /// </summary>
- byte[] dataBuffer = new byte[ushort.MaxValue];
-
- /// <summary>
- /// Creates a new UdpClientConnection.
- /// </summary>
- public UdpClientConnection()
- : base()
- {
-
- }
-
- /// <inheritdoc />
- protected override void WriteBytesToConnection(byte[] bytes)
- {
- //Pack
- SocketAsyncEventArgs args = new SocketAsyncEventArgs();
- args.SetBuffer(bytes, 0, bytes.Length);
- args.RemoteEndPoint = RemoteEndPoint;
-
- lock (socketLock)
- {
- 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.SendToAsync(args);
- }
- 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 e)
- {
- HazelException he = new HazelException("Could not send data as a SocketException occured.", e);
- HandleDisconnect(he);
- throw he;
- }
- }
- }
-
- /// <inheritdoc />
- /// <param name="remoteEndPoint">A <see cref="NetworkEndPoint"/> to connect to.</param>
- public override void Connect(ConnectionEndPoint remoteEndPoint)
- {
- NetworkEndPoint nep = remoteEndPoint as NetworkEndPoint;
- if (nep == null)
- {
- throw new ArgumentException("The remote end point of a UDP connection must be a NetworkEndPoint.");
- }
-
- lock (socketLock)
- {
- this.EndPoint = nep;
- this.RemoteEndPoint = nep.EndPoint;
-
- if (nep.IPMode == IPMode.IPv4)
- socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
- else
- {
- socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
- socket.DualMode = true;
- }
-
- if (State != ConnectionState.NotConnected)
- throw new InvalidOperationException("Cannot connect as the Connection is already connected.");
-
- State = ConnectionState.Connecting;
-
- //Begin listening
- try
- {
- socket.Bind(new IPEndPoint(IPAddress.Any, 0));
- }
- catch (SocketException e)
- {
- throw new HazelException("A socket exception occured while binding to the port.", e);
- }
-
- try
- {
- StartListeningForData();
- }
- catch (ObjectDisposedException)
- {
- //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)
- {
- throw new HazelException("A Socket exception occured 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(() => { lock (socketLock) State = ConnectionState.Connected; });
-
- //Wait till hello packet is acknowledged and the state is set to Connected
- WaitOnConnect();
- }
-
- /// <summary>
- /// Instructs the listener to begin listening.
- /// </summary>
- void StartListeningForData()
- {
- lock (socketLock)
- socket.BeginReceive(dataBuffer, 0, dataBuffer.Length, SocketFlags.None, ReadCallback, dataBuffer);
- }
-
- /// <summary>
- /// Called when data has been received by the socket.
- /// </summary>
- /// <param name="result">The asyncronous operation's result.</param>
- void ReadCallback(IAsyncResult result)
- {
- int bytesReceived;
-
- //End the receive operation
- try
- {
- lock (socketLock)
- bytesReceived = socket.EndReceive(result);
- }
- catch (ObjectDisposedException)
- {
- //If the socket's been disposed then we can just end there.
- return;
- }
- catch (SocketException e)
- {
- HandleDisconnect(new HazelException("A socket exception occured while reading data.", e));
- return;
- }
-
- //Exit if no bytes read, we've failed.
- if (bytesReceived == 0)
- {
- HandleDisconnect();
- return;
- }
-
- //Decode the data received
- byte[] buffer = HandleReceive(dataBuffer, bytesReceived);
- SendOption sendOption = (SendOption)dataBuffer[0];
-
- //TODO may get better performance with Handle receive after and block copy call added
-
- //Begin receiving again
- try
- {
- StartListeningForData();
- }
- catch (SocketException e)
- {
- HandleDisconnect(new HazelException("A Socket exception occured while initiating a receive operation.", e));
- }
- catch (ObjectDisposedException)
- {
- //If the socket's been disposed then we can just end there.
- return;
- }
-
- if (buffer != null)
- InvokeDataReceived(buffer, sendOption);
- }
-
- /// <inheritdoc />
- protected override void HandleDisconnect(HazelException e = null)
- {
- bool invoke = false;
-
- lock (socketLock)
- {
- //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(e);
-
- Dispose();
- }
- }
-
- /// <inheritdoc />
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- //Send disconnect message if we're not already disconnecting
- if (State == ConnectionState.Connected)
- SendDisconnect();
-
- //Dispose of the socket
- lock (socketLock)
- {
- State = ConnectionState.NotConnected;
-
- socket.Dispose();
- }
- }
-
- base.Dispose(disposing);
- }
- }
-}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Hazel
-{
- partial class UdpConnection
- {
- /// <summary>
- /// The interval from data being received or transmitted to a keepalive packet being sent in milliseconds.
- /// </summary>
- /// <remarks>
- /// <para>
- /// Keepalive packets serve to close connections when an endpoint abruptly disconnects and to ensure than any
- /// NAT devices do not close their translation for our argument. By ensuring there is regular contact the
- /// connection can detect and prevent these issues.
- /// </para>
- /// <para>
- /// The default value is 10 seconds, set to System.Threading.Timeout.Infinite to disable keepalive packets.
- /// </para>
- /// </remarks>
- public int KeepAliveInterval
- {
- get
- {
- return keepAliveInterval;
- }
-
- set
- {
- keepAliveInterval = value;
-
- //Update timer
- ResetKeepAliveTimer();
- }
- }
- int keepAliveInterval = 10000;
-
- /// <summary>
- /// The timer creating keepalive pulses.
- /// </summary>
- Timer keepAliveTimer;
-
- /// <summary>
- /// Lock for keep alive timer.
- /// </summary>
- Object keepAliveTimerLock = new Object();
-
- /// <summary>
- /// Has the keep alive timer been disposed already?
- /// </summary>
- bool keepAliveTimerDisposed;
-
- /// <summary>
- /// Starts the keepalive timer.
- /// </summary>
- void InitializeKeepAliveTimer()
- {
- lock (keepAliveTimerLock)
- {
- keepAliveTimer = new Timer(
- (o) =>
- {
- Trace.WriteLine("Keepalive packet sent.");
- SendHello(null);
- },
- null,
- keepAliveInterval,
- keepAliveInterval
- );
- }
- }
-
- /// <summary>
- /// Resets the keepalive timer to zero.
- /// </summary>
- void ResetKeepAliveTimer()
- {
- lock (keepAliveTimerLock)
- keepAliveTimer.Change(keepAliveInterval, keepAliveInterval);
- }
-
- /// <summary>
- /// Disposes of the keep alive timer.
- /// </summary>
- void DisposeKeepAliveTimer()
- {
- lock(keepAliveTimerLock)
- {
- if (!keepAliveTimerDisposed)
- keepAliveTimer.Dispose();
- keepAliveTimerDisposed = true;
- }
- }
- }
-}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Hazel
-{
- partial class UdpConnection
- {
- /// <summary>
- /// The starting timeout, in miliseconds, at which data will be resent.
- /// </summary>
- /// <remarks>
- /// For reliable delivery data is resent at specified intervals unless an acknowledgement is received from the
- /// receiving device. The ResendTimeout specifies the interval between the packets being resent, each time a packet
- /// is resent the interval is doubled for that packet until the number of resends exceeds the
- /// <see cref="ResendsBeforeDisconnect"/> value.
- /// </remarks>
- public int ResendTimeout { get { return resendTimeout; } set { resendTimeout = value; } }
- private volatile int resendTimeout = 200; //TODO this based of average ping?
-
- /// <summary>
- /// Holds the last ID allocated.
- /// </summary>
- volatile ushort lastIDAllocated;
-
- /// <summary>
- /// The packets of data that have been transmitted reliably and not acknowledged.
- /// </summary>
- Dictionary<ushort, Packet> reliableDataPacketsSent = new Dictionary<ushort, Packet>();
-
- /// <summary>
- /// The last packets that were received.
- /// </summary>
- HashSet<ushort> reliableDataPacketsMissing = new HashSet<ushort>();
-
- /// <summary>
- /// The packet id that was received last.
- /// </summary>
- volatile ushort reliableReceiveLast = 0;
-
- /// <summary>
- /// Has the connection received anything yet
- /// </summary>
- volatile bool hasReceivedSomething = false;
-
- /// <summary>
- /// The maximum times a message should be resent before marking the endpoint as disconnected.
- /// </summary>
- /// <remarks>
- /// Reliable packets will be resent at an interval defined in <see cref="ResendInterval"/> for the number of times
- /// specified here. Once a packet has been retransmitted this number of times and has not been acknowledged the
- /// connection will be marked as disconnected and the <see cref="Connection.Disconnected">Disconnected</see> event
- /// will be invoked.
- /// </remarks>
- public int ResendsBeforeDisconnect { get { return resendsBeforeDisconnect; } set { resendsBeforeDisconnect = value; } }
- private volatile int resendsBeforeDisconnect = 3;
-
- /// <summary>
- /// Class to hold packet data
- /// </summary>
- class Packet : IRecyclable, IDisposable
- {
- /// <summary>
- /// Object pool for this event.
- /// </summary>
- static readonly ObjectPool<Packet> objectPool = new ObjectPool<Packet>(() => new Packet());
-
- /// <summary>
- /// Returns an instance of this object from the pool.
- /// </summary>
- /// <returns></returns>
- internal static Packet GetObject()
- {
- return objectPool.GetObject();
- }
-
- public byte[] Data;
- public Timer Timer;
- public volatile int LastTimeout;
- public Action AckCallback;
- public volatile bool Acknowledged;
- public volatile int Retransmissions;
-
- Packet()
- {
-
- }
-
- internal void Set(byte[] data, Action<Packet> resendAction, int timeout, Action ackCallback)
- {
- Data = data;
-
- Timer = new Timer(
- (object obj) => resendAction(this),
- null,
- timeout,
- Timeout.Infinite
- );
-
- LastTimeout = timeout;
- AckCallback = ackCallback;
- Acknowledged = false;
- Retransmissions = 0;
- }
-
- /// <summary>
- /// Returns this object back to the object pool from whence it came.
- /// </summary>
- public void Recycle()
- {
- lock (Timer)
- Timer.Dispose();
-
- objectPool.PutObject(this);
- }
-
- /// <summary>
- /// Disposes of this object.
- /// </summary>
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- protected void Dispose(bool disposing)
- {
- if (disposing)
- {
- lock (Timer)
- Timer.Dispose();
- }
- }
- }
-
- /// <summary>
- /// Writes the bytes neccessary for a reliable send and stores the send.
- /// </summary>
- /// <param name="bytes">The byte array to write to.</param>
- void WriteReliableSendHeader(byte[] bytes, Action ackCallback)
- {
- lock (reliableDataPacketsSent)
- {
- //Find an ID not used yet.
- ushort id;
-
- do
- id = ++lastIDAllocated;
- while (reliableDataPacketsSent.ContainsKey(id));
-
- //Write ID
- bytes[1] = (byte)((id >> 8) & 0xFF);
- bytes[2] = (byte)id;
-
- //Create packet object
- Packet packet = Packet.GetObject();
- packet.Set(
- bytes,
- (Packet p) =>
- {
- //Double packet timeout
- lock (p.Timer)
- {
- if (!p.Acknowledged)
- {
- p.Timer.Change(p.LastTimeout *= 2, Timeout.Infinite);
- if (++p.Retransmissions > ResendsBeforeDisconnect)
- {
- HandleDisconnect();
- p.Recycle();
- return;
- }
- }
- }
-
- WriteBytesToConnection(p.Data);
-
- Trace.WriteLine("Resend.");
- },
- resendTimeout,
- ackCallback
- );
-
- //Remember packet
- reliableDataPacketsSent.Add(id, packet);
- }
- }
-
- /// <summary>
- /// Handles receives from reliable packets.
- /// </summary>
- /// <param name="bytes">The buffer containing the data.</param>
- /// <returns>Whether the packet was a new packet or not.</returns>
- bool HandleReliableReceive(byte[] bytes)
- {
- //Get the ID form the packet
- ushort id = (ushort)((bytes[1] << 8) + bytes[2]);
-
- //Send an acknowledgement
- SendAck(bytes[1], bytes[2]);
-
- /*
- * It gets a little complicated here (note the fact I'm actually using a multiline comment for once...)
- *
- * In a simple world if our data is greater than the last reliable packet received (reliableReceiveLast)
- * then it is guaranteed to be a new packet, if it's not we can see if we are missing that packet (lookup
- * in reliableDataPacketsMissing).
- *
- * --------rrl############# (1)
- *
- * (where --- are packets received already and #### are packets that will be counted as new)
- *
- * Unfortunately if id becomes greater than 65535 it will loop back to zero so we will add a pointer that
- * specifies any packets with an id behind it are also new (overwritePointer).
- *
- * ####op----------rrl##### (2)
- *
- * ------rll#########op---- (3)
- *
- * Anything behind than the reliableReceiveLast pointer (but greater than the overwritePointer is either a
- * missing packet or something we've already received so when we change the pointers we need to make sure
- * we keep note of what hasn't been received yet (reliableDataPacketsMissing).
- *
- * So...
- */
-
- lock (reliableDataPacketsMissing)
- {
- //Calculate overwritePointer
- ushort overwritePointer = (ushort)(reliableReceiveLast - 32768);
-
- //Calculate if it is a new packet by examining if it is within the range
- bool isNew;
- if (overwritePointer < reliableReceiveLast)
- isNew = id > reliableReceiveLast || id <= overwritePointer; //Figure (2)
- else
- isNew = id > reliableReceiveLast && id <= overwritePointer; //Figure (3)
-
- //If it's new or we've not received anything yet
- if (isNew || !hasReceivedSomething)
- {
- //Mark items between the most recent receive and the id received as missing
- for (ushort i = (ushort)(reliableReceiveLast + 1); i < id; i++)
- reliableDataPacketsMissing.Add(i);
-
- //Update the most recently received
- reliableReceiveLast = id;
- hasReceivedSomething = true;
- }
-
- //Else it could be a missing packet
- else
- {
- //See if we're missing it, else this packet is a duplicate as so we return false
- if (reliableDataPacketsMissing.Contains(id))
- reliableDataPacketsMissing.Remove(id);
- else
- return false;
- }
- }
-
- return true;
- }
-
- /// <summary>
- /// Handles acknowledgement packets to us.
- /// </summary>
- /// <param name="bytes">The buffer containing the data.</param>
- void HandleAcknowledgement(byte[] bytes)
- {
- //Get ID
- ushort id = (ushort)((bytes[1] << 8) + bytes[2]);
-
- lock (reliableDataPacketsSent)
- {
- //Dispose of timer and remove from dictionary
- if (reliableDataPacketsSent.ContainsKey(id))
- {
- Packet packet = reliableDataPacketsSent[id];
-
- packet.Acknowledged = true;
-
- if (packet.AckCallback != null)
- packet.AckCallback.Invoke();
-
- packet.Recycle();
-
- reliableDataPacketsSent.Remove(id);
- }
- }
- }
-
- /// <summary>
- /// Sends an acknowledgement for a packet given its identification bytes.
- /// </summary>
- /// <param name="byte1">The first identification byte.</param>
- /// <param name="byte2">The second identification byte.</param>
- internal void SendAck(byte byte1, byte byte2)
- {
- //Always reply with acknowledgement in order to stop the sender repeatedly sending it
- WriteBytesToConnection( //TODO group acks together
- new byte[]
- {
- (byte)SendOptionInternal.Acknowledgement,
- byte1,
- byte2
- }
- );
- }
- }
-}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Net.Sockets;
-using System.Text;
-using System.Threading;
-
-namespace Hazel
-{
- /// <summary>
- /// Represents a connection that uses the UDP protocol.
- /// </summary>
- /// <inheritdoc />
- public abstract partial class UdpConnection : NetworkConnection
- {
- /// <summary>
- /// Creates a new UdpConnection and initializes the keep alive timer.
- /// </summary>
- protected UdpConnection()
- {
- InitializeKeepAliveTimer();
- }
-
- /// <summary>
- /// Writes the given bytes to the connection.
- /// </summary>
- /// <param name="bytes">The bytes to write.</param>
- protected abstract void WriteBytesToConnection(byte[] bytes);
-
- /// <inheritdoc/>
- /// <remarks>
- /// <include file="DocInclude/common.xml" path="docs/item[@name='Connection_SendBytes_General']/*" />
- /// <para>
- /// Udp connections can currently send messages using <see cref="SendOption.None"/> and
- /// <see cref="SendOption.Reliable"/>. Fragmented messages are not currently supported and will default to
- /// <see cref="SendOption.None"/> until implemented.
- /// </para>
- /// </remarks>
- public override void SendBytes(byte[] bytes, SendOption sendOption = SendOption.None)
- {
- //Early check
- if (State != ConnectionState.Connected)
- throw new InvalidOperationException("Could not send data as this Connection is not connected. Did you disconnect?");
-
- //Add header information and send
- HandleSend(bytes, (byte)sendOption);
- }
-
- /// <summary>
- /// Handles the reliable/fragmented sending from this connection.
- /// </summary>
- /// <param name="data">The data being sent.</param>
- /// <param name="sendOption">The <see cref="SendOption"/> specified as its byte value.</param>
- /// <param name="ackCallback">The callback to invoke when this packet is acknowledged.</param>
- /// <returns>The bytes that should actually be sent.</returns>
- protected void HandleSend(byte[] data, byte sendOption, Action ackCallback = null)
- {
- byte[] bytes;
- switch (sendOption)
- {
- //Handle reliable header
- case (byte)SendOption.Reliable:
- bytes = new byte[data.Length + 3];
- WriteReliableSendHeader(bytes, ackCallback);
- break;
-
- //Handle hellos (ignore data)
- case (byte)SendOptionInternal.Hello:
- bytes = new byte[3];
- WriteReliableSendHeader(bytes, ackCallback);
- break;
-
- default:
- bytes = new byte[data.Length + 1];
- break;
- }
-
- //Add message type
- bytes[0] = sendOption;
-
- //Copy data into new array
- Buffer.BlockCopy(data, 0, bytes, bytes.Length - data.Length, data.Length);
-
- //Inform keepalive not to send for a while
- ResetKeepAliveTimer();
-
- //Write to connection
- WriteBytesToConnection(bytes);
-
- Statistics.LogSend(data.Length, bytes.Length);
- }
-
- /// <summary>
- /// Handles the receiving of data.
- /// </summary>
- /// <param name="buffer">The buffer containing the bytes received.</param>
- /// <param name="bytesReceived">The number of bytes that were received.</param>
- /// <returns>The bytes of data received.</returns>
- protected byte[] HandleReceive(byte[] buffer, int bytesReceived)
- {
- //Inform keepalive not to send for a while
- ResetKeepAliveTimer();
-
- int headerSize = 1;
- switch (buffer[0])
- {
- //Handle reliable receives
- case (byte)SendOption.Reliable:
- headerSize = 3;
-
- if (HandleReliableReceive(buffer) == false)
- return null;
- break;
-
- //Handle acknowledgments
- case (byte)SendOptionInternal.Acknowledgement:
- HandleAcknowledgement(buffer);
-
- return null;
-
- //We need to acknowledge hello messages so just use the same reliable receive
- //method
- case (byte)SendOptionInternal.Hello:
- HandleReliableReceive(buffer);
-
- return null;
-
- case (byte)SendOptionInternal.Disconnect:
- HandleDisconnect();
-
- return null;
- }
-
- byte[] dataBytes = new byte[bytesReceived - headerSize];
- Buffer.BlockCopy(buffer, headerSize, dataBytes, 0, dataBytes.Length);
-
- Statistics.LogReceive(dataBytes.Length, bytesReceived);
-
- return dataBytes;
- }
-
- /// <summary>
- /// Sends a hello packet to the remote endpoint.
- /// </summary>
- /// <param name="acknowledgeCallback">The callback to invoke when the hello packet is acknowledged.</param>
- protected void SendHello(Action acknowledgeCallback)
- {
- HandleSend(new byte[0], (byte)SendOptionInternal.Hello, acknowledgeCallback);
- }
-
- /// <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>
- /// Sends a disconnect message to the end point.
- /// </summary>
- protected void SendDisconnect()
- {
- HandleSend(new byte[0], (byte)SendOptionInternal.Disconnect); //TODO Should disconnect wait for an ack?
- }
-
- /// <inheritdoc/>
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- DisposeKeepAliveTimer();
- }
-
- base.Dispose(disposing);
- }
- }
-}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Net.Sockets;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Hazel
-{
- /// <summary>
- /// Listens for new UDP connections and creates UdpConnections for them.
- /// </summary>
- /// <inheritdoc />
- public class UdpConnectionListener : NetworkConnectionListener
- {
- /// <summary>
- /// The socket listening for connections.
- /// </summary>
- Socket listener;
-
- /// <summary>
- /// Buffer to store incoming data in.
- /// </summary>
- byte[] dataBuffer = new byte[ushort.MaxValue];
-
- /// <summary>
- /// The connections we currently hold
- /// </summary>
- Dictionary<EndPoint, UdpServerConnection> connections = new Dictionary<EndPoint, UdpServerConnection>();
-
- /// <summary>
- /// Creates a new ConnectionListener for the given <see cref="IPAddress"/>, port and <see cref="IPMode"/>.
- /// </summary>
- /// <param name="IPAddress">The IPAddress to listen on.</param>
- /// <param name="port">The port to listen on.</param>
- /// <param name="mode">The <see cref="IPMode"/> to listen with.</param>
- public UdpConnectionListener(IPAddress IPAddress, int port, IPMode mode = IPMode.IPv4AndIPv6)
- {
- this.IPAddress = IPAddress;
- this.Port = port;
-
- if (mode == IPMode.IPv4)
- this.listener = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
- else
- {
- this.listener = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
- this.listener.DualMode = true;
- }
- }
-
- /// <inheritdoc />
- public override void Start()
- {
- try
- {
- lock (listener)
- listener.Bind(new IPEndPoint(IPAddress, Port));
- }
- catch (SocketException e)
- {
- throw new HazelException("Could not start listening as a SocketException occured", e);
- }
-
- StartListeningForData();
- }
-
- /// <summary>
- /// Instructs the listener to begin listening.
- /// </summary>
- void StartListeningForData()
- {
- EndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
-
- try
- {
- lock (listener)
- listener.BeginReceiveFrom(dataBuffer, 0, dataBuffer.Length, SocketFlags.None, ref remoteEP, ReadCallback, dataBuffer);
- }
- catch (ObjectDisposedException)
- {
- return;
- }
- }
-
- /// <summary>
- /// Called when data has been received by the listener.
- /// </summary>
- /// <param name="result">The asyncronous operation's result.</param>
- void ReadCallback(IAsyncResult result)
- {
- int bytesReceived;
- EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
-
- //End the receive operation
- try
- {
- lock (listener)
- bytesReceived = listener.EndReceiveFrom(result, ref remoteEndPoint);
- }
- catch (ObjectDisposedException)
- {
- //If the socket's been disposed then we can just end there.
- return;
- }
- catch (SocketException e)
- {
- //Errrr... shit...
- //Not exactly much we can do if we've got here
- throw e;
- }
-
- //Exit if no bytes read, we've closed.
- if (bytesReceived == 0)
- return;
-
- //Copy to new buffer
- byte[] buffer = new byte[bytesReceived];
- Buffer.BlockCopy((byte[])result.AsyncState, 0, buffer, 0, bytesReceived);
-
- //Begin receiving again
- StartListeningForData();
-
- bool aware;
- UdpServerConnection connection;
- lock (connections)
- {
- aware = connections.ContainsKey(remoteEndPoint);
-
- //If we're aware of this connection use the one already
- if (aware)
- connection = connections[remoteEndPoint];
-
- //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);
-
- //Then ping back an ack to make sure they're happy
- connection.SendAck(buffer[1], buffer[2]);
- }
- }
-
- //And fire the corresponding event
- if (aware)
- connection.InvokeDataReceived(buffer);
- else
- InvokeNewConnection(connection);
- }
-
- /// <summary>
- /// Sends data from the listener socket.
- /// </summary>
- /// <param name="bytes">The bytes to send.</param>
- /// <param name="endPoint">The endpoint to send to.</param>
- internal void SendData(byte[] bytes, EndPoint endPoint)
- {
- SocketAsyncEventArgs args = new SocketAsyncEventArgs();
- args.SetBuffer(bytes, 0, bytes.Length);
- args.RemoteEndPoint = endPoint;
-
- try
- {
- lock (listener)
- listener.SendToAsync(args);
- }
- catch (SocketException e)
- {
- throw new HazelException("Could not send data as a SocketException occured.", e);
- }
- catch (ObjectDisposedException)
- {
- //Keep alive timer probably ran, ignore
- return;
- }
- }
-
- /// <summary>
- /// Removes a virtual connection from the list.
- /// </summary>
- /// <param name="endPoint">The endpoint of the virtual connection.</param>
- internal void RemoveConnectionTo(EndPoint endPoint)
- {
- lock (connections)
- connections.Remove(endPoint);
- }
-
- /// <inheritdoc />
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- lock (listener)
- listener.Dispose();
- }
-
- base.Dispose(disposing);
- }
- }
-}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Hazel
-{
- /// <summary>
- /// Represents a servers's connection to a client that uses the UDP protocol.
- /// </summary>
- /// <inheritdoc/>
- sealed class UdpServerConnection : UdpConnection
- {
- /// <summary>
- /// The connection listener that we use the socket of.
- /// </summary>
- /// <remarks>
- /// Udp server connections utilize the same socket in the listener for sends/receives, this is the listener that
- /// created this connection and is hence the listener this conenction sends and receives via.
- /// </remarks>
- public UdpConnectionListener Listener { get; private set; }
-
- /// <summary>
- /// Lock object for the writing to the state of the connection.
- /// </summary>
- Object stateLock = new Object();
-
- /// <summary>
- /// Creates a UdpConnection for the virtual connection to the endpoint.
- /// </summary>
- /// <param name="listener">The listener that created this connection.</param>
- /// <param name="endPoint">The endpoint that we are connected to.</param>
- internal UdpServerConnection(UdpConnectionListener listener, EndPoint endPoint)
- : base()
- {
- this.Listener = listener;
- this.RemoteEndPoint = endPoint;
- this.EndPoint = new NetworkEndPoint(endPoint);
-
- State = ConnectionState.Connected;
- }
-
- /// <inheritdoc />
- protected override void WriteBytesToConnection(byte[] bytes)
- {
- lock (stateLock)
- {
- if (State != ConnectionState.Connected)
- throw new InvalidOperationException("Could not send data as this Connection is not connected. Did you disconnect?");
-
- Listener.SendData(bytes, RemoteEndPoint);
- }
- }
-
- /// <inheritdoc />
- /// <remarks>
- /// This will always throw an InvalidOperationException.
- /// </remarks>
- public override void Connect(ConnectionEndPoint remoteEndPoint)
- {
- throw new InvalidOperationException("Cannot manually connect a UdpServerConnection, did you mean to use UdpClientConnection?");
- }
-
- /// <summary>
- /// Called by the listener when we have data.
- /// </summary>
- /// <param name="buffer"></param>
- internal void InvokeDataReceived(byte[] buffer)
- {
- byte[] data = HandleReceive(buffer, buffer.Length);
-
- if (data != null)
- InvokeDataReceived(data, (SendOption)buffer[0]);
- }
-
- /// <inheritdoc />
- 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(e);
-
- Dispose();
- }
- }
-
- /// <inheritdoc />
- protected override void Dispose(bool disposing)
- {
- //Here we just need to inform the listener we no longer need data.
- if (disposing)
- {
- //Send disconnect message if we're not already disconnecting
- if (State == ConnectionState.Connected)
- SendDisconnect();
-
- lock (stateLock)
- {
- Listener.RemoveConnectionTo(RemoteEndPoint);
-
- State = ConnectionState.NotConnected;
- }
- }
-
- base.Dispose(disposing);
- }
- }
-}