using System.Net;
using System.Threading;
-
-/*
-* Copyright (C) Jamie Read - All Rights Reserved
-* Unauthorized copying of this file, via any medium is strictly prohibited
-* Proprietary and confidential
-* Written by Jamie Read <jamie.read@outlook.com>, January 2016
-*/
-
namespace Hazel
{
/// <summary>
- /// Handles the sending and receiving of messages through the channel to give connection orientated, packet based transmission.
+ /// Base class for all connections.
/// </summary>
+ /// <remarks>
+ /// <para>
+ /// Connection is the base class for all connections that Hazel can make. It provides common functionality and a
+ /// standard interface to allow connections to be swapped easily.
+ /// </para>
+ /// <para>
+ /// Any class inheriting from Connection should provide the 3 standard guarantees that Hazel provides:
+ /// <list type="bullet">
+ /// <item>
+ /// <description>Thread Safe</description>
+ /// </item>
+ /// <item>
+ /// <description>Connection Orientated</description>
+ /// </item>
+ /// <item>
+ /// <description>Packet/Message Based</description>
+ /// </item>
+ /// </list>
+ /// </para>
+ /// </remarks>
+ /// <threadsafety static="true" instance="true"/>
public abstract class Connection : IDisposable
{
/// <summary>
/// Called when a message has been received.
/// </summary>
+ /// <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
+ /// event.
+ /// </para>
+ /// <include file="DocInclude/common.xml" path="docs/item[@name='Event_Thread_Safety_Warning']/*" />
+ /// </remarks>
+ /// <example>
+ /// <code language="C#" source="DocInclude/TcpClientExample.cs"/>
+ /// </example>
public event EventHandler<DataEventArgs> DataReceived;
/// <summary>
- /// Called when the end point disconnects from us or an error occurs.
+ /// Called when the end point disconnects or an error occurs.
/// </summary>
+ /// <remarks>
+ /// <para>
+ /// Disconnected is invoked when the connection is closed due to an exception occuring or because the remote
+ /// end point disconnected. If it was invoked due to an exception occuring then the exception is available
+ /// in the <see cref="DisconnectedEventArgs"/> passed with 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"/>
+ /// </example>
public event EventHandler<DisconnectedEventArgs> Disconnected;
/// <summary>
- /// The end point of this Connection.
+ /// The remote end point of this Connection.
/// </summary>
+ /// <remarks>
+ /// This is the end point that this connection is connected to (i.e. the other device). This returns an abstract
+ /// <see cref="ConnectionEndPoint"/> which can then be cast to an appropriate end point depending on the
+ /// connection type.
+ /// </remarks>
public ConnectionEndPoint EndPoint { get; protected set; }
/// <summary>
/// The traffic statistics about this Connection.
/// </summary>
+ /// <remarks>
+ /// Contains statistics about the number of messages and bytes sent and received by this connection.
+ /// </remarks>
public ConnectionStatistics Statistics { get; protected set; }
/// <summary>
/// The state of this connection.
/// </summary>
+ /// <remarks>
+ /// <para>
+ /// Connections go round 4 states in their lifetime: they start as <see cref="ConnectionState.NotConnected"/> to
+ /// indicate they have no endpoint, calling <see cref="Connect"/> takes them into
+ /// <see cref="ConnectionState.Connecting"/>, once they have received confirmation they are connected they enter
+ /// <see cref="ConnectionState.Connected"/> and finally calling <see cref="Close"/> sets them to
+ /// <see cref="ConnectionState.Disconnecting"/> and then the sequence repeats back to
+ /// <see cref="ConnectionState.NotConnected"/> once disconnection is complete.
+ /// </para>
+ /// <para>
+ /// Data can only be sent while in <see cref="ConnectionState.Connected"/> and all attempts to send data when
+ /// in any other state will throw an InvalidOperationException.
+ /// </para>
+ /// <para>
+ /// All implementers should be aware that when this is set to <see cref="ConnectionState.Connected"/> it will
+ /// release all threads that are blocked on <see cref="WaitOnConnect"/>.
+ /// </para>
+ /// </remarks>
public ConnectionState State
{
get
/// <summary>
/// Constructor that initializes the ConnecitonStatistics object.
/// </summary>
+ /// <remarks>
+ /// This constructor initialises <see cref="Statistics"/> with empty statistics and sets <see cref="State"/> to
+ /// <see cref="ConnectionState.NotConnected"/>.
+ /// </remarks>
protected Connection()
{
Statistics = new ConnectionStatistics();
}
/// <summary>
- /// Writes an array of bytes to the connection and prefixes the length.
+ /// Sends a number of bytes to the end point of the connection using the specified <see cref="SendOption"/>.
/// </summary>
/// <param name="bytes">The bytes of the message to send.</param>
- /// <param name="sendOption">The options this data is requested to send with.</param>
+ /// <param name="sendOption">The option specifying how the message should be sent.</param>
/// <remarks>
- /// The sendOptions parameter is only a request to use those options and the actual method used to send the
- /// data is up to the implementation. There are circumstances where this parameter may be ignored but in
- /// general any implementer should aim to always follow the user's request here.
+ /// <include file="DocInclude/common.xml" path="docs/item[@name='Connection_SendBytes_General']/*" />
+ /// <para>
+ /// The sendOptions parameter is only a request to use those options and the actual method used to send the
+ /// data is up to the implementation. There are circumstances where this parameter may be ignored but in
+ /// general any implementer should aim to always follow the user's request.
+ /// </para>
/// </remarks>
- public abstract void WriteBytes(byte[] bytes, SendOption sendOption = SendOption.None);
+ public abstract void SendBytes(byte[] bytes, SendOption sendOption = SendOption.None);
/// <summary>
- /// Connects the connection to a remote server and begins listening.
+ /// Connects the connection to a server and begins listening.
/// </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.
+ /// </remarks>
public abstract void Connect(ConnectionEndPoint remoteEndPoint);
/// <summary>
- /// Invokes the DataReceived event to alert subscribers we received data.
+ /// Invokes the DataReceived event.
/// </summary>
- /// <param name="bytes">The bytes to supply.</param>
- /// <param name="sendOption">The sendOption to supply.</param>
+ /// <param name="bytes">The bytes received.</param>
+ /// <param name="sendOption">The <see cref="SendOption"/> the message was received with.</param>
+ /// <remarks>
+ /// Invokes the <see cref="DataReceived"/> event on this connection to alert subscribers a new message has been
+ /// received. The bytes and the send option that the message was sent with should be passed in to give to the
+ /// subscribers.
+ /// </remarks>
protected void InvokeDataReceived(byte[] bytes, SendOption sendOption)
{
DataEventArgs args = DataEventArgs.GetObject();
}
/// <summary>
- /// Invokes the Disconnected event to alert hooked up methods there was an error or the remote end point disconnected.
+ /// Invokes the Disconnected event.
/// </summary>
/// <param name="e">The exception, if any, that occured to cause this.</param>
- protected void InvokeDisconnected(Exception e)
+ /// <remarks>
+ /// Invokes the <see cref="Disconnected"/> event to alert subscribres this connection has been disconnected either
+ /// by the end point or because an error occured. If an error occured the error should be passed in in order to
+ /// pass to the subscribers, otherwise null can be passed in.
+ /// </remarks>
+ protected void InvokeDisconnected(Exception e = null)
{
DisconnectedEventArgs args = DisconnectedEventArgs.GetObject();
args.Set(e);
/// <summary>
/// Blocks until the Connection is connected.
/// </summary>
+ /// <remarks>
+ /// This is a helper method for waiting until the connection is connected. It will block until the
+ /// <see cref="State"/> property is set to <see cref="ConnectionState.Connected"/> allowing the main thread to
+ /// wait until specific data is received etc. before returning to the user's code.
+ /// </remarks>
protected void WaitOnConnect()
{
connectWaitLock.WaitOne();
/// <summary>
/// Closes this connection safely.
/// </summary>
+ /// <remarks>
+ /// <para>
+ /// Informs the end point of the connection that we are disconnecting from them and disposes of this
+ /// connection.
+ /// </para>
+ /// <para>
+ /// 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>
+ /// </remarks>
public virtual void Close()
{
Dispose();
namespace Hazel
{
+ /// <summary>
+ /// Base class for all end points of connections.
+ /// </summary>
+ /// <threadsafety static="true" instance="true"/>
public abstract class ConnectionEndPoint
{
}
/// <summary>
/// Base class for all connection listeners.
/// </summary>
+ /// <remarks>
+ /// <para>
+ /// ConnectionListeners are server side objects that listen for clients and create matching server side connections
+ /// for each client in a similar way to TCP does. These connections should already have a
+ /// <see cref="Connection.State">State</see> of <see cref="ConnectionState.Connected"/> and so should be ready for
+ /// comunication immediately.
+ /// </para>
+ /// <para>
+ /// Each time a client connects the <see cref="NewConnection"/> event will be invoked to alert all subscribers to
+ /// the new connection. A disconnected event is then present on the <see cref="Connection"/> that is passed to the
+ /// subscribers.
+ /// </para>
+ /// </remarks>
+ /// <threadsafety static="true" instance="true"/>
public abstract class ConnectionListener : IDisposable
{
/// <summary>
- /// Invoked when a new TCP connection is heard.
+ /// Invoked when a new client connects.
/// </summary>
+ /// <remarks>
+ /// <para>
+ /// NewConnection is invoked each time a client connects to the listener. The
+ /// <see cref="NewConnectionEventArgs"/> contains the new <see cref="Connection"/> for communication with this
+ /// client.
+ /// </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
+ /// 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"/>
+ /// </example>
public event EventHandler<NewConnectionEventArgs> NewConnection;
-
+ //TODO add threadsafe markers on all xmldocs
/// <summary>
/// Makes this connection listener begin listening for connections.
/// </summary>
+ /// <remarks>
+ /// <para>
+ /// This instructs the listener to begin listening for new clients connecting to the server. When a new client
+ /// 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"/>.
+ /// </para>
+ /// </remarks>
+ /// <example>
+ /// <code language="C#" source="DocInclude/TcpListenerExample.cs"/>
+ /// </example>
public abstract void Start();
/// <summary>
- /// Invokes the NewConnection event with the supplied args.
+ /// Invokes the NewConnection event with the supplied connection.
/// </summary>
- /// <param name="args">The arguments for the event.</param>
+ /// <param name="args">The connection to pass to subscribers.</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.
+ /// </remarks>
protected void InvokeNewConnection(Connection connection)
{
//Get new args
namespace Hazel
{
/// <summary>
- /// Marks the state a Connection is currently in.
+ /// Represents the state a <see cref="Connection"/> is currently in.
/// </summary>
public enum ConnectionState
{
namespace Hazel
{
/// <summary>
- /// Holds statistics about the traffic through a Connection.
+ /// Holds statistics about the traffic through a <see cref="Connection"/>.
/// </summary>
+ /// <threadsafety static="true" instance="true"/>
public class ConnectionStatistics
{
/// <summary>
/// The number of messages sent.
/// </summary>
+ /// <remarks>
+ /// This is the number of messages that were sent from the <see cref="Connection"/>, incremented each time that
+ /// LogSend is called by the Connection. Messages that caused an error are not counted and messages are only
+ /// counted once all other operations in the send are complete.
+ /// </remarks>
public long MessagesSent
{
get
/// <summary>
/// The number of bytes of data sent.
/// </summary>
+ /// <remarks>
+ /// <para>
+ /// This is the number of bytes of data (i.e. user bytes) that were sent from the <see cref="Connection"/>,
+ /// accumulated each time that LogSend is called by the Connection. Messages that caused an error are not
+ /// counted and messages are only counted once all other operations in the send are complete.
+ /// </para>
+ /// <para>
+ /// For the number of bytes including protocol bytes see <see cref="TotalBytesSent"/>.
+ /// </para>
+ /// </remarks>
public long DataBytesSent
{
get
/// <summary>
/// The number of bytes sent in total.
/// </summary>
+ /// <remarks>
+ /// <para>
+ /// This is the total number of bytes (the data bytes plus protocol bytes) that were sent from the
+ /// <see cref="Connection"/>, accumulated each time that LogSend is called by the Connection. Messages that
+ /// caused an error are not counted and messages are only counted once all other operations in the send are
+ /// complete.
+ /// </para>
+ /// <para>
+ /// For the number of data bytes excluding protocol bytes see <see cref="DataBytesSent"/>.
+ /// </para>
+ /// </remarks>
public long TotalBytesSent
{
get
/// <summary>
/// The number of messages received.
/// </summary>
+ /// <remarks>
+ /// This is the number of messages that were received by the <see cref="Connection"/>, incremented each time that
+ /// LogReceive is called by the Connection. Messages are counted before the receive event is invoked.
+ /// </remarks>
public long MessagesReceived
{
get
/// <summary>
/// The number of bytes of data received.
/// </summary>
+ /// <remarks>
+ /// <para>
+ /// This is the number of bytes of data (i.e. user bytes) that were received by the <see cref="Connection"/>,
+ /// accumulated each time that LogReceive is called by the Connection. Messages are counted before the receive
+ /// event is invoked.
+ /// </para>
+ /// <para>
+ /// For the number of bytes including protocol bytes see <see cref="TotalBytesReceived"/>.
+ /// </para>
+ /// </remarks>
public long DataBytesReceived
{
get
/// <summary>
/// The number of bytes received in total.
/// </summary>
+ /// <remarks>
+ /// <para>
+ /// This is the total number of bytes (the data bytes plus protocol bytes) that were received by the
+ /// <see cref="Connection"/>, accumulated each time that LogReceive is called by the Connection. Messages are
+ /// counted before the receive event is invoked.
+ /// </para>
+ /// <para>
+ /// For the number of data bytes excluding protocol bytes see <see cref="DataBytesReceived"/>.
+ /// </para>
+ /// </remarks>
public long TotalBytesReceived
{
get
/// </summary>
/// <param name="dataLength">The number of bytes of data sent.</param>
/// <param name="totalLength">The total number of bytes sent.</param>
+ /// <remarks>
+ /// This should be called after the data has been sent and should only be called for data that is sent sucessfully.
+ /// </remarks>
internal void LogSend(int dataLength, int totalLength)
{
Interlocked.Increment(ref messagesSent);
/// </summary>
/// <param name="dataLength">The number of bytes of data received.</param>
/// <param name="totalLength">The total number of bytes received.</param>
+ /// <remarks>
+ /// This should be called before the received event is invoked so it is up to date for subscribers to that event.
+ /// </remarks>
internal void LogReceive(int dataLength, int totalLength)
{
Interlocked.Increment(ref messagesReceived);
using System.Linq;
using System.Text;
-/*
-* Copyright (C) Jamie Read - All Rights Reserved
-* Unauthorized copying of this file, via any medium is strictly prohibited
-* Proprietary and confidential
-* Written by Jamie Read <jamie.read@outlook.com>, January 2016
-*/
-
namespace Hazel
{
+ /// <summary>
+ /// Event arguments for the <see cref="Connection.DataEvent"/> 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>.
+ /// </para>
+ /// <include file="DocInclude/common.xml" path="docs/item[@name='Recyclable']/*" />
+ /// </remarks>
public class DataEventArgs : EventArgs, IRecyclable
{
/// <summary>
/// <summary>
/// Returns an instance of this object from the pool.
/// </summary>
- /// <returns></returns>
+ /// <returns>A new or recycled DataEventArgs object.</returns>
internal static DataEventArgs GetObject()
{
return objectPool.GetObject();
}
/// <summary>
- /// The bytes received.
+ /// The bytes received from the client.
/// </summary>
public byte[] Bytes { get; private set; }
/// <summary>
- /// The SendOption the data was sent with.
+ /// The <see cref="SendOption"/> the data was sent with.
/// </summary>
public object SendOption { get; private set; }
this.SendOption = sendOption;
}
- /// <summary>
- /// Returns this object back to the object pool.
- /// </summary>
+ /// <inheritdoc />
public void Recycle()
{
objectPool.PutObject(this);
namespace Hazel
{
/// <summary>
- /// Events args for disconnected events.
+ /// Event arguments for the <see cref="Connection.Disconnected"/> event.
/// </summary>
+ /// <remarks>
+ /// <para>
+ /// This contains information about the cause of a disconnection and is passed to subscribers of the
+ /// <see cref="Connection.Disconnected"/> event.
+ /// </para>
+ /// <include file="DocInclude/common.xml" path="docs/item[@name='Recyclable']/*" />
+ /// </remarks>
public class DisconnectedEventArgs : IRecyclable
{
/// <summary>
/// <summary>
/// Returns an instance of this object from the pool.
/// </summary>
- /// <returns></returns>
+ /// <returns>A new or recycled DisconnectedEventArgs object.</returns>
internal static DisconnectedEventArgs GetObject()
{
return objectPool.GetObject();
}
/// <summary>
- /// The exception, if any, that caused the disconnect, otherwise null.
+ /// 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.
+ /// </remarks>
public Exception Exception { get; private set; }
/// <summary>
this.Exception = e;
}
- /// <summary>
- /// Returns this object back to the object pool.
- /// </summary>
+ /// <inheritdoc />
public void Recycle()
{
objectPool.PutObject(this);
--- /dev/null
+class TcpClientExample
+{
+ static void Main(string[] args)
+ {
+ using (TcpConnection connection = new TcpConnection())
+ {
+ ManualResetEvent e = new ManualResetEvent(false);
+
+ //Whenever we receive data print the number of bytes and how it was sent
+ connection.DataReceived += (object sender, DataEventArgs a) =>
+ Console.WriteLine("Received {0} bytes via {1}!", a.Bytes.Length, a.SendOption);
+
+ //When the end point disconnects from us then release the main thread and exit
+ connection.Disconnected += (object sender, DisconnectedEventArgs a) =>
+ e.Set();
+
+ //Connect to a server
+ connection.Connect(new NetworkEndPoint("127.0.0.1", 4296));
+
+ //Wait until the end point disconnects from us
+ e.WaitOne();
+ }
+ }
+}
--- /dev/null
+class UdpClientExample
+{
+ static void Main(string[] args)
+ {
+ using (UdpConnection connection = new UdpConnection())
+ {
+ ManualResetEvent e = new ManualResetEvent(false);
+
+ //Whenever we receive data print the number of bytes and how it was sent
+ connection.DataReceived += (object sender, DataEventArgs a) =>
+ Console.WriteLine("Received {0} bytes via {1}!", a.Bytes.Length, a.SendOption);
+
+ //When the end point disconnects from us then release the main thread and exit
+ connection.Disconnected += (object sender, DisconnectedEventArgs a) =>
+ e.Set();
+
+ //Connect to a server
+ connection.Connect(new NetworkEndPoint("127.0.0.1", 4296));
+
+ //Wait until the end point disconnects from us
+ e.WaitOne();
+ }
+ }
+}
--- /dev/null
+class UdpListenerExample
+{
+ static void Main(string[] args)
+ {
+ //Setup listener
+ using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296))
+ {
+ //Start listening for new connection events
+ listener.NewConnection += delegate(object sender, NewConnectionEventArgs a)
+ {
+ //Send the client some data
+ a.Connection.SendBytes(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 }, SendOption.Reliable);
+
+ //Disconnect from the client
+ a.Connection.Close();
+ };
+
+ listener.Start();
+
+ Console.ReadKey();
+ }
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8" ?>
+
+<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.
+ </para>
+ </item>
+ <item name="Recyclable">
+ <para>
+ This object implements IRecyclable and hence can be recycled in order to reduce the number of objects the
+ GC has to deal with. When you are done with the object you can either leave it unreferenced as you usually
+ would and the GC will collect it or you can call <see cref="Recycle"/> to inform Hazel that the object
+ should be reused. Once recycle has been called the contents can be overwritten at any time and so only
+ call it once you are completely finished with the object.
+ </para>
+ </item>
+ <item name="Connection_SendBytes_General">
+ <para>
+ 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.
+ </para>
+ </item>
+</docs>
\ No newline at end of file
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<PropertyGroup>
- <AssemblyOriginatorKeyFile>Hazel.snk</AssemblyOriginatorKeyFile>
+ <AssemblyOriginatorKeyFile>
+ </AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Compile Include="ConnectionState.cs" />
<Compile Include="DataEventArgs.cs" />
<Compile Include="DisconnectedEventArgs.cs" />
+ <None Include="DocInclude\TcpClientExample.cs" />
+ <None Include="DocInclude\UdpClientExample.cs" />
+ <None Include="DocInclude\TcpListenerExample.cs" />
+ <None Include="DocInclude\UdpListenerExample.cs" />
<Compile Include="HazelException.cs" />
+ <Compile Include="IPMode.cs" />
<Compile Include="IRecyclable.cs" />
<Compile Include="NetworkConnection.cs" />
<Compile Include="NetworkConnectionListener.cs" />
<Compile Include="UdpConnection.Reliable.cs" />
<Compile Include="UdpConnectionListener.cs" />
<Compile Include="UdpServerConnection.cs" />
- <Compile Include="Utility.cs" />
</ItemGroup>
<ItemGroup>
- <None Include="Hazel.snk" />
+ <Content Include="DocInclude\common.xml">
+ <SubType>Designer</SubType>
+ </Content>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
/// <summary>
/// Returns this object back to the object pool.
/// </summary>
+ /// <remarks>
+ /// <para>
+ /// Calling this when you are done with the object returns the object back to a pool in order to be reused.
+ /// This can reduce the amount of work the GC has to do dramatically but it is optional to call this.
+ /// </para>
+ /// <para>
+ /// Calling this indicates to Hazel that this can be reused and thus you should only call this when you are
+ /// completely finished with the object as the contents can be overwritten at any point after.
+ /// </para>
+ /// </remarks>
void Recycle();
}
}
using System.Text;
using System.Threading.Tasks;
-/*
-* Copyright (C) Jamie Read - All Rights Reserved
-* Unauthorized copying of this file, via any medium is strictly prohibited
-* Proprietary and confidential
-* Written by Jamie Read <jamie.read@outlook.com>, January 2016
-*/
-
namespace Hazel
{
/// <summary>
- /// A connection to a remote end point via a network protocol.
+ /// Abstract base class for a <see cref="Connection"/> to a remote end point via a network protocol like TCP or UDP.
/// </summary>
public abstract class NetworkConnection : Connection
{
/// <summary>
/// The remote end point of this connection.
/// </summary>
+ /// <remarks>
+ /// This is the end point of the other device given as an <see cref="System.Net.EndPoint"/> rather than a generic
+ /// <see cref="ConnectionEndPoint"/> as the base <see cref="Connection"/> does.
+ /// </remarks>
public EndPoint RemoteEndPoint { get; protected set; }
}
}
using System.Text;
using System.Threading.Tasks;
-/*
-* Copyright (C) Jamie Read - All Rights Reserved
-* Unauthorized copying of this file, via any medium is strictly prohibited
-* Proprietary and confidential
-* Written by Jamie Read <jamie.read@outlook.com>, January 2016
-*/
-
namespace Hazel
{
/// <summary>
- /// Connection listener for network based connections.
+ /// Abstract base class for a <see cref="ConnectionListener"/> for network based connections.
/// </summary>
public abstract class NetworkConnectionListener : ConnectionListener
{
/// <summary>
- /// The IP address we're listening on.
+ /// The local IP address the listener is listening for new clients on.
/// </summary>
public IPAddress IPAddress { get; protected set; }
/// <summary>
- /// The port we're listening on.
+ /// The port the listener is listening for new clients on.
/// </summary>
public int Port { get; protected set; }
}
/// <summary>
/// Represents an endpoint to a remote resource on a network.
/// </summary>
- public class NetworkEndPoint : ConnectionEndPoint
+ /// <remarks>
+ /// This wraps a <see cref="System.Net.EndPoint"/> for connecting across a network using protocols like TCP or UDP.
+ /// </remarks>
+ public sealed class NetworkEndPoint : ConnectionEndPoint
{
/// <summary>
- /// The EndPoint this points to.
+ /// The <see cref="System.Net.EndPoint">EndPoint</see> this points to.
/// </summary>
public EndPoint EndPoint { get; set; }
/// <summary>
- /// Creates a NetworkEndPoint from a given EndPoint.
+ /// The <see cref="IPMode"/> this will instruct connections to use.
/// </summary>
- /// <param name="endPoint">The endpoint we represent./param>
- public NetworkEndPoint(EndPoint endPoint)
+ public IPMode IPMode { get; set; }
+
+ /// <summary>
+ /// Creates a NetworkEndPoint from a given <see cref="System.Net.EndPoint">EndPoint</see>.
+ /// </summary>
+ /// <param name="endPoint">The end point to wrap./param>
+ public NetworkEndPoint(EndPoint endPoint, IPMode mode = IPMode.IPv4AndIPv6)
{
this.EndPoint = endPoint;
+ this.IPMode = mode;
}
/// <summary>
- /// Create a NetworkEndPoint to the specified address and port.
+ /// Create a NetworkEndPoint to the specified <see cref="System.Net.IPAddress">IPAddress</see> and port.
/// </summary>
/// <param name="address">The IP address of the server.</param>
/// <param name="port">The port the server is listening on.</param>
- public NetworkEndPoint(IPAddress address, int port) : this(new IPEndPoint(address, port))
+ /// <remarks>
+ /// When using this constructor <see cref="EndPoint"/> will contain an <see cref="IPEndPoint"/>.
+ /// </remarks>
+ public NetworkEndPoint(IPAddress address, int port, IPMode mode = IPMode.IPv4AndIPv6)
+ : this(new IPEndPoint(address, port))
{
}
/// </summary>
/// <param name="IP">A valid IP address of the server.</param>
/// <param name="port">The port the server is listening on.</param>
- public NetworkEndPoint(string IP, int port) : this(IPAddress.Parse(IP), port)
+ /// <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)
{
}
namespace Hazel
{
/// <summary>
- /// Event args for new connection events.
+ /// Event arguments for the <see cref="ConnectionListener.NewConnection"/> event.
/// </summary>
+ /// <remarks>
+ /// <para>
+ /// This contains the new connection for the client that connection and is passed to subscribers of the
+ /// <see cref="ConnectionListener.NewConnection"/> event.
+ /// </para>
+ /// <include file="DocInclude/common.xml" path="docs/item[@name='Recyclable']/*" />
+ /// </remarks>
public class NewConnectionEventArgs : EventArgs, IRecyclable
{
/// <summary>
/// <summary>
/// Returns an instance of this object from the pool.
/// </summary>
- /// <returns></returns>
+ /// <returns>A new or recycled NewConnectionEventArgs object.</returns>
internal static NewConnectionEventArgs GetObject()
{
return objectPool.GetObject();
}
/// <summary>
- /// The new connection.
+ /// The <see cref="Connection"/> to the new client.
/// </summary>
public Connection Connection { get; private set; }
/// <summary>
- /// Private constructor for thread pool.
+ /// Private constructor for object pool.
/// </summary>
NewConnectionEventArgs()
{
/// <summary>
/// Sets the members of the arguments.
/// </summary>
- /// <param name="Connection"></param>
+ /// <param name="Connection">The new connection</param>
internal void Set(Connection Connection)
{
this.Connection = Connection;
}
- /// <summary>
- /// Returns this object back to the object pool.
- /// </summary>
+ /// <inheritdoc />
public void Recycle()
{
objectPool.PutObject(this);
/// A fairly simple object pool for items that will be created a lot.
/// </summary>
/// <typeparam name="T">The type that is pooled.</typeparam>
- class ObjectPool<T> where T : IRecyclable
+ sealed class ObjectPool<T> where T : IRecyclable
{
/// <summary>
/// Our pool of objects
namespace Hazel
{
/// <summary>
- /// Specifies how a message should be sent.
+ /// Specifies how a message should be sent between connections.
/// </summary>
[Flags]
public enum SendOption : byte
{
/// <summary>
- /// Requests unreliable delivery with no framentation or ordering.
+ /// Requests unreliable delivery with no framentation.
/// </summary>
+ /// <remarks>
+ /// Sending data using unreliable delivery means that data is not guaranteed to arrive at it's destination nor is
+ /// it guarenteed to arrive only once. However, unreliable delivery can be faster than other methods and it
+ /// typically requires a smaller number of protocol bytes than other methods. There is also typically less
+ /// processing involved and less memory needed as packets are not stored once sent.
+ /// </remarks>
None = 0,
/// <summary>
- /// Requests data be sent reliably. Data is guaranteed to arrive at it's destination.
- /// </summary>
- Reliable = 1,
-
- /// <summary>
- /// Requests that data should be sent in order.
+ /// Requests data be sent reliably but with no fragmentation.
/// </summary>
/// <remarks>
- /// Any packets that are out of order in this option will be dropped.
+ /// Sending data reliably means that data is guarenteed to arrive and to arrive only once. Reliable delivery
+ /// 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>
- Ordered = 2,
+ Reliable = 16,
/// <summary>
- /// Requests that data should be sent in order and reliably.
+ /// Requests data be sent so that large messages are fragmented into smaller chunks of
+ /// data and reassembled when received.
/// </summary>
/// <remarks>
- /// Only messages that are sent using OrderedReliable or OrderedFragmentedReliable will arrive
- /// in order, other messages
- /// may arrive in between.
+ /// Fragmented messages allow large amounts of data to be transmitted in smaller chunks when using connections
+ /// 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>
- OrderedReliable = 3,
+ Fragmented = 32,
/// <summary>
/// Requests data be sent so that large messages are fragmented into smaller chunks of
/// data and reassembled when received.
/// </summary>
- FragmentedReliable = 5,
-
- /// <summary>
- /// Requests data be sent so that large messages are fragmented into smaller chunks of data and
- /// reassembled when received and that the message arrives in order with other messages.
- /// </summary>
- OrderedFragmentedReliable = 7
+ /// <remarks>
+ /// Fragmented messages allow large amounts of data to be transmitted in smaller chunks when using connections
+ /// that do not support the transmission of large messages. By specifying reliable delivery messages are
+ /// 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
}
}
namespace Hazel
{
/// <summary>
- /// Extra internal states for SendOption enumeration.
+ /// Extra internal states for SendOption enumeration when using UDP.
/// </summary>
enum SendOptionInternal : byte
{
/// <summary>
/// Hello message for initiating communication.
/// </summary>
- Hello = 253,
+ Hello = 128,
/// <summary>
/// Message for discontinuing communication.
/// </summary>
- Disconnect = 254,
+ Disconnect = 129,
/// <summary>
/// Message acknowledging the receipt of a message.
/// </summary>
- Acknowledgement = 255
+ Acknowledgement = 130
}
}
/// <summary>
/// Represents the state of the current receive operation for TCP connections.
/// </summary>
- public struct StateObject
+ struct StateObject
{
/// <summary>
/// The buffer we're receiving.
using System.Net.Sockets;
using System.Text;
-/*
-* Copyright (C) Jamie Read - All Rights Reserved
-* Unauthorized copying of this file, via any medium is strictly prohibited
-* Proprietary and confidential
-* Written by Jamie Read <jamie.read@outlook.com>, January 2016
-*/
-
namespace Hazel
{
/// <summary>
/// Represents a connection that uses the TCP protocol.
/// </summary>
- public class TcpConnection : NetworkConnection
+ /// <inheritdoc />
+ public sealed class TcpConnection : NetworkConnection
{
/// <summary>
/// The socket we're managing.
/// </summary>
- public Socket Socket { get; private set; }
+ 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"></param>
+ /// <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.");
- this.EndPoint = new NetworkEndPoint(socket.RemoteEndPoint);
- this.RemoteEndPoint = socket.RemoteEndPoint;
-
- this.Socket = socket;
-
- lock (this.Socket)
+ lock (this.socketLock)
{
- this.Socket.NoDelay = true;
+ this.EndPoint = new NetworkEndPoint(socket.RemoteEndPoint);
+ this.RemoteEndPoint = socket.RemoteEndPoint;
+
+ this.socket = socket;
+ this.socket.NoDelay = true;
State = ConnectionState.Connected;
}
/// </summary>
public TcpConnection()
{
- //Create and connect a socket
- Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
-
- Socket.NoDelay = true;
+
}
/// <summary>
}
}
- /// <summary>
- /// Connects this TCP connection to the endpoint.
- /// </summary>
- /// <param name="remotEndPoint">The location of the server to connect to.</param>
+ /// <inheritdoc />
+ /// <param name="remoteEndPoint">A <see cref="NetworkEndPoint"/> to connect to.</param>
public override void Connect(ConnectionEndPoint remoteEndPoint)
{
NetworkEndPoint nep = remoteEndPoint as NetworkEndPoint;
{
throw new ArgumentException("The remote end point of a TCP connection must be a NetworkEndPoint.");
}
-
- this.EndPoint = remoteEndPoint;
- this.RemoteEndPoint = nep.EndPoint;
- //Connect
- lock (Socket)
+ 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);
+ socket.Connect(nep.EndPoint);
}
catch (SocketException e)
{
throw new HazelException("Could not connect as a socket exception occured.", e);
}
- }
- //Start receiving data
- try
- {
- StartWaitingForHeader();
- }
- catch (SocketException e)
- {
- throw new HazelException("A Socket exception occured while initiating a receive operation.", e);
- }
+ //Start receiving data
+ StartListening();
- //Set connected
- lock (Socket)
+ //Set connected
State = ConnectionState.Connected;
+ }
}
- /// <summary>
- /// Writes an array of bytes to the connection and prefixes the length.
- /// </summary>
- /// <param name="bytes">The bytes of the message to send.</param>
- /// <param name="sendOption">The options this data is requested to send with.</param>
+ /// <inheritdoc/>
/// <remarks>
- /// The sendOptions parameter is ignored by the TcpConnection as TCP only supports OrderedFragmentedReliable communication.
+ /// <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 WriteBytes(byte[] bytes, SendOption sendOption = SendOption.OrderedFragmentedReliable)
+ public override void SendBytes(byte[] bytes, SendOption sendOption = SendOption.FragmentedReliable)
{
//Get bytes for length
- byte[] fullBytes = Utility.AppendLengthHeader(bytes);
+ byte[] fullBytes = AppendLengthHeader(bytes);
//Write the bytes to the socket
- lock (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);
+ socket.BeginSend(fullBytes, 0, fullBytes.Length, SocketFlags.None, null, null);
}
catch (SocketException e)
{
/// <summary>
/// Called when a 4 byte header has been received.
/// </summary>
- /// <param name="result">The result of the async operation.</param>
- protected virtual void HeaderReadCallback(byte[] bytes)
+ /// <param name="bytes">The 4 header bytes read.</param>
+ void HeaderReadCallback(byte[] bytes)
{
//Get length
- int length = Utility.GetLengthFromBytes(bytes);
+ int length = GetLengthFromBytes(bytes);
//Begin receiving the body
try
/// <summary>
/// Callback for when a body has been read.
/// </summary>
- /// <param name="result"></param>
- protected virtual void BodyReadCallback(byte[] bytes)
+ /// <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.OrderedFragmentedReliable);
+ InvokeDataReceived(bytes, SendOption.FragmentedReliable);
}
/// <summary>
/// Starts this connections waiting for the header.
/// </summary>
- protected void StartWaitingForHeader()
+ void StartWaitingForHeader()
{
StartWaitingForBytes(4, HeaderReadCallback);
}
/// </summary>
/// <param name="length">The number of bytes to receive.</param>
/// <param name="callback">The callback </param>
- protected virtual void StartWaitingForBytes(int length, Action<byte[]> callback)
+ void StartWaitingForBytes(int length, Action<byte[]> callback)
{
StateObject state = new StateObject(length, callback);
/// Waits for the next chunk of data from this socket.
/// </summary>
/// <param name="state">The StateObject for the receive operation.</param>
- protected virtual void StartWaitingForChunk(StateObject state)
+ void StartWaitingForChunk(StateObject state)
{
- lock (Socket)
+ 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);
+ socket.BeginReceive(state.buffer, state.totalBytesReceived, state.buffer.Length, SocketFlags.None, ChunkReadCallback, state);
else
HandleDisconnect();
}
/// Called when a chunk has been read.
/// </summary>
/// <param name="result"></param>
- protected virtual void ChunkReadCallback(IAsyncResult result)
+ void ChunkReadCallback(IAsyncResult result)
{
int bytesReceived;
//End the receive operation
try
{
- lock (Socket)
- bytesReceived = Socket.EndReceive(result);
+ lock (socketLock)
+ bytesReceived = socket.EndReceive(result);
}
catch (ObjectDisposedException)
{
{
bool invoke = false;
- lock (Socket)
+ lock (socketLock)
{
//Only invoke the disconnected event if we're not already disconnecting
if (State == ConnectionState.Connected)
}
/// <summary>
- /// Closes this connections safely.
+ /// 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 (Socket)
+ lock (socketLock)
{
State = ConnectionState.NotConnected;
- if (Socket.Connected)
- Socket.Shutdown(SocketShutdown.Send);
- Socket.Dispose();
+ if (socket.Connected)
+ socket.Shutdown(SocketShutdown.Send);
+ socket.Dispose();
}
}
using System.Text;
using System.Threading.Tasks;
-/*
-* Copyright (C) Jamie Read - All Rights Reserved
-* Unauthorized copying of this file, via any medium is strictly prohibited
-* Proprietary and confidential
-* Written by Jamie Read <jamie.read@outlook.com>, January 2016
-*/
+//TODO replace copyright notices with MIT licenses
namespace Hazel
{
/// <summary>
/// Listens for new TCP connections and creates TCPConnections for them.
/// </summary>
- public class TcpConnectionListener : NetworkConnectionListener
+ /// <inheritdoc />
+ public sealed class TcpConnectionListener : NetworkConnectionListener
{
/// <summary>
/// The socket listening for connections.
Socket listener;
/// <summary>
- /// Creates a new ConnectionListener for the given IP and port.
+ /// Creates a new TcpConnectionListener for the given <see cref="IPAddress"/>, port and <see cref="IPMode"/>.
/// </summary>
- /// <param name="ipAdress">The IPAddress to listen on.</param>
+ /// <param name="IPAddress">The IPAddress to listen on.</param>
/// <param name="port">The port to listen on.</param>
- public TcpConnectionListener(IPAddress IPAddress, int port)
+ /// <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;
- this.listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ 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;
}
- /// <summary>
- /// Makes this connection listener begin listening for connections.
- /// </summary>
+ /// <inheritdoc />
public override void Start()
{
try
}
}
- /// <summary>
- /// Called when the object is being disposed.
- /// </summary>
- /// <param name="disposing">Are we being disposed?</param>
+ /// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (disposing)
namespace Hazel
{
- public class UdpClientConnection : UdpConnection
+ /// <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>
public UdpClientConnection()
: base()
{
- socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
- }
-
- /// <summary>
- /// Writes an array of bytes to the connection.
- /// </summary>
- /// <param name="bytes">The bytes of the message to send.</param>
- /// <param name="sendOption">The option this data is requested to send with.</param>
- public override void WriteBytes(byte[] bytes, SendOption sendOption = SendOption.None)
- {
- 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>
- /// Writes bytes to the socket.
- /// </summary>
- /// <param name="bytes">The bytes to send.</param>
+ /// <inheritdoc />
protected override void WriteBytesToConnection(byte[] bytes)
{
//Pack
args.SetBuffer(bytes, 0, bytes.Length);
args.RemoteEndPoint = RemoteEndPoint;
- lock (socket)
+ 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?");
}
}
- /// <summary>
- /// Connects this Connection to a given remote server and begins listening for data.
- /// </summary>
+ /// <inheritdoc />
+ /// <param name="remoteEndPoint">A <see cref="NetworkEndPoint"/> to connect to.</param>
public override void Connect(ConnectionEndPoint remoteEndPoint)
{
NetworkEndPoint nep = remoteEndPoint as NetworkEndPoint;
throw new ArgumentException("The remote end point of a UDP connection must be a NetworkEndPoint.");
}
- this.EndPoint = nep;
- this.RemoteEndPoint = nep.EndPoint;
-
- lock (socket)
+ 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.");
//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(() => State = ConnectionState.Connected);
+ SendHello(() => { lock (socketLock) State = ConnectionState.Connected; });
//Wait till hello packet is acknowledged and the state is set to Connected
WaitOnConnect();
/// </summary>
void StartListeningForData()
{
- socket.BeginReceive(dataBuffer, 0, dataBuffer.Length, SocketFlags.None, ReadCallback, dataBuffer);
+ lock (socketLock)
+ socket.BeginReceive(dataBuffer, 0, dataBuffer.Length, SocketFlags.None, ReadCallback, dataBuffer);
}
/// <summary>
//End the receive operation
try
{
- lock (socket)
+ lock (socketLock)
bytesReceived = socket.EndReceive(result);
}
catch (ObjectDisposedException)
//Begin receiving again
try
{
- lock (socket)
- StartListeningForData();
+ StartListeningForData();
}
catch (SocketException e)
{
InvokeDataReceived(buffer, sendOption);
}
- /// <summary>
- /// Called when the socket has been disconnected at the remote host.
- /// </summary>
- /// <param name="e">The exception if one was the cause.</param>
+ /// <inheritdoc />
protected override void HandleDisconnect(HazelException e = null)
{
bool invoke = false;
- lock (socket)
+ lock (socketLock)
{
//Only invoke the disconnected event if we're not already disconnecting
if (State == ConnectionState.Connected)
}
}
- /// <summary>
- /// Safely closes this connection.
- /// </summary>
+ /// <inheritdoc />
protected override void Dispose(bool disposing)
{
//Dispose of the socket
if (disposing)
{
- lock (socket)
+ lock (socketLock)
{
State = ConnectionState.NotConnected;
namespace Hazel
{
- /// <summary>
- /// UdpConnection part which handles keepalive packets.
- /// </summary>
partial class UdpConnection
{
/// <summary>
- /// The interval from data being received or transmitted to a keepalive packet being sent.
+ /// The interval from data being received or transmitted to a keepalive packet being sent in milliseconds.
/// </summary>
/// <remarks>
- /// Set to System.Threading.Timeout.Infinite to disable keepalive packets.
+ /// <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
{
{
partial class UdpConnection
{
- //TODO recycle dataevents and things?
-
/// <summary>
/// The starting timeout, in miliseconds, at which data will be resent.
/// </summary>
/// <remarks>
- /// On each resend this is doubled for that packet.
+ /// 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?
volatile bool hasReceivedSomething = false;
/// <summary>
- /// The maximum times a message should be retransmitted before marking the endpoint as disconnected.
+ /// The maximum times a message should be resent before marking the endpoint as disconnected.
/// </summary>
- public int RetransmissionsBeforeDisconnect { get { return retransmissionsBeforeDisconnect; } set { retransmissionsBeforeDisconnect = value; } }
- private volatile int retransmissionsBeforeDisconnect = 3;
+ /// <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
bytes,
(Packet p) =>
{
- WriteBytesToConnection(p.Data);
-
//Double packet timeout
lock (p.Timer)
{
if (!p.Acknowledged)
{
- p.Timer.Change(p.LastTimeout *= 2, Timeout.Infinite); //TODO disconnect after x tries
- if (++p.Retransmissions >= RetransmissionsBeforeDisconnect)
+ p.Timer.Change(p.LastTimeout *= 2, Timeout.Infinite);
+ if (++p.Retransmissions > ResendsBeforeDisconnect)
{
HandleDisconnect();
p.Recycle();
+ return;
}
}
}
+ WriteBytesToConnection(p.Data);
+
Trace.WriteLine("Resend.");
},
resendTimeout,
}
}
+ /// <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
using System.Text;
using System.Threading;
-/*
-* Copyright (C) Jamie Read - All Rights Reserved
-* Unauthorized copying of this file, via any medium is strictly prohibited
-* Proprietary and confidential
-* Written by Jamie Read <jamie.read@outlook.com>, January 2016
-*/
-
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);
- protected UdpConnection()
+ /// <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)
{
- InitializeKeepAliveTimer();
+ //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/ordered sending from this connection.
+ /// Handles the reliable/fragmented sending from this connection.
/// </summary>
/// <param name="data">The data being sent.</param>
- /// <param name="sendOption">The send option as a byte.</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)
{
/// <summary>
/// Handles the receiving of data.
/// </summary>
- /// <param name="buffer">The array of the data received.</param>
+ /// <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)
HandleSend(new byte[0], (byte)SendOptionInternal.Hello, acknowledgeCallback);
}
- /// <summary>
- /// Closes this connection safely.
- /// </summary>
+ /// <inheritdoc/>
public override void Close()
{
HandleSend(new byte[0], (byte)SendOptionInternal.Disconnect); //TODO Should disconnect wait for an ack?
/// <param name="e">The exception if one was the cause.</param>
protected abstract void HandleDisconnect(HazelException e = null);
- /// <summary>
- /// Called when things are being disposed of
- /// </summary>
- /// <param name="disposing"></param>
+ /// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (disposing)
using System.Text;
using System.Threading.Tasks;
-/*
-* Copyright (C) Jamie Read - All Rights Reserved
-* Unauthorized copying of this file, via any medium is strictly prohibited
-* Proprietary and confidential
-* Written by Jamie Read <jamie.read@outlook.com>, January 2016
-*/
-
namespace Hazel
{
/// <summary>
/// Listens for new UDP connections and creates UdpConnections for them.
/// </summary>
+ /// <inheritdoc />
public class UdpConnectionListener : NetworkConnectionListener
{
/// <summary>
Dictionary<EndPoint, UdpServerConnection> connections = new Dictionary<EndPoint, UdpServerConnection>();
/// <summary>
- /// Creates a new ConnectionListener for the given IP and port.
+ /// Creates a new ConnectionListener for the given <see cref="IPAddress"/>, port and <see cref="IPMode"/>.
/// </summary>
- /// <param name="ipAdress">The IPAddress to listen on.</param>
+ /// <param name="IPAddress">The IPAddress to listen on.</param>
/// <param name="port">The port to listen on.</param>
- public UdpConnectionListener(IPAddress IPAddress, int port)
+ /// <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;
- this.listener = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+ 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;
+ }
}
- /// <summary>
- /// Instruct the listener to begin listening for connections.
- /// </summary>
+ /// <inheritdoc />
public override void Start()
{
try
connections.Remove(endPoint);
}
- /// <summary>
- /// Called when the listener is being disposed of
- /// </summary>
- /// <param name="disposing"></param>
+ /// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (disposing)
using System.Text;
using System.Threading.Tasks;
-/*
-* Copyright (C) Jamie Read - All Rights Reserved
-* Unauthorized copying of this file, via any medium is strictly prohibited
-* Proprietary and confidential
-* Written by Jamie Read <jamie.read@outlook.com>, January 2016
-*/
-
namespace Hazel
{
- class UdpServerConnection : UdpConnection
+ /// <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>
/// <summary>
/// Creates a UdpConnection for the virtual connection to the endpoint.
/// </summary>
- /// <param name="socket"></param>
+ /// <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()
{
State = ConnectionState.Connected;
}
- /// <summary>
- /// Writes an array of bytes to the connection.
- /// </summary>
- /// <param name="bytes">The bytes of the message to send.</param>
- /// <param name="sendOption">The option this data is requested to send with.</param>
- public override void WriteBytes(byte[] bytes, SendOption sendOption = SendOption.None)
- {
- HandleSend(bytes, (byte)sendOption);
- }
-
- /// <summary>
- /// Writes bytes to the listener to send.
- /// </summary>
- /// <param name="bytes">bytes to send.</param>
+ /// <inheritdoc />
protected override void WriteBytesToConnection(byte[] bytes)
{
lock (stateLock)
}
}
- /// <summary>
- /// Connects this Connection to a given remote server.
- /// </summary>
+ /// <inheritdoc />
/// <remarks>
/// This will always throw an InvalidOperationException.
/// </remarks>
InvokeDataReceived(data, (SendOption)buffer[0]);
}
- /// <summary>
- /// Called when the socket has been disconnected at the remote host.
- /// </summary>
- /// <param name="e">The exception if one was the cause.</param>
+ /// <inheritdoc />
protected override void HandleDisconnect(HazelException e = null)
{
bool invoke = false;
}
}
- /// <summary>
- /// Safely closes this connection.
- /// </summary>
+ /// <inheritdoc />
protected override void Dispose(bool disposing)
{
//Here we just need to inform the listener we no longer need data.
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Hazel
-{
- class Utility
- {
- /// <summary>
- /// Appends the length header to the bytes.
- /// </summary>
- /// <param name="bytes">The source bytes.</param>
- /// <returns></returns>
- internal 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"></param>
- /// <returns></returns>
- internal 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];
- }
- }
-}