commit 1225e3067a317179c5a68468666bea000d64c6e1 parent 530c1064119e9dfd48b75c1eece3c6286ad1cb2d Author: Mikolaj Lenczewski <33129490+EnderRifter@users.noreply.github.com> Date: Fri, 6 Dec 2019 18:54:51 +0000 Initial push Diffstat:
| A | NetSharp/NetSharp.sln | | | 31 | +++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Client.cs | | | 206 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Clients/TcpClient.cs | | | 67 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Clients/UdpClient.cs | | | 71 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Connection.cs | | | 303 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Extensions/ClientExtensions.cs | | | 210 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Extensions/ServerExtensions.cs | | | 48 | ++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Interfaces/IClient.cs | | | 87 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Interfaces/INetworkSerialisable.cs | | | 23 | +++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Interfaces/IPacket.cs | | | 19 | +++++++++++++++++++ |
| A | NetSharp/NetSharp/Interfaces/IRequestPacket.cs | | | 10 | ++++++++++ |
| A | NetSharp/NetSharp/Interfaces/IResponsePacket.cs | | | 15 | +++++++++++++++ |
| A | NetSharp/NetSharp/Interfaces/IServer.cs | | | 87 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Logging/Logger.cs | | | 200 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/NetSharp.csproj | | | 26 | ++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/NetSharp.xml | | | 2336 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Packets/Builtin/ConnectPacket.cs | | | 34 | ++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Packets/Builtin/ConnectResponsePacket.cs | | | 37 | +++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Packets/Builtin/DataPacket.cs | | | 57 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Packets/Builtin/DataResponsePacket.cs | | | 60 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Packets/Builtin/DisconnectPacket.cs | | | 34 | ++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Packets/Builtin/PingPacket.cs | | | 34 | ++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Packets/Builtin/PingResponsePacket.cs | | | 37 | +++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Packets/Builtin/SimpleDataPacket.cs | | | 57 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Packets/NetworkErrorCode.cs | | | 19 | +++++++++++++++++++ |
| A | NetSharp/NetSharp/Packets/Packet.cs | | | 98 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Packets/PacketRegistry.cs | | | 288 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Packets/PacketTypeIdAttribute.cs | | | 28 | ++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Server.cs | | | 623 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Servers/TcpServer.cs | | | 133 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Servers/UdpServer.cs | | | 151 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Utils/BiDictionary.cs | | | 202 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Utils/Constants.cs | | | 19 | +++++++++++++++++++ |
| A | NetSharp/NetSharp/Utils/Conversion/EndianAwareBitConverter.cs | | | 146 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Utils/NetworkOperations.cs | | | 199 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Utils/Socket Options/DefaultSocketOptions.cs | | | 47 | +++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Utils/Socket Options/SocketOptionManager.cs | | | 26 | ++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Utils/Socket Options/SocketOptions.cs | | | 107 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Utils/Socket Options/TcpSocketOptions.cs | | | 37 | +++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Utils/Socket Options/UdpSocketOptions.cs | | | 37 | +++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharp/Utils/TransmissionResult.cs | | | 40 | ++++++++++++++++++++++++++++++++++++++++ |
| A | NetSharp/NetSharpExamples/NetSharpExamples.csproj | | | 20 | ++++++++++++++++++++ |
| A | NetSharp/NetSharpExamples/NetSharpExamples.xml | | | 8 | ++++++++ |
| A | NetSharp/NetSharpExamples/Program.cs | | | 253 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
44 files changed, 6570 insertions(+), 0 deletions(-)
diff --git a/NetSharp/NetSharp.sln b/NetSharp/NetSharp.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29324.140 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetSharp", "NetSharp\NetSharp.csproj", "{424A38DB-7CA0-4C94-90B4-EF246CB4BDC4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetSharpExamples", "NetSharpExamples\NetSharpExamples.csproj", "{5861E518-E712-4031-9AA1-0313736FF957}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {424A38DB-7CA0-4C94-90B4-EF246CB4BDC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {424A38DB-7CA0-4C94-90B4-EF246CB4BDC4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {424A38DB-7CA0-4C94-90B4-EF246CB4BDC4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {424A38DB-7CA0-4C94-90B4-EF246CB4BDC4}.Release|Any CPU.Build.0 = Release|Any CPU + {5861E518-E712-4031-9AA1-0313736FF957}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5861E518-E712-4031-9AA1-0313736FF957}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5861E518-E712-4031-9AA1-0313736FF957}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5861E518-E712-4031-9AA1-0313736FF957}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BF508739-E20B-4750-96E8-6654DC14ECD8} + EndGlobalSection +EndGlobal diff --git a/NetSharp/NetSharp/Client.cs b/NetSharp/NetSharp/Client.cs @@ -0,0 +1,205 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using NetSharp.Extensions; +using NetSharp.Interfaces; +using NetSharp.Packets.Builtin; +using NetSharp.Utils.Socket_Options; + +namespace NetSharp +{ + /// <summary> + /// Provides methods for connecting to and talking with a <see cref="IServer"/> instance. + /// </summary> + public abstract class Client : Connection, IClient, IDisposable + { + /// <summary> + /// Provides <see cref="CancellationToken"/> instances for cancelling methods after a timeout period. + /// </summary> + protected readonly CancellationTokenSource cancellationTokenSource; + + /// <summary> + /// The <see cref="Socket"/> underlying the connection. + /// </summary> + protected readonly Socket socket; + + /// <summary> + /// Backing field for the <see cref="SocketOptions"/> property. + /// </summary> + protected readonly SocketOptions socketOptions; + + /// <summary> + /// The remote endpoint with which this client communicates. + /// </summary> + protected EndPoint remoteEndPoint; + + /// <summary> + /// Initialises a new instance of the <see cref="Client"/> class. + /// </summary> + private Client() + { + cancellationTokenSource = new CancellationTokenSource(); + + remoteEndPoint = new IPEndPoint(IPAddress.None, IPEndPoint.MinPort); + socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + socketOptions = new DefaultSocketOptions(ref socket); + } + + /// <summary> + /// Initialises a new instance of the <see cref="Client"/> class. + /// </summary> + /// <param name="socketType">The socket type for the underlying socket.</param> + /// <param name="protocolType">The protocol type for the underlying socket.</param> + /// <param name="socketManager">The <see cref="Utils.Socket_Options.SocketOptions"/> manager to use.</param> + protected Client(SocketType socketType, ProtocolType protocolType, SocketOptionManager socketManager) : this() + { + socket = new Socket(AddressFamily.InterNetwork, socketType, protocolType); + + socketOptions = socketManager switch + { + SocketOptionManager.Tcp => new TcpSocketOptions(ref socket) as SocketOptions, + SocketOptionManager.Udp => new UdpSocketOptions(ref socket) as SocketOptions, + _ => new DefaultSocketOptions(ref socket), + }; + } + + /// <summary> + /// Destroys an instance of the <see cref="Client"/> class. + /// </summary> + ~Client() + { + Dispose(false); + } + + /// <inheritdoc /> + public event Action<EndPoint>? Connected; + + /// <inheritdoc /> + public event Action<EndPoint>? Disconnected; + + /// <summary> + /// The configured socket options for the underlying connection. + /// </summary> + public SocketOptions SocketOptions + { + get { return socketOptions; } + } + + /// <summary> + /// Disposes of this <see cref="Client"/> instance. + /// </summary> + /// <param name="disposing">Whether this instance is being disposed.</param> + protected override void Dispose(bool disposing) + { + if (disposing) + { + cancellationTokenSource?.Dispose(); + socket?.Dispose(); + } + + base.Dispose(disposing); + } + + /// <summary> + /// Invokes the <see cref="Connected"/> event. + /// </summary> + /// <param name="endPoint">The remote endpoint with which a connection was made.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void OnConnected(EndPoint endPoint) => Connected?.Invoke(endPoint); + + /// <summary> + /// Invokes the <see cref="Disconnected"/> event. + /// </summary> + /// <param name="endPoint">The remote endpoint with which a connection was lost.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void OnDisconnected(EndPoint endPoint) => Disconnected?.Invoke(endPoint); + + /// <summary> + /// Disconnects the client from the remote endpoint. + /// </summary> + public void Disconnect() + { + this.SendSimple(new DisconnectPacket()); + + socket.Shutdown(SocketShutdown.Both); + socket.Disconnect(true); + socket.Close(); + } + + /// <inheritdoc /> + public abstract Task SendBytesAsync(byte[] buffer, TimeSpan timeout); + + /// <inheritdoc /> + public abstract Task<byte[]> SendBytesWithResponseAsync(byte[] buffer, TimeSpan timeout); + + /// <inheritdoc /> + public abstract Task<Rep> SendComplexAsync<Req, Rep>(Req request, TimeSpan timeout) + where Req : IRequestPacket, new() where Rep : IResponsePacket<Req>, new(); + + /// <inheritdoc /> + public abstract Task SendSimpleAsync<Req>(Req request, TimeSpan timeout) where Req : IRequestPacket, new(); + + /// <inheritdoc /> + public async Task<bool> TryBindAsync(IPAddress? localAddress, int? localPort, TimeSpan timeout) + { + EndPoint localEndPoint = new IPEndPoint(localAddress ?? IPAddress.Any, localPort ?? 0); + + try + { + cancellationTokenSource.CancelAfter(timeout); + + return await Task.Run(() => + { + socket.Bind(localEndPoint); + + return true; + }, cancellationTokenSource.Token); + } + catch (TaskCanceledException) + { + return false; + } + catch (SocketException ex) + { + logger.LogException($"Socket exception on binding socket to {localEndPoint}:", ex); + return false; + } + } + + /// <inheritdoc /> + public async Task<bool> TryConnectAsync(IPAddress remoteAddress, int remotePort, TimeSpan timeout) + { + remoteEndPoint = new IPEndPoint(remoteAddress, remotePort); + + try + { + cancellationTokenSource.CancelAfter(timeout); + + return await Task.Run(async () => + { + await socket.ConnectAsync(remoteEndPoint); + + ConnectResponsePacket connectionResponsePacket = + await SendComplexAsync<ConnectPacket, ConnectResponsePacket>(new ConnectPacket(), timeout); + + OnConnected(SocketOptions.RemoteIPEndPoint); + + return true; + }, cancellationTokenSource.Token); + } + catch (TaskCanceledException) + { + return false; + } + catch (SocketException ex) + { + logger.LogException($"Socket exception on connection to {remoteEndPoint}:", ex); + return false; + } + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Clients/TcpClient.cs b/NetSharp/NetSharp/Clients/TcpClient.cs @@ -0,0 +1,66 @@ +using System; +using System.Net.Sockets; +using System.Threading.Tasks; +using NetSharp.Packets; +using NetSharp.Packets.Builtin; +using NetSharp.Servers; +using NetSharp.Utils.Socket_Options; + +namespace NetSharp.Clients +{ + /// <summary> + /// Provides methods for TCP communication with a connected <see cref="TcpServer"/> instance. + /// </summary> + public sealed class TcpClient : Client + { + /// <inheritdoc /> + public TcpClient() : base(SocketType.Stream, ProtocolType.Tcp, SocketOptionManager.Tcp) + { + } + + /// <inheritdoc /> + public override async Task SendBytesAsync(byte[] buffer, TimeSpan timeout) + { + SimpleDataPacket packet = new SimpleDataPacket(buffer); + await SendSimpleAsync(packet, timeout); + } + + /// <inheritdoc /> + public override async Task<byte[]> SendBytesWithResponseAsync(byte[] buffer, TimeSpan timeout) + { + DataPacket packet = new DataPacket(buffer); + DataResponsePacket response = await SendComplexAsync<DataPacket, DataResponsePacket>(packet, timeout); + + return response.ResponseBuffer.ToArray(); + } + + /// <inheritdoc /> + public override async Task<Rep> SendComplexAsync<Req, Rep>(Req request, TimeSpan timeout) + { + uint packetTypeId = PacketRegistry.GetPacketId<Req>(); + + request.BeforeSerialisation(); + ReadOnlyMemory<byte> serialisedRequest = request.Serialise(); + Packet rawRequest = new Packet(serialisedRequest, packetTypeId, NetworkErrorCode.Ok); + await DoSendPacketAsync(socket, rawRequest, SocketFlags.None, timeout); + + Packet rawResponsePacket = await DoReceivePacketAsync(socket, SocketFlags.None, timeout); + Rep responsePacket = new Rep(); + responsePacket.Deserialise(rawResponsePacket.Buffer); + responsePacket.AfterDeserialisation(); + + return responsePacket; + } + + /// <inheritdoc /> + public override async Task SendSimpleAsync<Req>(Req request, TimeSpan timeout) + { + uint packetTypeId = PacketRegistry.GetPacketId<Req>(); + + request.BeforeSerialisation(); + ReadOnlyMemory<byte> serialisedRequest = request.Serialise(); + Packet rawRequest = new Packet(serialisedRequest, packetTypeId, NetworkErrorCode.Ok); + await DoSendPacketAsync(socket, rawRequest, SocketFlags.None, timeout); + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Clients/UdpClient.cs b/NetSharp/NetSharp/Clients/UdpClient.cs @@ -0,0 +1,70 @@ +using System; +using System.Net.Sockets; +using System.Threading.Tasks; +using NetSharp.Packets; +using NetSharp.Packets.Builtin; +using NetSharp.Servers; +using NetSharp.Utils; +using NetSharp.Utils.Socket_Options; + +namespace NetSharp.Clients +{ + /// <summary> + /// Provides methods for UDP communication with a connected <see cref="UdpServer"/> instance. + /// </summary> + public sealed class UdpClient : Client + { + /// <inheritdoc /> + public UdpClient() : base(SocketType.Dgram, ProtocolType.Udp, SocketOptionManager.Udp) + { + } + + /// <inheritdoc /> + public override async Task SendBytesAsync(byte[] buffer, TimeSpan timeout) + { + SimpleDataPacket packet = new SimpleDataPacket(buffer); + await SendSimpleAsync(packet, timeout); + } + + /// <inheritdoc /> + public override async Task<byte[]> SendBytesWithResponseAsync(byte[] buffer, TimeSpan timeout) + { + DataPacket packet = new DataPacket(buffer); + DataResponsePacket response = await SendComplexAsync<DataPacket, DataResponsePacket>(packet, timeout); + + return response.ResponseBuffer.ToArray(); + } + + /// <inheritdoc /> + public override async Task<Rep> SendComplexAsync<Req, Rep>(Req request, TimeSpan timeout) + { + uint packetTypeId = PacketRegistry.GetPacketId<Req>(); + + request.BeforeSerialisation(); + ReadOnlyMemory<byte> serialisedRequest = request.Serialise(); + Packet rawRequest = new Packet(serialisedRequest, packetTypeId, NetworkErrorCode.Ok); + await DoSendPacketToAsync(socket, remoteEndPoint, rawRequest, SocketFlags.None, timeout); + + (Packet rawResponsePacket, TransmissionResult packetResult) = + await DoReceivePacketFromAsync(socket, remoteEndPoint, SocketFlags.None, timeout); + remoteEndPoint = packetResult.RemoteEndPoint; + + Rep responsePacket = new Rep(); + responsePacket.Deserialise(rawResponsePacket.Buffer); + responsePacket.AfterDeserialisation(); + + return responsePacket; + } + + /// <inheritdoc /> + public override async Task SendSimpleAsync<Req>(Req request, TimeSpan timeout) + { + uint packetTypeId = PacketRegistry.GetPacketId<Req>(); + + request.BeforeSerialisation(); + ReadOnlyMemory<byte> serialisedRequest = request.Serialise(); + Packet rawRequest = new Packet(serialisedRequest, packetTypeId, NetworkErrorCode.Ok); + await DoSendPacketToAsync(socket, remoteEndPoint, rawRequest, SocketFlags.None, timeout); + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Connection.cs b/NetSharp/NetSharp/Connection.cs @@ -0,0 +1,302 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using NetSharp.Logging; +using NetSharp.Packets; +using NetSharp.Utils; + +namespace NetSharp +{ + /// <summary> + /// Base class for connections, holding methods shared between the <see cref="Client"/> and <see cref="Server"/> classes. + /// </summary> + public abstract class Connection : IDisposable + { + /// <summary> + /// Cancellation token source for the <see cref="DoReceivePacketAsync"/> method. + /// </summary> + private readonly CancellationTokenSource readBytesCancellationTokenSource; + + /// <summary> + /// Cancellation token source for the <see cref="DoSendPacketAsync"/> method. + /// </summary> + private readonly CancellationTokenSource writeBytesCancellationTokenSource; + + /// <summary> + /// Represents a packet that was not received correctly. + /// </summary> + protected static readonly Packet NullPacket = new Packet(new byte[0], 0, NetworkErrorCode.Error); + + /// <summary> + /// Represents a transmission result of an incorrect transmission. + /// </summary> + protected static readonly TransmissionResult NullTransmissionResult = + new TransmissionResult(new byte[0], -1, new IPEndPoint(IPAddress.None, IPEndPoint.MinPort)); + + /// <summary> + /// The logger to which the server can log messages. + /// </summary> + protected Logger logger; + + /// <summary> + /// Initialises a new instance of the <see cref="Connection"/> class. + /// </summary> + protected Connection() + { + logger = new Logger(Stream.Null); + + readBytesCancellationTokenSource = new CancellationTokenSource(); + writeBytesCancellationTokenSource = new CancellationTokenSource(); + } + + /// <summary> + /// Signifies that some data has been received from the remote endpoint. + /// </summary> + public event Action<EndPoint, int>? BytesReceived; + + /// <summary> + /// Signifies that some data was sent to the remote endpoint. + /// </summary> + public event Action<EndPoint, int>? BytesSent; + + /// <summary> + /// Disposes of this <see cref="Connection"/> instance. + /// </summary> + /// <param name="disposing">Whether this instance is being disposed.</param> + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + readBytesCancellationTokenSource?.Cancel(); + readBytesCancellationTokenSource?.Dispose(); + + writeBytesCancellationTokenSource?.Cancel(); + writeBytesCancellationTokenSource?.Dispose(); + + logger.Dispose(); + } + } + + /// <summary> + /// Listens for a packet to be received asynchronously within the given timeout, and returns the received packet. + /// </summary> + /// <param name="remoteSocket">The remote socket from which to receive data.</param> + /// <param name="socketFlags">The socket flags associated with the read operation.</param> + /// <param name="timeout"> + /// The timespan within which to wait for a packet, returning a null packet if this limit is exceeded. + /// </param> + /// <returns>The packet that was received. <see cref="NullPacket"/> if not received correctly.</returns> + protected async Task<Packet> DoReceivePacketAsync(Socket remoteSocket, SocketFlags socketFlags, TimeSpan timeout) + { + try + { + readBytesCancellationTokenSource.CancelAfter(timeout); + + Packet request = + await NetworkOperations.ReadPacketAsync(remoteSocket, socketFlags, + readBytesCancellationTokenSource.Token); + + OnBytesReceived(remoteSocket.RemoteEndPoint, request.TotalSize); + + return request; + } + catch (OperationCanceledException ex) + { + logger.LogException( + $"Could not receive a packet from {remoteSocket.RemoteEndPoint} within the given timeout ({timeout}):", + ex); + return NullPacket; + } + catch (SocketException ex) + { + logger.LogException($"Socket exception while reading bytes from {remoteSocket.RemoteEndPoint}:", ex); + return NullPacket; + } + catch (Exception ex) + { + logger.LogException($"Exception while reading bytes from {remoteSocket.RemoteEndPoint}:", ex); + return NullPacket; + } + } + + /// <summary> + /// Listens for a packet to be received asynchronously within the given timeout, and returns the received packet. + /// </summary> + /// <param name="socket">The socket which will receive the packet.</param> + /// <param name="remoteEndPoint">The remote endpoint from which to receive the packet.</param> + /// <param name="socketFlags">The socket flags associated with the read operation.</param> + /// <param name="timeout"> + /// The timespan within which to wait for a packet, returning a null packet if this limit is exceeded. + /// </param> + /// <returns> + /// The packet that was received and the associated transmission result. <see cref="NullPacket"/> if not received correctly. + /// </returns> + protected async Task<(Packet request, TransmissionResult packetResult)> DoReceivePacketFromAsync( + Socket socket, EndPoint remoteEndPoint, SocketFlags socketFlags, TimeSpan timeout) + { + try + { + readBytesCancellationTokenSource.CancelAfter(timeout); + + (Packet request, TransmissionResult packetResult) result = + await NetworkOperations.ReadPacketFromAsync(socket, remoteEndPoint, socketFlags, + readBytesCancellationTokenSource.Token); + + OnBytesReceived(remoteEndPoint, result.request.TotalSize); + + return result; + } + catch (OperationCanceledException ex) + { + logger.LogException( + $"Could not receive a packet from {remoteEndPoint} within the given timeout ({timeout}):", ex); + return (NullPacket, NullTransmissionResult); + } + catch (SocketException ex) + { + logger.LogException($"Socket exception while reading bytes from {remoteEndPoint}:", ex); + return (NullPacket, NullTransmissionResult); + } + catch (Exception ex) + { + logger.LogException($"Exception while reading bytes from {remoteEndPoint}:", ex); + return (NullPacket, NullTransmissionResult); + } + } + + /// <summary> + /// Sends the given packet asynchronously within the given timeout. + /// </summary> + /// <param name="remoteSocket">The remote socket to which to send the packet.</param> + /// <param name="packet">The packet to send.</param> + /// <param name="socketFlags">The socket flags associated with the write operation.</param> + /// <param name="timeout"> + /// The timespan within which to send the packet, returning <c>false</c> if this limit is exceeded. + /// </param> + /// <returns>Whether the packet was successfully sent.</returns> + protected async Task<bool> DoSendPacketAsync(Socket remoteSocket, Packet packet, SocketFlags socketFlags, TimeSpan timeout) + { + try + { + writeBytesCancellationTokenSource.CancelAfter(timeout); + + await NetworkOperations.WritePacketAsync(remoteSocket, packet, socketFlags, + writeBytesCancellationTokenSource.Token); + + OnBytesSent(remoteSocket.RemoteEndPoint, packet.TotalSize); + + return true; + } + catch (OperationCanceledException ex) + { + logger.LogException( + $"Could not send the packet to {remoteSocket.RemoteEndPoint} within the given timeout ({timeout}):", + ex); + return false; + } + catch (SocketException ex) + { + logger.LogException($"Socket exception while sending bytes to {remoteSocket.RemoteEndPoint}:", ex); + return false; + } + catch (Exception ex) + { + logger.LogException($"Exception while sending bytes to {remoteSocket.RemoteEndPoint}:", ex); + return false; + } + } + + /// <summary> + /// Sends the given packet asynchronously within the given timeout. + /// </summary> + /// <param name="socket">The socket which should send the packet.</param> + /// <param name="remoteEndPoint">The remote endpoint to which to send the packet.</param> + /// <param name="packet">The packet to send.</param> + /// <param name="socketFlags">The socket flags associated with the write operation.</param> + /// <param name="timeout"> + /// The timespan within which to send the packet, returning <c>false</c> if this limit is exceeded. + /// </param> + /// <returns>Whether the packet was successfully sent.</returns> + protected async Task<bool> DoSendPacketToAsync(Socket socket, EndPoint remoteEndPoint, Packet packet, + SocketFlags socketFlags, TimeSpan timeout) + { + try + { + writeBytesCancellationTokenSource.CancelAfter(timeout); + + if (packet.TotalSize > Constants.UdpMaxBufferSize) + { + throw new Exception($"The given UDP packet exceeded the maximum allowed size of {Constants.UdpMaxBufferSize}, " + + "and could not be sent. Consider splitting up the packet into multiple, smaller packets " + + "that are less likely to get lost due to fragmentation, or using TCP instead."); + } + + await NetworkOperations.WritePacketToAsync(socket, remoteEndPoint, packet, socketFlags, + writeBytesCancellationTokenSource.Token); + + OnBytesSent(remoteEndPoint, packet.TotalSize); + + return true; + } + catch (OperationCanceledException ex) + { + logger.LogException( + $"Could not send the packet to {remoteEndPoint} within the given timeout ({timeout}):", ex); + return false; + } + catch (SocketException ex) + { + logger.LogException($"Socket exception while sending bytes to {remoteEndPoint}:", ex); + return false; + } + catch (Exception ex) + { + logger.LogException($"Exception while sending bytes to {remoteEndPoint}:", ex); + return false; + } + } + + /// <summary> + /// Invokes the <see cref="BytesReceived"/> event. + /// </summary> + /// <param name="remoteEndPoint">The remote endpoint from which the bytes were received.</param> + /// <param name="bytesReceived">The number of bytes that were received from the remote endpoint.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void OnBytesReceived(EndPoint remoteEndPoint, int bytesReceived) => + BytesReceived?.Invoke(remoteEndPoint, bytesReceived); + + /// <summary> + /// Invokes the <see cref="BytesSent"/> event. + /// </summary> + /// <param name="remoteEndPoint">The remote endpoint to which the bytes were sent.</param> + /// <param name="bytesSent">The number of bytes that were sent to the remote endpoint.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void OnBytesSent(EndPoint remoteEndPoint, int bytesSent) => + BytesSent?.Invoke(remoteEndPoint, bytesSent); + + /// <summary> + /// Makes the client log to the given stream. + /// </summary> + /// <param name="loggingStream">The stream that new messages should be logged to.</param> + /// <param name="minimumMessageSeverityLevel"> + /// The minimum severity level that new messages must have to be logged to the stream. + /// </param> + public void ChangeLoggingStream(Stream loggingStream, LogLevel minimumMessageSeverityLevel = LogLevel.Info) + { + logger = new Logger(loggingStream); + + logger.SetMinimumLogSeverity(minimumMessageSeverityLevel); + } + + /// <inheritdoc /> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Extensions/ClientExtensions.cs b/NetSharp/NetSharp/Extensions/ClientExtensions.cs @@ -0,0 +1,209 @@ +using System; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using NetSharp.Interfaces; + +namespace NetSharp.Extensions +{ + /// <summary> + /// Provides additional methods and functionality to the <see cref="Client"/> class. + /// </summary> + public static class ClientExtensions + { + /// <summary> + /// Sends the given byte buffer to the connected remote endpoint. Blocks until the bytes are all sent, and does + /// not timeout. + /// </summary> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="buffer">The bytes that should be sent to the connected remote endpoint.</param> + public static void SendBytes(this Client instance, byte[] buffer) => + instance.SendBytesAsync(buffer, Timeout.InfiniteTimeSpan).Wait(); + + /// <summary> + /// Sends the given byte buffer to the connected remote endpoint. Blocks until the bytes are all sent, whilst + /// observing a timeout of the given length. + /// </summary> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="buffer">The bytes that should be sent to the connected remote endpoint.</param> + /// <param name="timeout">The timeout after which to cancel the transmission attempt.</param> + public static void SendBytes(this Client instance, byte[] buffer, TimeSpan timeout) => + instance.SendBytesAsync(buffer, timeout).Wait(); + + /// <summary> + /// Sends the given byte buffer to the connected remote endpoint asynchronously. Does not block, and does not + /// timeout. + /// </summary> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="buffer">The bytes that should be sent to the connected remote endpoint.</param> + public static async Task SendBytesAsync(this Client instance, byte[] buffer) => + await instance.SendBytesAsync(buffer, Timeout.InfiniteTimeSpan); + + /// <summary> + /// Sends the given byte buffer to the connected remote endpoint and waits for the response. Blocks until the + /// bytes are all sent and the response has been received, and does not timeout. + /// </summary> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="buffer">The bytes that should be sent to the connected remote endpoint.</param> + /// <returns>The byte buffer that was received as a response.</returns> + public static byte[] SendBytesWithResponse(this Client instance, byte[] buffer) => + instance.SendBytesWithResponseAsync(buffer, Timeout.InfiniteTimeSpan).Result; + + /// <summary> + /// Sends the given byte buffer to the connected remote endpoint and waits for the response. Blocks until the + /// bytes are all sent and the response has been received, whilst observing a timeout of the given length. + /// </summary> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="buffer">The bytes that should be sent to the connected remote endpoint.</param> + /// <param name="timeout">The timeout after which to cancel the transmission attempt.</param> + /// <returns>The byte buffer that was received as a response.</returns> + public static byte[] SendBytesWithResponse(this Client instance, byte[] buffer, TimeSpan timeout) => + instance.SendBytesWithResponseAsync(buffer, timeout).Result; + + /// <summary> + /// Sends the given byte buffer to the connected remote endpoint and waits for the response asynchronously. + /// Does not block, and does not timeout. + /// </summary> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="buffer">The bytes that should be sent to the connected remote endpoint.</param> + /// <returns>The byte buffer received as a response to the sent buffer.</returns> + public static async Task<byte[]> SendBytesWithResponseAsync(this Client instance, byte[] buffer) => + await instance.SendBytesWithResponseAsync(buffer, Timeout.InfiniteTimeSpan); + + /// <summary> + /// Sends the given request and listens for a response of the given type. Blocks until the response is received. + /// Does not timeout. + /// </summary> + /// <typeparam name="Req">The type of request packet to send.</typeparam> + /// <typeparam name="Rep">The type of response packet to receive.</typeparam> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="request">The request packet to send.</param> + /// <returns>The received instance.</returns> + public static Rep SendComplex<Req, Rep>(this Client instance, Req request) + where Req : IRequestPacket, new() where Rep : IResponsePacket<Req>, new() => + instance.SendComplexAsync<Req, Rep>(request, Timeout.InfiniteTimeSpan).Result; + + /// <summary> + /// Sends the given request and listens for a response of the given type. Blocks until the response is received. + /// Cancels the operation if the given timeout is exceeded. + /// </summary> + /// <typeparam name="Req">The type of request packet to send.</typeparam> + /// <typeparam name="Rep">The type of response packet to receive.</typeparam> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="request">The request packet to send.</param> + /// <param name="timeout">The timeout for which to wait for the operation to complete.</param> + /// <returns>The received instance.</returns> + public static Rep SendComplex<Req, Rep>(this Client instance, Req request, TimeSpan timeout) + where Req : IRequestPacket, new() where Rep : IResponsePacket<Req>, new() => + instance.SendComplexAsync<Req, Rep>(request, timeout).Result; + + /// <summary> + /// Sends the given request and listens for a response of the given type asynchronously. Does not block. Does not timeout. + /// </summary> + /// <typeparam name="Req">The type of request packet to send.</typeparam> + /// <typeparam name="Rep">The type of response packet to receive.</typeparam> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="request">The request packet to send.</param> + /// <returns>The received instance.</returns> + public static async Task<Rep> SendComplexAsync<Req, Rep>(this Client instance, Req request) + where Req : IRequestPacket, new() where Rep : IResponsePacket<Req>, new() => + await instance.SendComplexAsync<Req, Rep>(request, Timeout.InfiniteTimeSpan); + + /// <summary> + /// Sends the given request without listening for a response, blocking until it is sent. Does not timeout. + /// </summary> + /// <typeparam name="Req">The type of request packet to send.</typeparam> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="request">The request packet to send.</param> + public static void SendSimple<Req>(this Client instance, Req request) where Req : IRequestPacket, new() => + instance.SendSimpleAsync(request, Timeout.InfiniteTimeSpan); + + /// <summary> + /// Sends the given request without listening for a response, blocking until it is sent. + /// Cancels the operation if the given timeout is exceeded. + /// </summary> + /// <typeparam name="Req">The type of request packet to send.</typeparam> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="request">The request packet to send.</param> + /// <param name="timeout">The timeout for which to wait for the operation to complete.</param> + public static void SendSimple<Req>(this Client instance, Req request, TimeSpan timeout) where Req : IRequestPacket, new() => + instance.SendSimpleAsync(request, timeout); + + /// <summary> + /// Sends the given request asynchronously without listening for a response, not blocking until it is sent. + /// Does not timeout. + /// </summary> + /// <typeparam name="Req">The type of request packet to send.</typeparam> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="request">The request packet to send.</param> + public static async Task SendSimpleAsync<Req>(this Client instance, Req request) where Req : IRequestPacket, new() => + await instance.SendSimpleAsync(request, Timeout.InfiniteTimeSpan); + + /// <summary> + /// Attempts to synchronously bind the underlying socket to the given local address and port. Blocks. Does not timeout. + /// </summary> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="localAddress">The local IP address to bind to. Null if any IP address will suffice.</param> + /// <param name="localPort">The local port to bind to. Null if any port will suffice.</param> + /// <returns>Whether the binding was successful or not.</returns> + public static bool TryBind(this Client instance, IPAddress? localAddress, int? localPort) + => instance.TryBindAsync(localAddress, localPort, Timeout.InfiniteTimeSpan).Result; + + /// <summary> + /// Attempts to synchronously bind the underlying socket to the given local address and port. Blocks. + /// If the timeout is exceeded the binding attempt is aborted and the method returns false. + /// </summary> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="localAddress">The local IP address to bind to. Null if any IP address will suffice.</param> + /// <param name="localPort">The local port to bind to. Null if any port will suffice.</param> + /// <param name="timeout">The timeout within which to attempt the binding.</param> + /// <returns>Whether the binding was successful or not.</returns> + public static bool TryBind(this Client instance, IPAddress? localAddress, int? localPort, TimeSpan timeout) + => instance.TryBindAsync(localAddress, localPort, timeout).Result; + + /// <summary> + /// Attempts to asynchronously bind the underlying socket to the given local address and port. Does not block. + /// Does not timeout. + /// </summary> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="localAddress">The local IP address to bind to. Null if any IP address will suffice.</param> + /// <param name="localPort">The local port to bind to. Null if any port will suffice.</param> + /// <returns>Whether the binding was successful or not.</returns> + public static async Task<bool> TryBindAsync(this Client instance, IPAddress? localAddress, int? localPort) + => await instance.TryBindAsync(localAddress, localPort, Timeout.InfiniteTimeSpan); + + /// <summary> + /// Attempts to connect to the remote <see cref="Server"/> at the given <see cref="IPAddress"/> and over the + /// given port. Does not timeout. + /// </summary> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="remoteAddress">The remote IP address to connect to.</param> + /// <param name="remotePort">The remote port to connect over.</param> + /// <returns>Whether the connection was successful or not.</returns> + public static bool TryConnect(this Client instance, IPAddress remoteAddress, int remotePort) => + instance.TryConnectAsync(remoteAddress, remotePort).Wait(Timeout.InfiniteTimeSpan); + + /// <summary> + /// Attempts to connect to the remote <see cref="Server"/> at the given <see cref="IPAddress"/> and over the + /// given port. If the timeout is exceeded the connection attempt is aborted and the method returns false. + /// </summary> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="remoteAddress">The remote IP address to connect to.</param> + /// <param name="remotePort">The remote port to connect over.</param> + /// <param name="timeout">The timeout within which to attempt the connection.</param> + /// <returns>Whether the connection was successful or not.</returns> + public static bool TryConnect(this Client instance, IPAddress remoteAddress, int remotePort, TimeSpan timeout) => + instance.TryConnectAsync(remoteAddress, remotePort).Wait(timeout); + + /// <summary> + /// Attempts to connect asynchronously to the remote <see cref="Server"/> at the given <see cref="IPAddress"/> + /// and over the given port. Does not timeout. + /// </summary> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="remoteAddress">The remote IP address to connect to.</param> + /// <param name="remotePort">The remote port to connect over.</param> + /// <returns>Whether the connection was successful or not.</returns> + public static async Task<bool> TryConnectAsync(this Client instance, IPAddress remoteAddress, int remotePort) => + await instance.TryConnectAsync(remoteAddress, remotePort, Timeout.InfiniteTimeSpan); + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Extensions/ServerExtensions.cs b/NetSharp/NetSharp/Extensions/ServerExtensions.cs @@ -0,0 +1,47 @@ +using System.Net; +using System.Threading.Tasks; +using NetSharp.Utils; + +namespace NetSharp.Extensions +{ + /// <summary> + /// Provides additional methods and functionality to the <see cref="Server"/> class. + /// </summary> + public static class ServerExtensions + { + /// <summary> + /// Starts the server synchronously and starts accepting client connections. Blocks. + /// </summary> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="localAddress">The local IP address to bind to.</param> + /// <param name="localPort">The local port to bind to.</param> + public static void Run(this Server instance, IPAddress localAddress, int localPort) => + instance.RunAsync(localAddress, localPort).Wait(); + + /// <summary> + /// Starts the server synchronously and starts accepting client connections. Blocks. Uses the default connection port. + /// </summary> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="localAddress">The local IP address to bind to.</param> + public static void Run(this Server instance, IPAddress localAddress) => + instance.RunAsync(localAddress, Constants.DefaultPort).Wait(); + + /// <summary> + /// Starts the server asynchronously and starts accepting client connections. Does not block. Uses the default + /// connection port. + /// </summary> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="localAddress">The local IP address to bind to.</param> + public static async Task RunAsync(this Server instance, IPAddress localAddress) => + await instance.RunAsync(localAddress, Constants.DefaultPort); + + /// <summary> + /// Starts the server asynchronously and starts accepting client connections. Does not block. + /// </summary> + /// <param name="instance">The instance on which this extension method should be called.</param> + /// <param name="localAddress">The local IP address to bind to.</param> + /// <param name="localPort">The local port to bind to.</param> + public static async Task RunAsync(this Server instance, IPAddress localAddress, int localPort) => + await instance.RunAsync(new IPEndPoint(localAddress, localPort)); + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Interfaces/IClient.cs b/NetSharp/NetSharp/Interfaces/IClient.cs @@ -0,0 +1,86 @@ +using System; +using System.Net; +using System.Threading.Tasks; + +namespace NetSharp.Interfaces +{ + /// <summary> + /// Describes a client capable of asynchronous communication with an <see cref="IServer"/> connection. + /// </summary> + public interface IClient + { + /// <summary> + /// Signifies that a connection with the remote endpoint has been made. + /// </summary> + public event Action<EndPoint>? Connected; + + /// <summary> + /// Signifies that the connection with the remote endpoint was severed. + /// </summary> + public event Action<EndPoint>? Disconnected; + + /// <summary> + /// Sends the given byte buffer to the connected remote endpoint asynchronously. Does not block, and observes + /// a timeout of the given length. + /// timeout. + /// </summary> + /// <param name="buffer">The bytes that should be sent to the connected remote endpoint.</param> + /// <param name="timeout">The timeout after which to cancel the transmission attempt.</param> + public Task SendBytesAsync(byte[] buffer, TimeSpan timeout); + + /// <summary> + /// Sends the given byte buffer to the connected remote endpoint and waits for the response asynchronously. + /// Does not block, and observes a timeout of the given length. + /// timeout. + /// </summary> + /// <param name="buffer">The bytes that should be sent to the connected remote endpoint.</param> + /// <param name="timeout"> + /// The timeout after which to cancel the transmission attempt. This timeout is reused by both the 'send' and + /// 'receive' parts of the transmission attempt, such that the maximum timeout is equal to 2 times the given + /// value. + /// </param> + /// <returns>The byte buffer received as a response to the sent buffer.</returns> + public Task<byte[]> SendBytesWithResponseAsync(byte[] buffer, TimeSpan timeout); + + /// <summary> + /// Sends the given request and listens for a response of the given type asynchronously. Does not block. + /// Cancels the operation if the given timeout is exceeded + /// </summary> + /// <typeparam name="Req">The type of request packet to send.</typeparam> + /// <typeparam name="Rep">The type of response packet to receive.</typeparam> + /// <param name="request">The request packet to send.</param> + /// <param name="timeout">The timeout for which to wait for the operation to complete.</param> + /// <returns>The received instance.</returns> + public Task<Rep> SendComplexAsync<Req, Rep>(Req request, TimeSpan timeout) + where Req : IRequestPacket, new() where Rep : IResponsePacket<Req>, new(); + + /// <summary> + /// Sends the given request asynchronously without listening for a response, not blocking until it is sent. + /// Cancels the operation if the given timeout is exceeded. + /// </summary> + /// <typeparam name="Req">The type of request packet to send.</typeparam> + /// <param name="request">The request packet to send.</param> + /// <param name="timeout">The timeout for which to wait for the operation to complete.</param> + public Task SendSimpleAsync<Req>(Req request, TimeSpan timeout) where Req : IRequestPacket, new(); + + /// <summary> + /// Attempts to asynchronously bind the underlying socket to the given local address and port. Does not block. + /// If the timeout is exceeded the binding attempt is aborted and the method returns false. + /// </summary> + /// <param name="localAddress">The local IP address to bind to. Null if any IP address will suffice.</param> + /// <param name="localPort">The local port to bind to. Null if any port will suffice.</param> + /// <param name="timeout">The timeout within which to attempt the binding.</param> + /// <returns>Whether the binding was successful or not.</returns> + public Task<bool> TryBindAsync(IPAddress? localAddress, int? localPort, TimeSpan timeout); + + /// <summary> + /// Attempts to connect asynchronously to the remote <see cref="Server"/> at the given <see cref="IPAddress"/> + /// and over the given port. If the timeout is exceeded the connection attempt is aborted and the method returns false. + /// </summary> + /// <param name="remoteAddress">The remote IP address to connect to.</param> + /// <param name="remotePort">The remote port to connect over.</param> + /// <param name="timeout">The timeout within which to attempt the connection.</param> + /// <returns>Whether the connection was successful or not.</returns> + public Task<bool> TryConnectAsync(IPAddress remoteAddress, int remotePort, TimeSpan timeout); + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Interfaces/INetworkSerialisable.cs b/NetSharp/NetSharp/Interfaces/INetworkSerialisable.cs @@ -0,0 +1,22 @@ +using System; + +namespace NetSharp.Interfaces +{ + /// <summary> + /// Describes an object that can be serialised to be sent across the network. + /// </summary> + public interface INetworkSerialisable + { + /// <summary> + /// Deserialises the object instance from a byte array. + /// </summary> + /// <param name="serialisedObject">The memory containing the serialised object instance.</param> + void Deserialise(ReadOnlyMemory<byte> serialisedObject); + + /// <summary> + /// Serialises the object instance into a byte array. + /// </summary> + /// <returns>The memory containing the serialised object instance.</returns> + ReadOnlyMemory<byte> Serialise(); + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Interfaces/IPacket.cs b/NetSharp/NetSharp/Interfaces/IPacket.cs @@ -0,0 +1,18 @@ +namespace NetSharp.Interfaces +{ + /// <summary> + /// Describes the methods and properties that every packet + /// </summary> + public interface IPacket + { + /// <summary> + /// Allows for custom fields to be converted from their serialised format, after being received from the network. + /// </summary> + void AfterDeserialisation(); + + /// <summary> + /// Allows for custom fields to be converted into another format prior to being sent via the network. + /// </summary> + void BeforeSerialisation(); + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Interfaces/IRequestPacket.cs b/NetSharp/NetSharp/Interfaces/IRequestPacket.cs @@ -0,0 +1,9 @@ +namespace NetSharp.Interfaces +{ + /// <summary> + /// Describes a request packet. + /// </summary> + public interface IRequestPacket : IPacket, INetworkSerialisable + { + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Interfaces/IResponsePacket.cs b/NetSharp/NetSharp/Interfaces/IResponsePacket.cs @@ -0,0 +1,14 @@ +namespace NetSharp.Interfaces +{ + /// <summary> + /// Describes a response packet to a request packet. + /// </summary> + /// <typeparam name="TReq">The request packet that this type is a response to.</typeparam> + public interface IResponsePacket<out TReq> : IPacket, INetworkSerialisable where TReq : IRequestPacket + { + /// <summary> + /// The request packet that was handled with this response packet. + /// </summary> + TReq RequestPacket { get; } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Interfaces/IServer.cs b/NetSharp/NetSharp/Interfaces/IServer.cs @@ -0,0 +1,86 @@ +using System; +using System.Net; +using System.Threading.Tasks; + +namespace NetSharp.Interfaces +{ + /// <summary> + /// Describes a server capable of asynchronously handling multiple <see cref="IClient"/> connections at once. + /// </summary> + public interface IServer + { + /// <summary> + /// Signifies that a connection with a remote endpoint has been made. + /// </summary> + public event Action<EndPoint>? ClientConnected; + + //protected IResponsePacket<IRequestPacket> DeserialiseResponsePacket(in Packet) + /// <summary> + /// Signifies that a connection with a remote endpoint has been lost. + /// </summary> + public event Action<EndPoint>? ClientDisconnected; + + /// <summary> + /// Signifies that the server was started and clients will start being accepted. + /// </summary> + public event Action? ServerStarted; + + /// <summary> + /// Signifies that the server was stopped and clients will stop being accepted. + /// </summary> + public event Action? ServerStopped; + + /// <summary> + /// Starts the server asynchronously and starts accepting client connections. Does not block. + /// </summary> + /// <param name="localEndPoint">The local endpoint to bind to.</param> + public Task RunAsync(EndPoint localEndPoint); + + /// <summary> + /// Shuts down the server. + /// </summary> + public void Shutdown(); + + /// <summary> + /// Attempts to deregister the complex packet handler delegate for all packets of the given type. If a handler + /// method doesn't exist for the given packet type, <paramref name="oldHandlerDelegate"/> will be <c>default</c>. + /// </summary> + /// <typeparam name="Req">The type of request packet for which to deregister the handler delegate.</typeparam> + /// <typeparam name="Rep">The type of response packet that is generated by the delegate method.</typeparam> + /// <param name="oldHandlerDelegate">The old handler delegate method that was previously registered.</param> + /// <returns>Whether the packet handler delegate was successfully deregistered.</returns> + public bool TryDeregisterComplexPacketHandler<Req, Rep>(out ComplexPacketHandler<Req, Rep>? oldHandlerDelegate) + where Req : class, IRequestPacket, new() where Rep : class, IResponsePacket<Req>, new(); + + /// <summary> + /// Attempts to deregister the simple packet handler delegate for all packets of the given type. If a handler + /// method doesn't exist for the given packet type, <paramref name="oldHandlerDelegate"/> will be <c>default</c>. + /// </summary> + /// <typeparam name="Req">The type of request packet for which to deregister the handler delegate.</typeparam> + /// <param name="oldHandlerDelegate">The old handler delegate method that was previously registered.</param> + /// <returns>Whether the packet handler delegate was successfully deregistered.</returns> + public bool TryDeregisterSimplePacketHandler<Req>(out SimplePacketHandler<Req>? oldHandlerDelegate) + where Req : class, IRequestPacket, new(); + + /// <summary> + /// Attempts to register a complex packet handler delegate for all packets of the given type. If a handler + /// method already exists for the given packet type, it will be updated and replaced with the given one. + /// </summary> + /// <typeparam name="Req">The type of request packet for which to register the handler delegate.</typeparam> + /// <typeparam name="Rep">The type of response packet that is generated by the delegate method.</typeparam> + /// <param name="handlerDelegate">The delegate method to register as the complex packet handler.</param> + /// <returns>Whether the packet handler delegate was successfully registered.</returns> + public bool TryRegisterComplexPacketHandler<Req, Rep>(ComplexPacketHandler<Req, Rep> handlerDelegate) + where Req : class, IRequestPacket, new() where Rep : class, IResponsePacket<Req>, new(); + + /// <summary> + /// Attempts to register a simple packet handler delegate for all packets of the given type. If a handler + /// method already exists for the given packet type, it will be updated and replaced with the given one. + /// </summary> + /// <typeparam name="Req">The type of request packet for which to register the handler delegate.</typeparam> + /// <param name="handlerDelegate">The delegate method to register as the simple packet handler.</param> + /// <returns>Whether the packet handler delegate was successfully registered.</returns> + public bool TryRegisterSimplePacketHandler<Req>(SimplePacketHandler<Req> handlerDelegate) + where Req : class, IRequestPacket, new(); + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Logging/Logger.cs b/NetSharp/NetSharp/Logging/Logger.cs @@ -0,0 +1,199 @@ +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +namespace NetSharp.Logging +{ + /// <summary> + /// Specifies the severity level of a log message. + /// </summary> + public enum LogLevel + { + /// <summary> + /// The logged message contains some information. Lowest severity. + /// </summary> + Info, + + /// <summary> + /// The logged message contains a warning. Higher severity. + /// </summary> + Warn, + + /// <summary> + /// The logged message contains details about an error. Higher severity. + /// </summary> + Error, + + /// <summary> + /// The logged message contains details about an exception. Highest severity. + /// </summary> + Exception + } + + /// <summary> + /// A simple logger capable of writing text to a stream. + /// </summary> + public struct Logger : IDisposable + { + /// <summary> + /// The stream to which messages will be logged. + /// </summary> + private readonly Stream loggingStream; + + /// <summary> + /// The text writer we will use to log messages to the underlying stream. + /// </summary> + private readonly StreamWriter writer; + + /// <summary> + /// The minimum severity that log messages need to be logged to the underlying stream. + /// </summary> + private LogLevel minimumSeverity; + + /// <summary> + /// Initialises a new instance of the <see cref="Logger"/> struct. + /// </summary> + /// <param name="streamToLogTo">The stream that the logger instance should log messages to.</param> + public Logger(Stream streamToLogTo) + { + loggingStream = streamToLogTo; + writer = new StreamWriter(loggingStream, Encoding.Default) { AutoFlush = true }; + + minimumSeverity = LogLevel.Info; + } + + /// <inheritdoc /> + public void Dispose() + { + loggingStream.Dispose(); + writer.Dispose(); + } + + /// <summary> + /// Logs a message to the underlying stream, along with the given exception and at the given severity. + /// </summary> + /// <param name="message">The message that should be logged.</param> + /// <param name="exception">The exception that occurred (if any).</param> + /// <param name="severity">The severity of the message that is being logged.</param> + public void Log(string message, Exception? exception, LogLevel severity) + { + if (severity < minimumSeverity) return; + + string severityTag = severity switch + { + LogLevel.Info => "Info ", + LogLevel.Warn => "Warn ", + LogLevel.Error => "Error", + LogLevel.Exception => "Excep", + _ => "Info " + }; + + writer.WriteLine($"[{severityTag}] {message} {exception}"); + } + + /// <summary> + /// Logs a message asynchronously to the underlying stream, along with the given exception and at the given severity. + /// </summary> + /// <param name="message">The message that should be logged.</param> + /// <param name="exception">The exception that occurred (if any).</param> + /// <param name="severity">The severity of the message that is being logged.</param> + public async Task LogAsync(string message, Exception? exception, LogLevel severity) + { + if (loggingStream.Equals(Stream.Null)) + { + // ignore log request if the underlying stream is null + return; + } + + if (!exception?.Equals(default) ?? false) + { + severity = LogLevel.Exception; + } + + if (severity >= minimumSeverity) + { + string severityTag = severity switch + { + LogLevel.Info => "Info ", + LogLevel.Warn => "Warn ", + LogLevel.Error => "Error", + LogLevel.Exception => "Excep", + _ => "Info " + }; + + await writer.WriteLineAsync($"[{severityTag}] {message} {exception}"); + } + } + + /// <summary> + /// Logs an error to the underlying stream, with severity <see cref="LogLevel.Info"/>. + /// </summary> + /// <param name="message">The error that should be logged.</param> + public void LogError(string message) => Log(message, null, LogLevel.Error); + + /// <summary> + /// Logs an error to the underlying stream asynchronously, with severity <see cref="LogLevel.Error"/>. + /// </summary> + /// <param name="message">The error that should be logged.</param> + public async Task LogErrorAsync(string message) => await LogAsync(message, null, LogLevel.Error); + + /// <summary> + /// Logs an exception to the underlying stream, with severity <see cref="LogLevel.Exception"/>. + /// </summary> + /// <param name="exception">The exception that should be logged.</param> + public void LogException(Exception exception) => Log("", exception, LogLevel.Exception); + + /// <summary> + /// Logs an exception to the underlying stream, along with a short debug message, with severity + /// <see cref="LogLevel.Exception"/>. + /// </summary> + /// <param name="message">The debug message that should be logged with the exception.</param> + /// <param name="exception">The exception that should be logged.</param> + public void LogException(string message, Exception exception) => Log(message, exception, LogLevel.Exception); + + /// <summary> + /// Logs an exception to the underlying stream asynchronously, with severity <see cref="LogLevel.Exception"/>. + /// </summary> + /// <param name="exception">The exception that should be logged.</param> + public async Task LogExceptionAsync(Exception exception) => await LogAsync("", exception, LogLevel.Exception); + + /// <summary> + /// Logs an exception to the underlying stream asynchronously, along with a short debug message, with severity + /// <see cref="LogLevel.Exception"/>. + /// </summary> + /// <param name="message">The debug message that should be logged with the exception.</param> + /// <param name="exception">The exception that should be logged.</param> + public async Task LogExceptionAsync(string message, Exception exception) => await LogAsync(message, exception, LogLevel.Exception); + + /// <summary> + /// Logs a message to the underlying stream, with severity <see cref="LogLevel.Info"/>. + /// </summary> + /// <param name="message">The message that should be logged.</param> + public void LogMessage(string message) => Log(message, null, LogLevel.Info); + + /// <summary> + /// Logs a message to the underlying stream asynchronously, with severity <see cref="LogLevel.Info"/>. + /// </summary> + /// <param name="message">The message that should be logged.</param> + public async Task LogMessageAsync(string message) => await LogAsync(message, null, LogLevel.Info); + + /// <summary> + /// Logs a warning to the underlying stream, with severity <see cref="LogLevel.Info"/>. + /// </summary> + /// <param name="message">The warning that should be logged.</param> + public void LogWarning(string message) => Log(message, null, LogLevel.Warn); + + /// <summary> + /// Logs a warning to the underlying stream asynchronously, with severity <see cref="LogLevel.Warn"/>. + /// </summary> + /// <param name="message">The warning that should be logged.</param> + public async Task LogWarningAsync(string message) => await LogAsync(message, null, LogLevel.Warn); + + /// <summary> + /// Sets the minimum severity level that new messages need to be logged to the underlying stream. + /// </summary> + /// <param name="minimumSeverityLevel">The new minimum severity level.</param> + public void SetMinimumLogSeverity(LogLevel minimumSeverityLevel) => minimumSeverity = minimumSeverityLevel; + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/NetSharp.csproj b/NetSharp/NetSharp/NetSharp.csproj @@ -0,0 +1,25 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <Nullable>enable</Nullable> + <TargetFramework>netstandard2.1</TargetFramework> + </PropertyGroup> + + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> + <DocumentationFile>NetSharp.xml</DocumentationFile> + </PropertyGroup> + + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> + <DocumentationFile>NetSharp.xml</DocumentationFile> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <DebugType>full</DebugType> + <DebugSymbols>true</DebugSymbols> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.CSharp" Version="4.6.0" /> + <PackageReference Include="System.Dynamic.Runtime" Version="4.3.0" /> + <PackageReference Include="System.Text.Json" Version="4.6.0" /> + <PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.10.0" /> + </ItemGroup> +</Project> +\ No newline at end of file diff --git a/NetSharp/NetSharp/NetSharp.xml b/NetSharp/NetSharp/NetSharp.xml @@ -0,0 +1,2336 @@ +<?xml version="1.0"?> +<doc> + <assembly> + <name>NetSharp</name> + </assembly> + <members> + <member name="T:NetworkLib.Client"> + <summary> + Provides methods for connecting to and talking with a <see cref="T:NetworkLib.Interfaces.IServer"/> instance. + </summary> + </member> + <member name="F:NetworkLib.Client.cancellationTokenSource"> + <summary> + Provides <see cref="T:System.Threading.CancellationToken"/> instances for cancelling methods after a timeout period. + </summary> + </member> + <member name="F:NetworkLib.Client.socket"> + <summary> + The <see cref="T:System.Net.Sockets.Socket"/> underlying the connection. + </summary> + </member> + <member name="F:NetworkLib.Client.socketOptions"> + <summary> + Backing field for the <see cref="P:NetworkLib.Client.SocketOptions"/> property. + </summary> + </member> + <member name="F:NetworkLib.Client.remoteEndPoint"> + <summary> + The remote endpoint with which this client communicates. + </summary> + </member> + <member name="M:NetworkLib.Client.#ctor"> + <summary> + Initialises a new instance of the <see cref="T:NetworkLib.Client"/> class. + </summary> + </member> + <member name="M:NetworkLib.Client.#ctor(System.Net.Sockets.SocketType,System.Net.Sockets.ProtocolType,NetworkLib.Utils.Socket_Options.SocketOptionManager)"> + <summary> + Initialises a new instance of the <see cref="T:NetworkLib.Client"/> class. + </summary> + <param name="socketType">The socket type for the underlying socket.</param> + <param name="protocolType">The protocol type for the underlying socket.</param> + <param name="socketManager">The <see cref="T:NetworkLib.Utils.Socket_Options.SocketOptions"/> manager to use.</param> + </member> + <member name="M:NetworkLib.Client.Finalize"> + <summary> + Destroys an instance of the <see cref="T:NetworkLib.Client"/> class. + </summary> + </member> + <member name="E:NetworkLib.Client.Connected"> + <inheritdoc /> + </member> + <member name="E:NetworkLib.Client.Disconnected"> + <inheritdoc /> + </member> + <member name="P:NetworkLib.Client.SocketOptions"> + <summary> + The configured socket options for the underlying connection. + </summary> + </member> + <member name="M:NetworkLib.Client.Dispose(System.Boolean)"> + <summary> + Disposes of this <see cref="T:NetworkLib.Client"/> instance. + </summary> + <param name="disposing">Whether this instance is being disposed.</param> + </member> + <member name="M:NetworkLib.Client.OnConnected(System.Net.EndPoint)"> + <summary> + Invokes the <see cref="E:NetworkLib.Client.Connected"/> event. + </summary> + <param name="endPoint">The remote endpoint with which a connection was made.</param> + </member> + <member name="M:NetworkLib.Client.OnDisconnected(System.Net.EndPoint)"> + <summary> + Invokes the <see cref="E:NetworkLib.Client.Disconnected"/> event. + </summary> + <param name="endPoint">The remote endpoint with which a connection was lost.</param> + </member> + <member name="M:NetworkLib.Client.Disconnect"> + <summary> + Disconnects the client from the remote endpoint. + </summary> + </member> + <member name="M:NetworkLib.Client.SendBytesAsync(System.Byte[],System.TimeSpan)"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Client.SendBytesWithResponseAsync(System.Byte[],System.TimeSpan)"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Client.SendComplexAsync``2(``0,System.TimeSpan)"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Client.SendSimpleAsync``1(``0,System.TimeSpan)"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Client.TryBindAsync(System.Net.IPAddress,System.Nullable{System.Int32},System.TimeSpan)"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Client.TryConnectAsync(System.Net.IPAddress,System.Int32,System.TimeSpan)"> + <inheritdoc /> + </member> + <member name="T:NetworkLib.Clients.TcpClient"> + <summary> + Provides methods for TCP communication with a connected <see cref="T:NetworkLib.Servers.TcpServer"/> instance. + </summary> + </member> + <member name="M:NetworkLib.Clients.TcpClient.#ctor"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Clients.TcpClient.SendBytesAsync(System.Byte[],System.TimeSpan)"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Clients.TcpClient.SendBytesWithResponseAsync(System.Byte[],System.TimeSpan)"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Clients.TcpClient.SendComplexAsync``2(``0,System.TimeSpan)"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Clients.TcpClient.SendSimpleAsync``1(``0,System.TimeSpan)"> + <inheritdoc /> + </member> + <member name="T:NetworkLib.Clients.UdpClient"> + <summary> + Provides methods for UDP communication with a connected <see cref="T:NetworkLib.Servers.UdpServer"/> instance. + </summary> + </member> + <member name="M:NetworkLib.Clients.UdpClient.#ctor"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Clients.UdpClient.SendBytesAsync(System.Byte[],System.TimeSpan)"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Clients.UdpClient.SendBytesWithResponseAsync(System.Byte[],System.TimeSpan)"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Clients.UdpClient.SendComplexAsync``2(``0,System.TimeSpan)"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Clients.UdpClient.SendSimpleAsync``1(``0,System.TimeSpan)"> + <inheritdoc /> + </member> + <member name="T:NetworkLib.Connection"> + <summary> + Base class for connections, holding methods shared between the <see cref="T:NetworkLib.Client"/> and <see cref="T:NetworkLib.Server"/> classes. + </summary> + </member> + <member name="F:NetworkLib.Connection.readBytesCancellationTokenSource"> + <summary> + Cancellation token source for the <see cref="M:NetworkLib.Connection.DoReceivePacketAsync(System.Net.Sockets.Socket,System.Net.Sockets.SocketFlags,System.TimeSpan)"/> method. + </summary> + </member> + <member name="F:NetworkLib.Connection.writeBytesCancellationTokenSource"> + <summary> + Cancellation token source for the <see cref="M:NetworkLib.Connection.DoSendPacketAsync(System.Net.Sockets.Socket,NetworkLib.Packets.Packet,System.Net.Sockets.SocketFlags,System.TimeSpan)"/> method. + </summary> + </member> + <member name="F:NetworkLib.Connection.NullPacket"> + <summary> + Represents a packet that was not received correctly. + </summary> + </member> + <member name="F:NetworkLib.Connection.NullTransmissionResult"> + <summary> + Represents a transmission result of an incorrect transmission. + </summary> + </member> + <member name="F:NetworkLib.Connection.logger"> + <summary> + The logger to which the server can log messages. + </summary> + </member> + <member name="M:NetworkLib.Connection.#ctor"> + <summary> + Initialises a new instance of the <see cref="T:NetworkLib.Connection"/> class. + </summary> + </member> + <member name="E:NetworkLib.Connection.BytesReceived"> + <summary> + Signifies that some data has been received from the remote endpoint. + </summary> + </member> + <member name="E:NetworkLib.Connection.BytesSent"> + <summary> + Signifies that some data was sent to the remote endpoint. + </summary> + </member> + <member name="M:NetworkLib.Connection.Dispose(System.Boolean)"> + <summary> + Disposes of this <see cref="T:NetworkLib.Connection"/> instance. + </summary> + <param name="disposing">Whether this instance is being disposed.</param> + </member> + <member name="M:NetworkLib.Connection.DoReceivePacketAsync(System.Net.Sockets.Socket,System.Net.Sockets.SocketFlags,System.TimeSpan)"> + <summary> + Listens for a packet to be received asynchronously within the given timeout, and returns the received packet. + </summary> + <param name="remoteSocket">The remote socket from which to receive data.</param> + <param name="socketFlags">The socket flags associated with the read operation.</param> + <param name="timeout"> + The timespan within which to wait for a packet, returning a null packet if this limit is exceeded. + </param> + <returns>The packet that was received. <see cref="F:NetworkLib.Connection.NullPacket"/> if not received correctly.</returns> + </member> + <member name="M:NetworkLib.Connection.DoReceivePacketFromAsync(System.Net.Sockets.Socket,System.Net.EndPoint,System.Net.Sockets.SocketFlags,System.TimeSpan)"> + <summary> + Listens for a packet to be received asynchronously within the given timeout, and returns the received packet. + </summary> + <param name="socket">The socket which will receive the packet.</param> + <param name="remoteEndPoint">The remote endpoint from which to receive the packet.</param> + <param name="socketFlags">The socket flags associated with the read operation.</param> + <param name="timeout"> + The timespan within which to wait for a packet, returning a null packet if this limit is exceeded. + </param> + <returns> + The packet that was received and the associated transmission result. <see cref="F:NetworkLib.Connection.NullPacket"/> if not received correctly. + </returns> + </member> + <member name="M:NetworkLib.Connection.DoSendPacketAsync(System.Net.Sockets.Socket,NetworkLib.Packets.Packet,System.Net.Sockets.SocketFlags,System.TimeSpan)"> + <summary> + Sends the given packet asynchronously within the given timeout. + </summary> + <param name="remoteSocket">The remote socket to which to send the packet.</param> + <param name="packet">The packet to send.</param> + <param name="socketFlags">The socket flags associated with the write operation.</param> + <param name="timeout"> + The timespan within which to send the packet, returning <c>false</c> if this limit is exceeded. + </param> + <returns>Whether the packet was successfully sent.</returns> + </member> + <member name="M:NetworkLib.Connection.DoSendPacketToAsync(System.Net.Sockets.Socket,System.Net.EndPoint,NetworkLib.Packets.Packet,System.Net.Sockets.SocketFlags,System.TimeSpan)"> + <summary> + Sends the given packet asynchronously within the given timeout. + </summary> + <param name="socket">The socket which should send the packet.</param> + <param name="remoteEndPoint">The remote endpoint to which to send the packet.</param> + <param name="packet">The packet to send.</param> + <param name="socketFlags">The socket flags associated with the write operation.</param> + <param name="timeout"> + The timespan within which to send the packet, returning <c>false</c> if this limit is exceeded. + </param> + <returns>Whether the packet was successfully sent.</returns> + </member> + <member name="M:NetworkLib.Connection.OnBytesReceived(System.Net.EndPoint,System.Int32)"> + <summary> + Invokes the <see cref="E:NetworkLib.Connection.BytesReceived"/> event. + </summary> + <param name="remoteEndPoint">The remote endpoint from which the bytes were received.</param> + <param name="bytesReceived">The number of bytes that were received from the remote endpoint.</param> + </member> + <member name="M:NetworkLib.Connection.OnBytesSent(System.Net.EndPoint,System.Int32)"> + <summary> + Invokes the <see cref="E:NetworkLib.Connection.BytesSent"/> event. + </summary> + <param name="remoteEndPoint">The remote endpoint to which the bytes were sent.</param> + <param name="bytesSent">The number of bytes that were sent to the remote endpoint.</param> + </member> + <member name="M:NetworkLib.Connection.ChangeLoggingStream(System.IO.Stream,NetworkLib.Logging.LogLevel)"> + <summary> + Makes the client log to the given stream. + </summary> + <param name="loggingStream">The stream that new messages should be logged to.</param> + <param name="minimumMessageSeverityLevel"> + The minimum severity level that new messages must have to be logged to the stream. + </param> + </member> + <member name="M:NetworkLib.Connection.Dispose"> + <inheritdoc /> + </member> + <member name="T:NetworkLib.Extensions.ClientExtensions"> + <summary> + Provides additional methods and functionality to the <see cref="T:NetworkLib.Client"/> class. + </summary> + </member> + <member name="M:NetworkLib.Extensions.ClientExtensions.SendBytes(NetworkLib.Client,System.Byte[])"> + <summary> + Sends the given byte buffer to the connected remote endpoint. Blocks until the bytes are all sent, and does + not timeout. + </summary> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="buffer">The bytes that should be sent to the connected remote endpoint.</param> + </member> + <member name="M:NetworkLib.Extensions.ClientExtensions.SendBytes(NetworkLib.Client,System.Byte[],System.TimeSpan)"> + <summary> + Sends the given byte buffer to the connected remote endpoint. Blocks until the bytes are all sent, whilst + observing a timeout of the given length. + </summary> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="buffer">The bytes that should be sent to the connected remote endpoint.</param> + <param name="timeout">The timeout after which to cancel the transmission attempt.</param> + </member> + <member name="M:NetworkLib.Extensions.ClientExtensions.SendBytesAsync(NetworkLib.Client,System.Byte[])"> + <summary> + Sends the given byte buffer to the connected remote endpoint asynchronously. Does not block, and does not + timeout. + </summary> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="buffer">The bytes that should be sent to the connected remote endpoint.</param> + </member> + <member name="M:NetworkLib.Extensions.ClientExtensions.SendBytesWithResponse(NetworkLib.Client,System.Byte[])"> + <summary> + Sends the given byte buffer to the connected remote endpoint and waits for the response. Blocks until the + bytes are all sent and the response has been received, and does not timeout. + </summary> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="buffer">The bytes that should be sent to the connected remote endpoint.</param> + <returns>The byte buffer that was received as a response.</returns> + </member> + <member name="M:NetworkLib.Extensions.ClientExtensions.SendBytesWithResponse(NetworkLib.Client,System.Byte[],System.TimeSpan)"> + <summary> + Sends the given byte buffer to the connected remote endpoint and waits for the response. Blocks until the + bytes are all sent and the response has been received, whilst observing a timeout of the given length. + </summary> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="buffer">The bytes that should be sent to the connected remote endpoint.</param> + <param name="timeout">The timeout after which to cancel the transmission attempt.</param> + <returns>The byte buffer that was received as a response.</returns> + </member> + <member name="M:NetworkLib.Extensions.ClientExtensions.SendBytesWithResponseAsync(NetworkLib.Client,System.Byte[])"> + <summary> + Sends the given byte buffer to the connected remote endpoint and waits for the response asynchronously. + Does not block, and does not timeout. + </summary> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="buffer">The bytes that should be sent to the connected remote endpoint.</param> + <returns>The byte buffer received as a response to the sent buffer.</returns> + </member> + <member name="M:NetworkLib.Extensions.ClientExtensions.SendComplex``2(NetworkLib.Client,``0)"> + <summary> + Sends the given request and listens for a response of the given type. Blocks until the response is received. + Does not timeout. + </summary> + <typeparam name="Req">The type of request packet to send.</typeparam> + <typeparam name="Rep">The type of response packet to receive.</typeparam> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="request">The request packet to send.</param> + <returns>The received instance.</returns> + </member> + <member name="M:NetworkLib.Extensions.ClientExtensions.SendComplex``2(NetworkLib.Client,``0,System.TimeSpan)"> + <summary> + Sends the given request and listens for a response of the given type. Blocks until the response is received. + Cancels the operation if the given timeout is exceeded. + </summary> + <typeparam name="Req">The type of request packet to send.</typeparam> + <typeparam name="Rep">The type of response packet to receive.</typeparam> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="request">The request packet to send.</param> + <param name="timeout">The timeout for which to wait for the operation to complete.</param> + <returns>The received instance.</returns> + </member> + <member name="M:NetworkLib.Extensions.ClientExtensions.SendComplexAsync``2(NetworkLib.Client,``0)"> + <summary> + Sends the given request and listens for a response of the given type asynchronously. Does not block. Does not timeout. + </summary> + <typeparam name="Req">The type of request packet to send.</typeparam> + <typeparam name="Rep">The type of response packet to receive.</typeparam> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="request">The request packet to send.</param> + <returns>The received instance.</returns> + </member> + <member name="M:NetworkLib.Extensions.ClientExtensions.SendSimple``1(NetworkLib.Client,``0)"> + <summary> + Sends the given request without listening for a response, blocking until it is sent. Does not timeout. + </summary> + <typeparam name="Req">The type of request packet to send.</typeparam> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="request">The request packet to send.</param> + </member> + <member name="M:NetworkLib.Extensions.ClientExtensions.SendSimple``1(NetworkLib.Client,``0,System.TimeSpan)"> + <summary> + Sends the given request without listening for a response, blocking until it is sent. + Cancels the operation if the given timeout is exceeded. + </summary> + <typeparam name="Req">The type of request packet to send.</typeparam> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="request">The request packet to send.</param> + <param name="timeout">The timeout for which to wait for the operation to complete.</param> + </member> + <member name="M:NetworkLib.Extensions.ClientExtensions.SendSimpleAsync``1(NetworkLib.Client,``0)"> + <summary> + Sends the given request asynchronously without listening for a response, not blocking until it is sent. + Does not timeout. + </summary> + <typeparam name="Req">The type of request packet to send.</typeparam> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="request">The request packet to send.</param> + </member> + <member name="M:NetworkLib.Extensions.ClientExtensions.TryBind(NetworkLib.Client,System.Net.IPAddress,System.Nullable{System.Int32})"> + <summary> + Attempts to synchronously bind the underlying socket to the given local address and port. Blocks. Does not timeout. + </summary> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="localAddress">The local IP address to bind to. Null if any IP address will suffice.</param> + <param name="localPort">The local port to bind to. Null if any port will suffice.</param> + <returns>Whether the binding was successful or not.</returns> + </member> + <member name="M:NetworkLib.Extensions.ClientExtensions.TryBind(NetworkLib.Client,System.Net.IPAddress,System.Nullable{System.Int32},System.TimeSpan)"> + <summary> + Attempts to synchronously bind the underlying socket to the given local address and port. Blocks. + If the timeout is exceeded the binding attempt is aborted and the method returns false. + </summary> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="localAddress">The local IP address to bind to. Null if any IP address will suffice.</param> + <param name="localPort">The local port to bind to. Null if any port will suffice.</param> + <param name="timeout">The timeout within which to attempt the binding.</param> + <returns>Whether the binding was successful or not.</returns> + </member> + <member name="M:NetworkLib.Extensions.ClientExtensions.TryBindAsync(NetworkLib.Client,System.Net.IPAddress,System.Nullable{System.Int32})"> + <summary> + Attempts to asynchronously bind the underlying socket to the given local address and port. Does not block. + Does not timeout. + </summary> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="localAddress">The local IP address to bind to. Null if any IP address will suffice.</param> + <param name="localPort">The local port to bind to. Null if any port will suffice.</param> + <returns>Whether the binding was successful or not.</returns> + </member> + <member name="M:NetworkLib.Extensions.ClientExtensions.TryConnect(NetworkLib.Client,System.Net.IPAddress,System.Int32)"> + <summary> + Attempts to connect to the remote <see cref="T:NetworkLib.Server"/> at the given <see cref="T:System.Net.IPAddress"/> and over the + given port. Does not timeout. + </summary> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="remoteAddress">The remote IP address to connect to.</param> + <param name="remotePort">The remote port to connect over.</param> + <returns>Whether the connection was successful or not.</returns> + </member> + <member name="M:NetworkLib.Extensions.ClientExtensions.TryConnect(NetworkLib.Client,System.Net.IPAddress,System.Int32,System.TimeSpan)"> + <summary> + Attempts to connect to the remote <see cref="T:NetworkLib.Server"/> at the given <see cref="T:System.Net.IPAddress"/> and over the + given port. If the timeout is exceeded the connection attempt is aborted and the method returns false. + </summary> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="remoteAddress">The remote IP address to connect to.</param> + <param name="remotePort">The remote port to connect over.</param> + <param name="timeout">The timeout within which to attempt the connection.</param> + <returns>Whether the connection was successful or not.</returns> + </member> + <member name="M:NetworkLib.Extensions.ClientExtensions.TryConnectAsync(NetworkLib.Client,System.Net.IPAddress,System.Int32)"> + <summary> + Attempts to connect asynchronously to the remote <see cref="T:NetworkLib.Server"/> at the given <see cref="T:System.Net.IPAddress"/> + and over the given port. Does not timeout. + </summary> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="remoteAddress">The remote IP address to connect to.</param> + <param name="remotePort">The remote port to connect over.</param> + <returns>Whether the connection was successful or not.</returns> + </member> + <member name="T:NetworkLib.Extensions.ServerExtensions"> + <summary> + Provides additional methods and functionality to the <see cref="T:NetworkLib.Server"/> class. + </summary> + </member> + <member name="M:NetworkLib.Extensions.ServerExtensions.Run(NetworkLib.Server,System.Net.IPAddress,System.Int32)"> + <summary> + Starts the server synchronously and starts accepting client connections. Blocks. + </summary> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="localAddress">The local IP address to bind to.</param> + <param name="localPort">The local port to bind to.</param> + </member> + <member name="M:NetworkLib.Extensions.ServerExtensions.Run(NetworkLib.Server,System.Net.IPAddress)"> + <summary> + Starts the server synchronously and starts accepting client connections. Blocks. Uses the default connection port. + </summary> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="localAddress">The local IP address to bind to.</param> + </member> + <member name="M:NetworkLib.Extensions.ServerExtensions.RunAsync(NetworkLib.Server,System.Net.IPAddress)"> + <summary> + Starts the server asynchronously and starts accepting client connections. Does not block. Uses the default + connection port. + </summary> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="localAddress">The local IP address to bind to.</param> + </member> + <member name="M:NetworkLib.Extensions.ServerExtensions.RunAsync(NetworkLib.Server,System.Net.IPAddress,System.Int32)"> + <summary> + Starts the server asynchronously and starts accepting client connections. Does not block. + </summary> + <param name="instance">The instance on which this extension method should be called.</param> + <param name="localAddress">The local IP address to bind to.</param> + <param name="localPort">The local port to bind to.</param> + </member> + <member name="T:NetworkLib.Interfaces.IClient"> + <summary> + Describes a client capable of asynchronous communication with an <see cref="T:NetworkLib.Interfaces.IServer"/> connection. + </summary> + </member> + <member name="E:NetworkLib.Interfaces.IClient.Connected"> + <summary> + Signifies that a connection with the remote endpoint has been made. + </summary> + </member> + <member name="E:NetworkLib.Interfaces.IClient.Disconnected"> + <summary> + Signifies that the connection with the remote endpoint was severed. + </summary> + </member> + <member name="M:NetworkLib.Interfaces.IClient.SendBytesAsync(System.Byte[],System.TimeSpan)"> + <summary> + Sends the given byte buffer to the connected remote endpoint asynchronously. Does not block, and observes + a timeout of the given length. + timeout. + </summary> + <param name="buffer">The bytes that should be sent to the connected remote endpoint.</param> + <param name="timeout">The timeout after which to cancel the transmission attempt.</param> + </member> + <member name="M:NetworkLib.Interfaces.IClient.SendBytesWithResponseAsync(System.Byte[],System.TimeSpan)"> + <summary> + Sends the given byte buffer to the connected remote endpoint and waits for the response asynchronously. + Does not block, and observes a timeout of the given length. + timeout. + </summary> + <param name="buffer">The bytes that should be sent to the connected remote endpoint.</param> + <param name="timeout"> + The timeout after which to cancel the transmission attempt. This timeout is reused by both the 'send' and + 'receive' parts of the transmission attempt, such that the maximum timeout is equal to 2 times the given + value. + </param> + <returns>The byte buffer received as a response to the sent buffer.</returns> + </member> + <member name="M:NetworkLib.Interfaces.IClient.SendComplexAsync``2(``0,System.TimeSpan)"> + <summary> + Sends the given request and listens for a response of the given type asynchronously. Does not block. + Cancels the operation if the given timeout is exceeded + </summary> + <typeparam name="Req">The type of request packet to send.</typeparam> + <typeparam name="Rep">The type of response packet to receive.</typeparam> + <param name="request">The request packet to send.</param> + <param name="timeout">The timeout for which to wait for the operation to complete.</param> + <returns>The received instance.</returns> + </member> + <member name="M:NetworkLib.Interfaces.IClient.SendSimpleAsync``1(``0,System.TimeSpan)"> + <summary> + Sends the given request asynchronously without listening for a response, not blocking until it is sent. + Cancels the operation if the given timeout is exceeded. + </summary> + <typeparam name="Req">The type of request packet to send.</typeparam> + <param name="request">The request packet to send.</param> + <param name="timeout">The timeout for which to wait for the operation to complete.</param> + </member> + <member name="M:NetworkLib.Interfaces.IClient.TryBindAsync(System.Net.IPAddress,System.Nullable{System.Int32},System.TimeSpan)"> + <summary> + Attempts to asynchronously bind the underlying socket to the given local address and port. Does not block. + If the timeout is exceeded the binding attempt is aborted and the method returns false. + </summary> + <param name="localAddress">The local IP address to bind to. Null if any IP address will suffice.</param> + <param name="localPort">The local port to bind to. Null if any port will suffice.</param> + <param name="timeout">The timeout within which to attempt the binding.</param> + <returns>Whether the binding was successful or not.</returns> + </member> + <member name="M:NetworkLib.Interfaces.IClient.TryConnectAsync(System.Net.IPAddress,System.Int32,System.TimeSpan)"> + <summary> + Attempts to connect asynchronously to the remote <see cref="T:NetworkLib.Server"/> at the given <see cref="T:System.Net.IPAddress"/> + and over the given port. If the timeout is exceeded the connection attempt is aborted and the method returns false. + </summary> + <param name="remoteAddress">The remote IP address to connect to.</param> + <param name="remotePort">The remote port to connect over.</param> + <param name="timeout">The timeout within which to attempt the connection.</param> + <returns>Whether the connection was successful or not.</returns> + </member> + <member name="T:NetworkLib.Interfaces.INetworkSerialisable"> + <summary> + Describes an object that can be serialised to be sent across the network. + </summary> + </member> + <member name="M:NetworkLib.Interfaces.INetworkSerialisable.Deserialise(System.ReadOnlyMemory{System.Byte})"> + <summary> + Deserialises the object instance from a byte array. + </summary> + <param name="serialisedObject">The memory containing the serialised object instance.</param> + </member> + <member name="M:NetworkLib.Interfaces.INetworkSerialisable.Serialise"> + <summary> + Serialises the object instance into a byte array. + </summary> + <returns>The memory containing the serialised object instance.</returns> + </member> + <member name="T:NetworkLib.Interfaces.IPacket"> + <summary> + Describes the methods and properties that every packet + </summary> + </member> + <member name="M:NetworkLib.Interfaces.IPacket.AfterDeserialisation"> + <summary> + Allows for custom fields to be converted from their serialised format, after being received from the network. + </summary> + </member> + <member name="M:NetworkLib.Interfaces.IPacket.BeforeSerialisation"> + <summary> + Allows for custom fields to be converted into another format prior to being sent via the network. + </summary> + </member> + <member name="T:NetworkLib.Interfaces.IRequestPacket"> + <summary> + Describes a request packet. + </summary> + </member> + <member name="T:NetworkLib.Interfaces.IResponsePacket`1"> + <summary> + Describes a response packet to a request packet. + </summary> + <typeparam name="TReq">The request packet that this type is a response to.</typeparam> + </member> + <member name="P:NetworkLib.Interfaces.IResponsePacket`1.RequestPacket"> + <summary> + The request packet that was handled with this response packet. + </summary> + </member> + <member name="T:NetworkLib.Interfaces.IServer"> + <summary> + Describes a server capable of asynchronously handling multiple <see cref="T:NetworkLib.Interfaces.IClient"/> connections at once. + </summary> + </member> + <member name="E:NetworkLib.Interfaces.IServer.ClientConnected"> + <summary> + Signifies that a connection with a remote endpoint has been made. + </summary> + </member> + <member name="E:NetworkLib.Interfaces.IServer.ClientDisconnected"> + <summary> + Signifies that a connection with a remote endpoint has been lost. + </summary> + </member> + <member name="E:NetworkLib.Interfaces.IServer.ServerStarted"> + <summary> + Signifies that the server was started and clients will start being accepted. + </summary> + </member> + <member name="E:NetworkLib.Interfaces.IServer.ServerStopped"> + <summary> + Signifies that the server was stopped and clients will stop being accepted. + </summary> + </member> + <member name="M:NetworkLib.Interfaces.IServer.RunAsync(System.Net.EndPoint)"> + <summary> + Starts the server asynchronously and starts accepting client connections. Does not block. + </summary> + <param name="localEndPoint">The local endpoint to bind to.</param> + </member> + <member name="M:NetworkLib.Interfaces.IServer.Shutdown"> + <summary> + Shuts down the server. + </summary> + </member> + <member name="M:NetworkLib.Interfaces.IServer.TryDeregisterComplexPacketHandler``2(NetworkLib.ComplexPacketHandler{``0,``1}@)"> + <summary> + Attempts to deregister the complex packet handler delegate for all packets of the given type. If a handler + method doesn't exist for the given packet type, <paramref name="oldHandlerDelegate"/> will be <c>default</c>. + </summary> + <typeparam name="Req">The type of request packet for which to deregister the handler delegate.</typeparam> + <typeparam name="Rep">The type of response packet that is generated by the delegate method.</typeparam> + <param name="oldHandlerDelegate">The old handler delegate method that was previously registered.</param> + <returns>Whether the packet handler delegate was successfully deregistered.</returns> + </member> + <member name="M:NetworkLib.Interfaces.IServer.TryDeregisterSimplePacketHandler``1(NetworkLib.SimplePacketHandler{``0}@)"> + <summary> + Attempts to deregister the simple packet handler delegate for all packets of the given type. If a handler + method doesn't exist for the given packet type, <paramref name="oldHandlerDelegate"/> will be <c>default</c>. + </summary> + <typeparam name="Req">The type of request packet for which to deregister the handler delegate.</typeparam> + <param name="oldHandlerDelegate">The old handler delegate method that was previously registered.</param> + <returns>Whether the packet handler delegate was successfully deregistered.</returns> + </member> + <member name="M:NetworkLib.Interfaces.IServer.TryRegisterComplexPacketHandler``2(NetworkLib.ComplexPacketHandler{``0,``1})"> + <summary> + Attempts to register a complex packet handler delegate for all packets of the given type. If a handler + method already exists for the given packet type, it will be updated and replaced with the given one. + </summary> + <typeparam name="Req">The type of request packet for which to register the handler delegate.</typeparam> + <typeparam name="Rep">The type of response packet that is generated by the delegate method.</typeparam> + <param name="handlerDelegate">The delegate method to register as the complex packet handler.</param> + <returns>Whether the packet handler delegate was successfully registered.</returns> + </member> + <member name="M:NetworkLib.Interfaces.IServer.TryRegisterSimplePacketHandler``1(NetworkLib.SimplePacketHandler{``0})"> + <summary> + Attempts to register a simple packet handler delegate for all packets of the given type. If a handler + method already exists for the given packet type, it will be updated and replaced with the given one. + </summary> + <typeparam name="Req">The type of request packet for which to register the handler delegate.</typeparam> + <param name="handlerDelegate">The delegate method to register as the simple packet handler.</param> + <returns>Whether the packet handler delegate was successfully registered.</returns> + </member> + <member name="T:NetworkLib.Logging.LogLevel"> + <summary> + Specifies the severity level of a log message. + </summary> + </member> + <member name="F:NetworkLib.Logging.LogLevel.Info"> + <summary> + The logged message contains some information. Lowest severity. + </summary> + </member> + <member name="F:NetworkLib.Logging.LogLevel.Warn"> + <summary> + The logged message contains a warning. Higher severity. + </summary> + </member> + <member name="F:NetworkLib.Logging.LogLevel.Error"> + <summary> + The logged message contains details about an error. Higher severity. + </summary> + </member> + <member name="F:NetworkLib.Logging.LogLevel.Exception"> + <summary> + The logged message contains details about an exception. Highest severity. + </summary> + </member> + <member name="T:NetworkLib.Logging.Logger"> + <summary> + A simple logger capable of writing text to a stream. + </summary> + </member> + <member name="F:NetworkLib.Logging.Logger.loggingStream"> + <summary> + The stream to which messages will be logged. + </summary> + </member> + <member name="F:NetworkLib.Logging.Logger.writer"> + <summary> + The text writer we will use to log messages to the underlying stream. + </summary> + </member> + <member name="F:NetworkLib.Logging.Logger.minimumSeverity"> + <summary> + The minimum severity that log messages need to be logged to the underlying stream. + </summary> + </member> + <member name="M:NetworkLib.Logging.Logger.#ctor(System.IO.Stream)"> + <summary> + Initialises a new instance of the <see cref="T:NetworkLib.Logging.Logger"/> struct. + </summary> + <param name="streamToLogTo">The stream that the logger instance should log messages to.</param> + </member> + <member name="M:NetworkLib.Logging.Logger.Dispose"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Logging.Logger.Log(System.String,System.Exception,NetworkLib.Logging.LogLevel)"> + <summary> + Logs a message to the underlying stream, along with the given exception and at the given severity. + </summary> + <param name="message">The message that should be logged.</param> + <param name="exception">The exception that occurred (if any).</param> + <param name="severity">The severity of the message that is being logged.</param> + </member> + <member name="M:NetworkLib.Logging.Logger.LogAsync(System.String,System.Exception,NetworkLib.Logging.LogLevel)"> + <summary> + Logs a message asynchronously to the underlying stream, along with the given exception and at the given severity. + </summary> + <param name="message">The message that should be logged.</param> + <param name="exception">The exception that occurred (if any).</param> + <param name="severity">The severity of the message that is being logged.</param> + </member> + <member name="M:NetworkLib.Logging.Logger.LogError(System.String)"> + <summary> + Logs an error to the underlying stream, with severity <see cref="F:NetworkLib.Logging.LogLevel.Info"/>. + </summary> + <param name="message">The error that should be logged.</param> + </member> + <member name="M:NetworkLib.Logging.Logger.LogErrorAsync(System.String)"> + <summary> + Logs an error to the underlying stream asynchronously, with severity <see cref="F:NetworkLib.Logging.LogLevel.Error"/>. + </summary> + <param name="message">The error that should be logged.</param> + </member> + <member name="M:NetworkLib.Logging.Logger.LogException(System.Exception)"> + <summary> + Logs an exception to the underlying stream, with severity <see cref="F:NetworkLib.Logging.LogLevel.Exception"/>. + </summary> + <param name="exception">The exception that should be logged.</param> + </member> + <member name="M:NetworkLib.Logging.Logger.LogException(System.String,System.Exception)"> + <summary> + Logs an exception to the underlying stream, along with a short debug message, with severity + <see cref="F:NetworkLib.Logging.LogLevel.Exception"/>. + </summary> + <param name="message">The debug message that should be logged with the exception.</param> + <param name="exception">The exception that should be logged.</param> + </member> + <member name="M:NetworkLib.Logging.Logger.LogExceptionAsync(System.Exception)"> + <summary> + Logs an exception to the underlying stream asynchronously, with severity <see cref="F:NetworkLib.Logging.LogLevel.Exception"/>. + </summary> + <param name="exception">The exception that should be logged.</param> + </member> + <member name="M:NetworkLib.Logging.Logger.LogExceptionAsync(System.String,System.Exception)"> + <summary> + Logs an exception to the underlying stream asynchronously, along with a short debug message, with severity + <see cref="F:NetworkLib.Logging.LogLevel.Exception"/>. + </summary> + <param name="message">The debug message that should be logged with the exception.</param> + <param name="exception">The exception that should be logged.</param> + </member> + <member name="M:NetworkLib.Logging.Logger.LogMessage(System.String)"> + <summary> + Logs a message to the underlying stream, with severity <see cref="F:NetworkLib.Logging.LogLevel.Info"/>. + </summary> + <param name="message">The message that should be logged.</param> + </member> + <member name="M:NetworkLib.Logging.Logger.LogMessageAsync(System.String)"> + <summary> + Logs a message to the underlying stream asynchronously, with severity <see cref="F:NetworkLib.Logging.LogLevel.Info"/>. + </summary> + <param name="message">The message that should be logged.</param> + </member> + <member name="M:NetworkLib.Logging.Logger.LogWarning(System.String)"> + <summary> + Logs a warning to the underlying stream, with severity <see cref="F:NetworkLib.Logging.LogLevel.Info"/>. + </summary> + <param name="message">The warning that should be logged.</param> + </member> + <member name="M:NetworkLib.Logging.Logger.LogWarningAsync(System.String)"> + <summary> + Logs a warning to the underlying stream asynchronously, with severity <see cref="F:NetworkLib.Logging.LogLevel.Warn"/>. + </summary> + <param name="message">The warning that should be logged.</param> + </member> + <member name="M:NetworkLib.Logging.Logger.SetMinimumLogSeverity(NetworkLib.Logging.LogLevel)"> + <summary> + Sets the minimum severity level that new messages need to be logged to the underlying stream. + </summary> + <param name="minimumSeverityLevel">The new minimum severity level.</param> + </member> + <member name="T:NetworkLib.Packets.Builtin.ConnectPacket"> + <summary> + A simple connection request packet for the UDP protocol. + </summary> + </member> + <member name="M:NetworkLib.Packets.Builtin.ConnectPacket.AfterDeserialisation"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.ConnectPacket.BeforeSerialisation"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.ConnectPacket.Deserialise(System.ReadOnlyMemory{System.Byte})"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.ConnectPacket.Serialise"> + <inheritdoc /> + </member> + <member name="T:NetworkLib.Packets.Builtin.ConnectResponsePacket"> + <summary> + A response packet for the <see cref="T:NetworkLib.Packets.Builtin.ConnectPacket"/>. + </summary> + </member> + <member name="P:NetworkLib.Packets.Builtin.ConnectResponsePacket.RequestPacket"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.ConnectResponsePacket.AfterDeserialisation"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.ConnectResponsePacket.BeforeSerialisation"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.ConnectResponsePacket.Deserialise(System.ReadOnlyMemory{System.Byte})"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.ConnectResponsePacket.Serialise"> + <inheritdoc /> + </member> + <member name="T:NetworkLib.Packets.Builtin.DataPacket"> + <summary> + A simple data transfer packet, that allows for the transmission of an arbitrary number of frames. + </summary> + </member> + <member name="F:NetworkLib.Packets.Builtin.DataPacket.RequestBuffer"> + <summary> + The data that should be transferred across the network. + </summary> + </member> + <member name="M:NetworkLib.Packets.Builtin.DataPacket.#ctor"> + <summary> + Initialises a new instance of the <see cref="T:NetworkLib.Packets.Builtin.DataPacket"/> class. + </summary> + </member> + <member name="M:NetworkLib.Packets.Builtin.DataPacket.#ctor(System.ReadOnlyMemory{System.Byte})"> + <summary> + Initialises a new instance of the <see cref="T:NetworkLib.Packets.Builtin.DataPacket"/> class. + </summary> + <param name="buffer">The data that this request packet should contain.</param> + </member> + <member name="M:NetworkLib.Packets.Builtin.DataPacket.AfterDeserialisation"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.DataPacket.BeforeSerialisation"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.DataPacket.Deserialise(System.ReadOnlyMemory{System.Byte})"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.DataPacket.Serialise"> + <inheritdoc /> + </member> + <member name="T:NetworkLib.Packets.Builtin.DataResponsePacket"> + <summary> + A response packet for the <see cref="T:NetworkLib.Packets.Builtin.DataPacket"/>. + </summary> + </member> + <member name="F:NetworkLib.Packets.Builtin.DataResponsePacket.ResponseBuffer"> + <summary> + The data that should be transferred across the network. + </summary> + </member> + <member name="M:NetworkLib.Packets.Builtin.DataResponsePacket.#ctor"> + <summary> + Initialises a new instance of the <see cref="T:NetworkLib.Packets.Builtin.DataResponsePacket"/> class. + </summary> + </member> + <member name="M:NetworkLib.Packets.Builtin.DataResponsePacket.#ctor(System.ReadOnlyMemory{System.Byte})"> + <summary> + Initialises a new instance of the <see cref="T:NetworkLib.Packets.Builtin.DataResponsePacket"/> class. + </summary> + <param name="buffer">The data that this response packet should contain.</param> + </member> + <member name="P:NetworkLib.Packets.Builtin.DataResponsePacket.RequestPacket"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.DataResponsePacket.AfterDeserialisation"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.DataResponsePacket.BeforeSerialisation"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.DataResponsePacket.Deserialise(System.ReadOnlyMemory{System.Byte})"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.DataResponsePacket.Serialise"> + <inheritdoc /> + </member> + <member name="T:NetworkLib.Packets.Builtin.DisconnectPacket"> + <summary> + A simple disconnect packet for the UDP protocol. + </summary> + </member> + <member name="M:NetworkLib.Packets.Builtin.DisconnectPacket.AfterDeserialisation"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.DisconnectPacket.BeforeSerialisation"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.DisconnectPacket.Deserialise(System.ReadOnlyMemory{System.Byte})"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.DisconnectPacket.Serialise"> + <inheritdoc /> + </member> + <member name="T:NetworkLib.Packets.Builtin.PingPacket"> + <summary> + A simple ping request packet for heartbeat monitoring and RTT measurement. + </summary> + </member> + <member name="M:NetworkLib.Packets.Builtin.PingPacket.AfterDeserialisation"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.PingPacket.BeforeSerialisation"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.PingPacket.Deserialise(System.ReadOnlyMemory{System.Byte})"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.PingPacket.Serialise"> + <inheritdoc /> + </member> + <member name="T:NetworkLib.Packets.Builtin.PingResponsePacket"> + <summary> + A response packet for the <see cref="T:NetworkLib.Packets.Builtin.PingPacket"/>. + </summary> + </member> + <member name="P:NetworkLib.Packets.Builtin.PingResponsePacket.RequestPacket"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.PingResponsePacket.AfterDeserialisation"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.PingResponsePacket.BeforeSerialisation"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.PingResponsePacket.Deserialise(System.ReadOnlyMemory{System.Byte})"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.PingResponsePacket.Serialise"> + <inheritdoc /> + </member> + <member name="T:NetworkLib.Packets.Builtin.SimpleDataPacket"> + <summary> + A simple one-time-use data transfer packet, that allows for the transmission of an arbitrary number of frames. + </summary> + </member> + <member name="F:NetworkLib.Packets.Builtin.SimpleDataPacket.RequestBuffer"> + <summary> + The data that should be transferred across the network. + </summary> + </member> + <member name="M:NetworkLib.Packets.Builtin.SimpleDataPacket.#ctor"> + <summary> + Initialises a new instance of the <see cref="T:NetworkLib.Packets.Builtin.SimpleDataPacket"/> class. + </summary> + </member> + <member name="M:NetworkLib.Packets.Builtin.SimpleDataPacket.#ctor(System.ReadOnlyMemory{System.Byte})"> + <summary> + Initialises a new instance of the <see cref="T:NetworkLib.Packets.Builtin.SimpleDataPacket"/> class. + </summary> + <param name="buffer">The data that this request packet should contain.</param> + </member> + <member name="M:NetworkLib.Packets.Builtin.SimpleDataPacket.AfterDeserialisation"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.SimpleDataPacket.BeforeSerialisation"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.SimpleDataPacket.Deserialise(System.ReadOnlyMemory{System.Byte})"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Packets.Builtin.SimpleDataPacket.Serialise"> + <inheritdoc /> + </member> + <member name="T:NetworkLib.Packets.NetworkErrorCode"> + <summary> + Enumerates the possible error codes for network operations, being held in the packet. + </summary> + </member> + <member name="F:NetworkLib.Packets.NetworkErrorCode.Ok"> + <summary> + Signifies that there was no error during transmission. + </summary> + </member> + <member name="F:NetworkLib.Packets.NetworkErrorCode.Error"> + <summary> + A generic error occurred during packet transmission. + </summary> + </member> + <member name="T:NetworkLib.Packets.Packet"> + <summary> + Represents a packet that is transmitted over the network. + </summary> + </member> + <member name="F:NetworkLib.Packets.Packet.HeaderSize"> + <summary> + The size of the packet header in bytes. + </summary> + </member> + <member name="F:NetworkLib.Packets.Packet.Buffer"> + <summary> + The data held in this packet. + </summary> + </member> + <member name="F:NetworkLib.Packets.Packet.Count"> + <summary> + The size of the packet's data. + </summary> + </member> + <member name="F:NetworkLib.Packets.Packet.ErrorCode"> + <summary> + The error code for this packet. + </summary> + </member> + <member name="F:NetworkLib.Packets.Packet.Type"> + <summary> + The packet type. + </summary> + </member> + <member name="M:NetworkLib.Packets.Packet.#ctor(System.ReadOnlyMemory{System.Byte},System.UInt32,NetworkLib.Packets.NetworkErrorCode)"> + <summary> + Initialises a new instance of the <see cref="T:NetworkLib.Packets.Packet"/> struct. + </summary> + <param name="data">The data that should be transmitted in the packet.</param> + <param name="type">The packet type.</param> + <param name="errorCode">The error code associated with this transmission.</param> + </member> + <member name="P:NetworkLib.Packets.Packet.TotalSize"> + <summary> + Returns the total size of the serialised packet (including the header) in bytes. + </summary> + <returns>The total size of the serialised packet (including the header) in bytes.</returns> + </member> + <member name="M:NetworkLib.Packets.Packet.Deserialise(System.Memory{System.Byte})"> + <summary> + Deserialises the given buffer into a packet instance. + </summary> + <param name="buffer">The byte buffer to serialise.</param> + <returns>The deserialised packet instance.</returns> + </member> + <member name="M:NetworkLib.Packets.Packet.Serialise(NetworkLib.Packets.Packet)"> + <summary> + Serialises the given packet instance into a single byte buffer. + </summary> + <param name="instance">The packet instance to serialise.</param> + <returns>The byte buffer that represents the packet instance.</returns> + </member> + <member name="T:NetworkLib.Packets.PacketRegistry"> + <summary> + Provides method of registering request packets and their relevant response packets, as well as mapping their ids. + </summary> + </member> + <member name="F:NetworkLib.Packets.PacketRegistry.AutomaticPacketTypeIdStartPoint"> + <summary> + The start id for automatically generated packet type ids. Any custom packet type ids lower than this value + that come from external assemblies will be incremented by this value, to ensure that there are no clashes. + </summary> + </member> + <member name="F:NetworkLib.Packets.PacketRegistry.currentAutomaticPacketTypeIdCounterLockObject"> + <summary> + The lock object for synchronising access to the <see cref="F:NetworkLib.Packets.PacketRegistry.currentAutomaticPacketTypeIdCounter"/> field. + </summary> + </member> + <member name="F:NetworkLib.Packets.PacketRegistry.idToPacketTypeMap"> + <summary> + Maps a packet type id to its relevant packet type, and vice-versa. + </summary> + </member> + <member name="F:NetworkLib.Packets.PacketRegistry.LibraryAssembly"> + <summary> + The assembly that represents the library, where all of the builtin packets are defined. + </summary> + </member> + <member name="F:NetworkLib.Packets.PacketRegistry.requestToResponseMap"> + <summary> + Maps a request packet to its relevant response packet, and vice-versa. + </summary> + </member> + <member name="F:NetworkLib.Packets.PacketRegistry.currentAutomaticPacketTypeIdCounter"> + <summary> + The current id for registered packets. + </summary> + </member> + <member name="M:NetworkLib.Packets.PacketRegistry.#cctor"> + <summary> + Initialises a new instance of the <see cref="T:NetworkLib.Packets.PacketRegistry"/> class. + </summary> + </member> + <member name="M:NetworkLib.Packets.PacketRegistry.GetNewPacketTypeId(System.Type)"> + <summary> + Fetches the packet type id of the given packet type. If the packet type is declared outside of the library + assembly, then its value is incremented by the <see cref="F:NetworkLib.Packets.PacketRegistry.AutomaticPacketTypeIdStartPoint"/> value. This ensure that + there are no clashes between the packet type ids of packets declared in the library and external packets. + </summary> + <param name="packetType">The packet type whose id should be fetched.</param> + <returns>The id of the given packet type.</returns> + </member> + <member name="M:NetworkLib.Packets.PacketRegistry.DeregisterPacketType(System.Type,System.Type)"> + <summary> + Deregisters the given packet type from the registry. + </summary> + <param name="requestPacketType">The request packet type to deregister, if it is registered.</param> + <param name="responsePacketType">The response packet associated with the request packet.</param> + </member> + <member name="M:NetworkLib.Packets.PacketRegistry.DeregisterPacketTypes(System.Collections.Generic.Dictionary{System.Type,System.Type})"> + <summary> + Deregisters the given packet types from the registry. + </summary> + <param name="requestToResponsePacketTypeMap">The list of packet types to deregister, if they are registered.</param> + </member> + <member name="M:NetworkLib.Packets.PacketRegistry.GetPacketId(System.Type)"> + <summary> + Returns the packet type id associated with the given packet type. + </summary> + <param name="packetType">The packet type whose id to fetch.</param> + <returns>The id of the packet type given.</returns> + </member> + <member name="M:NetworkLib.Packets.PacketRegistry.GetPacketId``1"> + <summary> + Returns the packet type id associated with the given packet type. + </summary> + <typeparam name="TPacket">The packet type whose id to fetch.</typeparam> + <returns>The id of the packet type given.</returns> + </member> + <member name="M:NetworkLib.Packets.PacketRegistry.GetPacketType(System.UInt32)"> + <summary> + Returns the packet type associated with the given id. + </summary> + <param name="packetTypeId">The packet id whose mapped type to fetch.</param> + <returns>The packet type mapped by the given id.</returns> + </member> + <member name="M:NetworkLib.Packets.PacketRegistry.GetRequestPacketType``1"> + <summary> + Returns the type of request packet mapped by the given response packet type. + </summary> + <typeparam name="TResponse">The response packet type whose request packet type to fetch.</typeparam> + <returns>The request packet type, <c>null</c> if no type is mapped.</returns> + </member> + <member name="M:NetworkLib.Packets.PacketRegistry.GetRequestPacketType(System.Type)"> + <summary> + Returns the type of request packet mapped by the given response packet type. + </summary> + <param name="responsePacketType">The response packet type whose request packet type to fetch.</param> + <returns>The request packet type, <c>null</c> if no type is mapped.</returns> + </member> + <member name="M:NetworkLib.Packets.PacketRegistry.GetResponsePacketType``1"> + <summary> + Returns the type of response packet mapped by the given request packet type. + </summary> + <typeparam name="TRequest">The request packet type whose response packet type to fetch.</typeparam> + <returns>The response packet type, <c>null</c> if no type is mapped.</returns> + </member> + <member name="M:NetworkLib.Packets.PacketRegistry.GetResponsePacketType(System.Type)"> + <summary> + Returns the type of response packet mapped by the given request packet type. + </summary> + <param name="requestPacketType">The request packet type whose response packet type to fetch.</param> + <returns>The response packet type, <c>null</c> if no type is mapped.</returns> + </member> + <member name="M:NetworkLib.Packets.PacketRegistry.RegisterPacketSourceAssemblies(System.Reflection.Assembly[])"> + <summary> + Rebuilds the packet registry, by registering every <see cref="T:NetworkLib.Interfaces.IPacket"/> inheritor in the given assemblies. + </summary> + <param name="packetSourceAssemblies"> + The assemblies from which the packet types to register are sourced. + </param> + </member> + <member name="M:NetworkLib.Packets.PacketRegistry.RegisterPacketSourceAssembly(System.Reflection.Assembly)"> + <summary> + Registers all the <see cref="T:NetworkLib.Interfaces.IPacket"/> implementors in the given assembly. + </summary> + <param name="packetSourceAssembly">The assembly whose packet types to register.</param> + </member> + <member name="M:NetworkLib.Packets.PacketRegistry.RegisterPacketType(System.Type,System.Type)"> + <summary> + Registers the given packet type to the registry. + </summary> + <param name="requestPacketType">The request packet type to register, if it is not registered.</param> + <param name="responsePacketType">The response packet associated with the request packet.</param> + </member> + <member name="M:NetworkLib.Packets.PacketRegistry.RegisterPacketTypes(System.Collections.Generic.Dictionary{System.Type,System.Type})"> + <summary> + Registers the given packet types to the registry. + </summary> + <param name="requestToResponsePacketTypeMap"> + The dictionary mapping the request packet types to register, to their relevant response packet types. + The response packet type can be null; then the request packet type is treated as a 'simple' packet. + </param> + </member> + <member name="T:NetworkLib.Packets.PacketTypeIdAttribute"> + <summary> + Allows the placing of a custom packet type on a class or struct. This is used if the class or struct + inherits from <see cref="T:NetworkLib.Interfaces.IRequestPacket"/> or <see cref="T:NetworkLib.Interfaces.IResponsePacket`1"/>. + </summary> + </member> + <member name="M:NetworkLib.Packets.PacketTypeIdAttribute.#ctor(System.UInt32)"> + <summary> + Initialises a new instance of the <see cref="T:NetworkLib.Packets.PacketTypeIdAttribute"/> attribute. + </summary> + <param name="type">The custom type id that the decorated packet type should have.</param> + </member> + <member name="P:NetworkLib.Packets.PacketTypeIdAttribute.Id"> + <summary> + The custom type id that the decorated packet type should have. This overrides the automatically generated id. + </summary> + </member> + <member name="T:NetworkLib.ComplexPacketHandler`2"> + <summary> + Represents a method that receives a request packet of the given type (<typeparamref name="TReq"/>) and + handles the request, returning a response packet of the given type (<typeparamref name="TRep"/>). + </summary> + <typeparam name="TReq">The type of request packet handled by this delegate method.</typeparam> + <typeparam name="TRep">The type of response packet returned by this delegate method.</typeparam> + <param name="requestPacket">The request packet that should be handled by this delegate method.</param> + <param name="remoteEndPoint">The remote endpoint from which the request originated.</param> + <returns>The response packet to send back to the remote endpoint from which the request originated.</returns> + </member> + <member name="T:NetworkLib.SimplePacketHandler`1"> + <summary> + Represents a method that receives a simple request packet of the given type (<typeparamref name="TReq"/>) and + handles the request, not returning any response packets. + </summary> + <typeparam name="TReq">The type of request packet handled by this delegate method.</typeparam> + <param name="requestPacket">The request packet that should be handled by this delegate method.</param> + <param name="remoteEndPoint">The remote endpoint from which the request originated.</param> + </member> + <member name="T:NetworkLib.Server"> + <summary> + Provides methods for handling connected <see cref="T:NetworkLib.Interfaces.IClient"/> instances. + </summary> + </member> + <member name="F:NetworkLib.Server.bindSocketCancellationTokenSource"> + <summary> + Cancellation token source for the <see cref="M:NetworkLib.Server.TryBindAsync(System.Net.IPAddress,System.Int32)"/> method. + </summary> + </member> + <member name="F:NetworkLib.Server.complexPacketHandlers"> + <summary> + Maps a packet type id to the complex packet handler for that packet type. + </summary> + </member> + <member name="F:NetworkLib.Server.requestPacketDeserialisers"> + <summary> + Maps a packet type id to the raw packet deserialiser that deserialises raw packets to + <see cref="T:NetworkLib.Interfaces.IRequestPacket"/> implementors. + </summary> + </member> + <member name="F:NetworkLib.Server.simplePacketHandlers"> + <summary> + Maps a packet type id to the simple packet handler for that packet type. + </summary> + </member> + <member name="F:NetworkLib.Server.PendingConnectionBacklog"> + <summary> + The maximum number of connections that are allowed in the connection backlog. + </summary> + </member> + <member name="F:NetworkLib.Server.DefaultNetworkOperationTimeout"> + <summary> + The default timeout value for all network operations. + </summary> + </member> + <member name="F:NetworkLib.Server.serverShutdownCancellationTokenSource"> + <summary> + Cancellation token source to stop handling client sockets when the server should be shut down. + </summary> + </member> + <member name="F:NetworkLib.Server.socket"> + <summary> + The <see cref="T:System.Net.Sockets.Socket"/> underlying the connection. + </summary> + </member> + <member name="F:NetworkLib.Server.socketOptions"> + <summary> + Backing field for the <see cref="P:NetworkLib.Server.SocketOptions"/> property. + </summary> + </member> + <member name="F:NetworkLib.Server.runServer"> + <summary> + Whether the server should be ran. + </summary> + </member> + <member name="M:NetworkLib.Server.#ctor"> + <summary> + Initialises a new instance of the <see cref="T:NetworkLib.Server"/> class. + </summary> + </member> + <member name="M:NetworkLib.Server.#ctor(System.Net.Sockets.SocketType,System.Net.Sockets.ProtocolType,NetworkLib.Utils.Socket_Options.SocketOptionManager)"> + <summary> + Initialises a new instance of the <see cref="T:NetworkLib.Server"/> class. + </summary> + <param name="socketType">The socket type for the underlying socket.</param> + <param name="protocolType">The protocol type for the underlying socket.</param> + <param name="socketManager">The <see cref="T:NetworkLib.Utils.Socket_Options.SocketOptions"/> manager to use.</param> + </member> + <member name="M:NetworkLib.Server.Finalize"> + <summary> + Destroys an instance of the <see cref="T:NetworkLib.Server"/> class. + </summary> + </member> + <member name="T:NetworkLib.Server.RawRequestPacketDeserialiser"> + <summary> + Represents a method that receives a raw packet, and deserialises it into an <see cref="T:NetworkLib.Interfaces.IRequestPacket"/> implementor. + </summary> + <param name="rawPacket">The raw packet that was received from the network.</param> + <returns>The deserialised instance of the packet.</returns> + </member> + <member name="E:NetworkLib.Server.ClientConnected"> + <summary> + Signifies that a connection with a remote endpoint has been made. + </summary> + </member> + <member name="E:NetworkLib.Server.ClientDisconnected"> + <summary> + Signifies that a connection with a remote endpoint has been lost. + </summary> + </member> + <member name="E:NetworkLib.Server.ServerStarted"> + <summary> + Signifies that the server was started and clients will start being accepted. + </summary> + </member> + <member name="E:NetworkLib.Server.ServerStopped"> + <summary> + Signifies that the server was stopped and clients will stop being accepted. + </summary> + </member> + <member name="P:NetworkLib.Server.SocketOptions"> + <summary> + The configured socket options for the underlying connection. + </summary> + </member> + <member name="M:NetworkLib.Server.RegisterInternalPacketHandlers"> + <summary> + Registers packet handlers for every internal library packet. + </summary> + </member> + <member name="M:NetworkLib.Server.DeserialiseRequestPacket(System.UInt32,NetworkLib.Packets.Packet@)"> + <summary> + Deserialises the given <see cref="T:NetworkLib.Packets.Packet"/> struct into an <see cref="T:NetworkLib.Interfaces.IRequestPacket"/> implementor. + </summary> + <param name="packetType">The type id of packet that we should deserialise to.</param> + <param name="rawRequestPacket">The packet that should be deserialised.</param> + <returns>The deserialised packet instance, cast to the <see cref="T:NetworkLib.Interfaces.IRequestPacket"/> interface.</returns> + </member> + <member name="M:NetworkLib.Server.Dispose(System.Boolean)"> + <summary> + Disposes of this <see cref="T:NetworkLib.Server"/> instance. + </summary> + <param name="disposing">Whether this instance is being disposed.</param> + </member> + <member name="M:NetworkLib.Server.DoHandleClientAsync(System.Object)"> + <summary> + Provides a task that represents the handling of a client. + </summary> + <param name="clientHandlerArgsObj">The object representing the passed <see cref="T:NetworkLib.Server.ClientHandlerArgs"/> instance.</param> + </member> + <member name="M:NetworkLib.Server.HandleClientAsync(NetworkLib.Server.ClientHandlerArgs)"> + <summary> + Handles a new client asynchronously. + </summary> + <param name="args">The client handler arguments that should be passed to the client handler.</param> + </member> + <member name="M:NetworkLib.Server.HandleRequestPacket(System.UInt32,NetworkLib.Interfaces.IRequestPacket@,System.Net.EndPoint@)"> + <summary> + Handles the given request packet with a registered packet handler. In this case, a complex packet handler + will override any registered simple packet handlers. + </summary> + <param name="packetType">The type id of the packet that we should handle.</param> + <param name="requestPacket">The packet instance that should be handled.</param> + <param name="remoteEndPoint">The remote endpoint from which the request packet originated.</param> + <returns>The response packet that should be sent back to the remote endpoint.</returns> + </member> + <member name="M:NetworkLib.Server.OnClientConnected(System.Net.EndPoint)"> + <summary> + Invokes the <see cref="E:NetworkLib.Server.ClientConnected"/> event. + </summary> + <param name="remoteEndPoint">The remote endpoint with which a connection was made.</param> + </member> + <member name="M:NetworkLib.Server.OnClientDisconnected(System.Net.EndPoint)"> + <summary> + Invokes the <see cref="E:NetworkLib.Server.ClientDisconnected"/> event. + </summary> + <param name="remoteEndPoint">The remote endpoint with which a connection was lost.</param> + </member> + <member name="M:NetworkLib.Server.OnServerStarted"> + <summary> + Invokes the <see cref="E:NetworkLib.Server.ServerStarted"/> event. + </summary> + </member> + <member name="M:NetworkLib.Server.OnServerStopped"> + <summary> + Invokes the <see cref="E:NetworkLib.Server.ServerStopped"/> event. + </summary> + </member> + <member name="M:NetworkLib.Server.TryBind(System.Net.IPAddress,System.Int32)"> + <summary> + Attempts to synchronously bind the underlying socket to the given local address and port. Blocks. Does not timeout. + </summary> + <param name="localAddress">The local IP address to bind to.</param> + <param name="localPort">The local port to bind to.</param> + <returns>Whether the binding was successful or not.</returns> + </member> + <member name="M:NetworkLib.Server.TryBind(System.Net.IPAddress,System.Int32,System.TimeSpan)"> + <summary> + Attempts to synchronously bind the underlying socket to the given local address and port. Blocks. + If the timeout is exceeded the binding attempt is aborted and the method returns false. + </summary> + <param name="localAddress">The local IP address to bind to.</param> + <param name="localPort">The local port to bind to.</param> + <param name="timeout">The timeout within which to attempt the binding.</param> + <returns>Whether the binding was successful or not.</returns> + </member> + <member name="M:NetworkLib.Server.TryBindAsync(System.Net.IPAddress,System.Int32)"> + <summary> + Attempts to asynchronously bind the underlying socket to the given local address and port. Does not block. + Does not timeout. + </summary> + <param name="localAddress">The local IP address to bind to.</param> + <param name="localPort">The local port to bind to.</param> + <returns>Whether the binding was successful or not.</returns> + </member> + <member name="M:NetworkLib.Server.TryBindAsync(System.Net.IPAddress,System.Int32,System.TimeSpan)"> + <summary> + Attempts to asynchronously bind the underlying socket to the given local address and port. Does not block. + If the timeout is exceeded the binding attempt is aborted and the method returns false. + </summary> + <param name="localAddress">The local IP address to bind to.</param> + <param name="localPort">The local port to bind to.</param> + <param name="timeout">The timeout within which to attempt the binding.</param> + <returns>Whether the binding was successful or not.</returns> + </member> + <member name="M:NetworkLib.Server.TryBindAsync(System.Net.EndPoint)"> + <summary> + Attempts to asynchronously bind the underlying socket to the given local endpoint. Does not block. + Does not timeout. + </summary> + <param name="localEndPoint">The local endpoint to bind to.</param> + <returns>Whether the binding was successful or not.</returns> + </member> + <member name="M:NetworkLib.Server.TryBindAsync(System.Net.EndPoint,System.TimeSpan)"> + <summary> + Attempts to asynchronously bind the underlying socket to the given local endpoint. Does not block. + If the timeout is exceeded the binding attempt is aborted and the method returns false. + </summary> + <param name="localEndPoint">The local endpoint to bind to.</param> + <param name="timeout">The timeout within which to attempt the binding.</param> + <returns>Whether the binding was successful or not.</returns> + </member> + <member name="M:NetworkLib.Server.RunAsync(System.Net.EndPoint)"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Server.Shutdown"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Server.TryDeregisterComplexPacketHandler``2(NetworkLib.ComplexPacketHandler{``0,``1}@)"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Server.TryDeregisterSimplePacketHandler``1(NetworkLib.SimplePacketHandler{``0}@)"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Server.TryRegisterComplexPacketHandler``2(NetworkLib.ComplexPacketHandler{``0,``1})"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Server.TryRegisterSimplePacketHandler``1(NetworkLib.SimplePacketHandler{``0})"> + <inheritdoc /> + </member> + <member name="T:NetworkLib.Server.ClientHandlerArgs"> + <summary> + Holds information about the arguments passed to every client handler task. + </summary> + </member> + <member name="F:NetworkLib.Server.ClientHandlerArgs.ClientEndPoint"> + <summary> + The remote endpoint for the client being handled. + </summary> + </member> + <member name="F:NetworkLib.Server.ClientHandlerArgs.ClientSocket"> + <summary> + The client handler socket for the client being handled. Is only set if using TCP. + </summary> + </member> + <member name="M:NetworkLib.Server.ClientHandlerArgs.#ctor(System.Net.EndPoint,System.Net.Sockets.Socket)"> + <summary> + Initialises a new instance of the <see cref="T:NetworkLib.Server.ClientHandlerArgs"/> struct. + </summary> + <param name="remoteEndPoint">The remote endpoint of the client that should be handled.</param> + <param name="handlerSocket">The handler socket of the client that should be handled.</param> + </member> + <member name="M:NetworkLib.Server.ClientHandlerArgs.ForTcpClientHandler(System.Net.Sockets.Socket@)"> + <summary> + Constructs a new instance of the <see cref="T:NetworkLib.Server.ClientHandlerArgs"/> for a TCP client. + </summary> + <returns>A new instance of the <see cref="T:NetworkLib.Server.ClientHandlerArgs"/>, setup for a TCP client.</returns> + </member> + <member name="M:NetworkLib.Server.ClientHandlerArgs.ForUdpClientHandler(System.Net.EndPoint@)"> + <summary> + Constructs a new instance of the <see cref="T:NetworkLib.Server.ClientHandlerArgs"/> for a UDP client. + </summary> + <returns>A new instance of the <see cref="T:NetworkLib.Server.ClientHandlerArgs"/>, setup for a UDP client.</returns> + </member> + <member name="T:NetworkLib.Servers.TcpServer"> + <summary> + Provides methods for TCP communication with connected <see cref="T:NetworkLib.Clients.TcpClient"/> instances. + </summary> + </member> + <member name="M:NetworkLib.Servers.TcpServer.#ctor"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Servers.TcpServer.HandleClientAsync(NetworkLib.Server.ClientHandlerArgs)"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Servers.TcpServer.RunAsync(System.Net.EndPoint)"> + <inheritdoc /> + </member> + <member name="T:NetworkLib.Servers.UdpServer"> + <summary> + Provides methods for UDP communication with connected <see cref="T:NetworkLib.Clients.UdpClient"/> instances. + </summary> + </member> + <member name="F:NetworkLib.Servers.UdpServer.activeClients"> + <summary> + Holds currently connected and active clients, as well as their current received packet queues. + </summary> + </member> + <member name="M:NetworkLib.Servers.UdpServer.#ctor"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Servers.UdpServer.HandleClientAsync(NetworkLib.Server.ClientHandlerArgs)"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Servers.UdpServer.RunAsync(System.Net.EndPoint)"> + <inheritdoc /> + </member> + <member name="T:NetworkLib.Utils.BiDictionary`2"> + <summary> + Represents a concurrent two-way dictionary, that can be indexed by either a key or a value. + </summary> + <typeparam name="K">The type of key that will be stored.</typeparam> + <typeparam name="V">The type of value that will be stored.</typeparam> + </member> + <member name="F:NetworkLib.Utils.BiDictionary`2.keyToValueMap"> + <summary> + Maps keys to their corresponding values. + </summary> + </member> + <member name="F:NetworkLib.Utils.BiDictionary`2.valueToKeyMap"> + <summary> + Maps values to their corresponding keys. + </summary> + </member> + <member name="M:NetworkLib.Utils.BiDictionary`2.#ctor"> + <summary> + Initialises a new instance of the <see cref="T:NetworkLib.Utils.BiDictionary`2"/> class. + </summary> + </member> + <member name="P:NetworkLib.Utils.BiDictionary`2.Item(`1)"> + <summary> + Indexes this instance with the given value. + </summary> + <param name="index">The value whose key to get or set.</param> + <returns>The fetched key.</returns> + </member> + <member name="P:NetworkLib.Utils.BiDictionary`2.Item(`0)"> + <summary> + Indexes this instance with the given key. + </summary> + <param name="index">The key whose value to get or set.</param> + <returns>The fetched value.</returns> + </member> + <member name="M:NetworkLib.Utils.BiDictionary`2.Clear"> + <summary> + Clears this instance's <see cref="F:NetworkLib.Utils.BiDictionary`2.keyToValueMap"/> and <see cref="F:NetworkLib.Utils.BiDictionary`2.valueToKeyMap"/>. + </summary> + </member> + <member name="M:NetworkLib.Utils.BiDictionary`2.ContainsKey(`0@)"> + <summary> + Whether this instance contains the given key. + </summary> + <param name="key">The key to check.</param> + <returns>Whether the given key was found.</returns> + </member> + <member name="M:NetworkLib.Utils.BiDictionary`2.ContainsValue(`1@)"> + <summary> + Whether this instance contains the given value. + </summary> + <param name="value">The value to check.</param> + <returns>Whether the given value was found.</returns> + </member> + <member name="M:NetworkLib.Utils.BiDictionary`2.SetOrUpdateKey(`1,`0)"> + <summary> + Attempts to set the key associated with the given value. + </summary> + <param name="value">The value whose key to set.</param> + <param name="key">The new value for the value's associated key.</param> + <returns>Whether the new key was correctly set.</returns> + </member> + <member name="M:NetworkLib.Utils.BiDictionary`2.SetOrUpdateValue(`0,`1)"> + <summary> + Attempts to set the value associated with the given key. + </summary> + <param name="key">The key whose value to set.</param> + <param name="value">The new value for the key's associated value.</param> + <returns>Whether the new value was correctly set.</returns> + </member> + <member name="M:NetworkLib.Utils.BiDictionary`2.TryClearKey(`1@,`0@)"> + <summary> + Attempts to remove the key associated with the given value. + </summary> + <param name="value">The value whose key to remove.</param> + <param name="key">The old key value.</param> + <returns>Whether the given value had a valid key associated with it.</returns> + </member> + <member name="M:NetworkLib.Utils.BiDictionary`2.TryClearValue(`0@,`1@)"> + <summary> + Attempts to remove the value associated with the given key. + </summary> + <param name="key">The key whose value to remove.</param> + <param name="value">The old value.</param> + <returns>Whether the given key had a valid valid associated with it.</returns> + </member> + <member name="M:NetworkLib.Utils.BiDictionary`2.TryGetKey(`1@,`0@)"> + <summary> + Attempts to get the key associated with the given value. + </summary> + <param name="value">The value whose key to get.</param> + <param name="key">The returned key.</param> + <returns>Whether the given value has a valid key associated with it.</returns> + </member> + <member name="M:NetworkLib.Utils.BiDictionary`2.TryGetValue(`0@,`1@)"> + <summary> + Attempts to get the value associated with the given key. + </summary> + <param name="key">The key whose value to get.</param> + <param name="value">The returned value.</param> + <returns>Whether the given key as a valid value associated with it.</returns> + </member> + <member name="M:NetworkLib.Utils.BiDictionary`2.TrySetKey(`1@,`0@)"> + <summary> + Attempts to set the key associated with the given value. + </summary> + <param name="value">The value whose key to set.</param> + <param name="key">The key which should be set for the given value.</param> + <returns>Whether the given value was successfully set.</returns> + </member> + <member name="M:NetworkLib.Utils.BiDictionary`2.TrySetValue(`0@,`1@)"> + <summary> + Attempts to set the value associated with the given key. + </summary> + <param name="key">The key whose value to set.</param> + <param name="value">The value which should be set for the given key.</param> + <returns>Whether the given key was successfully set.</returns> + </member> + <member name="T:NetworkLib.Utils.Constants"> + <summary> + Holds internal default configurations and constants. + </summary> + </member> + <member name="F:NetworkLib.Utils.Constants.DefaultBufferSize"> + <summary> + The default size of an <see cref="T:NetworkLib.Interfaces.IClient"/> and <see cref="T:NetworkLib.Interfaces.IServer"/> instance's receive buffer. + </summary> + </member> + <member name="F:NetworkLib.Utils.Constants.UdpMaxBufferSize"> + <summary> + The largest byte buffer that can be sent via UDP. + </summary> + </member> + <member name="T:NetworkLib.Utils.Conversion.ObjectState"> + <summary> + Enumerates the possible states of a network object before and after deserialisation. + </summary> + </member> + <member name="F:NetworkLib.Utils.Conversion.ObjectState.Null"> + <summary> + The network object is null, so there is nothing to read from the network stream. + </summary> + </member> + <member name="F:NetworkLib.Utils.Conversion.ObjectState.NotNull"> + <summary> + The network object is not null, so there is something to read from the network stream. + </summary> + </member> + <member name="T:NetworkLib.Utils.Conversion.Converter"> + <summary> + Provides methods to serialise and deserialise an <see cref="T:System.Object"/> to and from its binary form. + </summary> + </member> + <member name="F:NetworkLib.Utils.Conversion.Converter.packetPropertyCache"> + <summary> + Caches packet <see cref="T:System.Type"/>s and their relevant <see cref="T:System.Reflection.PropertyInfo"/>s, to avoid slow and unnecessary reflection. + </summary> + </member> + <member name="F:NetworkLib.Utils.Conversion.Converter.packetPropertyCacheLock"> + <summary> + An object to synchronise multi-threaded access to the <see cref="F:NetworkLib.Utils.Conversion.Converter.packetPropertyCache"/>. + </summary> + </member> + <member name="M:NetworkLib.Utils.Conversion.Converter.DeserialiseObjectFromReader(System.Object,System.IO.BinaryReader)"> + <summary> + Deserialises all the properties on the given <see cref="T:System.Object"/> that can be deserialised from the given + <see cref="T:System.IO.BinaryReader"/>s underlying <see cref="T:System.IO.MemoryStream"/>. + </summary> + <param name="obj"> + The <see cref="T:System.Object"/> whose properties to deserialise using the given <see cref="T:System.IO.BinaryReader"/>. + </param> + <param name="binaryReader"> + The <see cref="T:System.IO.BinaryReader"/> from whose underlying <see cref="T:System.IO.MemoryStream"/> to deserialise the properties + of the given <see cref="T:System.Object"/>. + </param> + <returns>The given <see cref="T:System.Object"/> with all deserialisable properties set.</returns> + <remarks> + This method can only deserialise properties that lack the custom <see cref="T:NetworkLib.Utils.Conversion.NetworkIgnorePropertyAttribute"/>. + Any other properties will be left at their default values. + </remarks> + </member> + <member name="M:NetworkLib.Utils.Conversion.Converter.DeserialiseObjectFromReader(System.Reflection.PropertyInfo,System.IO.BinaryReader)"> + <summary> + Deserialises the given <see cref="T:System.Reflection.PropertyInfo"/> from the given <see cref="T:System.IO.BinaryReader"/>s underlying + <see cref="T:System.IO.MemoryStream"/>. + </summary> + <param name="propertyInfo"> + The <see cref="T:System.Reflection.PropertyInfo"/> to deserialise from the given <see cref="T:System.IO.BinaryReader"/>s underlying + <see cref="T:System.IO.MemoryStream"/>. + </param> + <param name="binaryReader"> + The <see cref="T:System.IO.BinaryReader"/> from whose underlying <see cref="T:System.IO.MemoryStream"/> to deserialise the given + <see cref="T:System.Reflection.PropertyInfo"/>. + </param> + <returns> + The <see cref="T:System.Object"/> deserialised from the <see cref="T:System.IO.MemoryStream"/>. This can be null if the + <see cref="T:NetworkLib.Utils.Conversion.ObjectState"/> is <see cref="F:NetworkLib.Utils.Conversion.ObjectState.Null"/>. + </returns> + </member> + <member name="M:NetworkLib.Utils.Conversion.Converter.GetTypeProperties(System.Type)"> + <summary> + Returns an array of the <see cref="T:System.Reflection.PropertyInfo"/>s that need to be serialised on the given <see cref="T:System.Type"/>. + If the given <see cref="T:System.Type"/> has already been cached, it will use the cached <see cref="T:System.Reflection.PropertyInfo"/> array, + to save CPU time. + </summary> + <param name="type">The <see cref="T:System.Type"/> whose serialisable properties to get.</param> + <returns> + An array of all <see cref="T:System.Reflection.PropertyInfo"/>s that should be serialised on the given <see cref="T:System.Type"/> + </returns> + </member> + <member name="M:NetworkLib.Utils.Conversion.Converter.ReadArrayFromStream(System.Reflection.PropertyInfo,System.IO.BinaryReader)"> + <summary> + Deserialises the given <see cref="T:System.Array"/> from the given <see cref="T:System.IO.BinaryReader"/>s underlying + <see cref="T:System.IO.MemoryStream"/>. Uses <see cref="M:NetworkLib.Utils.Conversion.Converter.DeserialiseObjectFromReader(System.Object,System.IO.BinaryReader)"/> to serialise + each of the <see cref="T:System.Array"/>s elements to the stream. + </summary> + <param name="propertyInfo">The <see cref="T:System.Reflection.PropertyInfo"/> holding the <see cref="T:System.Array"/>.</param> + <param name="binaryReader"> + The <see cref="T:System.IO.BinaryReader"/> from whose underlying <see cref="T:System.IO.MemoryStream"/> to deserialise the given + <see cref="T:System.Reflection.PropertyInfo"/>. + </param> + <exception cref="T:System.ArgumentNullException">Thrown if the <see cref="T:System.Array"/>s elements do not have a type.</exception> + </member> + <member name="M:NetworkLib.Utils.Conversion.Converter.ReadListFromStream(System.Reflection.PropertyInfo,System.IO.BinaryReader)"> + <summary> + Deserialises the given <see cref="T:System.Collections.IList"/> from the given <see cref="T:System.IO.BinaryReader"/>s underlying + <see cref="T:System.IO.MemoryStream"/>. Uses <see cref="M:NetworkLib.Utils.Conversion.Converter.DeserialiseObjectFromReader(System.Object,System.IO.BinaryReader)"/> to serialise + each of the <see cref="T:System.Collections.IList"/>s elements to the stream. + </summary> + <param name="propertyInfo">The <see cref="T:System.Reflection.PropertyInfo"/> holding the <see cref="T:System.Collections.IList"/>.</param> + <param name="binaryReader"> + The <see cref="T:System.IO.BinaryReader"/> from whose underlying <see cref="T:System.IO.MemoryStream"/> to deserialise the given + <see cref="T:System.Reflection.PropertyInfo"/>. + </param> + <exception cref="T:System.NullReferenceException"> + Thrown if the <see cref="T:System.Collections.IList"/> held in the <see cref="T:System.IO.MemoryStream"/> is null, or if the <see cref="T:System.Collections.IList"/>s + elements do not have a type. + </exception> + </member> + <member name="M:NetworkLib.Utils.Conversion.Converter.ReadPrimitiveFromStream(System.Type,System.IO.BinaryReader)"> + <summary> + Reads a primitive type from the given <see cref="T:System.IO.BinaryReader"/>s underlying <see cref="T:System.IO.MemoryStream"/> + and returns it. + </summary> + <param name="type"> + The <see cref="T:System.Type"/> of the primitive to read from the given <see cref="T:System.IO.BinaryReader"/>s underlying <see cref="T:System.IO.MemoryStream"/>. + </param> + <param name="binaryReader"> + The <see cref="T:System.IO.BinaryReader"/> from whose underlying <see cref="T:System.IO.MemoryStream"/> to read the primitive. + </param> + <returns> + The primitive that was read from the given <see cref="T:System.IO.BinaryReader"/>s underlying <see cref="T:System.IO.MemoryStream"/>. + </returns> + <exception cref="T:System.NotSupportedException"> + Thrown whenever a <see cref="T:System.Type"/> is passed to this method that is not a primitive. + </exception> + </member> + <member name="M:NetworkLib.Utils.Conversion.Converter.SerialiseArrayToWriter(System.Object,System.Reflection.PropertyInfo,System.IO.BinaryWriter)"> + <summary> + Serialises the given <see cref="T:System.Array"/> to the given <see cref="T:System.IO.BinaryWriter"/>s underlying <see cref="T:System.IO.MemoryStream"/>. + Uses <see cref="M:NetworkLib.Utils.Conversion.Converter.SerialiseObjectToWriter(System.Object,System.IO.BinaryWriter)"/> to serialise each of the <see cref="T:System.Array"/>s + elements to the stream. + </summary> + <param name="obj"> + The <see cref="T:System.Array"/> to serialise to the given <see cref="T:System.IO.BinaryWriter"/>s underlying <see cref="T:System.IO.MemoryStream"/>. + </param> + <param name="propertyInfo">The <see cref="T:System.Reflection.PropertyInfo"/> holding the <see cref="T:System.Array"/>.</param> + <param name="binaryWriter"> + The <see cref="T:System.IO.BinaryWriter"/> to whose underlying <see cref="T:System.IO.MemoryStream"/> to serialise the given <see cref="T:System.Reflection.PropertyInfo"/>. + </param> + <exception cref="T:System.NullReferenceException"> + Thrown if the <see cref="T:System.Array"/> held in the given <see cref="T:System.Reflection.PropertyInfo"/> is null, or if the <see cref="T:System.Array"/>s + elements do not have a type. + </exception> + </member> + <member name="M:NetworkLib.Utils.Conversion.Converter.SerialiseListToWriter(System.Object,System.Reflection.PropertyInfo,System.IO.BinaryWriter)"> + <summary> + Serialises the given <see cref="T:System.Collections.IList"/> to the given <see cref="T:System.IO.BinaryWriter"/>s underlying <see cref="T:System.IO.MemoryStream"/>. + Uses <see cref="M:NetworkLib.Utils.Conversion.Converter.SerialiseObjectToWriter(System.Object,System.IO.BinaryWriter)"/> to serialise each of the <see cref="T:System.Collections.IList"/>s + elements to the stream. + </summary> + <param name="obj"> + The <see cref="T:System.Collections.IList"/> to serialise to the given <see cref="T:System.IO.BinaryWriter"/>s underlying <see cref="T:System.IO.MemoryStream"/>. + </param> + <param name="propertyInfo">The <see cref="T:System.Reflection.PropertyInfo"/> holding the <see cref="T:System.Collections.IList"/>. </param> + <param name="binaryWriter"> + The <see cref="T:System.IO.BinaryWriter"/> to whose underlying <see cref="T:System.IO.MemoryStream"/> to serialise the given <see cref="T:System.Reflection.PropertyInfo"/>. + </param> + <exception cref="T:System.NullReferenceException"> + Thrown if the <see cref="T:System.Collections.IList"/> held in the given <see cref="T:System.Reflection.PropertyInfo"/> is null, or if the <see cref="T:System.Collections.IList"/>s + elements do not have a type. + </exception> + </member> + <member name="M:NetworkLib.Utils.Conversion.Converter.SerialiseObjectToWriter(System.Object,System.IO.BinaryWriter)"> + <summary> + Serialises all the properties on the given <see cref="T:System.Object"/> that need to be serialised to the given + <see cref="T:System.IO.BinaryWriter"/>s underlying <see cref="T:System.IO.MemoryStream"/>. + </summary> + <param name="obj"> + The <see cref="T:System.Object"/> whose properties to serialise using the given <see cref="T:System.IO.BinaryWriter"/>. + </param> + <param name="binaryWriter"> + The <see cref="T:System.IO.BinaryWriter"/> to whose underlying <see cref="T:System.IO.MemoryStream"/> to serialise the properties of + the given <see cref="T:System.Object"/>. + </param> + <remarks> + This method can only serialise properties that lack the custom <see cref="T:NetworkLib.Utils.Conversion.NetworkIgnorePropertyAttribute"/>. + </remarks> + </member> + <member name="M:NetworkLib.Utils.Conversion.Converter.SerialiseObjectToWriter(System.Object,System.Reflection.PropertyInfo,System.IO.BinaryWriter)"> + <summary> + Serialises the given <see cref="T:System.Reflection.PropertyInfo"/> to the given <see cref="T:System.IO.BinaryWriter"/>s underlying <see cref="T:System.IO.MemoryStream"/>. + </summary> + <param name="obj">The <see cref="T:System.Object"/> whose <see cref="T:System.Reflection.PropertyInfo"/> value to serialise.</param> + <param name="propertyInfo"> + The <see cref="T:System.Reflection.PropertyInfo"/> to serialise to the given <see cref="T:System.IO.BinaryWriter"/>s underlying <see cref="T:System.IO.MemoryStream"/>. + </param> + <param name="binaryWriter"> + The <see cref="T:System.IO.BinaryWriter"/> to whose underlying <see cref="T:System.IO.MemoryStream"/> to serialise the given <see cref="T:System.Reflection.PropertyInfo"/>. + </param> + </member> + <member name="M:NetworkLib.Utils.Conversion.Converter.GetBytes(System.Object)"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Utils.Conversion.Converter.GetBytes``1(``0)"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Utils.Conversion.Converter.GetObject(System.Type,System.Byte[])"> + <inheritdoc /> + </member> + <member name="M:NetworkLib.Utils.Conversion.Converter.GetObject``1(System.Byte[])"> + <inheritdoc /> + </member> + <member name="T:NetworkLib.Utils.Conversion.ConverterHelper"> + <summary> + Provides helper methods for serialising and deserialising packets to and from their binary form. + </summary> + </member> + <member name="F:NetworkLib.Utils.Conversion.ConverterHelper.PropertyIgnoreAttributeType"> + <summary> + The <see cref="T:System.Type"/> of the custom property that will cause a property to be ignored during serialisation. + See <see cref="T:NetworkLib.Utils.Conversion.NetworkIgnorePropertyAttribute"/> for more information regarding its usage. + </summary> + </member> + <member name="M:NetworkLib.Utils.Conversion.ConverterHelper.GetObjectProperties(System.Object)"> + <summary> + Gets all the <see cref="T:System.Reflection.PropertyInfo"/>s of the given <see cref="T:System.Object"/> that should be serialised and + returns them as an array. See <see cref="M:NetworkLib.Utils.Conversion.ConverterHelper.GetTypeProperties(System.Type)"/> for more information. + </summary> + <param name="_object">The <see cref="T:System.Object"/> whose <see cref="T:System.Reflection.PropertyInfo"/>s to read.</param> + <returns>An array of all the <see cref="T:System.Reflection.PropertyInfo"/>s on the given object.</returns> + </member> + <member name="M:NetworkLib.Utils.Conversion.ConverterHelper.GetTypeProperties(System.Type)"> + <summary> + Gets all the <see cref="T:System.Reflection.PropertyInfo"/>s of the given <see cref="T:System.Type"/> that should be serialised (lack the + <see cref="T:NetworkLib.Utils.Conversion.NetworkIgnorePropertyAttribute"/> attribute) and returns them as an array. + </summary> + <param name="type">The <see cref="T:System.Type"/> whose <see cref="T:System.Reflection.PropertyInfo"/>s to read.</param> + <returns>An array of all the <see cref="T:System.Reflection.PropertyInfo"/>s on the given object.</returns> + </member> + <member name="M:NetworkLib.Utils.Conversion.ConverterHelper.InstantiateGenericObject``1"> + <summary> + Instantiates and returns a default <see cref="T:System.Object"/> of the given generic type. + </summary> + <typeparam name="O">The generic type of the object to instantiate.</typeparam> + <returns>The default instance of the given generic type.</returns> + </member> + <member name="M:NetworkLib.Utils.Conversion.ConverterHelper.InstantiateObject(System.Type)"> + <summary> + Instantiates and returns a default <see cref="T:System.Object"/> of the given <see cref="T:System.Type"/>. + </summary> + <param name="objectType">The <see cref="T:System.Type"/> to instantiate.</param> + <returns>The default instance of the given <see cref="T:System.Type"/>.</returns> + </member> + <member name="M:NetworkLib.Utils.Conversion.ConverterHelper.PropertyIsPrimitive(System.Reflection.PropertyInfo)"> + <summary> + Checks whether the underlying <see cref="T:System.Type"/> of the given property is a primitive type. See + <see cref="M:NetworkLib.Utils.Conversion.ConverterHelper.TypeIsPrimitive(System.Type)"/> for more information regarding its usage. + </summary> + <param name="property">The <see cref="T:System.Reflection.PropertyInfo"/> to test.</param> + <returns>Whether the given <see cref="T:System.Reflection.PropertyInfo"/>s underlying <see cref="T:System.Type"/> is primitive.</returns> + <remarks> + If the given properties underlying element <see cref="T:System.Type"/> is null, then the method will return false. + </remarks> + </member> + <member name="M:NetworkLib.Utils.Conversion.ConverterHelper.TypeIsPrimitive(System.Type)"> + <summary> + Checks whether the given <see cref="T:System.Type"/> is a primitive type, that is if it lives in the 'System' namespace. + </summary> + <param name="type">The <see cref="T:System.Type"/> to test.</param> + <returns>Whether the given <see cref="T:System.Type"/> is a primitive.</returns> + <remarks>If the given <see cref="T:System.Type"/> is null, then the method will return false.</remarks> + </member> + <member name="T:NetworkLib.Utils.Conversion.EndianAwareBitConverter"> + <summary> + Wraps the <see cref="T:System.BitConverter"/> class to provide conversion that is endian-aware. + </summary> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.ReverseAsNeeded(System.Span{System.Byte},System.Boolean)"> + <summary> + Reverses the given bytes if the endian-nes doesn't match. + </summary> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.GetBytes(System.Boolean,System.Boolean)"> + <inheritdoc cref="M:System.BitConverter.GetBytes(System.Boolean)"/> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.GetBytes(System.Char,System.Boolean)"> + <inheritdoc cref="M:System.BitConverter.GetBytes(System.Char)"/> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.GetBytes(System.Double,System.Boolean)"> + <inheritdoc cref="M:System.BitConverter.GetBytes(System.Double)"/> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.GetBytes(System.Single,System.Boolean)"> + <inheritdoc cref="M:System.BitConverter.GetBytes(System.Single)"/> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.GetBytes(System.Int32,System.Boolean)"> + <inheritdoc cref="M:System.BitConverter.GetBytes(System.Int32)"/> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.GetBytes(System.Int64,System.Boolean)"> + <inheritdoc cref="M:System.BitConverter.GetBytes(System.Int64)"/> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.GetBytes(System.Int16,System.Boolean)"> + <inheritdoc cref="M:System.BitConverter.GetBytes(System.Int16)"/> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.GetBytes(System.UInt32,System.Boolean)"> + <inheritdoc cref="M:System.BitConverter.GetBytes(System.UInt32)"/> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.GetBytes(System.UInt64,System.Boolean)"> + <inheritdoc cref="M:System.BitConverter.GetBytes(System.UInt64)"/> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.GetBytes(System.UInt16,System.Boolean)"> + <inheritdoc cref="M:System.BitConverter.GetBytes(System.UInt16)"/> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.ToBoolean(System.Span{System.Byte},System.Boolean)"> + <inheritdoc cref="M:System.BitConverter.ToBoolean(System.ReadOnlySpan{System.Byte})"/> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.ToChar(System.Span{System.Byte},System.Boolean)"> + <inheritdoc cref="M:System.BitConverter.ToChar(System.ReadOnlySpan{System.Byte})"/> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.ToDouble(System.Span{System.Byte},System.Boolean)"> + <inheritdoc cref="M:System.BitConverter.ToDouble(System.ReadOnlySpan{System.Byte})"/> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.ToInt16(System.Span{System.Byte},System.Boolean)"> + <inheritdoc cref="M:System.BitConverter.ToInt16(System.ReadOnlySpan{System.Byte})"/> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.ToInt32(System.Span{System.Byte},System.Boolean)"> + <inheritdoc cref="M:System.BitConverter.ToInt32(System.ReadOnlySpan{System.Byte})"/> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.ToInt64(System.Span{System.Byte},System.Boolean)"> + <inheritdoc cref="M:System.BitConverter.ToInt64(System.ReadOnlySpan{System.Byte})"/> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.ToSingle(System.Span{System.Byte},System.Boolean)"> + <inheritdoc cref="M:System.BitConverter.ToSingle(System.ReadOnlySpan{System.Byte})"/> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.ToUInt16(System.Byte[],System.Boolean)"> + <inheritdoc cref="M:System.BitConverter.ToUInt16(System.ReadOnlySpan{System.Byte})"/> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.ToUInt32(System.Span{System.Byte},System.Boolean)"> + <inheritdoc cref="M:System.BitConverter.ToUInt32(System.ReadOnlySpan{System.Byte})"/> + </member> + <member name="M:NetworkLib.Utils.Conversion.EndianAwareBitConverter.ToUInt64(System.Span{System.Byte},System.Boolean)"> + <inheritdoc cref="M:System.BitConverter.ToUInt64(System.ReadOnlySpan{System.Byte})"/> + </member> + <member name="T:NetworkLib.Utils.Conversion.NetworkIgnorePropertyAttribute"> + <summary> + Marks a property to be ignored by the binary serialiser. + </summary> + </member> + <member name="T:NetworkLib.Utils.NetworkOperations"> + <summary> + Helper class for asynchronously performing common network operations, for both the UDP and TCP protocols. + </summary> + </member> + <member name="M:NetworkLib.Utils.NetworkOperations.ReadAsync(System.Net.Sockets.Socket,System.Int32,System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)"> + <summary> + Reads the specified amount of data asynchronously from the network, via the given socket. + The given <see cref="T:System.Net.Sockets.SocketFlags"/> are associated with the read, and the given <see cref="T:System.Threading.CancellationToken"/> + is used to allow for asynchronous task cancellation. + </summary> + <param name="socket">The socket which should read data from the network.</param> + <param name="count">The number of bytes to read from the network.</param> + <param name="socketFlags">The socket flags associated with the receive operation.</param> + <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> + <returns>The result of the receive operation.</returns> + </member> + <member name="M:NetworkLib.Utils.NetworkOperations.ReadFromAsync(System.Net.Sockets.Socket,System.Net.EndPoint,System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)"> + <summary> + Reads a datagram asynchronously from the given remote endpoint, via the given socket. + The given <see cref="T:System.Net.Sockets.SocketFlags"/> are associated with the read, and the given <see cref="T:System.Threading.CancellationToken"/> + is used to allow for asynchronous task cancellation. + </summary> + <param name="socket">The socket which should read data from the network.</param> + <param name="remoteEndPoint">The remote endpoint from which data should be read.</param> + <param name="socketFlags">The socket flags associated with the receive operation.</param> + <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> + <returns>The result of the receive operation.</returns> + </member> + <member name="M:NetworkLib.Utils.NetworkOperations.ReadPacketAsync(System.Net.Sockets.Socket,System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)"> + <summary> + Reads a packet asynchronously from network, via the given socket. The given <see cref="T:System.Net.Sockets.SocketFlags"/> are + associated with the read, and the given <see cref="T:System.Threading.CancellationToken"/> is used to allow for asynchronous + task cancellation. + </summary> + <param name="socket">The socket which should read the packet from the network.</param> + <param name="socketFlags">The socket flags associated with the receive operation.</param> + <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> + <returns>The read packet.</returns> + </member> + <member name="M:NetworkLib.Utils.NetworkOperations.ReadPacketFromAsync(System.Net.Sockets.Socket,System.Net.EndPoint,System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)"> + <summary> + Reads a packet asynchronously from the given remote endpoint, via the given socket. The given + <see cref="T:System.Net.Sockets.SocketFlags"/> are associated with the read, and the given <see cref="T:System.Threading.CancellationToken"/> is + used to allow for asynchronous task cancellation. + </summary> + <param name="socket">The socket which should read the packet from the network.</param> + <param name="remoteEndPoint">The remote endpoint from which a packet should be read.</param> + <param name="socketFlags">The socket flags associated with the receive operation.</param> + <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> + <returns>The read packet and associated transmission results.</returns> + </member> + <member name="M:NetworkLib.Utils.NetworkOperations.WriteAsync(System.Net.Sockets.Socket,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)"> + <summary> + Writes the given buffer asynchronously to the network, via the given socket. + The given <see cref="T:System.Net.Sockets.SocketFlags"/> are associated with the write, and the given <see cref="T:System.Threading.CancellationToken"/> + is used to allow for asynchronous task cancellation. + </summary> + <param name="socket">The socket which should write data to the network.</param> + <param name="buffer">The buffer that should be written to the network.</param> + <param name="socketFlags">The socket flags associated with the send operation.</param> + <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> + </member> + <member name="M:NetworkLib.Utils.NetworkOperations.WritePacketAsync(System.Net.Sockets.Socket,NetworkLib.Packets.Packet,System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)"> + <summary> + Writes the given packet asynchronously to the network, via the given socket. The given <see cref="T:System.Net.Sockets.SocketFlags"/> + are associated with the write, and the given <see cref="T:System.Threading.CancellationToken"/> is used to allow for asynchronous + task cancellation. + </summary> + <param name="socket">The socket which should write data to the network.</param> + <param name="packet">The packet that should be written to the network.</param> + <param name="socketFlags">The socket flags associated with the send operation.</param> + <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> + </member> + <member name="M:NetworkLib.Utils.NetworkOperations.WritePacketToAsync(System.Net.Sockets.Socket,System.Net.EndPoint,NetworkLib.Packets.Packet,System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)"> + <summary> + Writes the given packet asynchronously to the given remote endpoint, via the given socket. + The given <see cref="T:System.Net.Sockets.SocketFlags"/> are associated with the write, and the given <see cref="T:System.Threading.CancellationToken"/> + is used to allow for asynchronous task cancellation. + </summary> + <param name="socket">The socket which should write data to the network.</param> + <param name="remoteEndPoint">The remote endpoint to which data should be written.</param> + <param name="packet">The packet that should be written to the remote endpoint.</param> + <param name="socketFlags">The socket flags associated with the send operation.</param> + <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> + </member> + <member name="M:NetworkLib.Utils.NetworkOperations.WriteToAsync(System.Net.Sockets.Socket,System.Net.EndPoint,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)"> + <summary> + Writes the given buffer asynchronously to the given remote endpoint, via the given socket. + The given <see cref="T:System.Net.Sockets.SocketFlags"/> are associated with the write, and the given <see cref="T:System.Threading.CancellationToken"/> + is used to allow for asynchronous task cancellation. + </summary> + <param name="socket">The socket which should write data to the network.</param> + <param name="remoteEndPoint">The remote endpoint to which data should be written.</param> + <param name="buffer">The buffer that should be written to the network.</param> + <param name="socketFlags">The socket flags associated with the send operation.</param> + <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> + </member> + <member name="T:NetworkLib.Utils.Socket_Options.DefaultSocketOptions"> + <summary> + Allows for manipulation of socket options. + </summary> + </member> + <member name="M:NetworkLib.Utils.Socket_Options.DefaultSocketOptions.#ctor(System.Net.Sockets.Socket@)"> + <inheritdoc /> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.DefaultSocketOptions.HopLimit"> + <inheritdoc /> + <exception cref="T:System.NotSupportedException"> + This property is not supported when using the default socket option manager. + </exception> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.DefaultSocketOptions.IsRoutingEnabled"> + <inheritdoc /> + <exception cref="T:System.NotSupportedException"> + This property is not supported when using the default socket option manager. + </exception> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.DefaultSocketOptions.UseLoopback"> + <inheritdoc /> + <exception cref="T:System.NotSupportedException"> + This property is not supported when using the default socket option manager. + </exception> + </member> + <member name="T:NetworkLib.Utils.Socket_Options.SocketOptionManager"> + <summary> + Enumerates the possible socket option manager types to instantiate for a <see cref="T:NetworkLib.Client"/> and <see cref="T:NetworkLib.Server"/> + instance. + </summary> + </member> + <member name="F:NetworkLib.Utils.Socket_Options.SocketOptionManager.Default"> + <summary> + Causes a <see cref="T:NetworkLib.Utils.Socket_Options.DefaultSocketOptions"/> instance to be created as the socket option manager. + This means that certain socket options will throw an error, as the socket type is not specified. + </summary> + </member> + <member name="F:NetworkLib.Utils.Socket_Options.SocketOptionManager.Tcp"> + <summary> + Causes a <see cref="T:NetworkLib.Utils.Socket_Options.TcpSocketOptions"/> instance to be created as the socket option manager. + </summary> + </member> + <member name="F:NetworkLib.Utils.Socket_Options.SocketOptionManager.Udp"> + <summary> + Causes a <see cref="T:NetworkLib.Utils.Socket_Options.UdpSocketOptions"/> instance to be created as the socket option manager. + </summary> + </member> + <member name="T:NetworkLib.Utils.Socket_Options.SocketOptions"> + <summary> + Allows for manipulation of socket options. + </summary> + </member> + <member name="F:NetworkLib.Utils.Socket_Options.SocketOptions.managedSocket"> + <summary> + The <see cref="T:System.Net.Sockets.Socket"/> instance whose settings are being managed. + </summary> + </member> + <member name="M:NetworkLib.Utils.Socket_Options.SocketOptions.#ctor(System.Net.Sockets.Socket@)"> + <summary> + Initialises a new instance of the <see cref="T:NetworkLib.Utils.Socket_Options.SocketOptions"/> class. + </summary> + <param name="socket">The <see cref="T:System.Net.Sockets.Socket"/> instance whose options should be managed.</param> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.SocketOptions.DualMode"> + <summary> + Whether this <see cref="T:System.Net.Sockets.Socket"/> can operate in dual IPv4 / IPv6 mode. + </summary> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.SocketOptions.ForceFlush"> + <summary> + Whether sending a packet flushes underlying <see cref="T:System.Net.Sockets.NetworkStream"/>. + </summary> + <remarks> + This value is only used in a <see cref="T:System.Net.Sockets.TcpClient"/> instance, which uses a <see cref="T:System.Net.Sockets.NetworkStream"/> + to send and receive data. A <see cref="T:System.Net.Sockets.UdpClient"/> is unaffected by this value. + </remarks> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.SocketOptions.Fragment"> + <summary> + Whether this <see cref="T:System.Net.Sockets.Socket"/> is allowed to fragment frames that are too large to send in one go. + </summary> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.SocketOptions.HopLimit"> + <summary> + The hop limit for packets sent by this <see cref="T:System.Net.Sockets.Socket"/>. Comparable to IPv4s TTL (Time To Live). + </summary> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.SocketOptions.IsChecksumEnabled"> + <summary> + Whether a checksum should be created for each UDP packet sent. + </summary> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.SocketOptions.IsRoutingEnabled"> + <summary> + Whether the packet should be sent directly to its destination or allowed to be routed through multiple destinations + first. + </summary> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.SocketOptions.LocalEndPoint"> + <summary> + The local <see cref="T:System.Net.EndPoint"/> for the <see cref="F:NetworkLib.Utils.Socket_Options.SocketOptions.managedSocket"/>. + </summary> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.SocketOptions.LocalIPEndPoint"> + <summary> + The local <see cref="T:System.Net.IPEndPoint"/> for this <see cref="T:System.Net.Sockets.Socket"/> instance. + </summary> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.SocketOptions.RemoteEndPoint"> + <summary> + The remote <see cref="T:System.Net.EndPoint"/> for the <see cref="F:NetworkLib.Utils.Socket_Options.SocketOptions.managedSocket"/>. + </summary> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.SocketOptions.RemoteIPEndPoint"> + <summary> + The remote <see cref="T:System.Net.IPEndPoint"/> that this <see cref="T:System.Net.Sockets.Socket"/> instance communicates with. + </summary> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.SocketOptions.Ttl"> + <summary> + The 'Time To Live' for this <see cref="T:System.Net.Sockets.Socket"/>. + </summary> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.SocketOptions.UseLoopback"> + <summary> + Whether this <see cref="T:System.Net.Sockets.Socket"/> should use a loopback address and bypass hardware. + </summary> + </member> + <member name="T:NetworkLib.Utils.Socket_Options.TcpSocketOptions"> + <summary> + Allows for manipulation of TCP socket options. + </summary> + </member> + <member name="M:NetworkLib.Utils.Socket_Options.TcpSocketOptions.#ctor(System.Net.Sockets.Socket@)"> + <inheritdoc /> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.TcpSocketOptions.HopLimit"> + <inheritdoc /> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.TcpSocketOptions.IsRoutingEnabled"> + <inheritdoc /> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.TcpSocketOptions.UseLoopback"> + <inheritdoc /> + </member> + <member name="T:NetworkLib.Utils.Socket_Options.UdpSocketOptions"> + <summary> + Allows for manipulation of UDP socket options. + </summary> + </member> + <member name="M:NetworkLib.Utils.Socket_Options.UdpSocketOptions.#ctor(System.Net.Sockets.Socket@)"> + <inheritdoc /> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.UdpSocketOptions.HopLimit"> + <inheritdoc /> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.UdpSocketOptions.IsRoutingEnabled"> + <inheritdoc /> + </member> + <member name="P:NetworkLib.Utils.Socket_Options.UdpSocketOptions.UseLoopback"> + <inheritdoc /> + </member> + <member name="T:NetworkLib.Utils.TransmissionResult"> + <summary> + Represents the result of a socket transmission. + </summary> + </member> + <member name="F:NetworkLib.Utils.TransmissionResult.Buffer"> + <summary> + The byte buffer that was transmitted across the network. + </summary> + </member> + <member name="F:NetworkLib.Utils.TransmissionResult.Count"> + <summary> + The number of bytes that were transmitted across the network. + </summary> + </member> + <member name="F:NetworkLib.Utils.TransmissionResult.RemoteEndPoint"> + <summary> + The remote endpoint to which the buffer was transmitted. + </summary> + </member> + <member name="M:NetworkLib.Utils.TransmissionResult.#ctor(System.Memory{System.Byte},System.Int32,System.Net.EndPoint)"> + <summary> + Initialises a new instance of the <see cref="T:NetworkLib.Utils.TransmissionResult"/> struct. + </summary> + <param name="buffer">The byte buffer that was transmitted.</param> + <param name="count">The number of bytes that were transmitted.</param> + <param name="remoteEndPoint">The remote endpoint to which the buffer was transmitted.</param> + </member> + </members> +</doc> diff --git a/NetSharp/NetSharp/Packets/Builtin/ConnectPacket.cs b/NetSharp/NetSharp/Packets/Builtin/ConnectPacket.cs @@ -0,0 +1,33 @@ +using System; +using NetSharp.Interfaces; + +namespace NetSharp.Packets.Builtin +{ + /// <summary> + /// A simple connection request packet for the UDP protocol. + /// </summary> + [PacketTypeId(1)] + internal class ConnectPacket : IRequestPacket + { + /// <inheritdoc /> + public void AfterDeserialisation() + { + } + + /// <inheritdoc /> + public void BeforeSerialisation() + { + } + + /// <inheritdoc /> + public void Deserialise(ReadOnlyMemory<byte> serialisedObject) + { + } + + /// <inheritdoc /> + public ReadOnlyMemory<byte> Serialise() + { + return ReadOnlyMemory<byte>.Empty; + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Packets/Builtin/ConnectResponsePacket.cs b/NetSharp/NetSharp/Packets/Builtin/ConnectResponsePacket.cs @@ -0,0 +1,36 @@ +using System; +using NetSharp.Interfaces; + +namespace NetSharp.Packets.Builtin +{ + /// <summary> + /// A response packet for the <see cref="ConnectPacket"/>. + /// </summary> + [PacketTypeId(2)] + internal class ConnectResponsePacket : IResponsePacket<ConnectPacket> + { + /// <inheritdoc /> + public ConnectPacket RequestPacket { get; set; } = new ConnectPacket(); + + /// <inheritdoc /> + public void AfterDeserialisation() + { + } + + /// <inheritdoc /> + public void BeforeSerialisation() + { + } + + /// <inheritdoc /> + public void Deserialise(ReadOnlyMemory<byte> serialisedObject) + { + } + + /// <inheritdoc /> + public ReadOnlyMemory<byte> Serialise() + { + return ReadOnlyMemory<byte>.Empty; + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Packets/Builtin/DataPacket.cs b/NetSharp/NetSharp/Packets/Builtin/DataPacket.cs @@ -0,0 +1,56 @@ +using System; +using NetSharp.Interfaces; + +namespace NetSharp.Packets.Builtin +{ + /// <summary> + /// A simple data transfer packet, that allows for the transmission of an arbitrary number of frames. + /// </summary> + [PacketTypeId(5)] + public class DataPacket : IRequestPacket + { + /// <summary> + /// The data that should be transferred across the network. + /// </summary> + public ReadOnlyMemory<byte> RequestBuffer; + + /// <summary> + /// Initialises a new instance of the <see cref="DataPacket"/> class. + /// </summary> + public DataPacket() + { + RequestBuffer = new byte[0]; + } + + /// <summary> + /// Initialises a new instance of the <see cref="DataPacket"/> class. + /// </summary> + /// <param name="buffer">The data that this request packet should contain.</param> + public DataPacket(ReadOnlyMemory<byte> buffer) + { + RequestBuffer = buffer; + } + + /// <inheritdoc /> + public void AfterDeserialisation() + { + } + + /// <inheritdoc /> + public void BeforeSerialisation() + { + } + + /// <inheritdoc /> + public void Deserialise(ReadOnlyMemory<byte> serialisedObject) + { + RequestBuffer = serialisedObject; + } + + /// <inheritdoc /> + public ReadOnlyMemory<byte> Serialise() + { + return RequestBuffer; + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Packets/Builtin/DataResponsePacket.cs b/NetSharp/NetSharp/Packets/Builtin/DataResponsePacket.cs @@ -0,0 +1,59 @@ +using System; +using NetSharp.Interfaces; + +namespace NetSharp.Packets.Builtin +{ + /// <summary> + /// A response packet for the <see cref="DataPacket"/>. + /// </summary> + [PacketTypeId(6)] + public class DataResponsePacket : IResponsePacket<DataPacket> + { + /// <summary> + /// The data that should be transferred across the network. + /// </summary> + public ReadOnlyMemory<byte> ResponseBuffer; + + /// <summary> + /// Initialises a new instance of the <see cref="DataResponsePacket"/> class. + /// </summary> + public DataResponsePacket() + { + ResponseBuffer = new byte[0]; + } + + /// <summary> + /// Initialises a new instance of the <see cref="DataResponsePacket"/> class. + /// </summary> + /// <param name="buffer">The data that this response packet should contain.</param> + public DataResponsePacket(ReadOnlyMemory<byte> buffer) + { + ResponseBuffer = buffer; + } + + /// <inheritdoc /> + public DataPacket RequestPacket { get; internal set; } = new DataPacket(); + + /// <inheritdoc /> + public void AfterDeserialisation() + { + } + + /// <inheritdoc /> + public void BeforeSerialisation() + { + } + + /// <inheritdoc /> + public void Deserialise(ReadOnlyMemory<byte> serialisedObject) + { + ResponseBuffer = serialisedObject; + } + + /// <inheritdoc /> + public ReadOnlyMemory<byte> Serialise() + { + return ResponseBuffer; + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Packets/Builtin/DisconnectPacket.cs b/NetSharp/NetSharp/Packets/Builtin/DisconnectPacket.cs @@ -0,0 +1,33 @@ +using System; +using NetSharp.Interfaces; + +namespace NetSharp.Packets.Builtin +{ + /// <summary> + /// A simple disconnect packet for the UDP protocol. + /// </summary> + [PacketTypeId(0)] + internal class DisconnectPacket : IRequestPacket + { + /// <inheritdoc /> + public void AfterDeserialisation() + { + } + + /// <inheritdoc /> + public void BeforeSerialisation() + { + } + + /// <inheritdoc /> + public void Deserialise(ReadOnlyMemory<byte> serialisedObject) + { + } + + /// <inheritdoc /> + public ReadOnlyMemory<byte> Serialise() + { + return ReadOnlyMemory<byte>.Empty; + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Packets/Builtin/PingPacket.cs b/NetSharp/NetSharp/Packets/Builtin/PingPacket.cs @@ -0,0 +1,33 @@ +using System; +using NetSharp.Interfaces; + +namespace NetSharp.Packets.Builtin +{ + /// <summary> + /// A simple ping request packet for heartbeat monitoring and RTT measurement. + /// </summary> + [PacketTypeId(3)] + public class PingPacket : IRequestPacket + { + /// <inheritdoc /> + public void AfterDeserialisation() + { + } + + /// <inheritdoc /> + public void BeforeSerialisation() + { + } + + /// <inheritdoc /> + public void Deserialise(ReadOnlyMemory<byte> serialisedObject) + { + } + + /// <inheritdoc /> + public ReadOnlyMemory<byte> Serialise() + { + return ReadOnlyMemory<byte>.Empty; + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Packets/Builtin/PingResponsePacket.cs b/NetSharp/NetSharp/Packets/Builtin/PingResponsePacket.cs @@ -0,0 +1,36 @@ +using System; +using NetSharp.Interfaces; + +namespace NetSharp.Packets.Builtin +{ + /// <summary> + /// A response packet for the <see cref="PingPacket"/>. + /// </summary> + [PacketTypeId(4)] + public class PingResponsePacket : IResponsePacket<PingPacket> + { + /// <inheritdoc /> + public PingPacket RequestPacket { get; internal set; } = new PingPacket(); + + /// <inheritdoc /> + public void AfterDeserialisation() + { + } + + /// <inheritdoc /> + public void BeforeSerialisation() + { + } + + /// <inheritdoc /> + public void Deserialise(ReadOnlyMemory<byte> serialisedObject) + { + } + + /// <inheritdoc /> + public ReadOnlyMemory<byte> Serialise() + { + return ReadOnlyMemory<byte>.Empty; + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Packets/Builtin/SimpleDataPacket.cs b/NetSharp/NetSharp/Packets/Builtin/SimpleDataPacket.cs @@ -0,0 +1,56 @@ +using System; +using NetSharp.Interfaces; + +namespace NetSharp.Packets.Builtin +{ + /// <summary> + /// A simple one-time-use data transfer packet, that allows for the transmission of an arbitrary number of frames. + /// </summary> + [PacketTypeId(7)] + public class SimpleDataPacket : IRequestPacket + { + /// <summary> + /// The data that should be transferred across the network. + /// </summary> + public ReadOnlyMemory<byte> RequestBuffer; + + /// <summary> + /// Initialises a new instance of the <see cref="SimpleDataPacket"/> class. + /// </summary> + public SimpleDataPacket() + { + RequestBuffer = new byte[0]; + } + + /// <summary> + /// Initialises a new instance of the <see cref="SimpleDataPacket"/> class. + /// </summary> + /// <param name="buffer">The data that this request packet should contain.</param> + public SimpleDataPacket(ReadOnlyMemory<byte> buffer) + { + RequestBuffer = buffer; + } + + /// <inheritdoc /> + public void AfterDeserialisation() + { + } + + /// <inheritdoc /> + public void BeforeSerialisation() + { + } + + /// <inheritdoc /> + public void Deserialise(ReadOnlyMemory<byte> serialisedObject) + { + RequestBuffer = serialisedObject; + } + + /// <inheritdoc /> + public ReadOnlyMemory<byte> Serialise() + { + return RequestBuffer; + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Packets/NetworkErrorCode.cs b/NetSharp/NetSharp/Packets/NetworkErrorCode.cs @@ -0,0 +1,18 @@ +namespace NetSharp.Packets +{ + /// <summary> + /// Enumerates the possible error codes for network operations, being held in the packet. + /// </summary> + public enum NetworkErrorCode : uint + { + /// <summary> + /// Signifies that there was no error during transmission. + /// </summary> + Ok = 0, + + /// <summary> + /// A generic error occurred during packet transmission. + /// </summary> + Error = 1 << 1, + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Packets/Packet.cs b/NetSharp/NetSharp/Packets/Packet.cs @@ -0,0 +1,97 @@ +using System; +using NetSharp.Utils.Conversion; + +namespace NetSharp.Packets +{ + /// <summary> + /// Represents a packet that is transmitted over the network. + /// </summary> + public readonly struct Packet + { + /// <summary> + /// The size of the packet header in bytes. + /// </summary> + public static readonly int HeaderSize = sizeof(int) + sizeof(uint) + sizeof(uint); + + /// <summary> + /// The data held in this packet. + /// </summary> + public readonly ReadOnlyMemory<byte> Buffer; + + /// <summary> + /// The size of the packet's data. + /// </summary> + public readonly int Count; + + /// <summary> + /// The error code for this packet. + /// </summary> + public readonly NetworkErrorCode ErrorCode; + + /// <summary> + /// The packet type. + /// </summary> + public readonly uint Type; + + /// <summary> + /// Initialises a new instance of the <see cref="Packet"/> struct. + /// </summary> + /// <param name="data">The data that should be transmitted in the packet.</param> + /// <param name="type">The packet type.</param> + /// <param name="errorCode">The error code associated with this transmission.</param> + public Packet(ReadOnlyMemory<byte> data, uint type, NetworkErrorCode errorCode) + { + Buffer = data; + Count = data.Length; + Type = type; + + ErrorCode = errorCode; + } + + /// <summary> + /// Returns the total size of the serialised packet (including the header) in bytes. + /// </summary> + /// <returns>The total size of the serialised packet (including the header) in bytes.</returns> + public int TotalSize { get { return HeaderSize + Count; } } + + /// <summary> + /// Deserialises the given buffer into a packet instance. + /// </summary> + /// <param name="buffer">The byte buffer to serialise.</param> + /// <returns>The deserialised packet instance.</returns> + public static Packet Deserialise(Memory<byte> buffer) + { + Span<byte> serialisedType = buffer.Slice(sizeof(int), sizeof(uint)).Span; + Span<byte> serialisedErrorCode = buffer.Slice(sizeof(int) + sizeof(uint), sizeof(uint)).Span; + Memory<byte> serialisedData = buffer.Slice(HeaderSize); + + return new Packet(serialisedData, + EndianAwareBitConverter.ToUInt32(serialisedType), + (NetworkErrorCode)EndianAwareBitConverter.ToUInt32(serialisedErrorCode)); + } + + /// <summary> + /// Serialises the given packet instance into a single byte buffer. + /// </summary> + /// <param name="instance">The packet instance to serialise.</param> + /// <returns>The byte buffer that represents the packet instance.</returns> + public static Memory<byte> Serialise(Packet instance) + { + byte[] buffer = new byte[HeaderSize + instance.Count]; + + Span<byte> serialisedInstanceLength = new Span<byte>(buffer, 0, sizeof(int)); + EndianAwareBitConverter.GetBytes(instance.Count).CopyTo(serialisedInstanceLength); + + Span<byte> serialisedInstanceType = new Span<byte>(buffer, sizeof(int), sizeof(uint)); + EndianAwareBitConverter.GetBytes(instance.Type).CopyTo(serialisedInstanceType); + + Span<byte> serialisedErrorCode = new Span<byte>(buffer, sizeof(int) + sizeof(uint), sizeof(uint)); + EndianAwareBitConverter.GetBytes((uint)instance.ErrorCode).CopyTo(serialisedErrorCode); + + Memory<byte> serialisedInstanceData = new Memory<byte>(buffer, HeaderSize, instance.Count); + instance.Buffer.CopyTo(serialisedInstanceData); + + return new Memory<byte>(buffer); + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Packets/PacketRegistry.cs b/NetSharp/NetSharp/Packets/PacketRegistry.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using NetSharp.Interfaces; +using NetSharp.Utils; + +namespace NetSharp.Packets +{ + /// <summary> + /// Provides method of registering request packets and their relevant response packets, as well as mapping their ids. + /// </summary> + internal static class PacketRegistry + { + /// <summary> + /// The start id for automatically generated packet type ids. Any custom packet type ids lower than this value + /// that come from external assemblies will be incremented by this value, to ensure that there are no clashes. + /// </summary> + private const uint AutomaticPacketTypeIdStartPoint = 100; + + /// <summary> + /// The lock object for synchronising access to the <see cref="currentAutomaticPacketTypeIdCounter"/> field. + /// </summary> + private static readonly object currentAutomaticPacketTypeIdCounterLockObject = new object(); + + /// <summary> + /// Maps a packet type id to its relevant packet type, and vice-versa. + /// </summary> + private static readonly BiDictionary<uint, Type> idToPacketTypeMap; + + /// <summary> + /// The assembly that represents the library, where all of the builtin packets are defined. + /// </summary> + private static readonly Assembly LibraryAssembly = Assembly.GetAssembly(typeof(PacketRegistry)); + + /// <summary> + /// Maps a request packet to its relevant response packet, and vice-versa. + /// </summary> + private static readonly BiDictionary<Type, Type> requestToResponseMap; + + /// <summary> + /// The current id for registered packets. + /// </summary> + private static uint currentAutomaticPacketTypeIdCounter = AutomaticPacketTypeIdStartPoint; + + /// <summary> + /// Initialises a new instance of the <see cref="PacketRegistry"/> class. + /// </summary> + static PacketRegistry() + { + idToPacketTypeMap = new BiDictionary<uint, Type>(); + + requestToResponseMap = new BiDictionary<Type, Type>(); + + RegisterPacketSourceAssembly(LibraryAssembly); + } + + /// <summary> + /// Fetches the packet type id of the given packet type. If the packet type is declared outside of the library + /// assembly, then its value is incremented by the <see cref="AutomaticPacketTypeIdStartPoint"/> value. This ensure that + /// there are no clashes between the packet type ids of packets declared in the library and external packets. + /// </summary> + /// <param name="packetType">The packet type whose id should be fetched.</param> + /// <returns>The id of the given packet type.</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint GetNewPacketTypeId(Type packetType) + { + uint packetTypeId; + + if (packetType.Assembly != LibraryAssembly) + { + lock (currentAutomaticPacketTypeIdCounterLockObject) + { + packetTypeId = currentAutomaticPacketTypeIdCounter++; + } + } + else + { + PacketTypeIdAttribute customPacketTypeIdAttribute = + (PacketTypeIdAttribute)packetType.GetCustomAttributes(typeof(PacketTypeIdAttribute)).First(); + + packetTypeId = customPacketTypeIdAttribute.Id; + } + + return packetTypeId; + } + + /// <summary> + /// Deregisters the given packet type from the registry. + /// </summary> + /// <param name="requestPacketType">The request packet type to deregister, if it is registered.</param> + /// <param name="responsePacketType">The response packet associated with the request packet.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void DeregisterPacketType(Type requestPacketType, Type? responsePacketType) + { + if (idToPacketTypeMap.ContainsValue(requestPacketType)) + { + idToPacketTypeMap.TryClearKey(requestPacketType, out _); + } + + // if the given response packet is null, then skip deregistering a response packet type + if (responsePacketType == default) return; + + if (!idToPacketTypeMap.ContainsValue(responsePacketType)) + { + idToPacketTypeMap.TryClearKey(responsePacketType, out _); + } + + if (!requestToResponseMap.ContainsValue(requestPacketType)) + { + requestToResponseMap.TryClearKey(requestPacketType, out _); + } + } + + /// <summary> + /// Deregisters the given packet types from the registry. + /// </summary> + /// <param name="requestToResponsePacketTypeMap">The list of packet types to deregister, if they are registered.</param> + internal static void DeregisterPacketTypes(Dictionary<Type, Type?> requestToResponsePacketTypeMap) + { + foreach ((Type requestPacketType, Type? responsePacketType) in requestToResponsePacketTypeMap) + { + DeregisterPacketType(requestPacketType, responsePacketType); + } + } + + /// <summary> + /// Returns the packet type id associated with the given packet type. + /// </summary> + /// <param name="packetType">The packet type whose id to fetch.</param> + /// <returns>The id of the packet type given.</returns> + internal static uint GetPacketId(Type packetType) => idToPacketTypeMap[packetType]; + + /// <summary> + /// Returns the packet type id associated with the given packet type. + /// </summary> + /// <typeparam name="TPacket">The packet type whose id to fetch.</typeparam> + /// <returns>The id of the packet type given.</returns> + internal static uint GetPacketId<TPacket>() where TPacket : IPacket => idToPacketTypeMap[typeof(TPacket)]; + + /// <summary> + /// Returns the packet type associated with the given id. + /// </summary> + /// <param name="packetTypeId">The packet id whose mapped type to fetch.</param> + /// <returns>The packet type mapped by the given id.</returns> + internal static Type GetPacketType(uint packetTypeId) => idToPacketTypeMap[packetTypeId]; + + /// <summary> + /// Returns the type of request packet mapped by the given response packet type. + /// </summary> + /// <typeparam name="TResponse">The response packet type whose request packet type to fetch.</typeparam> + /// <returns>The request packet type, <c>null</c> if no type is mapped.</returns> + internal static Type GetRequestPacketType<TResponse>() where TResponse : IResponsePacket<IRequestPacket> + { + requestToResponseMap.TryGetKey(typeof(TResponse), out Type requestPacketType); + + return requestPacketType; + } + + /// <summary> + /// Returns the type of request packet mapped by the given response packet type. + /// </summary> + /// <param name="responsePacketType">The response packet type whose request packet type to fetch.</param> + /// <returns>The request packet type, <c>null</c> if no type is mapped.</returns> + internal static Type GetRequestPacketType(Type responsePacketType) + { + requestToResponseMap.TryGetKey(responsePacketType, out Type requestPacketType); + + return requestPacketType; + } + + /// <summary> + /// Returns the type of response packet mapped by the given request packet type. + /// </summary> + /// <typeparam name="TRequest">The request packet type whose response packet type to fetch.</typeparam> + /// <returns>The response packet type, <c>null</c> if no type is mapped.</returns> + internal static Type? GetResponsePacketType<TRequest>() where TRequest : IRequestPacket + { + return requestToResponseMap.TryGetValue(typeof(TRequest), out Type responsePacketType) ? responsePacketType : null; + } + + /// <summary> + /// Returns the type of response packet mapped by the given request packet type. + /// </summary> + /// <param name="requestPacketType">The request packet type whose response packet type to fetch.</param> + /// <returns>The response packet type, <c>null</c> if no type is mapped.</returns> + internal static Type? GetResponsePacketType(Type requestPacketType) + { + return requestToResponseMap.TryGetValue(requestPacketType, out Type responsePacketType) ? responsePacketType : null; + } + + /// <summary> + /// Rebuilds the packet registry, by registering every <see cref="IPacket"/> inheritor in the given assemblies. + /// </summary> + /// <param name="packetSourceAssemblies"> + /// The assemblies from which the packet types to register are sourced. + /// </param> + internal static void RegisterPacketSourceAssemblies(params Assembly[] packetSourceAssemblies) + { + foreach (Assembly assembly in packetSourceAssemblies) + { + RegisterPacketSourceAssembly(assembly); + } + } + + /// <summary> + /// Registers all the <see cref="IPacket"/> implementors in the given assembly. + /// </summary> + /// <param name="packetSourceAssembly">The assembly whose packet types to register.</param> + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void RegisterPacketSourceAssembly(Assembly packetSourceAssembly) + { + Dictionary<Type, Type?> requestToResponseTypeMap = new Dictionary<Type, Type?>(); + + foreach (Type type in packetSourceAssembly.DefinedTypes) + { + foreach (Type interfaceType in type.GetInterfaces()) + { + if (!typeof(IPacket).IsAssignableFrom(interfaceType) || interfaceType == typeof(IPacket)) + { + continue; + } + + if (interfaceType == typeof(IRequestPacket)) + { + requestToResponseTypeMap[type] = default; + } + else //if (interfaceType == typeof(IResponsePacket<>)) + { + Type handledRequestType = interfaceType.GetGenericArguments()[0]; + + requestToResponseTypeMap[handledRequestType] = type; + } + } + } + + RegisterPacketTypes(requestToResponseTypeMap); + } + + /// <summary> + /// Registers the given packet type to the registry. + /// </summary> + /// <param name="requestPacketType">The request packet type to register, if it is not registered.</param> + /// <param name="responsePacketType">The response packet associated with the request packet.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void RegisterPacketType(Type requestPacketType, Type? responsePacketType) + { + if (!idToPacketTypeMap.ContainsValue(requestPacketType)) + { + uint requestPacketTypeId = GetNewPacketTypeId(requestPacketType); + + idToPacketTypeMap.TrySetValue(requestPacketTypeId, requestPacketType); + } + + // if the given response packet is null, then skip registering a response packet type + if (responsePacketType == default) return; + + if (!idToPacketTypeMap.ContainsValue(responsePacketType)) + { + uint responsePacketTypeId = GetNewPacketTypeId(responsePacketType); + + idToPacketTypeMap.TrySetValue(responsePacketTypeId, responsePacketType); + } + + if (!requestToResponseMap.ContainsValue(requestPacketType)) + { + requestToResponseMap.TrySetValue(requestPacketType, responsePacketType); + } + } + + /// <summary> + /// Registers the given packet types to the registry. + /// </summary> + /// <param name="requestToResponsePacketTypeMap"> + /// The dictionary mapping the request packet types to register, to their relevant response packet types. + /// The response packet type can be null; then the request packet type is treated as a 'simple' packet. + /// </param> + internal static void RegisterPacketTypes(Dictionary<Type, Type?> requestToResponsePacketTypeMap) + { + foreach ((Type requestPacketType, Type? responsePacketType) in requestToResponsePacketTypeMap) + { + RegisterPacketType(requestPacketType, responsePacketType); + } + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Packets/PacketTypeIdAttribute.cs b/NetSharp/NetSharp/Packets/PacketTypeIdAttribute.cs @@ -0,0 +1,27 @@ +using System; +using NetSharp.Interfaces; + +namespace NetSharp.Packets +{ + /// <summary> + /// Allows the placing of a custom packet type on a class or struct. This is used if the class or struct + /// inherits from <see cref="IRequestPacket"/> or <see cref="IResponsePacket{TReq}"/>. + /// </summary> + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] + internal sealed class PacketTypeIdAttribute : Attribute + { + /// <summary> + /// Initialises a new instance of the <see cref="PacketTypeIdAttribute"/> attribute. + /// </summary> + /// <param name="type">The custom type id that the decorated packet type should have.</param> + internal PacketTypeIdAttribute(uint type) + { + Id = type; + } + + /// <summary> + /// The custom type id that the decorated packet type should have. This overrides the automatically generated id. + /// </summary> + internal uint Id { get; } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Server.cs b/NetSharp/NetSharp/Server.cs @@ -0,0 +1,622 @@ +using System; +using System.Collections.Concurrent; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using NetSharp.Interfaces; +using NetSharp.Packets; +using NetSharp.Packets.Builtin; +using NetSharp.Utils.Socket_Options; + +namespace NetSharp +{ + /// <summary> + /// Represents a method that receives a request packet of the given type (<typeparamref name="TReq"/>) and + /// handles the request, returning a response packet of the given type (<typeparamref name="TRep"/>). + /// </summary> + /// <typeparam name="TReq">The type of request packet handled by this delegate method.</typeparam> + /// <typeparam name="TRep">The type of response packet returned by this delegate method.</typeparam> + /// <param name="requestPacket">The request packet that should be handled by this delegate method.</param> + /// <param name="remoteEndPoint">The remote endpoint from which the request originated.</param> + /// <returns>The response packet to send back to the remote endpoint from which the request originated.</returns> + public delegate TRep ComplexPacketHandler<in TReq, out TRep>(TReq requestPacket, EndPoint remoteEndPoint) + where TReq : class, IRequestPacket, new() where TRep : class, IResponsePacket<TReq>, new(); + + /// <summary> + /// Represents a method that receives a simple request packet of the given type (<typeparamref name="TReq"/>) and + /// handles the request, not returning any response packets. + /// </summary> + /// <typeparam name="TReq">The type of request packet handled by this delegate method.</typeparam> + /// <param name="requestPacket">The request packet that should be handled by this delegate method.</param> + /// <param name="remoteEndPoint">The remote endpoint from which the request originated.</param> + public delegate void SimplePacketHandler<in TReq>(TReq requestPacket, EndPoint remoteEndPoint) + where TReq : class, IRequestPacket, new(); + + /// <summary> + /// Provides methods for handling connected <see cref="IClient"/> instances. + /// </summary> + public abstract class Server : Connection, IServer, IDisposable + { + /// <summary> + /// Cancellation token source for the <see cref="TryBindAsync(IPAddress,int)"/> method. + /// </summary> + private readonly CancellationTokenSource bindSocketCancellationTokenSource; + + /// <summary> + /// Maps a packet type id to the complex packet handler for that packet type. + /// </summary> + private readonly ConcurrentDictionary<uint, Func<IRequestPacket, EndPoint, IResponsePacket<IRequestPacket>>> + complexPacketHandlers; + + /// <summary> + /// Maps a packet type id to the raw packet deserialiser that deserialises raw packets to + /// <see cref="IRequestPacket"/> implementors. + /// </summary> + private readonly ConcurrentDictionary<uint, RawRequestPacketDeserialiser> requestPacketDeserialisers; + + /// <summary> + /// Maps a packet type id to the simple packet handler for that packet type. + /// </summary> + private readonly ConcurrentDictionary<uint, Action<IRequestPacket, EndPoint>> simplePacketHandlers; + + /// <summary> + /// The maximum number of connections that are allowed in the connection backlog. + /// </summary> + protected const int PendingConnectionBacklog = 100; + + /// <summary> + /// The default timeout value for all network operations. + /// </summary> + protected static readonly TimeSpan DefaultNetworkOperationTimeout = TimeSpan.FromMilliseconds(10_000); + + /// <summary> + /// Cancellation token source to stop handling client sockets when the server should be shut down. + /// </summary> + protected readonly CancellationTokenSource serverShutdownCancellationTokenSource; + + /// <summary> + /// The <see cref="Socket"/> underlying the connection. + /// </summary> + protected readonly Socket socket; + + /// <summary> + /// Backing field for the <see cref="SocketOptions"/> property. + /// </summary> + protected readonly SocketOptions socketOptions; + + /// <summary> + /// Whether the server should be ran. + /// </summary> + protected volatile bool runServer; + + /// <summary> + /// Initialises a new instance of the <see cref="Server"/> class. + /// </summary> + private Server() + { + serverShutdownCancellationTokenSource = new CancellationTokenSource(); + bindSocketCancellationTokenSource = new CancellationTokenSource(); + + requestPacketDeserialisers = new ConcurrentDictionary<uint, RawRequestPacketDeserialiser>(); + + simplePacketHandlers = new ConcurrentDictionary<uint, Action<IRequestPacket, EndPoint>>(); + complexPacketHandlers = + new ConcurrentDictionary<uint, Func<IRequestPacket, EndPoint, IResponsePacket<IRequestPacket>>>(); + + RegisterInternalPacketHandlers(); + + socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + socketOptions = new DefaultSocketOptions(ref socket); + } + + /// <summary> + /// Initialises a new instance of the <see cref="Server"/> class. + /// </summary> + /// <param name="socketType">The socket type for the underlying socket.</param> + /// <param name="protocolType">The protocol type for the underlying socket.</param> + /// <param name="socketManager">The <see cref="Utils.Socket_Options.SocketOptions"/> manager to use.</param> + protected Server(SocketType socketType, ProtocolType protocolType, SocketOptionManager socketManager) : this() + { + socket = new Socket(AddressFamily.InterNetwork, socketType, protocolType); + + socketOptions = socketManager switch + { + SocketOptionManager.Tcp => new TcpSocketOptions(ref socket) as SocketOptions, + SocketOptionManager.Udp => new UdpSocketOptions(ref socket) as SocketOptions, + _ => new DefaultSocketOptions(ref socket), + }; + } + + /// <summary> + /// Destroys an instance of the <see cref="Server"/> class. + /// </summary> + ~Server() + { + Dispose(false); + } + + /// <summary> + /// Represents a method that receives a raw packet, and deserialises it into an <see cref="IRequestPacket"/> implementor. + /// </summary> + /// <param name="rawPacket">The raw packet that was received from the network.</param> + /// <returns>The deserialised instance of the packet.</returns> + private delegate IRequestPacket RawRequestPacketDeserialiser(in Packet rawPacket); + + /// <summary> + /// Signifies that a connection with a remote endpoint has been made. + /// </summary> + public event Action<EndPoint>? ClientConnected; + + //protected IResponsePacket<IRequestPacket> DeserialiseResponsePacket(in Packet) + /// <summary> + /// Signifies that a connection with a remote endpoint has been lost. + /// </summary> + public event Action<EndPoint>? ClientDisconnected; + + /// <summary> + /// Signifies that the server was started and clients will start being accepted. + /// </summary> + public event Action? ServerStarted; + + /// <summary> + /// Signifies that the server was stopped and clients will stop being accepted. + /// </summary> + public event Action? ServerStopped; + + /// <summary> + /// The configured socket options for the underlying connection. + /// </summary> + public SocketOptions SocketOptions + { + get { return socketOptions; } + } + + /// <summary> + /// Registers packet handlers for every internal library packet. + /// </summary> + private void RegisterInternalPacketHandlers() + { + TryRegisterSimplePacketHandler((DisconnectPacket packet, EndPoint remoteEndPoint) => + { + OnClientDisconnected(remoteEndPoint); + }); + + TryRegisterSimplePacketHandler((SimpleDataPacket packet, EndPoint remoteEndPoint) => + { +#if DEBUG + logger.LogMessage($"Received {packet.RequestBuffer.Length} bytes from {remoteEndPoint}"); +#endif + }); + + TryRegisterComplexPacketHandler((ConnectPacket packet, EndPoint remoteEndPoint) => + { + OnClientConnected(remoteEndPoint); + + return new ConnectResponsePacket { RequestPacket = packet }; + }); + + TryRegisterComplexPacketHandler((PingPacket packet, EndPoint remoteEndPoint) => + new PingResponsePacket { RequestPacket = packet }); + + TryRegisterComplexPacketHandler((DataPacket packet, EndPoint remoteEndPoint) => + { +#if DEBUG + logger.LogMessage($"Received {packet.RequestBuffer.Length} bytes from {remoteEndPoint}"); + logger.LogMessage($"Sending {packet.RequestBuffer.Length} bytes to {remoteEndPoint}"); +#endif + return new DataResponsePacket { RequestPacket = packet, ResponseBuffer = packet.RequestBuffer }; + }); + } + + /// <summary> + /// Deserialises the given <see cref="Packet"/> struct into an <see cref="IRequestPacket"/> implementor. + /// </summary> + /// <param name="packetType">The type id of packet that we should deserialise to.</param> + /// <param name="rawRequestPacket">The packet that should be deserialised.</param> + /// <returns>The deserialised packet instance, cast to the <see cref="IRequestPacket"/> interface.</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected IRequestPacket? DeserialiseRequestPacket(uint packetType, in Packet rawRequestPacket) + { + if (requestPacketDeserialisers.TryGetValue(packetType, out RawRequestPacketDeserialiser deserialiser)) + { + return deserialiser.Invoke(rawRequestPacket); + } +#if DEBUG + logger.LogWarning($"No packet deserialiser was registered for packet of type {packetType}"); +#endif + return default; + } + + /// <summary> + /// Disposes of this <see cref="Server"/> instance. + /// </summary> + /// <param name="disposing">Whether this instance is being disposed.</param> + protected override void Dispose(bool disposing) + { + if (disposing) + { + bindSocketCancellationTokenSource?.Cancel(); + bindSocketCancellationTokenSource?.Dispose(); + + socket?.Dispose(); + } + + base.Dispose(disposing); + } + + /// <summary> + /// Provides a task that represents the handling of a client. + /// </summary> + /// <param name="clientHandlerArgsObj">The object representing the passed <see cref="ClientHandlerArgs"/> instance.</param> + protected async Task DoHandleClientAsync(object clientHandlerArgsObj) + { + ClientHandlerArgs clientHandlerArgs = (ClientHandlerArgs)clientHandlerArgsObj; + + try + { + await HandleClientAsync(clientHandlerArgs); + } + catch (TaskCanceledException) { logger.LogMessage("Client handling was cancelled via a task cancellation."); } + catch (OperationCanceledException) { logger.LogMessage("Client handling was cancelled via an operation cancellation."); } + catch (Exception ex) + { + logger.LogException("Exception during client handling", ex); + } + finally + { + if (clientHandlerArgs.ClientSocket != null) + { + logger.LogMessage("Closing and releasing all resources associated with client handler socket"); + + clientHandlerArgs.ClientSocket.Close(1); + clientHandlerArgs.ClientSocket.Shutdown(SocketShutdown.Both); + clientHandlerArgs.ClientSocket.Disconnect(true); + clientHandlerArgs.ClientSocket.Dispose(); + } + } + } + + /// <summary> + /// Handles a new client asynchronously. + /// </summary> + /// <param name="args">The client handler arguments that should be passed to the client handler.</param> + protected abstract Task HandleClientAsync(ClientHandlerArgs args); + + /// <summary> + /// Handles the given request packet with a registered packet handler. In this case, a complex packet handler + /// will override any registered simple packet handlers. + /// </summary> + /// <param name="packetType">The type id of the packet that we should handle.</param> + /// <param name="requestPacket">The packet instance that should be handled.</param> + /// <param name="remoteEndPoint">The remote endpoint from which the request packet originated.</param> + /// <returns>The response packet that should be sent back to the remote endpoint.</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected IResponsePacket<IRequestPacket>? HandleRequestPacket(uint packetType, in IRequestPacket requestPacket, + in EndPoint remoteEndPoint) + { + try + { + if (complexPacketHandlers.ContainsKey(packetType)) + { + IResponsePacket<IRequestPacket> response = + (IResponsePacket<IRequestPacket>)complexPacketHandlers[packetType] + .DynamicInvoke(requestPacket, remoteEndPoint); + + return response; + } + + if (simplePacketHandlers.ContainsKey(packetType)) + { + simplePacketHandlers[packetType].DynamicInvoke(requestPacket, remoteEndPoint); + return null; + } +#if DEBUG + logger.LogWarning($"No packet handler was registered for packet of type {packetType}"); +#endif + } + catch (Exception ex) + { + logger.LogException( + $"Exception when invoking packet handler for packet (type: {packetType}) received from {remoteEndPoint}", + ex); + } + + return default; + } + + /// <summary> + /// Invokes the <see cref="ClientConnected"/> event. + /// </summary> + /// <param name="remoteEndPoint">The remote endpoint with which a connection was made.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void OnClientConnected(EndPoint remoteEndPoint) => ClientConnected?.Invoke(remoteEndPoint); + + /// <summary> + /// Invokes the <see cref="ClientDisconnected"/> event. + /// </summary> + /// <param name="remoteEndPoint">The remote endpoint with which a connection was lost.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void OnClientDisconnected(EndPoint remoteEndPoint) => ClientDisconnected?.Invoke(remoteEndPoint); + + /// <summary> + /// Invokes the <see cref="ServerStarted"/> event. + /// </summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void OnServerStarted() => ServerStarted?.Invoke(); + + /// <summary> + /// Invokes the <see cref="ServerStopped"/> event. + /// </summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void OnServerStopped() => ServerStopped?.Invoke(); + + /// <summary> + /// Attempts to synchronously bind the underlying socket to the given local address and port. Blocks. Does not timeout. + /// </summary> + /// <param name="localAddress">The local IP address to bind to.</param> + /// <param name="localPort">The local port to bind to.</param> + /// <returns>Whether the binding was successful or not.</returns> + protected bool TryBind(IPAddress localAddress, int localPort) => + TryBindAsync(localAddress, localPort, Timeout.InfiniteTimeSpan).Result; + + /// <summary> + /// Attempts to synchronously bind the underlying socket to the given local address and port. Blocks. + /// If the timeout is exceeded the binding attempt is aborted and the method returns false. + /// </summary> + /// <param name="localAddress">The local IP address to bind to.</param> + /// <param name="localPort">The local port to bind to.</param> + /// <param name="timeout">The timeout within which to attempt the binding.</param> + /// <returns>Whether the binding was successful or not.</returns> + protected bool TryBind(IPAddress localAddress, int localPort, TimeSpan timeout) => + TryBindAsync(localAddress, localPort, timeout).Result; + + /// <summary> + /// Attempts to asynchronously bind the underlying socket to the given local address and port. Does not block. + /// Does not timeout. + /// </summary> + /// <param name="localAddress">The local IP address to bind to.</param> + /// <param name="localPort">The local port to bind to.</param> + /// <returns>Whether the binding was successful or not.</returns> + protected async Task<bool> TryBindAsync(IPAddress localAddress, int localPort) => + await TryBindAsync(localAddress, localPort, Timeout.InfiniteTimeSpan); + + /// <summary> + /// Attempts to asynchronously bind the underlying socket to the given local address and port. Does not block. + /// If the timeout is exceeded the binding attempt is aborted and the method returns false. + /// </summary> + /// <param name="localAddress">The local IP address to bind to.</param> + /// <param name="localPort">The local port to bind to.</param> + /// <param name="timeout">The timeout within which to attempt the binding.</param> + /// <returns>Whether the binding was successful or not.</returns> + protected async Task<bool> TryBindAsync(IPAddress localAddress, int localPort, TimeSpan timeout) => + await TryBindAsync(new IPEndPoint(localAddress, localPort), timeout); + + /// <summary> + /// Attempts to asynchronously bind the underlying socket to the given local endpoint. Does not block. + /// Does not timeout. + /// </summary> + /// <param name="localEndPoint">The local endpoint to bind to.</param> + /// <returns>Whether the binding was successful or not.</returns> + protected async Task<bool> TryBindAsync(EndPoint localEndPoint) => + await TryBindAsync(localEndPoint, Timeout.InfiniteTimeSpan); + + /// <summary> + /// Attempts to asynchronously bind the underlying socket to the given local endpoint. Does not block. + /// If the timeout is exceeded the binding attempt is aborted and the method returns false. + /// </summary> + /// <param name="localEndPoint">The local endpoint to bind to.</param> + /// <param name="timeout">The timeout within which to attempt the binding.</param> + /// <returns>Whether the binding was successful or not.</returns> + protected async Task<bool> TryBindAsync(EndPoint localEndPoint, TimeSpan timeout) + { + try + { + bindSocketCancellationTokenSource.CancelAfter(timeout); + + return await Task.Run(() => + { + socket.Bind(localEndPoint); + + return true; + }, bindSocketCancellationTokenSource.Token); + } + catch (TaskCanceledException) + { + return false; + } + catch (SocketException ex) + { + logger.LogException($"Socket exception on binding socket to {localEndPoint}:", ex); + return false; + } + } + + /// <inheritdoc /> + public abstract Task RunAsync(EndPoint localEndPoint); + + /// <inheritdoc /> + public void Shutdown() + { + runServer = false; + logger.LogMessage("Signalling server shutdown to all client handlers..."); + serverShutdownCancellationTokenSource.Cancel(); + } + + /// <inheritdoc /> + public bool TryDeregisterComplexPacketHandler<Req, Rep>(out ComplexPacketHandler<Req, Rep>? oldHandlerDelegate) + where Req : class, IRequestPacket, new() where Rep : class, IResponsePacket<Req>, new() + { + uint packetTypeId = PacketRegistry.GetPacketId<Req>(); + oldHandlerDelegate = default; + + try + { + requestPacketDeserialisers.TryRemove(packetTypeId, out _); + + if (!complexPacketHandlers.TryGetValue(packetTypeId, + out Func<IRequestPacket, EndPoint, IResponsePacket<IRequestPacket>> oldDelegate)) + return false; + + oldHandlerDelegate = (p, ep) => (Rep)oldDelegate(p, ep); + return true; + } + catch (Exception ex) + { + logger.LogException("Exception when deregistering complex packet handler", ex); + } + + return false; + } + + /// <inheritdoc /> + public bool TryDeregisterSimplePacketHandler<Req>(out SimplePacketHandler<Req>? oldHandlerDelegate) + where Req : class, IRequestPacket, new() + { + uint packetTypeId = PacketRegistry.GetPacketId<Req>(); + oldHandlerDelegate = default; + + try + { + requestPacketDeserialisers.TryRemove(packetTypeId, out _); + + if (!simplePacketHandlers.TryGetValue(packetTypeId, out Action<IRequestPacket, EndPoint> oldDelegate)) + return false; + + oldHandlerDelegate = (p, ep) => oldDelegate(p, ep); + return true; + } + catch (Exception ex) + { + logger.LogException("Exception when deregistering simple packet handler", ex); + } + + return false; + } + + /// <inheritdoc /> + public bool TryRegisterComplexPacketHandler<Req, Rep>(ComplexPacketHandler<Req, Rep> handlerDelegate) + where Req : class, IRequestPacket, new() where Rep : class, IResponsePacket<Req>, new() + { + uint packetTypeId = PacketRegistry.GetPacketId<Req>(); + + static IRequestPacket PacketDeserialiser(in Packet packet) + { + Req request = new Req(); + request.Deserialise(packet.Buffer); + request.AfterDeserialisation(); + + return request; + } + + IResponsePacket<IRequestPacket> MappedHandlerDelegate(IRequestPacket p, EndPoint ep) + { + Rep responsePacket = handlerDelegate((Req)p, ep); + return responsePacket; + } + + try + { + requestPacketDeserialisers.AddOrUpdate(packetTypeId, + key => PacketDeserialiser, + (key, oldDeserialiser) => PacketDeserialiser); + + complexPacketHandlers.AddOrUpdate(packetTypeId, + key => MappedHandlerDelegate, + (key, oldDelegate) => MappedHandlerDelegate); + + return true; + } + catch (Exception ex) + { + logger.LogException("Exception when registering complex packet handler", ex); + } + + return false; + } + + /// <inheritdoc /> + public bool TryRegisterSimplePacketHandler<Req>(SimplePacketHandler<Req> handlerDelegate) + where Req : class, IRequestPacket, new() + { + uint packetTypeId = PacketRegistry.GetPacketId<Req>(); + + static IRequestPacket PacketDeserialiser(in Packet packet) + { + Req request = new Req(); + request.Deserialise(packet.Buffer); + request.AfterDeserialisation(); + + return request; + } + + void MappedHandlerDelegate(IRequestPacket p, EndPoint ep) => handlerDelegate((Req)p, ep); + + try + { + requestPacketDeserialisers.AddOrUpdate(packetTypeId, + key => PacketDeserialiser, + (key, oldDeserialiser) => PacketDeserialiser); + + simplePacketHandlers.AddOrUpdate(packetTypeId, + key => MappedHandlerDelegate, + (key, oldDelegate) => MappedHandlerDelegate); + + return true; + } + catch (Exception ex) + { + logger.LogException("Exception when registering simple packet handler", ex); + } + + return false; + } + + /// <summary> + /// Holds information about the arguments passed to every client handler task. + /// </summary> + protected readonly struct ClientHandlerArgs + { + /// <summary> + /// The remote endpoint for the client being handled. + /// </summary> + public readonly EndPoint ClientEndPoint; + + /// <summary> + /// The client handler socket for the client being handled. Is only set if using TCP. + /// </summary> + public readonly Socket? ClientSocket; + + /// <summary> + /// Initialises a new instance of the <see cref="ClientHandlerArgs"/> struct. + /// </summary> + /// <param name="remoteEndPoint">The remote endpoint of the client that should be handled.</param> + /// <param name="handlerSocket">The handler socket of the client that should be handled.</param> + private ClientHandlerArgs(EndPoint remoteEndPoint, Socket? handlerSocket) + { + ClientEndPoint = remoteEndPoint; + + ClientSocket = handlerSocket; + } + + /// <summary> + /// Constructs a new instance of the <see cref="ClientHandlerArgs"/> for a TCP client. + /// </summary> + /// <returns>A new instance of the <see cref="ClientHandlerArgs"/>, setup for a TCP client.</returns> + public static ClientHandlerArgs ForTcpClientHandler(in Socket clientHandlerSocket) + { + return new ClientHandlerArgs(clientHandlerSocket.RemoteEndPoint, clientHandlerSocket); + } + + /// <summary> + /// Constructs a new instance of the <see cref="ClientHandlerArgs"/> for a UDP client. + /// </summary> + /// <returns>A new instance of the <see cref="ClientHandlerArgs"/>, setup for a UDP client.</returns> + public static ClientHandlerArgs ForUdpClientHandler(in EndPoint clientEndPoint) + { + return new ClientHandlerArgs(clientEndPoint, null); + } + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Servers/TcpServer.cs b/NetSharp/NetSharp/Servers/TcpServer.cs @@ -0,0 +1,132 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NetSharp.Interfaces; +using NetSharp.Packets; +using NetSharp.Packets.Builtin; +using NetSharp.Utils.Socket_Options; + +namespace NetSharp.Servers +{ + /// <summary> + /// Provides methods for TCP communication with connected <see cref="Clients.TcpClient"/> instances. + /// </summary> + public sealed class TcpServer : Server + { + /// <inheritdoc /> + public TcpServer() : base(SocketType.Stream, ProtocolType.Tcp, SocketOptionManager.Tcp) + { + } + + /// <inheritdoc /> + protected override async Task HandleClientAsync(ClientHandlerArgs args) + { + using Socket clientHandlerSocket = args.ClientSocket ?? new Socket(SocketType.Unknown, ProtocolType.Unknown); + + EndPoint remoteEp = clientHandlerSocket.RemoteEndPoint; + + logger.LogMessage($"Initialised client handler for client socket: [Remote EP: {remoteEp}]"); + + try + { + do + { + // receive a single raw packet from the network + Packet rawRequest = + await DoReceivePacketAsync(clientHandlerSocket, SocketFlags.None, Timeout.InfiniteTimeSpan); + + if (rawRequest.Equals(NullPacket) || rawRequest.Type == PacketRegistry.GetPacketId<DisconnectPacket>()) + { + logger.LogMessage($"Received a disconnect packet from client socket: [Remote EP: {remoteEp}]"); + break; + } + + IRequestPacket? requestPacket = DeserialiseRequestPacket(rawRequest.Type, in rawRequest); + Type requestPacketType = PacketRegistry.GetPacketType(rawRequest.Type); + + // the request packet is only null if no packet handler was registered for it + if (requestPacket == null) continue; + + logger.LogMessage($"Received {rawRequest.Count} bytes from {remoteEp}"); + + logger.LogMessage($"Received request: {Encoding.UTF8.GetString(rawRequest.Buffer.Span)}"); + + IResponsePacket<IRequestPacket>? responsePacket = + HandleRequestPacket(rawRequest.Type, requestPacket, remoteEp); + + // the response packet is only null if the given request packet was registered as a 'simple' request packet + if (responsePacket == null) continue; + + Type? responsePacketType = + PacketRegistry.GetResponsePacketType(requestPacketType); + + if (responsePacketType == null) + { + logger.LogError($"Response packet type for request packet of type {requestPacketType} is null"); + continue; + } + + uint responsePacketTypeId = PacketRegistry.GetPacketId(responsePacketType); + + responsePacket.BeforeSerialisation(); + Packet rawResponse = new Packet(responsePacket.Serialise(), responsePacketTypeId, NetworkErrorCode.Ok); + + // echo back the processed raw response to the network + bool sentCorrectly = + await DoSendPacketAsync(clientHandlerSocket, rawResponse, SocketFlags.None, DefaultNetworkOperationTimeout); + + if (!sentCorrectly) + { + logger.LogMessage($"Could not send response back to client socket: [Remote EP: {remoteEp}]"); + break; + } + + logger.LogMessage($"Sent {rawResponse.TotalSize} bytes to {remoteEp}"); + } while (true); + + logger.LogMessage($"Stopping client handler for client socket: [Remote EP: {remoteEp}]"); + } + catch (TaskCanceledException) { logger.LogMessage("Client handling was cancelled via a task cancellation."); } + catch (OperationCanceledException) { logger.LogMessage("Client handling was cancelled via an operation cancellation."); } + catch (Exception ex) + { + logger.LogException("Exception during client socket handling:", ex); + } + } + + /// <inheritdoc /> + public override async Task RunAsync(EndPoint localEndPoint) + { + bool bound = await TryBindAsync(localEndPoint); + + logger.LogMessage($"Is server socket bound: {bound}"); + + if (!bound) + { + logger.LogError("Server socket was not bound successfully, shutting down server."); + return; + } + + socket.Listen(PendingConnectionBacklog); + + OnServerStarted(); + runServer = true; + + while (runServer) + { + Socket clientSocket = await socket.AcceptAsync(); + ClientHandlerArgs args = ClientHandlerArgs.ForTcpClientHandler(in clientSocket); + + await Task.Factory.StartNew(DoHandleClientAsync, args, + serverShutdownCancellationTokenSource.Token, + TaskCreationOptions.LongRunning, + TaskScheduler.Current); + } + + OnServerStopped(); + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Servers/UdpServer.cs b/NetSharp/NetSharp/Servers/UdpServer.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Concurrent; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; +using System.Threading.Tasks.Dataflow; +using NetSharp.Interfaces; +using NetSharp.Packets; +using NetSharp.Packets.Builtin; +using NetSharp.Utils; +using NetSharp.Utils.Socket_Options; + +namespace NetSharp.Servers +{ + /// <summary> + /// Provides methods for UDP communication with connected <see cref="Clients.UdpClient"/> instances. + /// </summary> + public sealed class UdpServer : Server + { + /// <summary> + /// Holds currently connected and active clients, as well as their current received packet queues. + /// </summary> + private readonly ConcurrentDictionary<EndPoint, BufferBlock<Packet>> activeClients; + + /// <inheritdoc /> + public UdpServer() : base(SocketType.Dgram, ProtocolType.Udp, SocketOptionManager.Udp) + { + activeClients = new ConcurrentDictionary<EndPoint, BufferBlock<Packet>>(); + } + + /// <inheritdoc /> + protected override async Task HandleClientAsync(ClientHandlerArgs args) + { + EndPoint clientEndPoint = args.ClientEndPoint; + BufferBlock<Packet> clientPacketBuffer = activeClients[clientEndPoint]; + + logger.LogMessage($"Initialised client handler for client socket: [Remote EP: {clientEndPoint}]"); + + try + { + do + { + // receive a single raw packet from the network + Packet rawRequest = await clientPacketBuffer.ReceiveAsync(serverShutdownCancellationTokenSource.Token); + + if (rawRequest.Equals(NullPacket) || rawRequest.Type == PacketRegistry.GetPacketId<DisconnectPacket>()) + { + logger.LogMessage($"Received a disconnect packet from client socket: [Remote EP: {clientEndPoint}]"); + break; + } + + IRequestPacket? requestPacket = DeserialiseRequestPacket(rawRequest.Type, in rawRequest); + Type requestPacketType = PacketRegistry.GetPacketType(rawRequest.Type); + + // the request packet is only null if no packet handler was registered for it + if (requestPacket == null) continue; + + IResponsePacket<IRequestPacket>? responsePacket = + HandleRequestPacket(rawRequest.Type, requestPacket, clientEndPoint); + + // the response packet is only null if the given request packet was registered as a 'simple' request packet + if (responsePacket == null) continue; + + Type? responsePacketType = + PacketRegistry.GetResponsePacketType(requestPacketType); + + if (responsePacketType == null) + { + logger.LogError($"Response packet type for request packet of type {requestPacketType} is null"); + continue; + } + + uint responsePacketTypeId = PacketRegistry.GetPacketId(responsePacketType); + + responsePacket.BeforeSerialisation(); + Packet rawResponse = new Packet(responsePacket.Serialise(), responsePacketTypeId, NetworkErrorCode.Ok); + + // echo back the processed raw response to the network + bool sentCorrectly = + await DoSendPacketToAsync(socket, clientEndPoint, rawResponse, SocketFlags.None, + DefaultNetworkOperationTimeout); + + if (!sentCorrectly) + { + logger.LogWarning($"Could not send response back to client socket: [Remote EP: {clientEndPoint}]"); + break; + } + } while (true); + + logger.LogMessage($"Stopping client handler for client socket: [Remote EP: {clientEndPoint}]"); + + activeClients.TryRemove(clientEndPoint, out BufferBlock<Packet> remainingPackets); + logger.LogMessage($"Client handler has {remainingPackets.Count} packets left, which will be dropped"); + } + catch (TaskCanceledException) { logger.LogMessage("Client handling was cancelled via a task cancellation."); } + catch (OperationCanceledException) { logger.LogMessage("Client handling was cancelled via an operation cancellation."); } + catch (Exception ex) + { + logger.LogException("Exception during client socket handling:", ex); + } + } + + /// <inheritdoc /> + public override async Task RunAsync(EndPoint localEndPoint) + { + bool bound = await TryBindAsync(localEndPoint); + + if (!bound) + { + logger.LogError("Server socket was not bound successfully, shutting down server."); + return; + } + + OnServerStarted(); + runServer = true; + + while (runServer) + { + EndPoint nullEndPoint = new IPEndPoint(IPAddress.Any, 0); + (Packet request, TransmissionResult packetResult) = + await DoReceivePacketFromAsync(socket, nullEndPoint, SocketFlags.None, DefaultNetworkOperationTimeout); + EndPoint clientEndPoint = packetResult.RemoteEndPoint; + + if (request.Equals(NullPacket) || packetResult.Equals(NullTransmissionResult)) + { + continue; + } + + if (!activeClients.TryGetValue(clientEndPoint, out BufferBlock<Packet> clientPacketBuffer)) + { + ClientHandlerArgs args = ClientHandlerArgs.ForUdpClientHandler(in clientEndPoint); + + activeClients.TryAdd(clientEndPoint, new BufferBlock<Packet>()); + activeClients[clientEndPoint].Post(request); + + await Task.Factory.StartNew(DoHandleClientAsync, args, + serverShutdownCancellationTokenSource.Token, + TaskCreationOptions.LongRunning, + TaskScheduler.Current); + + continue; + } + + clientPacketBuffer.Post(request); + } + + OnServerStopped(); + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Utils/BiDictionary.cs b/NetSharp/NetSharp/Utils/BiDictionary.cs @@ -0,0 +1,201 @@ +using System.Collections.Concurrent; + +namespace NetSharp.Utils +{ + /// <summary> + /// Represents a concurrent two-way dictionary, that can be indexed by either a key or a value. + /// </summary> + /// <typeparam name="K">The type of key that will be stored.</typeparam> + /// <typeparam name="V">The type of value that will be stored.</typeparam> + public class BiDictionary<K, V> + { + /// <summary> + /// Maps keys to their corresponding values. + /// </summary> + private readonly ConcurrentDictionary<K, V> keyToValueMap; + + /// <summary> + /// Maps values to their corresponding keys. + /// </summary> + private readonly ConcurrentDictionary<V, K> valueToKeyMap; + + /// <summary> + /// Initialises a new instance of the <see cref="BiDictionary{K,V}"/> class. + /// </summary> + public BiDictionary() + { + keyToValueMap = new ConcurrentDictionary<K, V>(); + + valueToKeyMap = new ConcurrentDictionary<V, K>(); + } + + /// <summary> + /// Indexes this instance with the given value. + /// </summary> + /// <param name="index">The value whose key to get or set.</param> + /// <returns>The fetched key.</returns> + public K this[V index] + { + get + { + valueToKeyMap.TryGetValue(index, out K key); + + return key; + } + + set { valueToKeyMap.AddOrUpdate(index, value, (v, k) => value); } + } + + /// <summary> + /// Indexes this instance with the given key. + /// </summary> + /// <param name="index">The key whose value to get or set.</param> + /// <returns>The fetched value.</returns> + public V this[K index] + { + get + { + keyToValueMap.TryGetValue(index, out V value); + + return value; + } + + set { keyToValueMap.AddOrUpdate(index, value, (k, v) => value); } + } + + /// <summary> + /// Clears this instance's <see cref="keyToValueMap"/> and <see cref="valueToKeyMap"/>. + /// </summary> + public void Clear() + { + keyToValueMap.Clear(); + valueToKeyMap.Clear(); + } + + /// <summary> + /// Whether this instance contains the given key. + /// </summary> + /// <param name="key">The key to check.</param> + /// <returns>Whether the given key was found.</returns> + public bool ContainsKey(in K key) => keyToValueMap.ContainsKey(key); + + /// <summary> + /// Whether this instance contains the given value. + /// </summary> + /// <param name="value">The value to check.</param> + /// <returns>Whether the given value was found.</returns> + public bool ContainsValue(in V value) => valueToKeyMap.ContainsKey(value); + + /// <summary> + /// Attempts to set the key associated with the given value. + /// </summary> + /// <param name="value">The value whose key to set.</param> + /// <param name="key">The new value for the value's associated key.</param> + /// <returns>Whether the new key was correctly set.</returns> + public void SetOrUpdateKey(V value, K key) + { + valueToKeyMap.AddOrUpdate(value, key, (v, k) => key); + + keyToValueMap.AddOrUpdate(key, value, (k, v) => value); + } + + /// <summary> + /// Attempts to set the value associated with the given key. + /// </summary> + /// <param name="key">The key whose value to set.</param> + /// <param name="value">The new value for the key's associated value.</param> + /// <returns>Whether the new value was correctly set.</returns> + public void SetOrUpdateValue(K key, V value) + { + keyToValueMap.AddOrUpdate(key, value, (k, v) => value); + + valueToKeyMap.AddOrUpdate(value, key, (v, k) => key); + } + + /// <summary> + /// Attempts to remove the key associated with the given value. + /// </summary> + /// <param name="value">The value whose key to remove.</param> + /// <param name="key">The old key value.</param> + /// <returns>Whether the given value had a valid key associated with it.</returns> + public bool TryClearKey(in V value, out K key) + { + bool clearedValue = valueToKeyMap.TryRemove(value, out key); + + bool clearedKey = keyToValueMap.TryRemove(key, out _); + + return clearedValue && clearedKey; + } + + /// <summary> + /// Attempts to remove the value associated with the given key. + /// </summary> + /// <param name="key">The key whose value to remove.</param> + /// <param name="value">The old value.</param> + /// <returns>Whether the given key had a valid valid associated with it.</returns> + public bool TryClearValue(in K key, out V value) + { + bool clearedKey = keyToValueMap.TryRemove(key, out value); + + bool clearedValue = valueToKeyMap.TryRemove(value, out _); + + return clearedKey && clearedValue; + } + + /// <summary> + /// Attempts to get the key associated with the given value. + /// </summary> + /// <param name="value">The value whose key to get.</param> + /// <param name="key">The returned key.</param> + /// <returns>Whether the given value has a valid key associated with it.</returns> + public bool TryGetKey(in V value, out K key) + { + return valueToKeyMap.TryGetValue(value, out key); + } + + /// <summary> + /// Attempts to get the value associated with the given key. + /// </summary> + /// <param name="key">The key whose value to get.</param> + /// <param name="value">The returned value.</param> + /// <returns>Whether the given key as a valid value associated with it.</returns> + public bool TryGetValue(in K key, out V value) + { + return keyToValueMap.TryGetValue(key, out value); + } + + /// <summary> + /// Attempts to set the key associated with the given value. + /// </summary> + /// <param name="value">The value whose key to set.</param> + /// <param name="key">The key which should be set for the given value.</param> + /// <returns>Whether the given value was successfully set.</returns> + public bool TrySetKey(in V value, in K key) + { + K newKey = key; + V newValue = value; + + K setKey = valueToKeyMap.AddOrUpdate(value, v => newKey, (v, k) => newKey); + V setValue = keyToValueMap.AddOrUpdate(key, k => newValue, (k, v) => newValue); + + return (setKey?.Equals(key) ?? false) && (setValue?.Equals(value) ?? false); + } + + /// <summary> + /// Attempts to set the value associated with the given key. + /// </summary> + /// <param name="key">The key whose value to set.</param> + /// <param name="value">The value which should be set for the given key.</param> + /// <returns>Whether the given key was successfully set.</returns> + public bool TrySetValue(in K key, in V value) + { + V newValue = value; + K newKey = key; + + V setValue = keyToValueMap.AddOrUpdate(key, k => newValue, (k, v) => newValue); + K setKey = valueToKeyMap.AddOrUpdate(value, v => newKey, (v, k) => newKey); + + return (setValue?.Equals(value) ?? false) && (setKey?.Equals(key) ?? false); + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Utils/Constants.cs b/NetSharp/NetSharp/Utils/Constants.cs @@ -0,0 +1,18 @@ +namespace NetSharp.Utils +{ + /// <summary> + /// Holds internal default configurations and constants. + /// </summary> + internal static class Constants + { + /// <summary> + /// The default port over which a connection is made. + /// </summary> + internal const int DefaultPort = 12374; + + /// <summary> + /// The largest byte buffer that can be sent via UDP. + /// </summary> + internal const int UdpMaxBufferSize = 60_000; + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Utils/Conversion/EndianAwareBitConverter.cs b/NetSharp/NetSharp/Utils/Conversion/EndianAwareBitConverter.cs @@ -0,0 +1,145 @@ +using System; +using System.Runtime.CompilerServices; + +namespace NetSharp.Utils.Conversion +{ + /// <summary> + /// Wraps the <see cref="BitConverter"/> class to provide conversion that is endian-aware. + /// </summary> + public static class EndianAwareBitConverter + { + /// <summary> + /// Reverses the given bytes if the endian-nes doesn't match. + /// </summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Span<byte> ReverseAsNeeded(Span<byte> bytes, bool toLittleEndian) + { + if (toLittleEndian != BitConverter.IsLittleEndian) + { + bytes.Reverse(); + } + + return bytes; + } + + /// <inheritdoc cref="BitConverter.GetBytes(bool)"/> + public static Span<byte> GetBytes(bool value, bool littleEndian = false) + { + return ReverseAsNeeded(BitConverter.GetBytes(value), littleEndian); + } + + /// <inheritdoc cref="BitConverter.GetBytes(char)"/> + public static Span<byte> GetBytes(char value, bool littleEndian = false) + { + return ReverseAsNeeded(BitConverter.GetBytes(value), littleEndian); + } + + /// <inheritdoc cref="BitConverter.GetBytes(double)"/> + public static Span<byte> GetBytes(double value, bool littleEndian = false) + { + return ReverseAsNeeded(BitConverter.GetBytes(value), littleEndian); + } + + /// <inheritdoc cref="BitConverter.GetBytes(float)"/> + public static Span<byte> GetBytes(float value, bool littleEndian = false) + { + return ReverseAsNeeded(BitConverter.GetBytes(value), littleEndian); + } + + /// <inheritdoc cref="BitConverter.GetBytes(int)"/> + public static Span<byte> GetBytes(int value, bool littleEndian = false) + { + return ReverseAsNeeded(BitConverter.GetBytes(value), littleEndian); + } + + /// <inheritdoc cref="BitConverter.GetBytes(long)"/> + public static Span<byte> GetBytes(long value, bool littleEndian = false) + { + return ReverseAsNeeded(BitConverter.GetBytes(value), littleEndian); + } + + /// <inheritdoc cref="BitConverter.GetBytes(short)"/> + public static Span<byte> GetBytes(short value, bool littleEndian = false) + { + return ReverseAsNeeded(BitConverter.GetBytes(value), littleEndian); + } + + /// <inheritdoc cref="BitConverter.GetBytes(uint)"/> + public static Span<byte> GetBytes(uint value, bool littleEndian = false) + { + return ReverseAsNeeded(BitConverter.GetBytes(value), littleEndian); + } + + /// <inheritdoc cref="BitConverter.GetBytes(ulong)"/> + public static Span<byte> GetBytes(ulong value, bool littleEndian = false) + { + return ReverseAsNeeded(BitConverter.GetBytes(value), littleEndian); + } + + /// <inheritdoc cref="BitConverter.GetBytes(ushort)"/> + public static Span<byte> GetBytes(ushort value, bool littleEndian = false) + { + return ReverseAsNeeded(BitConverter.GetBytes(value), littleEndian); + } + + /// <inheritdoc cref="BitConverter.ToBoolean(ReadOnlySpan{byte})"/> + public static bool ToBoolean(Span<byte> bytes, bool littleEndian = false) + { + return BitConverter.ToBoolean(ReverseAsNeeded(bytes, littleEndian)); + } + + /// <inheritdoc cref="BitConverter.ToChar(ReadOnlySpan{byte})"/> + public static char ToChar(Span<byte> bytes, bool littleEndian = false) + { + return BitConverter.ToChar(ReverseAsNeeded(bytes, littleEndian)); + } + + /// <inheritdoc cref="BitConverter.ToDouble(ReadOnlySpan{byte})"/> + public static double ToDouble(Span<byte> bytes, bool littleEndian = false) + { + return BitConverter.ToDouble(ReverseAsNeeded(bytes, littleEndian)); + } + + /// <inheritdoc cref="BitConverter.ToInt16(ReadOnlySpan{byte})"/> + public static short ToInt16(Span<byte> bytes, bool littleEndian = false) + { + return BitConverter.ToInt16(ReverseAsNeeded(bytes, littleEndian)); + } + + /// <inheritdoc cref="BitConverter.ToInt32(ReadOnlySpan{byte})"/> + public static int ToInt32(Span<byte> bytes, bool littleEndian = false) + { + return BitConverter.ToInt32(ReverseAsNeeded(bytes, littleEndian)); + } + + /// <inheritdoc cref="BitConverter.ToInt64(ReadOnlySpan{byte})"/> + public static long ToInt64(Span<byte> bytes, bool littleEndian = false) + { + return BitConverter.ToInt64(ReverseAsNeeded(bytes, littleEndian)); + } + + /// <inheritdoc cref="BitConverter.ToSingle(ReadOnlySpan{byte})"/> + public static float ToSingle(Span<byte> bytes, bool littleEndian = false) + { + return BitConverter.ToSingle(ReverseAsNeeded(bytes, littleEndian)); + } + + /// <inheritdoc cref="BitConverter.ToUInt16(ReadOnlySpan{byte})"/> + public static ushort ToUInt16(byte[] bytes, bool littleEndian = false) + { + return BitConverter.ToUInt16(ReverseAsNeeded(bytes, littleEndian)); + } + + /// <inheritdoc cref="BitConverter.ToUInt32(ReadOnlySpan{byte})"/> + public static uint ToUInt32(Span<byte> bytes, bool littleEndian = false) + { + return BitConverter.ToUInt32(ReverseAsNeeded(bytes, littleEndian)); + } + + /// <inheritdoc cref="BitConverter.ToUInt64(ReadOnlySpan{byte})"/> + public static ulong ToUInt64(Span<byte> bytes, bool littleEndian = false) + { + return BitConverter.ToUInt64(ReverseAsNeeded(bytes, littleEndian)); + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Utils/NetworkOperations.cs b/NetSharp/NetSharp/Utils/NetworkOperations.cs @@ -0,0 +1,198 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using NetSharp.Packets; +using NetSharp.Utils.Conversion; + +namespace NetSharp.Utils +{ + /// <summary> + /// Helper class for asynchronously performing common network operations, for both the UDP and TCP protocols. + /// </summary> + public static class NetworkOperations + { + /// <summary> + /// Reads the specified amount of data asynchronously from the network, via the given socket. + /// The given <see cref="SocketFlags"/> are associated with the read, and the given <see cref="CancellationToken"/> + /// is used to allow for asynchronous task cancellation. + /// </summary> + /// <param name="socket">The socket which should read data from the network.</param> + /// <param name="count">The number of bytes to read from the network.</param> + /// <param name="socketFlags">The socket flags associated with the receive operation.</param> + /// <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> + /// <returns>The result of the receive operation.</returns> + public static async Task<TransmissionResult> ReadAsync(Socket socket, int count, SocketFlags socketFlags, + CancellationToken cancellationToken) + { + byte[] byteBuffer = new byte[count]; + + int receivedByteCount = await socket.ReceiveAsync(byteBuffer, socketFlags, cancellationToken); + + return new TransmissionResult(new Memory<byte>(byteBuffer, 0, receivedByteCount), receivedByteCount, + socket.RemoteEndPoint); + } + + /// <summary> + /// Reads a datagram asynchronously from the given remote endpoint, via the given socket. + /// The given <see cref="SocketFlags"/> are associated with the read, and the given <see cref="CancellationToken"/> + /// is used to allow for asynchronous task cancellation. + /// </summary> + /// <param name="socket">The socket which should read data from the network.</param> + /// <param name="remoteEndPoint">The remote endpoint from which data should be read.</param> + /// <param name="socketFlags">The socket flags associated with the receive operation.</param> + /// <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> + /// <returns>The result of the receive operation.</returns> + public static async Task<TransmissionResult> ReadFromAsync(Socket socket, EndPoint remoteEndPoint, + SocketFlags socketFlags, CancellationToken cancellationToken) + { + return await Task.Factory.StartNew(() => + { + byte[] byteBuffer = new byte[Constants.UdpMaxBufferSize]; + + EndPoint actualRemoteEndPoint = remoteEndPoint; + + int receivedByteCount = socket.ReceiveFrom(byteBuffer, socketFlags, ref actualRemoteEndPoint); + + return new TransmissionResult(new Memory<byte>(byteBuffer, 0, receivedByteCount), receivedByteCount, + actualRemoteEndPoint); + }, cancellationToken); + } + + /// <summary> + /// Reads a packet asynchronously from network, via the given socket. The given <see cref="SocketFlags"/> are + /// associated with the read, and the given <see cref="CancellationToken"/> is used to allow for asynchronous + /// task cancellation. + /// </summary> + /// <param name="socket">The socket which should read the packet from the network.</param> + /// <param name="socketFlags">The socket flags associated with the receive operation.</param> + /// <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> + /// <returns>The read packet.</returns> + public static async Task<Packet> ReadPacketAsync(Socket socket, SocketFlags socketFlags, CancellationToken cancellationToken) + { + TransmissionResult packetHeaderResult = await ReadAsync(socket, Packet.HeaderSize, socketFlags, cancellationToken); + + int packetSize = EndianAwareBitConverter.ToInt32(packetHeaderResult.Buffer.Span.Slice(0, sizeof(int))); + + if (packetSize == 0) + { + return Packet.Deserialise(packetHeaderResult.Buffer); + } + + TransmissionResult packetDataResult = await ReadAsync(socket, packetSize, socketFlags, cancellationToken); + + byte[] serialisedPacket = new byte[Packet.HeaderSize + packetSize]; + + Memory<byte> serialisedPacketHeader = new Memory<byte>(serialisedPacket, 0, Packet.HeaderSize); + packetHeaderResult.Buffer.CopyTo(serialisedPacketHeader); + + Memory<byte> serialisedPacketData = new Memory<byte>(serialisedPacket, Packet.HeaderSize, packetSize); + packetDataResult.Buffer.CopyTo(serialisedPacketData); + + return Packet.Deserialise(serialisedPacket); + } + + /// <summary> + /// Reads a packet asynchronously from the given remote endpoint, via the given socket. The given + /// <see cref="SocketFlags"/> are associated with the read, and the given <see cref="CancellationToken"/> is + /// used to allow for asynchronous task cancellation. + /// </summary> + /// <param name="socket">The socket which should read the packet from the network.</param> + /// <param name="remoteEndPoint">The remote endpoint from which a packet should be read.</param> + /// <param name="socketFlags">The socket flags associated with the receive operation.</param> + /// <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> + /// <returns>The read packet and associated transmission results.</returns> + public static async Task<(Packet packet, TransmissionResult packetResult)> ReadPacketFromAsync( + Socket socket, EndPoint remoteEndPoint, SocketFlags socketFlags, CancellationToken cancellationToken) + { + TransmissionResult packetResult = + await ReadFromAsync(socket, remoteEndPoint, socketFlags, cancellationToken); + + return (Packet.Deserialise(packetResult.Buffer), packetResult); + } + + /// <summary> + /// Writes the given buffer asynchronously to the network, via the given socket. + /// The given <see cref="SocketFlags"/> are associated with the write, and the given <see cref="CancellationToken"/> + /// is used to allow for asynchronous task cancellation. + /// </summary> + /// <param name="socket">The socket which should write data to the network.</param> + /// <param name="buffer">The buffer that should be written to the network.</param> + /// <param name="socketFlags">The socket flags associated with the send operation.</param> + /// <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> + public static async Task WriteAsync(Socket socket, ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, + CancellationToken cancellationToken) + { + int bytesToSend = buffer.Length; + int sentBytesCount = 0; + + while (bytesToSend > sentBytesCount) + { + ReadOnlyMemory<byte> bufferSegment = buffer.Slice(sentBytesCount, bytesToSend - sentBytesCount); + + sentBytesCount += await socket.SendAsync(bufferSegment, socketFlags, cancellationToken); + } + } + + /// <summary> + /// Writes the given packet asynchronously to the network, via the given socket. The given <see cref="SocketFlags"/> + /// are associated with the write, and the given <see cref="CancellationToken"/> is used to allow for asynchronous + /// task cancellation. + /// </summary> + /// <param name="socket">The socket which should write data to the network.</param> + /// <param name="packet">The packet that should be written to the network.</param> + /// <param name="socketFlags">The socket flags associated with the send operation.</param> + /// <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> + public static async Task WritePacketAsync(Socket socket, Packet packet, SocketFlags socketFlags, + CancellationToken cancellationToken) + { + await WriteAsync(socket, Packet.Serialise(packet), socketFlags, cancellationToken); + } + + /// <summary> + /// Writes the given packet asynchronously to the given remote endpoint, via the given socket. + /// The given <see cref="SocketFlags"/> are associated with the write, and the given <see cref="CancellationToken"/> + /// is used to allow for asynchronous task cancellation. + /// </summary> + /// <param name="socket">The socket which should write data to the network.</param> + /// <param name="remoteEndPoint">The remote endpoint to which data should be written.</param> + /// <param name="packet">The packet that should be written to the remote endpoint.</param> + /// <param name="socketFlags">The socket flags associated with the send operation.</param> + /// <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> + public static async Task WritePacketToAsync(Socket socket, EndPoint remoteEndPoint, Packet packet, + SocketFlags socketFlags, CancellationToken cancellationToken) + { + await WriteToAsync(socket, remoteEndPoint, Packet.Serialise(packet), socketFlags, cancellationToken); + } + + /// <summary> + /// Writes the given buffer asynchronously to the given remote endpoint, via the given socket. + /// The given <see cref="SocketFlags"/> are associated with the write, and the given <see cref="CancellationToken"/> + /// is used to allow for asynchronous task cancellation. + /// </summary> + /// <param name="socket">The socket which should write data to the network.</param> + /// <param name="remoteEndPoint">The remote endpoint to which data should be written.</param> + /// <param name="buffer">The buffer that should be written to the network.</param> + /// <param name="socketFlags">The socket flags associated with the send operation.</param> + /// <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> + public static async Task WriteToAsync(Socket socket, EndPoint remoteEndPoint, ReadOnlyMemory<byte> buffer, + SocketFlags socketFlags, CancellationToken cancellationToken) + { + await Task.Factory.StartNew(async () => + { + byte[] heapAllocatedBuffer = buffer.ToArray(); + int bytesToSend = buffer.Length; + int sentBytesCount = 0; + + while (bytesToSend > sentBytesCount) + { + ArraySegment<byte> bufferSegment = + new ArraySegment<byte>(heapAllocatedBuffer, sentBytesCount, bytesToSend - sentBytesCount); + + sentBytesCount += await socket.SendToAsync(bufferSegment, socketFlags, remoteEndPoint); + } + }, cancellationToken); + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Utils/Socket Options/DefaultSocketOptions.cs b/NetSharp/NetSharp/Utils/Socket Options/DefaultSocketOptions.cs @@ -0,0 +1,46 @@ +using System; +using System.Net.Sockets; + +namespace NetSharp.Utils.Socket_Options +{ + /// <summary> + /// Allows for manipulation of socket options. + /// </summary> + public sealed class DefaultSocketOptions : SocketOptions + { + /// <inheritdoc /> + public DefaultSocketOptions(ref Socket socket) : base(ref socket) + { + } + + /// <inheritdoc /> + /// <exception cref="NotSupportedException"> + /// This property is not supported when using the default socket option manager. + /// </exception> + public override int HopLimit + { + get { throw new NotSupportedException("This property is not supported in the default socket options manager."); } + set { throw new NotSupportedException("This property is not supported in the default socket options manager."); } + } + + /// <inheritdoc /> + /// <exception cref="NotSupportedException"> + /// This property is not supported when using the default socket option manager. + /// </exception> + public override bool IsRoutingEnabled + { + get { throw new NotSupportedException("This property is not supported in the default socket options manager."); } + set { throw new NotSupportedException("This property is not supported in the default socket options manager."); } + } + + /// <inheritdoc /> + /// <exception cref="NotSupportedException"> + /// This property is not supported when using the default socket option manager. + /// </exception> + public override bool UseLoopback + { + get { throw new NotSupportedException("This property is not supported in the default socket options manager."); } + set { throw new NotSupportedException("This property is not supported in the default socket options manager."); } + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Utils/Socket Options/SocketOptionManager.cs b/NetSharp/NetSharp/Utils/Socket Options/SocketOptionManager.cs @@ -0,0 +1,25 @@ +namespace NetSharp.Utils.Socket_Options +{ + /// <summary> + /// Enumerates the possible socket option manager types to instantiate for a <see cref="Client"/> and <see cref="Server"/> + /// instance. + /// </summary> + public enum SocketOptionManager + { + /// <summary> + /// Causes a <see cref="DefaultSocketOptions"/> instance to be created as the socket option manager. + /// This means that certain socket options will throw an error, as the socket type is not specified. + /// </summary> + Default, + + /// <summary> + /// Causes a <see cref="TcpSocketOptions"/> instance to be created as the socket option manager. + /// </summary> + Tcp, + + /// <summary> + /// Causes a <see cref="UdpSocketOptions"/> instance to be created as the socket option manager. + /// </summary> + Udp, + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Utils/Socket Options/SocketOptions.cs b/NetSharp/NetSharp/Utils/Socket Options/SocketOptions.cs @@ -0,0 +1,106 @@ +using System.Net; +using System.Net.Sockets; + +namespace NetSharp.Utils.Socket_Options +{ + /// <summary> + /// Allows for manipulation of socket options. + /// </summary> + public abstract class SocketOptions + { + /// <summary> + /// The <see cref="Socket"/> instance whose settings are being managed. + /// </summary> + protected readonly Socket managedSocket; + + /// <summary> + /// Initialises a new instance of the <see cref="SocketOptions"/> class. + /// </summary> + /// <param name="socket">The <see cref="Socket"/> instance whose options should be managed.</param> + protected SocketOptions(ref Socket socket) + { + managedSocket = socket; + } + + /// <summary> + /// Whether this <see cref="Socket"/> can operate in dual IPv4 / IPv6 mode. + /// </summary> + public bool DualMode { get { return managedSocket.DualMode; } set { managedSocket.DualMode = value; } } + + /// <summary> + /// Whether sending a packet flushes underlying <see cref="NetworkStream"/>. + /// </summary> + /// <remarks> + /// This value is only used in a <see cref="TcpClient"/> instance, which uses a <see cref="NetworkStream"/> + /// to send and receive data. A <see cref="UdpClient"/> is unaffected by this value. + /// </remarks> + public bool ForceFlush { get; set; } = true; + + /// <summary> + /// Whether this <see cref="Socket"/> is allowed to fragment frames that are too large to send in one go. + /// </summary> + public bool Fragment { get { return !managedSocket.DontFragment; } set { managedSocket.DontFragment = !value; } } + + /// <summary> + /// The hop limit for packets sent by this <see cref="Socket"/>. Comparable to IPv4s TTL (Time To Live). + /// </summary> + public abstract int HopLimit { get; set; } + + /// <summary> + /// Whether a checksum should be created for each UDP packet sent. + /// </summary> + public bool IsChecksumEnabled + { + get { return (int)managedSocket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoChecksum) == 0; } + set { managedSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoChecksum, value ? 0 : -1); } + } + + /// <summary> + /// Whether the packet should be sent directly to its destination or allowed to be routed through multiple destinations + /// first. + /// </summary> + public abstract bool IsRoutingEnabled { get; set; } + + /// <summary> + /// The local <see cref="EndPoint"/> for the <see cref="managedSocket"/>. + /// </summary> + public EndPoint LocalEndPoint { get { return managedSocket.LocalEndPoint; } } + + /// <summary> + /// The local <see cref="IPEndPoint"/> for this <see cref="Socket"/> instance. + /// </summary> + public EndPoint LocalIPEndPoint + { + get + { + return managedSocket?.LocalEndPoint as IPEndPoint ?? new IPEndPoint(IPAddress.None, IPEndPoint.MinPort); + } + } + + /// <summary> + /// The remote <see cref="EndPoint"/> for the <see cref="managedSocket"/>. + /// </summary> + public EndPoint RemoteEndPoint { get { return managedSocket.RemoteEndPoint; } } + + /// <summary> + /// The remote <see cref="IPEndPoint"/> that this <see cref="Socket"/> instance communicates with. + /// </summary> + public EndPoint RemoteIPEndPoint + { + get + { + return managedSocket?.RemoteEndPoint as IPEndPoint ?? new IPEndPoint(IPAddress.None, IPEndPoint.MinPort); + } + } + + /// <summary> + /// The 'Time To Live' for this <see cref="Socket"/>. + /// </summary> + public short Ttl { get { return managedSocket.Ttl; } set { managedSocket.Ttl = value; } } + + /// <summary> + /// Whether this <see cref="Socket"/> should use a loopback address and bypass hardware. + /// </summary> + public abstract bool UseLoopback { get; set; } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Utils/Socket Options/TcpSocketOptions.cs b/NetSharp/NetSharp/Utils/Socket Options/TcpSocketOptions.cs @@ -0,0 +1,36 @@ +using System.Net.Sockets; + +namespace NetSharp.Utils.Socket_Options +{ + /// <summary> + /// Allows for manipulation of TCP socket options. + /// </summary> + public sealed class TcpSocketOptions : SocketOptions + { + /// <inheritdoc /> + public TcpSocketOptions(ref Socket socket) : base(ref socket) + { + } + + /// <inheritdoc /> + public override int HopLimit + { + get { return (int)managedSocket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.HopLimit); } + set { managedSocket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.HopLimit, value); } + } + + /// <inheritdoc /> + public override bool IsRoutingEnabled + { + get { return !(bool)managedSocket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.DontRoute); } + set { managedSocket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.DontRoute, !value); } + } + + /// <inheritdoc /> + public override bool UseLoopback + { + get { return (bool)managedSocket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.UseLoopback); } + set { managedSocket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.UseLoopback, value); } + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Utils/Socket Options/UdpSocketOptions.cs b/NetSharp/NetSharp/Utils/Socket Options/UdpSocketOptions.cs @@ -0,0 +1,36 @@ +using System.Net.Sockets; + +namespace NetSharp.Utils.Socket_Options +{ + /// <summary> + /// Allows for manipulation of UDP socket options. + /// </summary> + public sealed class UdpSocketOptions : SocketOptions + { + /// <inheritdoc /> + public UdpSocketOptions(ref Socket socket) : base(ref socket) + { + } + + /// <inheritdoc /> + public override int HopLimit + { + get { return (int)managedSocket.GetSocketOption(SocketOptionLevel.Udp, SocketOptionName.HopLimit); } + set { managedSocket.SetSocketOption(SocketOptionLevel.Udp, SocketOptionName.HopLimit, value); } + } + + /// <inheritdoc /> + public override bool IsRoutingEnabled + { + get { return !(bool)managedSocket.GetSocketOption(SocketOptionLevel.Udp, SocketOptionName.DontRoute); } + set { managedSocket.SetSocketOption(SocketOptionLevel.Udp, SocketOptionName.DontRoute, !value); } + } + + /// <inheritdoc /> + public override bool UseLoopback + { + get { return (bool)managedSocket.GetSocketOption(SocketOptionLevel.Udp, SocketOptionName.UseLoopback); } + set { managedSocket.SetSocketOption(SocketOptionLevel.Udp, SocketOptionName.UseLoopback, value); } + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Utils/TransmissionResult.cs b/NetSharp/NetSharp/Utils/TransmissionResult.cs @@ -0,0 +1,39 @@ +using System; +using System.Net; + +namespace NetSharp.Utils +{ + /// <summary> + /// Represents the result of a socket transmission. + /// </summary> + public readonly struct TransmissionResult + { + /// <summary> + /// The byte buffer that was transmitted across the network. + /// </summary> + public readonly Memory<byte> Buffer; + + /// <summary> + /// The number of bytes that were transmitted across the network. + /// </summary> + public readonly int Count; + + /// <summary> + /// The remote endpoint to which the buffer was transmitted. + /// </summary> + public readonly EndPoint RemoteEndPoint; + + /// <summary> + /// Initialises a new instance of the <see cref="TransmissionResult"/> struct. + /// </summary> + /// <param name="buffer">The byte buffer that was transmitted.</param> + /// <param name="count">The number of bytes that were transmitted.</param> + /// <param name="remoteEndPoint">The remote endpoint to which the buffer was transmitted.</param> + public TransmissionResult(Memory<byte> buffer, int count, EndPoint remoteEndPoint) + { + Buffer = buffer; + Count = count; + RemoteEndPoint = remoteEndPoint; + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharpExamples/NetSharpExamples.csproj b/NetSharp/NetSharpExamples/NetSharpExamples.csproj @@ -0,0 +1,19 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>netcoreapp3.1</TargetFramework> + </PropertyGroup> + + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> + <DocumentationFile>NetSharpExamples.xml</DocumentationFile> + </PropertyGroup> + + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> + <DocumentationFile>NetSharpExamples.xml</DocumentationFile> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\NetSharp\NetSharp.csproj" /> + </ItemGroup> +</Project> +\ No newline at end of file diff --git a/NetSharp/NetSharpExamples/NetSharpExamples.xml b/NetSharp/NetSharpExamples/NetSharpExamples.xml @@ -0,0 +1,8 @@ +<?xml version="1.0"?> +<doc> + <assembly> + <name>NetSharpExamples</name> + </assembly> + <members> + </members> +</doc> diff --git a/NetSharp/NetSharpExamples/Program.cs b/NetSharp/NetSharpExamples/Program.cs @@ -0,0 +1,252 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NetSharp; +using NetSharp.Clients; +using NetSharp.Extensions; +using NetSharp.Servers; + +namespace NetSharpExamples +{ + internal class Program + { + private static IPAddress serverAddress; + + private static int serverPort; + + private static async Task Main() + { + Console.WriteLine("Hello World!"); + + //await TestTaskCancellation(); + + //TestThreadCancellation(); + + //TestManualResetEvent(); + + serverAddress = IPAddress.Loopback; + serverPort = 12374; + + Console.WriteLine("Test server (y/n): "); + if (Console.ReadLine()?.ToLower().Equals("y") ?? false) + { + try + { + await TestServer(); + } + catch (Exception ex) + { + Console.WriteLine($"Exception: {ex}"); + } + + Console.ReadLine(); + } + else + { + try + { + await TestClient(); + } + catch (Exception ex) + { + Console.WriteLine($"Exception: {ex}"); + } + + Console.ReadLine(); + } + } + + private static async Task TestClient() + { + TimeSpan socketTimeout = Timeout.InfiniteTimeSpan; //TimeSpan.FromSeconds(10); + + const int clientCount = 1; + const int sentPacketCount = 1_000_000; + + static Client ClientFactory() + { + return new TcpClient(); + } + + for (int i = 0; i < clientCount; i++) + { + await Task.Factory.StartNew(async () => + { + using Client client = ClientFactory(); + client.ChangeLoggingStream(Console.OpenStandardOutput()); + + if (await client.TryBindAsync(null, null, socketTimeout)) + { + Console.WriteLine($"Socket bound successfully: {client.SocketOptions.LocalIPEndPoint}"); + + if (await client.TryConnectAsync(serverAddress, serverPort, socketTimeout)) + { + Console.WriteLine( + $"Socket connected successfully: {client.SocketOptions.RemoteIPEndPoint}, sending messages to server..."); + + //var message = new SimpleRequestPacket { Message = "Hello World" }; + byte[] message = Encoding.UTF8.GetBytes("Hello World!"); + + Stopwatch messageStopwatch = Stopwatch.StartNew(); + + for (int j = 0; j < sentPacketCount; j++) + { + await client.SendBytesAsync(message); + //Console.WriteLine($"[{j}] Sent message to server."); + + //byte[] response = await client.SendBytesWithResponseAsync(message); + //Console.WriteLine($"[{j}] Received response: {Encoding.UTF8.GetString(response)}"); + + //await Task.Delay(new Random(DateTime.Now.Millisecond).Next(200, 500)); + } + + messageStopwatch.Stop(); + + Console.WriteLine( + $"Sending {sentPacketCount} packets of {message.Length} bytes long took {messageStopwatch.Elapsed}"); + + double bandwidth = message.Length * sentPacketCount / (messageStopwatch.ElapsedMilliseconds / 1000.0); + + Console.WriteLine($"Approximate bandwidth for single connection is {bandwidth} Bytes per second"); + + client.Disconnect(); + } + else + { + Console.WriteLine("Socket could not connect"); + } + } + else + { + Console.WriteLine("Socket could not be bound"); + } + }, TaskCreationOptions.LongRunning); + } + + Console.ReadLine(); + } + + private static void TestManualResetEvent() + { + using CancellationTokenSource cts = new CancellationTokenSource(); + + ManualResetEventSlim reset = new ManualResetEventSlim(false); + + Thread waitThread = new Thread(tokenObj => + { + CancellationToken token = (CancellationToken)tokenObj; + + try + { + Console.WriteLine("Waiting for manual reset event to be signaled."); + reset.Wait(token); + } + catch (Exception ex) + { + Console.WriteLine($"Exception while waiting: {ex}"); + } + }); + + waitThread.Start(cts.Token); + + Thread.Sleep(100); + + reset.Set(); + + waitThread.Join(); + } + + private static async Task TestServer() + { + const string serverLogFile = @"./serverLog.txt"; + File.Delete(serverLogFile); + await using Stream serverOutputStream = File.OpenWrite(serverLogFile); + + using Server server = new TcpServer(); + //server.ChangeLoggingStream(Console.OpenStandardOutput()); + //server.ChangeLoggingStream(serverOutputStream); + + Console.WriteLine("Starting server..."); + await server.RunAsync(serverAddress, serverPort); + Console.WriteLine("Server stopped"); + + Console.ReadLine(); + + serverOutputStream.Close(); + } + + private static async Task TestTaskCancellation() + { + using CancellationTokenSource _cts = new CancellationTokenSource(); + + // simulates heavy thread work with a timeout value. + async Task<bool> ProcessTask(CancellationTokenSource cts, int taskProcessTimeMs, int timeoutMs) + { + try + { + int threadSleep = taskProcessTimeMs, timeout = timeoutMs; + + Console.WriteLine($"Task process time ms: {threadSleep}, task timeout ms: {timeout}"); + + cts.CancelAfter(timeout); + + bool wasCancelled = await Task.Run(() => + { + Thread.Sleep(threadSleep); + return true; + }, cts.Token); + + Console.WriteLine($"Was task successful: {wasCancelled}"); + + return wasCancelled; + } + catch (TaskCanceledException ex) + { + Console.WriteLine($"Cancellation Token cancelled during task execution: {ex.Message}"); + + return false; + } + } + + await ProcessTask(_cts, 50, 100); + + await ProcessTask(_cts, 1000, 100); + } + + private static void TestThreadCancellation() + { + using CancellationTokenSource cts = new CancellationTokenSource(); + + Thread processingThread = new Thread(tokenObj => + { + CancellationToken token = (CancellationToken)tokenObj; + + try + { + Console.WriteLine("Simulating heavy work and unresponsive thread..."); + + while (true) + { + // simulate very heave work + + token.ThrowIfCancellationRequested(); + } + } + catch (OperationCanceledException ex) + { + Console.WriteLine($"Cancellation token was cancelled during task: {ex.Message}"); + } + }); + + cts.CancelAfter(1000); + + processingThread.Start(cts.Token); + + processingThread.Join(); + } + } +} +\ No newline at end of file