NetSharp

NetSharp.git
git clone git://git.lenczewski.org/NetSharp.git
Log | Files | Refs | README | LICENSE

commit 9ccdaa5fdbc86a7ae839905661948171a0b23eb4
parent efce29d97388bd19c651aab3996475308ccfa0ee
Author: Mikolaj Lenczewski <33129490+EnderRifter@users.noreply.github.com>
Date:   Sun, 23 Feb 2020 16:50:55 +0000

SendBytesWithResponse is now broken for UDP. nice one.

Diffstat:
MNetSharp/NetSharp/Client.cs | 16+++-------------
MNetSharp/NetSharp/Clients/TcpClient.cs | 12++++++------
MNetSharp/NetSharp/Clients/UdpClient.cs | 20+++++++++++---------
MNetSharp/NetSharp/Connection.cs | 119++++++++++++++++++++++++++-----------------------------------------------------
MNetSharp/NetSharp/Extensions/ServerExtensions.cs | 1+
ANetSharp/NetSharp/Extensions/SocketExtensions.cs | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MNetSharp/NetSharp/Interfaces/INetworkSerialisable.cs | 2+-
MNetSharp/NetSharp/NetSharp.csproj | 1+
MNetSharp/NetSharp/NetSharp.xml | 292++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
MNetSharp/NetSharp/Packets/Builtin/ConnectPacket.cs | 4++--
MNetSharp/NetSharp/Packets/Builtin/ConnectResponsePacket.cs | 4++--
MNetSharp/NetSharp/Packets/Builtin/DataPacket.cs | 8++++----
MNetSharp/NetSharp/Packets/Builtin/DataResponsePacket.cs | 8++++----
MNetSharp/NetSharp/Packets/Builtin/DisconnectPacket.cs | 4++--
MNetSharp/NetSharp/Packets/Builtin/PingPacket.cs | 4++--
MNetSharp/NetSharp/Packets/Builtin/PingResponsePacket.cs | 4++--
MNetSharp/NetSharp/Packets/Builtin/SimpleDataPacket.cs | 8++++----
ANetSharp/NetSharp/Packets/NetworkPacket.cs | 203+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
DNetSharp/NetSharp/Packets/Packet.cs | 98-------------------------------------------------------------------------------
MNetSharp/NetSharp/Packets/PacketRegistry.cs | 28++++++++++++++--------------
ANetSharp/NetSharp/Packets/SerialisedPacket.cs | 49+++++++++++++++++++++++++++++++++++++++++++++++++
MNetSharp/NetSharp/Server.cs | 104+++++++++++++++++++++----------------------------------------------------------
MNetSharp/NetSharp/Servers/TcpServer.cs | 28+++++++++++++---------------
MNetSharp/NetSharp/Servers/UdpServer.cs | 43+++++++++++++++++++++++--------------------
MNetSharp/NetSharp/Utils/Constants.cs | 2++
MNetSharp/NetSharp/Utils/NetworkOperations.cs | 316+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
MNetSharp/NetSharpExamples/Program.cs | 8+++++---
27 files changed, 1044 insertions(+), 513 deletions(-)

diff --git a/NetSharp/NetSharp/Client.cs b/NetSharp/NetSharp/Client.cs @@ -1,11 +1,9 @@ using System; using System.Net; using System.Net.Sockets; -using System.Reflection; 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; @@ -144,7 +142,7 @@ namespace NetSharp /// <inheritdoc /> public Task<bool> TryBindAsync(IPAddress? localAddress, int? localPort, TimeSpan timeout) { - CancellationTokenSource cts = new CancellationTokenSource(timeout); + using CancellationTokenSource cts = new CancellationTokenSource(timeout); EndPoint localEndPoint = new IPEndPoint(localAddress ?? IPAddress.Any, localPort ?? 0); try @@ -165,16 +163,12 @@ namespace NetSharp logger.LogException($"Socket exception on binding socket to {localEndPoint}:", ex); return Task.FromResult(false); } - finally - { - cts.Dispose(); - } } /// <inheritdoc /> public async Task<bool> TryConnectAsync(IPAddress remoteAddress, int remotePort, TimeSpan timeout) { - CancellationTokenSource cts = new CancellationTokenSource(timeout); + using CancellationTokenSource cts = new CancellationTokenSource(timeout); remoteEndPoint = new IPEndPoint(remoteAddress, remotePort); try @@ -186,7 +180,7 @@ namespace NetSharp ConnectResponsePacket connectionResponsePacket = await SendComplexAsync<ConnectPacket, ConnectResponsePacket>(new ConnectPacket(), timeout); - OnConnected(SocketOptions.RemoteIPEndPoint); + OnConnected(remoteEndPoint); return true; }, cts.Token); @@ -200,10 +194,6 @@ namespace NetSharp logger.LogException($"Socket exception on connection to {remoteEndPoint}:", ex); return false; } - finally - { - cts.Dispose(); - } } } } \ No newline at end of file diff --git a/NetSharp/NetSharp/Clients/TcpClient.cs b/NetSharp/NetSharp/Clients/TcpClient.cs @@ -41,13 +41,13 @@ namespace NetSharp.Clients uint packetTypeId = PacketRegistry.GetPacketId<Req>(); request.BeforeSerialisation(); - ReadOnlyMemory<byte> serialisedRequest = request.Serialise(); - Packet rawRequest = new Packet(serialisedRequest, packetTypeId, NetworkErrorCode.Ok); + Memory<byte> serialisedRequest = request.Serialise(); + SerialisedPacket rawRequest = new SerialisedPacket(serialisedRequest, packetTypeId); await DoSendPacketAsync(socket, rawRequest, SocketFlags.None, timeout); - Packet rawResponsePacket = await DoReceivePacketAsync(socket, SocketFlags.None, timeout); + SerialisedPacket rawResponsePacket = await DoReceivePacketAsync(socket, SocketFlags.None, timeout); Rep responsePacket = new Rep(); - responsePacket.Deserialise(rawResponsePacket.Buffer); + responsePacket.Deserialise(rawResponsePacket.Contents); responsePacket.AfterDeserialisation(); return responsePacket; @@ -59,8 +59,8 @@ namespace NetSharp.Clients uint packetTypeId = PacketRegistry.GetPacketId<Req>(); request.BeforeSerialisation(); - ReadOnlyMemory<byte> serialisedRequest = request.Serialise(); - Packet rawRequest = new Packet(serialisedRequest, packetTypeId, NetworkErrorCode.Ok); + Memory<byte> serialisedRequest = request.Serialise(); + SerialisedPacket rawRequest = new SerialisedPacket(serialisedRequest, packetTypeId); return await DoSendPacketAsync(socket, rawRequest, SocketFlags.None, timeout); } } diff --git a/NetSharp/NetSharp/Clients/UdpClient.cs b/NetSharp/NetSharp/Clients/UdpClient.cs @@ -1,6 +1,6 @@ using System; +using System.Net; using System.Net.Sockets; -using System.Threading; using System.Threading.Tasks; using NetSharp.Packets; using NetSharp.Packets.Builtin; @@ -42,16 +42,17 @@ namespace NetSharp.Clients 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); + Memory<byte> serialisedRequest = request.Serialise(); + SerialisedPacket rawRequest = new SerialisedPacket(serialisedRequest, packetTypeId); - (Packet rawResponsePacket, TransmissionResult packetResult) = + bool sentPacket = await DoSendPacketToAsync(socket, remoteEndPoint, rawRequest, SocketFlags.None, timeout); + + (SerialisedPacket rawResponsePacket, EndPoint responseEndPoint) = await DoReceivePacketFromAsync(socket, remoteEndPoint, SocketFlags.None, timeout); - remoteEndPoint = packetResult.RemoteEndPoint; + remoteEndPoint = responseEndPoint; Rep responsePacket = new Rep(); - responsePacket.Deserialise(rawResponsePacket.Buffer); + responsePacket.Deserialise(rawResponsePacket.Contents); responsePacket.AfterDeserialisation(); return responsePacket; @@ -63,8 +64,9 @@ namespace NetSharp.Clients uint packetTypeId = PacketRegistry.GetPacketId<Req>(); request.BeforeSerialisation(); - ReadOnlyMemory<byte> serialisedRequest = request.Serialise(); - Packet rawRequest = new Packet(serialisedRequest, packetTypeId, NetworkErrorCode.Ok); + Memory<byte> serialisedRequest = request.Serialise(); + SerialisedPacket rawRequest = new SerialisedPacket(serialisedRequest, packetTypeId); + return await DoSendPacketToAsync(socket, remoteEndPoint, rawRequest, SocketFlags.None, timeout); } } diff --git a/NetSharp/NetSharp/Connection.cs b/NetSharp/NetSharp/Connection.cs @@ -5,6 +5,7 @@ using System.Net.Sockets; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using NetSharp.Interfaces; using NetSharp.Logging; using NetSharp.Packets; using NetSharp.Utils; @@ -17,17 +18,6 @@ namespace NetSharp public abstract class Connection : IDisposable { /// <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; @@ -55,45 +45,38 @@ namespace NetSharp /// <summary> /// Listens for a packet to be received from the network. /// </summary> - /// <param name="remoteSocket">The remote socket from which to receive data.</param> + /// <param name="socket">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 the packet should be received. After this timespan elapses, the receive task is cancelled. /// </param> /// <param name="cancellationToken">A pre-existing cancellation token that should be observed alongside the timeout.</param> /// <returns>The packet that was received. <see cref="NullPacket"/> if not received correctly.</returns> - protected Task<Packet> DoReceivePacketAsync(Socket remoteSocket, SocketFlags socketFlags, TimeSpan timeout, + protected async Task<SerialisedPacket> DoReceivePacketAsync(Socket socket, SocketFlags socketFlags, TimeSpan timeout, CancellationToken cancellationToken = default) { - CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource(timeout); - CancellationTokenSource cts = + using CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource(timeout); + using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCancellationTokenSource.Token, cancellationToken); try { - return Task.Factory.StartNew(() => - { - Packet request = NetworkOperations.ReadPacket(remoteSocket, socketFlags); + (SerialisedPacket packet, EndPoint endPoint) = + await NetworkOperations.ReadPacketAsync(socket, socketFlags, cts.Token); - OnBytesReceived(remoteSocket.RemoteEndPoint, request.TotalSize); + OnBytesReceived(endPoint, packet.Contents.Length); - return request; - }, cts.Token); + return packet; } catch (SocketException ex) { - logger.LogException($"Socket exception while reading bytes from {remoteSocket.RemoteEndPoint}:", ex); - return Task.FromResult(NullPacket); + logger.LogException($"Socket exception while reading bytes from {socket.RemoteEndPoint}:", ex); + return SerialisedPacket.Null; } catch (Exception ex) { - logger.LogException($"Exception while reading bytes from {remoteSocket.RemoteEndPoint}:", ex); - return Task.FromResult(NullPacket); - } - finally - { - cts.Dispose(); - timeoutCancellationTokenSource.Dispose(); + logger.LogException($"Exception while reading bytes from {socket.RemoteEndPoint}:", ex); + return SerialisedPacket.Null; } } @@ -110,39 +93,31 @@ namespace NetSharp /// <returns> /// The packet that was received and the associated transmission result. <see cref="NullPacket"/> if not received correctly. /// </returns> - protected Task<(Packet request, TransmissionResult packetResult)> DoReceivePacketFromAsync(Socket socket, - EndPoint remoteEndPoint, SocketFlags socketFlags, TimeSpan timeout, CancellationToken cancellationToken = default) + protected async Task<(SerialisedPacket packet, EndPoint remoteEndPoint)> DoReceivePacketFromAsync(Socket socket, EndPoint remoteEndPoint, SocketFlags socketFlags, + TimeSpan timeout, CancellationToken cancellationToken = default) { - CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource(timeout); - CancellationTokenSource cts = + using CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource(timeout); + using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCancellationTokenSource.Token, cancellationToken); try { - return Task.Factory.StartNew(() => - { - (Packet request, TransmissionResult packetResult) result = - NetworkOperations.ReadPacketFrom(socket, remoteEndPoint, socketFlags); + (SerialisedPacket packet, EndPoint endPoint) = + await NetworkOperations.ReadPacketFromAsync(socket, remoteEndPoint, socketFlags, cts.Token); - OnBytesReceived(result.packetResult.RemoteEndPoint, result.request.TotalSize); + OnBytesReceived(endPoint, packet.Contents.Length); - return result; - }, cts.Token); + return (packet, remoteEndPoint); } catch (SocketException ex) { logger.LogException($"Socket exception while reading bytes from {remoteEndPoint}:", ex); - return Task.FromResult((NullPacket, NullTransmissionResult)); + return (SerialisedPacket.Null, new IPEndPoint(IPAddress.None, IPEndPoint.MinPort)); } catch (Exception ex) { logger.LogException($"Exception while reading bytes from {remoteEndPoint}:", ex); - return Task.FromResult((NullPacket, NullTransmissionResult)); - } - finally - { - cts.Dispose(); - timeoutCancellationTokenSource.Dispose(); + return (SerialisedPacket.Null, new IPEndPoint(IPAddress.None, IPEndPoint.MinPort)); } } @@ -157,38 +132,30 @@ namespace NetSharp /// </param> /// <param name="cancellationToken">A pre-existing cancellation token that should be observed alongside the timeout.</param> /// <returns>Whether the packet was successfully sent.</returns> - protected Task<bool> DoSendPacketAsync(Socket remoteSocket, Packet packet, SocketFlags socketFlags, TimeSpan timeout, + protected async Task<bool> DoSendPacketAsync(Socket remoteSocket, SerialisedPacket packet, SocketFlags socketFlags, TimeSpan timeout, CancellationToken cancellationToken = default) { - CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource(timeout); - CancellationTokenSource cts = + using CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource(timeout); + using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCancellationTokenSource.Token, cancellationToken); try { - return Task.Factory.StartNew(() => - { - NetworkOperations.WritePacket(remoteSocket, packet, socketFlags); + await NetworkOperations.WritePacketAsync(remoteSocket, packet, socketFlags, cts.Token); - OnBytesSent(remoteSocket.RemoteEndPoint, packet.TotalSize); + OnBytesSent(remoteSocket.RemoteEndPoint, packet.Contents.Length); - return true; - }, cts.Token); + return true; } catch (SocketException ex) { logger.LogException($"Socket exception while sending bytes to {remoteSocket.RemoteEndPoint}:", ex); - return Task.FromResult(false); + return false; } catch (Exception ex) { logger.LogException($"Exception while sending bytes to {remoteSocket.RemoteEndPoint}:", ex); - return Task.FromResult(false); - } - finally - { - cts.Dispose(); - timeoutCancellationTokenSource.Dispose(); + return false; } } @@ -204,38 +171,30 @@ namespace NetSharp /// </param> /// <param name="cancellationToken">A pre-existing cancellation token that should be observed alongside the timeout.</param> /// <returns>Whether the packet was successfully sent.</returns> - protected Task<bool> DoSendPacketToAsync(Socket socket, EndPoint remoteEndPoint, Packet packet, SocketFlags socketFlags, + protected async Task<bool> DoSendPacketToAsync(Socket socket, EndPoint remoteEndPoint, SerialisedPacket packet, SocketFlags socketFlags, TimeSpan timeout, CancellationToken cancellationToken = default) { - CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource(timeout); - CancellationTokenSource cts = + using CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource(timeout); + using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCancellationTokenSource.Token, cancellationToken); try { - return Task.Factory.StartNew(() => - { - NetworkOperations.WritePacketTo(socket, remoteEndPoint, packet, socketFlags); + await NetworkOperations.WritePacketToAsync(socket, remoteEndPoint, packet, socketFlags, cts.Token); - OnBytesSent(remoteEndPoint, packet.TotalSize); + OnBytesSent(remoteEndPoint, packet.Contents.Length); - return true; - }, cts.Token); + return true; } catch (SocketException ex) { logger.LogException($"Socket exception while sending bytes to {remoteEndPoint}:", ex); - return Task.FromResult(false); + return false; } catch (Exception ex) { logger.LogException($"Exception while sending bytes to {remoteEndPoint}:", ex); - return Task.FromResult(false); - } - finally - { - cts.Dispose(); - timeoutCancellationTokenSource.Dispose(); + return false; } } diff --git a/NetSharp/NetSharp/Extensions/ServerExtensions.cs b/NetSharp/NetSharp/Extensions/ServerExtensions.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Threading; using System.Threading.Tasks; using NetSharp.Utils; diff --git a/NetSharp/NetSharp/Extensions/SocketExtensions.cs b/NetSharp/NetSharp/Extensions/SocketExtensions.cs @@ -0,0 +1,170 @@ +using System; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace NetSharp.Extensions +{ + /// <summary> + /// Provides additional methods and functionality to the <see cref="Socket"/> class. + /// </summary> + public static class SocketExtensions + { + /// <inheritdoc cref="Socket.ReceiveAsync"/> + public static SocketTask ReceiveAsync(this Socket instance, SocketTask awaitableTask) + { + awaitableTask.Reset(); + if (!instance.ReceiveAsync(awaitableTask.eventArgs)) + { + awaitableTask.wasCompleted = true; + } + + return awaitableTask; + } + + /// <inheritdoc cref="Socket.ReceiveFromAsync"/> + public static SocketTask ReceiveFromAsync(this Socket instance, SocketTask awaitableTask) + { + awaitableTask.Reset(); + if (!instance.ReceiveFromAsync(awaitableTask.eventArgs)) + { + awaitableTask.wasCompleted = true; + } + + return awaitableTask; + } + + /// <inheritdoc cref="Socket.ReceiveMessageFromAsync"/> + public static SocketTask ReceiveMessageFromAsync(this Socket instance, SocketTask awaitableTask) + { + awaitableTask.Reset(); + if (!instance.ReceiveMessageFromAsync(awaitableTask.eventArgs)) + { + awaitableTask.wasCompleted = true; + } + + return awaitableTask; + } + + /// <inheritdoc cref="Socket.SendAsync"/> + public static SocketTask SendAsync(this Socket instance, SocketTask awaitableTask) + { + awaitableTask.Reset(); + if (!instance.SendAsync(awaitableTask.eventArgs)) + { + awaitableTask.wasCompleted = true; + } + + return awaitableTask; + } + + /// <inheritdoc cref="Socket.SendToAsync"/> + public static SocketTask SendToAsync(this Socket instance, SocketTask awaitableTask) + { + awaitableTask.Reset(); + if (!instance.SendToAsync(awaitableTask.eventArgs)) + { + awaitableTask.wasCompleted = true; + } + + return awaitableTask; + } + } + + /// <summary> + /// Custom awaitable to ease the use of sockets with the TAP pattern. + /// Credit goes to https://devblogs.microsoft.com/pfxteam/awaiting-socket-operations/. + /// </summary> + public sealed class SocketTask : INotifyCompletion + { + /// <summary> + /// Representing a null action. + /// </summary> + private static readonly Action SentinelAction = () => { }; + + /// <summary> + /// The action that should be invoked upon the completion of the socket task. + /// </summary> + internal Action? continuationAction; + + /// <summary> + /// The underlying socket event args for this socket task. + /// </summary> + internal SocketAsyncEventArgs eventArgs; + + /// <summary> + /// Whether this socket task was completed. + /// </summary> + internal bool wasCompleted; + + /// <summary> + /// Resets this socket task to its default state, and sets <see cref="continuationAction"/> to <c>default</c>. + /// </summary> + internal void Reset() + { + wasCompleted = false; + continuationAction = default; + } + + /// <summary> + /// Initialises a new instance of the <see cref="SocketTask"/> class. + /// </summary> + /// <param name="asyncEventArgs">The socket event args that this socket task should wrap. Must not be <c>null</c>.</param> + /// <exception cref="ArgumentNullException">Thrown if the given <paramref name="asyncEventArgs"/> were <c>null</c>.</exception> + public SocketTask(SocketAsyncEventArgs? asyncEventArgs) + { + eventArgs = asyncEventArgs ?? + throw new ArgumentNullException(nameof(asyncEventArgs), "The given asynchronous socket event args were null."); + + eventArgs.Completed += delegate + { + Action? previousAction = continuationAction ?? + Interlocked.CompareExchange(ref continuationAction, SentinelAction, default); + + previousAction?.Invoke(); + }; + } + + /// <summary> + /// Whether this socket task has been completed. + /// </summary> + public bool IsCompleted + { + get { return wasCompleted; } + } + + /// <summary> + /// Returns this socket task instance. + /// </summary> + public SocketTask GetAwaiter() + { + return this; + } + + /// <summary> + /// Throws a <see cref="SocketException"/> if the wrapped <see cref="SocketAsyncEventArgs.SocketError"/> + /// is not equal to <see cref="SocketError.Success"/>. + /// </summary> + /// <exception cref="SocketException"> + /// Thrown if the wrapped <see cref="SocketAsyncEventArgs"/> did not complete successfully. + /// </exception> + public void GetResult() + { + if (eventArgs.SocketError != SocketError.Success) + { + throw new SocketException((int)eventArgs.SocketError); + } + } + + /// <inheritdoc /> + public void OnCompleted(Action? continuation) + { + if (continuationAction == SentinelAction || + Interlocked.CompareExchange(ref continuationAction, continuation, default) == SentinelAction) + { + Task.Run(continuation); + } + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Interfaces/INetworkSerialisable.cs b/NetSharp/NetSharp/Interfaces/INetworkSerialisable.cs @@ -17,6 +17,6 @@ namespace NetSharp.Interfaces /// Serialises the object instance into a byte array. /// </summary> /// <returns>The memory containing the serialised object instance.</returns> - ReadOnlyMemory<byte> Serialise(); + Memory<byte> Serialise(); } } \ No newline at end of file diff --git a/NetSharp/NetSharp/NetSharp.csproj b/NetSharp/NetSharp/NetSharp.csproj @@ -18,6 +18,7 @@ <ItemGroup> <PackageReference Include="Microsoft.CSharp" Version="4.7.0" /> + <PackageReference Include="Microsoft.Extensions.ObjectPool" Version="3.1.2" /> <PackageReference Include="System.Threading.Channels" Version="4.7.0" /> </ItemGroup> </Project> \ No newline at end of file diff --git a/NetSharp/NetSharp/NetSharp.xml b/NetSharp/NetSharp/NetSharp.xml @@ -139,16 +139,6 @@ Base class for connections, holding methods shared between the <see cref="T:NetSharp.Client"/> and <see cref="T:NetSharp.Server"/> classes. </summary> </member> - <member name="F:NetSharp.Connection.NullPacket"> - <summary> - Represents a packet that was not received correctly. - </summary> - </member> - <member name="F:NetSharp.Connection.NullTransmissionResult"> - <summary> - Represents a transmission result of an incorrect transmission. - </summary> - </member> <member name="F:NetSharp.Connection.logger"> <summary> The logger to which the server can log messages. @@ -169,13 +159,13 @@ <summary> Listens for a packet to be received from the network. </summary> - <param name="remoteSocket">The remote socket from which to receive data.</param> + <param name="socket">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 the packet should be received. After this timespan elapses, the receive task is cancelled. </param> <param name="cancellationToken">A pre-existing cancellation token that should be observed alongside the timeout.</param> - <returns>The packet that was received. <see cref="F:NetSharp.Connection.NullPacket"/> if not received correctly.</returns> + <returns>The packet that was received. <see cref="!:NullPacket"/> if not received correctly.</returns> </member> <member name="M:NetSharp.Connection.DoReceivePacketFromAsync(System.Net.Sockets.Socket,System.Net.EndPoint,System.Net.Sockets.SocketFlags,System.TimeSpan,System.Threading.CancellationToken)"> <summary> @@ -189,10 +179,10 @@ </param> <param name="cancellationToken">A pre-existing cancellation token that should be observed alongside the timeout.</param> <returns> - The packet that was received and the associated transmission result. <see cref="F:NetSharp.Connection.NullPacket"/> if not received correctly. + The packet that was received and the associated transmission result. <see cref="!:NullPacket"/> if not received correctly. </returns> </member> - <member name="M:NetSharp.Connection.DoSendPacketAsync(System.Net.Sockets.Socket,NetSharp.Packets.Packet,System.Net.Sockets.SocketFlags,System.TimeSpan,System.Threading.CancellationToken)"> + <member name="M:NetSharp.Connection.DoSendPacketAsync(System.Net.Sockets.Socket,NetSharp.Packets.SerialisedPacket,System.Net.Sockets.SocketFlags,System.TimeSpan,System.Threading.CancellationToken)"> <summary> Sends the given packet to the network. </summary> @@ -205,7 +195,7 @@ <param name="cancellationToken">A pre-existing cancellation token that should be observed alongside the timeout.</param> <returns>Whether the packet was successfully sent.</returns> </member> - <member name="M:NetSharp.Connection.DoSendPacketToAsync(System.Net.Sockets.Socket,System.Net.EndPoint,NetSharp.Packets.Packet,System.Net.Sockets.SocketFlags,System.TimeSpan,System.Threading.CancellationToken)"> + <member name="M:NetSharp.Connection.DoSendPacketToAsync(System.Net.Sockets.Socket,System.Net.EndPoint,NetSharp.Packets.SerialisedPacket,System.Net.Sockets.SocketFlags,System.TimeSpan,System.Threading.CancellationToken)"> <summary> Sends the given packet to the network. </summary> @@ -470,6 +460,86 @@ <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:NetSharp.Extensions.SocketExtensions"> + <summary> + Provides additional methods and functionality to the <see cref="T:System.Net.Sockets.Socket"/> class. + </summary> + </member> + <member name="M:NetSharp.Extensions.SocketExtensions.ReceiveAsync(System.Net.Sockets.Socket,NetSharp.Extensions.SocketTask)"> + <inheritdoc cref="M:System.Net.Sockets.Socket.ReceiveAsync(System.Net.Sockets.SocketAsyncEventArgs)"/> + </member> + <member name="M:NetSharp.Extensions.SocketExtensions.ReceiveFromAsync(System.Net.Sockets.Socket,NetSharp.Extensions.SocketTask)"> + <inheritdoc cref="M:System.Net.Sockets.Socket.ReceiveFromAsync(System.Net.Sockets.SocketAsyncEventArgs)"/> + </member> + <member name="M:NetSharp.Extensions.SocketExtensions.ReceiveMessageFromAsync(System.Net.Sockets.Socket,NetSharp.Extensions.SocketTask)"> + <inheritdoc cref="M:System.Net.Sockets.Socket.ReceiveMessageFromAsync(System.Net.Sockets.SocketAsyncEventArgs)"/> + </member> + <member name="M:NetSharp.Extensions.SocketExtensions.SendAsync(System.Net.Sockets.Socket,NetSharp.Extensions.SocketTask)"> + <inheritdoc cref="M:System.Net.Sockets.Socket.SendAsync(System.Net.Sockets.SocketAsyncEventArgs)"/> + </member> + <member name="M:NetSharp.Extensions.SocketExtensions.SendToAsync(System.Net.Sockets.Socket,NetSharp.Extensions.SocketTask)"> + <inheritdoc cref="M:System.Net.Sockets.Socket.SendToAsync(System.Net.Sockets.SocketAsyncEventArgs)"/> + </member> + <member name="T:NetSharp.Extensions.SocketTask"> + <summary> + Custom awaitable to ease the use of sockets with the TAP pattern. + Credit goes to https://devblogs.microsoft.com/pfxteam/awaiting-socket-operations/. + </summary> + </member> + <member name="F:NetSharp.Extensions.SocketTask.SentinelAction"> + <summary> + Representing a null action. + </summary> + </member> + <member name="F:NetSharp.Extensions.SocketTask.continuationAction"> + <summary> + The action that should be invoked upon the completion of the socket task. + </summary> + </member> + <member name="F:NetSharp.Extensions.SocketTask.eventArgs"> + <summary> + The underlying socket event args for this socket task. + </summary> + </member> + <member name="F:NetSharp.Extensions.SocketTask.wasCompleted"> + <summary> + Whether this socket task was completed. + </summary> + </member> + <member name="M:NetSharp.Extensions.SocketTask.Reset"> + <summary> + Resets this socket task to its default state, and sets <see cref="F:NetSharp.Extensions.SocketTask.continuationAction"/> to <c>default</c>. + </summary> + </member> + <member name="M:NetSharp.Extensions.SocketTask.#ctor(System.Net.Sockets.SocketAsyncEventArgs)"> + <summary> + Initialises a new instance of the <see cref="T:NetSharp.Extensions.SocketTask"/> class. + </summary> + <param name="asyncEventArgs">The socket event args that this socket task should wrap. Must not be <c>null</c>.</param> + <exception cref="T:System.ArgumentNullException">Thrown if the given <paramref name="asyncEventArgs"/> were <c>null</c>.</exception> + </member> + <member name="P:NetSharp.Extensions.SocketTask.IsCompleted"> + <summary> + Whether this socket task has been completed. + </summary> + </member> + <member name="M:NetSharp.Extensions.SocketTask.GetAwaiter"> + <summary> + Returns this socket task instance. + </summary> + </member> + <member name="M:NetSharp.Extensions.SocketTask.GetResult"> + <summary> + Throws a <see cref="T:System.Net.Sockets.SocketException"/> if the wrapped <see cref="P:System.Net.Sockets.SocketAsyncEventArgs.SocketError"/> + is not equal to <see cref="F:System.Net.Sockets.SocketError.Success"/>. + </summary> + <exception cref="T:System.Net.Sockets.SocketException"> + Thrown if the wrapped <see cref="T:System.Net.Sockets.SocketAsyncEventArgs"/> did not complete successfully. + </exception> + </member> + <member name="M:NetSharp.Extensions.SocketTask.OnCompleted(System.Action)"> + <inheritdoc /> + </member> <member name="T:NetSharp.Interfaces.IClient"> <summary> Describes a client capable of asynchronous communication with an <see cref="T:NetSharp.Interfaces.IServer"/> connection. @@ -859,7 +929,7 @@ Initialises a new instance of the <see cref="T:NetSharp.Packets.Builtin.DataPacket"/> class. </summary> </member> - <member name="M:NetSharp.Packets.Builtin.DataPacket.#ctor(System.ReadOnlyMemory{System.Byte})"> + <member name="M:NetSharp.Packets.Builtin.DataPacket.#ctor(System.Memory{System.Byte})"> <summary> Initialises a new instance of the <see cref="T:NetSharp.Packets.Builtin.DataPacket"/> class. </summary> @@ -892,7 +962,7 @@ Initialises a new instance of the <see cref="T:NetSharp.Packets.Builtin.DataResponsePacket"/> class. </summary> </member> - <member name="M:NetSharp.Packets.Builtin.DataResponsePacket.#ctor(System.ReadOnlyMemory{System.Byte})"> + <member name="M:NetSharp.Packets.Builtin.DataResponsePacket.#ctor(System.Memory{System.Byte})"> <summary> Initialises a new instance of the <see cref="T:NetSharp.Packets.Builtin.DataResponsePacket"/> class. </summary> @@ -982,7 +1052,7 @@ Initialises a new instance of the <see cref="T:NetSharp.Packets.Builtin.SimpleDataPacket"/> class. </summary> </member> - <member name="M:NetSharp.Packets.Builtin.SimpleDataPacket.#ctor(System.ReadOnlyMemory{System.Byte})"> + <member name="M:NetSharp.Packets.Builtin.SimpleDataPacket.#ctor(System.Memory{System.Byte})"> <summary> Initialises a new instance of the <see cref="T:NetSharp.Packets.Builtin.SimpleDataPacket"/> class. </summary> @@ -1015,64 +1085,93 @@ A generic error occurred during packet transmission. </summary> </member> - <member name="T:NetSharp.Packets.Packet"> + <member name="T:NetSharp.Packets.NetworkPacket"> <summary> - Represents a packet that is transmitted over the network. + Represents a low-level packet that is transmitted over the network. </summary> </member> - <member name="F:NetSharp.Packets.Packet.HeaderSize"> + <member name="M:NetSharp.Packets.NetworkPacket.#ctor(System.ReadOnlyMemory{System.Byte},NetSharp.Packets.NetworkPacketHeader,NetSharp.Packets.NetworkPacketFooter)"> <summary> - The size of the packet header in bytes. + Initialises a new instance of the <see cref="T:NetSharp.Packets.NetworkPacket"/> struct. </summary> + <param name="data">The data that should be transmitted in the packet.</param> + <param name="header">The header for the packet.</param> + <param name="footer">The footer for the packet.</param> </member> - <member name="F:NetSharp.Packets.Packet.Buffer"> + <member name="F:NetSharp.Packets.NetworkPacket.PacketSize"> <summary> - The data held in this packet. + The size of each packet, including its header, footer, and data segment. </summary> </member> - <member name="F:NetSharp.Packets.Packet.Count"> + <member name="F:NetSharp.Packets.NetworkPacket.DataSegmentSize"> <summary> - The size of the packet's data. + The number of bytes allocated in each packet for user data. </summary> </member> - <member name="F:NetSharp.Packets.Packet.ErrorCode"> + <member name="F:NetSharp.Packets.NetworkPacket.FooterSize"> <summary> - The error code for this packet. + The number of bytes taken up in each packet by its footer. </summary> </member> - <member name="F:NetSharp.Packets.Packet.Type"> + <member name="F:NetSharp.Packets.NetworkPacket.HeaderSize"> <summary> - The packet type. + The number of bytes taken up in each packet by its header. </summary> </member> - <member name="M:NetSharp.Packets.Packet.#ctor(System.ReadOnlyMemory{System.Byte},System.UInt32,NetSharp.Packets.NetworkErrorCode)"> + <member name="F:NetSharp.Packets.NetworkPacket.DataBuffer"> <summary> - Initialises a new instance of the <see cref="T:NetSharp.Packets.Packet"/> struct. + The data held in this packet. </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:NetSharp.Packets.Packet.TotalSize"> + <member name="M:NetSharp.Packets.NetworkPacket.#ctor(System.ReadOnlyMemory{System.Byte},System.Int32,System.UInt32,NetSharp.Packets.NetworkErrorCode,System.Boolean)"> <summary> - Returns the total size of the serialised packet (including the header) in bytes. + Initialises a new instance of the <see cref="T:NetSharp.Packets.NetworkPacket"/> struct. </summary> - <returns>The total size of the serialised packet (including the header) in bytes.</returns> + <param name="data">The data that should be transmitted in the packet.</param> + <param name="dataLength">The number of bytes that are held in the given data buffer.</param> + <param name="type">The packet type.</param> + <param name="errorCode">The error code associated with this transmission.</param> + <param name="hasSucceedingPacket">Whether this packet has a succeeding packet in the packet chain.</param> </member> - <member name="M:NetSharp.Packets.Packet.Deserialise(System.Memory{System.Byte})"> + <member name="M:NetSharp.Packets.NetworkPacket.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:NetSharp.Packets.Packet.Serialise(NetSharp.Packets.Packet)"> + <member name="M:NetSharp.Packets.NetworkPacket.Serialise(NetSharp.Packets.NetworkPacket)"> <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="F:NetSharp.Packets.NetworkPacketFooter.Size"> + <summary> + The number of bytes taken up by a packet footer. + </summary> + </member> + <member name="F:NetSharp.Packets.NetworkPacketHeader.Size"> + <summary> + The number of bytes taken up by a packet header. + </summary> + </member> + <member name="F:NetSharp.Packets.NetworkPacketHeader.DataLength"> + <summary> + The number of bytes of data held in the packet. + </summary> + </member> + <member name="F:NetSharp.Packets.NetworkPacketHeader.ErrorCode"> + <summary> + The error code for this packet. + </summary> + </member> + <member name="F:NetSharp.Packets.NetworkPacketHeader.Type"> + <summary> + The packet type. + </summary> + </member> <member name="T:NetSharp.Packets.PacketRegistry"> <summary> Provides method of registering request packets and their relevant response packets, as well as mapping their ids. @@ -1109,11 +1208,6 @@ The current id for registered packets. </summary> </member> - <member name="M:NetSharp.Packets.PacketRegistry.#cctor"> - <summary> - Initialises a new instance of the <see cref="T:NetSharp.Packets.PacketRegistry"/> class. - </summary> - </member> <member name="M:NetSharp.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 @@ -1215,6 +1309,11 @@ The response packet type can be null; then the request packet type is treated as a 'simple' packet. </param> </member> + <member name="M:NetSharp.Packets.PacketRegistry.#cctor"> + <summary> + Initialises a new instance of the <see cref="T:NetSharp.Packets.PacketRegistry"/> class. + </summary> + </member> <member name="T:NetSharp.Packets.PacketTypeIdAttribute"> <summary> Allows the placing of a custom packet type on a class or struct. This is used if the class or struct @@ -1232,6 +1331,24 @@ The custom type id that the decorated packet type should have. This overrides the automatically generated id. </summary> </member> + <member name="M:NetSharp.Packets.SerialisedPacket.From``1(``0)"> + <summary> + Serialises the given serialisable packet instance and returns the <see cref="T:NetSharp.Packets.SerialisedPacket"/> instance + that was generated. This method invokes <see cref="M:NetSharp.Interfaces.IPacket.BeforeSerialisation"/>. + </summary> + <typeparam name="T">The packet type that will be serialised.</typeparam> + <param name="serialisable">The packet instance that should be serialised.</param> + <returns>The serialised instance.</returns> + </member> + <member name="M:NetSharp.Packets.SerialisedPacket.To``1(NetSharp.Packets.SerialisedPacket@)"> + <summary> + Deserialises and returns a packet instance of the given type from the <see cref="T:NetSharp.Packets.SerialisedPacket"/> instance + that was given. This method invokes <see cref="M:NetSharp.Interfaces.IPacket.AfterDeserialisation"/>. + </summary> + <typeparam name="T">The packet type to which the packet should be deserialised.</typeparam> + <param name="instance">The serialised packet instance that should be deserialised.</param> + <returns>The deserialised instance.</returns> + </member> <member name="T:NetSharp.ComplexPacketHandler`2"> <summary> Represents a method that receives a request packet of the given type (<typeparamref name="TReq"/>) and @@ -1347,9 +1464,9 @@ <param name="socketManager">The <see cref="T:NetSharp.Utils.Socket_Options.SocketOptions"/> manager to use.</param> <param name="networkOperationTimeout">The timeout value for send and receive operations over the network.</param> </member> - <member name="M:NetSharp.Server.DeserialiseRequestPacket(System.UInt32,NetSharp.Packets.Packet@)"> + <member name="M:NetSharp.Server.DeserialiseRequestPacket(System.UInt32,NetSharp.Packets.SerialisedPacket@)"> <summary> - Deserialises the given <see cref="T:NetSharp.Packets.Packet"/> struct into an <see cref="T:NetSharp.Interfaces.IRequestPacket"/> implementor. + Deserialises the given <see cref="T:NetSharp.Packets.NetworkPacket"/> struct into an <see cref="T:NetSharp.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> @@ -1363,13 +1480,13 @@ </member> <member name="M:NetSharp.Server.DoHandleClientAsync(System.Object)"> <summary> - Provides a task that represents the handling of a client. + Provides a task that represents the handling of a client. Calls the abstract <see cref="M:NetSharp.Server.HandleClientAsync(NetSharp.Server.ClientHandlerArgs,System.Threading.CancellationToken)"/> method. </summary> <param name="clientHandlerArgsObj">The object representing the passed <see cref="T:NetSharp.Server.ClientHandlerArgs"/> instance.</param> </member> <member name="M:NetSharp.Server.HandleClientAsync(NetSharp.Server.ClientHandlerArgs,System.Threading.CancellationToken)"> <summary> - Handles a new client asynchronously. + Handles a client asynchronously. </summary> <param name="args">The client handler arguments that should be passed to the client handler.</param> <param name="cancellationToken">Cancellation token set when the server is shutting down.</param> @@ -1406,49 +1523,13 @@ Invokes the <see cref="E:NetSharp.Server.ServerStopped"/> event. </summary> </member> - <member name="M:NetSharp.Server.TryBind(System.Net.IPAddress,System.Int32)"> + <member name="M:NetSharp.Server.TryBind(System.Net.EndPoint,System.TimeSpan)"> <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:NetSharp.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. + Attempts to synchronously bind the underlying socket to the given local endpoint. 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:NetSharp.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:NetSharp.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:NetSharp.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> + <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:NetSharp.Server.TryBindAsync(System.Net.EndPoint,System.TimeSpan)"> @@ -1789,8 +1870,15 @@ <summary> Helper class for asynchronously performing common network operations, for both the UDP and TCP protocols. </summary> + TODO: Implement cancellation support for network operations + TODO: Somehow dont run into the exception below + [Excep] Socket exception while reading bytes from 0.0.0.0:0: System.Net.Sockets.SocketException (10040): Komunikat wysłany na gniazdo datagramu był większy niż wewnętrzny bufor lub przekraczał inny sieciowy limit albo bufor używany do odbierania datagramów był mniejszy niż sam datagram. + at NetSharp.Extensions.SocketTask.GetResult() in G:\Git Repos\EnderRifter\NetSharp\NetSharp\NetSharp\Extensions\SocketExtensions.cs:line 158 + at NetSharp.Utils.NetworkOperations.ReadFromAsync(Socket socket, Int32 count, EndPoint remoteEndPoint, SocketFlags socketFlags) in G:\Git Repos\EnderRifter\NetSharp\NetSharp\NetSharp\Utils\NetworkOperations.cs:line 78 + at NetSharp.Utils.NetworkOperations.ReadPacketFromAsync(Socket socket, EndPoint remoteEndPoint, SocketFlags socketFlags, CancellationToken cancellationToken) in G:\Git Repos\EnderRifter\NetSharp\NetSharp\NetSharp\Utils\NetworkOperations.cs:line 225 + at NetSharp.Connection.DoReceivePacketFromAsync(Socket socket, EndPoint remoteEndPoint, SocketFlags socketFlags, TimeSpan timeout, CancellationToken cancellationToken) in G:\Git Repos\EnderRifter\NetSharp\NetSharp\NetSharp\Connection.cs:line 114 </member> - <member name="M:NetSharp.Utils.NetworkOperations.Read(System.Net.Sockets.Socket,System.Int32,System.Net.Sockets.SocketFlags)"> + <member name="M:NetSharp.Utils.NetworkOperations.ReadAsync(System.Net.Sockets.Socket,System.Int32,System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)"> <summary> Reads the specified amount of data from the network, via the given socket. The given <see cref="T:System.Net.Sockets.SocketFlags"/> are associated with the read. @@ -1800,7 +1888,7 @@ <param name="socketFlags">The socket flags associated with the receive operation.</param> <returns>The result of the receive operation.</returns> </member> - <member name="M:NetSharp.Utils.NetworkOperations.ReadFrom(System.Net.Sockets.Socket,System.Int32,System.Net.EndPoint,System.Net.Sockets.SocketFlags)"> + <member name="M:NetSharp.Utils.NetworkOperations.ReadFromAsync(System.Net.Sockets.Socket,System.Int32,System.Net.EndPoint,System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)"> <summary> Reads a datagram segment of the given length from the given remote endpoint, via the given socket. The given <see cref="T:System.Net.Sockets.SocketFlags"/> are associated with the read. @@ -1811,7 +1899,7 @@ <param name="socketFlags">The socket flags associated with the receive operation.</param> <returns>The result of the receive operation.</returns> </member> - <member name="M:NetSharp.Utils.NetworkOperations.Write(System.Net.Sockets.Socket,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags)"> + <member name="M:NetSharp.Utils.NetworkOperations.WriteAsync(System.Net.Sockets.Socket,System.Memory{System.Byte},System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)"> <summary> Writes the given data buffer 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"/> @@ -1821,7 +1909,7 @@ <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> </member> - <member name="M:NetSharp.Utils.NetworkOperations.WriteTo(System.Net.Sockets.Socket,System.Net.EndPoint,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags)"> + <member name="M:NetSharp.Utils.NetworkOperations.WriteToAsync(System.Net.Sockets.Socket,System.Net.EndPoint,System.Memory{System.Byte},System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)"> <summary> Writes the given data buffer 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"/> @@ -1832,16 +1920,17 @@ <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> </member> - <member name="M:NetSharp.Utils.NetworkOperations.ReadPacket(System.Net.Sockets.Socket,System.Net.Sockets.SocketFlags)"> + <member name="M:NetSharp.Utils.NetworkOperations.ReadPacketAsync(System.Net.Sockets.Socket,System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)"> <summary> Reads a packet from network, via the given socket. The given <see cref="T:System.Net.Sockets.SocketFlags"/> are associated with the read. </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> - <returns>The read packet.</returns> + <param name="cancellationToken">The cancellation token that should be observed for the duration of the task.</param> + <returns>The read packet, and the endpoint from which it was read.</returns> </member> - <member name="M:NetSharp.Utils.NetworkOperations.ReadPacketFrom(System.Net.Sockets.Socket,System.Net.EndPoint,System.Net.Sockets.SocketFlags)"> + <member name="M:NetSharp.Utils.NetworkOperations.ReadPacketFromAsync(System.Net.Sockets.Socket,System.Net.EndPoint,System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)"> <summary> Reads a packet from the given remote endpoint, via the given socket. The given <see cref="T:System.Net.Sockets.SocketFlags"/> are associated with the read. @@ -1849,26 +1938,29 @@ <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> - <returns>The read packet and associated transmission results.</returns> + <param name="cancellationToken">The cancellation token that should be observed for the duration of the task.</param> + <returns>The read packet, and the endpoint from which it was read.</returns> </member> - <member name="M:NetSharp.Utils.NetworkOperations.WritePacket(System.Net.Sockets.Socket,NetSharp.Packets.Packet,System.Net.Sockets.SocketFlags)"> + <member name="M:NetSharp.Utils.NetworkOperations.WritePacketAsync(System.Net.Sockets.Socket,NetSharp.Packets.SerialisedPacket,System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)"> <summary> Writes the given packet to the network, via the given socket. The given <see cref="T:System.Net.Sockets.SocketFlags"/> are associated with the write. </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="serialisedPacket">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 that should be observed for the duration of the task.</param> </member> - <member name="M:NetSharp.Utils.NetworkOperations.WritePacketTo(System.Net.Sockets.Socket,System.Net.EndPoint,NetSharp.Packets.Packet,System.Net.Sockets.SocketFlags)"> + <member name="M:NetSharp.Utils.NetworkOperations.WritePacketToAsync(System.Net.Sockets.Socket,System.Net.EndPoint,NetSharp.Packets.SerialisedPacket,System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)"> <summary> Writes the given packet to the given remote endpoint, via the given socket. The given <see cref="T:System.Net.Sockets.SocketFlags"/> are associated with the write. </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="serialisedPacket">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 that should be observed for the duration of the task.</param> </member> <member name="T:NetSharp.Utils.Socket_Options.DefaultSocketOptions"> <summary> diff --git a/NetSharp/NetSharp/Packets/Builtin/ConnectPacket.cs b/NetSharp/NetSharp/Packets/Builtin/ConnectPacket.cs @@ -25,9 +25,9 @@ namespace NetSharp.Packets.Builtin } /// <inheritdoc /> - public ReadOnlyMemory<byte> Serialise() + public Memory<byte> Serialise() { - return ReadOnlyMemory<byte>.Empty; + return Memory<byte>.Empty; } } } \ No newline at end of file diff --git a/NetSharp/NetSharp/Packets/Builtin/ConnectResponsePacket.cs b/NetSharp/NetSharp/Packets/Builtin/ConnectResponsePacket.cs @@ -28,9 +28,9 @@ namespace NetSharp.Packets.Builtin } /// <inheritdoc /> - public ReadOnlyMemory<byte> Serialise() + public Memory<byte> Serialise() { - return ReadOnlyMemory<byte>.Empty; + return Memory<byte>.Empty; } } } \ No newline at end of file diff --git a/NetSharp/NetSharp/Packets/Builtin/DataPacket.cs b/NetSharp/NetSharp/Packets/Builtin/DataPacket.cs @@ -12,7 +12,7 @@ namespace NetSharp.Packets.Builtin /// <summary> /// The data that should be transferred across the network. /// </summary> - public ReadOnlyMemory<byte> RequestBuffer; + public Memory<byte> RequestBuffer; /// <summary> /// Initialises a new instance of the <see cref="DataPacket"/> class. @@ -26,7 +26,7 @@ namespace NetSharp.Packets.Builtin /// 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) + public DataPacket(Memory<byte> buffer) { RequestBuffer = buffer; } @@ -44,11 +44,11 @@ namespace NetSharp.Packets.Builtin /// <inheritdoc /> public void Deserialise(ReadOnlyMemory<byte> serialisedObject) { - RequestBuffer = serialisedObject; + RequestBuffer = serialisedObject.ToArray(); } /// <inheritdoc /> - public ReadOnlyMemory<byte> Serialise() + public Memory<byte> Serialise() { return RequestBuffer; } diff --git a/NetSharp/NetSharp/Packets/Builtin/DataResponsePacket.cs b/NetSharp/NetSharp/Packets/Builtin/DataResponsePacket.cs @@ -12,7 +12,7 @@ namespace NetSharp.Packets.Builtin /// <summary> /// The data that should be transferred across the network. /// </summary> - public ReadOnlyMemory<byte> ResponseBuffer; + public Memory<byte> ResponseBuffer; /// <summary> /// Initialises a new instance of the <see cref="DataResponsePacket"/> class. @@ -26,7 +26,7 @@ namespace NetSharp.Packets.Builtin /// 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) + public DataResponsePacket(Memory<byte> buffer) { ResponseBuffer = buffer; } @@ -47,11 +47,11 @@ namespace NetSharp.Packets.Builtin /// <inheritdoc /> public void Deserialise(ReadOnlyMemory<byte> serialisedObject) { - ResponseBuffer = serialisedObject; + ResponseBuffer = serialisedObject.ToArray(); } /// <inheritdoc /> - public ReadOnlyMemory<byte> Serialise() + public Memory<byte> Serialise() { return ResponseBuffer; } diff --git a/NetSharp/NetSharp/Packets/Builtin/DisconnectPacket.cs b/NetSharp/NetSharp/Packets/Builtin/DisconnectPacket.cs @@ -25,9 +25,9 @@ namespace NetSharp.Packets.Builtin } /// <inheritdoc /> - public ReadOnlyMemory<byte> Serialise() + public Memory<byte> Serialise() { - return ReadOnlyMemory<byte>.Empty; + return Memory<byte>.Empty; } } } \ No newline at end of file diff --git a/NetSharp/NetSharp/Packets/Builtin/PingPacket.cs b/NetSharp/NetSharp/Packets/Builtin/PingPacket.cs @@ -25,9 +25,9 @@ namespace NetSharp.Packets.Builtin } /// <inheritdoc /> - public ReadOnlyMemory<byte> Serialise() + public Memory<byte> Serialise() { - return ReadOnlyMemory<byte>.Empty; + return Memory<byte>.Empty; } } } \ No newline at end of file diff --git a/NetSharp/NetSharp/Packets/Builtin/PingResponsePacket.cs b/NetSharp/NetSharp/Packets/Builtin/PingResponsePacket.cs @@ -28,9 +28,9 @@ namespace NetSharp.Packets.Builtin } /// <inheritdoc /> - public ReadOnlyMemory<byte> Serialise() + public Memory<byte> Serialise() { - return ReadOnlyMemory<byte>.Empty; + return Memory<byte>.Empty; } } } \ No newline at end of file diff --git a/NetSharp/NetSharp/Packets/Builtin/SimpleDataPacket.cs b/NetSharp/NetSharp/Packets/Builtin/SimpleDataPacket.cs @@ -12,7 +12,7 @@ namespace NetSharp.Packets.Builtin /// <summary> /// The data that should be transferred across the network. /// </summary> - public ReadOnlyMemory<byte> RequestBuffer; + public Memory<byte> RequestBuffer; /// <summary> /// Initialises a new instance of the <see cref="SimpleDataPacket"/> class. @@ -26,7 +26,7 @@ namespace NetSharp.Packets.Builtin /// 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) + public SimpleDataPacket(Memory<byte> buffer) { RequestBuffer = buffer; } @@ -44,11 +44,11 @@ namespace NetSharp.Packets.Builtin /// <inheritdoc /> public void Deserialise(ReadOnlyMemory<byte> serialisedObject) { - RequestBuffer = serialisedObject; + RequestBuffer = serialisedObject.ToArray(); } /// <inheritdoc /> - public ReadOnlyMemory<byte> Serialise() + public Memory<byte> Serialise() { return RequestBuffer; } diff --git a/NetSharp/NetSharp/Packets/NetworkPacket.cs b/NetSharp/NetSharp/Packets/NetworkPacket.cs @@ -0,0 +1,202 @@ +using System; +using NetSharp.Utils.Conversion; + +namespace NetSharp.Packets +{ + /// <summary> + /// Represents a low-level packet that is transmitted over the network. + /// </summary> + public readonly struct NetworkPacket + { + /// <summary> + /// Initialises a new instance of the <see cref="NetworkPacket"/> struct. + /// </summary> + /// <param name="data">The data that should be transmitted in the packet.</param> + /// <param name="header">The header for the packet.</param> + /// <param name="footer">The footer for the packet.</param> + private NetworkPacket(ReadOnlyMemory<byte> data, NetworkPacketHeader header, NetworkPacketFooter footer) + { + Header = header; + + DataBuffer = data; + + Footer = footer; + } + + /// <summary> + /// The size of each packet, including its header, footer, and data segment. + /// </summary> + internal const int PacketSize = 1024; + + /// <summary> + /// The number of bytes allocated in each packet for user data. + /// </summary> + public const int DataSegmentSize = PacketSize - HeaderSize - FooterSize; + + /// <summary> + /// The number of bytes taken up in each packet by its footer. + /// </summary> + public const int FooterSize = NetworkPacketFooter.Size; + + /// <summary> + /// The number of bytes taken up in each packet by its header. + /// </summary> + public const int HeaderSize = NetworkPacketHeader.Size; + + /// <summary> + /// The data held in this packet. + /// </summary> + public readonly ReadOnlyMemory<byte> DataBuffer; + + public readonly NetworkPacketFooter Footer; + public readonly NetworkPacketHeader Header; + + /// <summary> + /// Initialises a new instance of the <see cref="NetworkPacket"/> struct. + /// </summary> + /// <param name="data">The data that should be transmitted in the packet.</param> + /// <param name="dataLength">The number of bytes that are held in the given data buffer.</param> + /// <param name="type">The packet type.</param> + /// <param name="errorCode">The error code associated with this transmission.</param> + /// <param name="hasSucceedingPacket">Whether this packet has a succeeding packet in the packet chain.</param> + public NetworkPacket(ReadOnlyMemory<byte> data, int dataLength, uint type, NetworkErrorCode errorCode, bool hasSucceedingPacket) + { + Header = new NetworkPacketHeader(type, errorCode, dataLength); + + DataBuffer = data; + + Footer = new NetworkPacketFooter(hasSucceedingPacket); + } + + /// <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 NetworkPacket Deserialise(Memory<byte> buffer) + { + Span<byte> serialisedPacketHeader = buffer.Slice(0, HeaderSize).Span; + NetworkPacketHeader header = NetworkPacketHeader.Deserialise(serialisedPacketHeader); + + Span<byte> serialisedPacketFooter = buffer.Slice(HeaderSize + DataSegmentSize, FooterSize).Span; + NetworkPacketFooter footer = NetworkPacketFooter.Deserialise(serialisedPacketFooter); + + // data segment + Memory<byte> packetData = buffer.Slice(HeaderSize, DataSegmentSize); + + return new NetworkPacket(packetData, header, footer); + } + + /// <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(NetworkPacket instance) + { + byte[] buffer = new byte[PacketSize]; + + Span<byte> serialisedPacketHeader = new Span<byte>(buffer, 0, HeaderSize); + NetworkPacketHeader.Serialise(serialisedPacketHeader, instance.Header); + + Span<byte> serialisedPacketFooter = new Span<byte>(buffer, HeaderSize + DataSegmentSize, FooterSize); + NetworkPacketFooter.Serialise(serialisedPacketFooter, instance.Footer); + + Memory<byte> serialisedInstanceData = new Memory<byte>(buffer, HeaderSize, instance.Header.DataLength); + instance.DataBuffer.CopyTo(serialisedInstanceData); + + return buffer; + } + } + + // TODO: Document + public readonly struct NetworkPacketFooter + { + private const int PacketHasNextStart = 0; + + /// <summary> + /// The number of bytes taken up by a packet footer. + /// </summary> + public const int Size = sizeof(bool); + + public readonly bool HasSucceedingPacket; + + public NetworkPacketFooter(bool hasSucceedingPacket) + { + HasSucceedingPacket = hasSucceedingPacket; + } + + public static NetworkPacketFooter Deserialise(Span<byte> buffer) + { + Span<byte> serialisedHasNextFlag = buffer.Slice(PacketHasNextStart, sizeof(bool)); + + return new NetworkPacketFooter( + EndianAwareBitConverter.ToBoolean(serialisedHasNextFlag)); + } + + public static void Serialise(Span<byte> buffer, NetworkPacketFooter instance) + { + Span<byte> serialisedHasNextFlag = buffer.Slice(PacketHasNextStart, sizeof(bool)); + + EndianAwareBitConverter.GetBytes(instance.HasSucceedingPacket).CopyTo(serialisedHasNextFlag); + } + } + + // TODO: Document + public readonly struct NetworkPacketHeader + { + private const int PacketDataLengthStart = 2 * sizeof(uint); + private const int PacketErrorCodeStart = sizeof(uint); + private const int PacketTypeStart = 0; + + /// <summary> + /// The number of bytes taken up by a packet header. + /// </summary> + public const int Size = sizeof(uint) + sizeof(uint) + sizeof(int); + + /// <summary> + /// The number of bytes of data held in the packet. + /// </summary> + public readonly int DataLength; + + /// <summary> + /// The error code for this packet. + /// </summary> + public readonly NetworkErrorCode ErrorCode; + + /// <summary> + /// The packet type. + /// </summary> + public readonly uint Type; + + public NetworkPacketHeader(uint packetType, NetworkErrorCode packetErrorCode, int packetDataLength) + { + Type = packetType; + ErrorCode = packetErrorCode; + DataLength = packetDataLength; + } + + public static NetworkPacketHeader Deserialise(Span<byte> buffer) + { + Span<byte> serialisedType = buffer.Slice(PacketTypeStart, sizeof(uint)); + Span<byte> serialisedErrorCode = buffer.Slice(PacketErrorCodeStart, sizeof(uint)); + Span<byte> serialisedDataLength = buffer.Slice(PacketDataLengthStart, sizeof(int)); + + return new NetworkPacketHeader( + EndianAwareBitConverter.ToUInt32(serialisedType), + (NetworkErrorCode)EndianAwareBitConverter.ToUInt32(serialisedErrorCode), + EndianAwareBitConverter.ToInt32(serialisedDataLength)); + } + + public static void Serialise(Span<byte> buffer, NetworkPacketHeader instance) + { + Span<byte> serialisedType = buffer.Slice(PacketTypeStart, sizeof(uint)); + Span<byte> serialisedErrorCode = buffer.Slice(PacketErrorCodeStart, sizeof(uint)); + Span<byte> serialisedDataLength = buffer.Slice(PacketDataLengthStart, sizeof(int)); + + EndianAwareBitConverter.GetBytes(instance.Type).CopyTo(serialisedType); + EndianAwareBitConverter.GetBytes((uint)instance.ErrorCode).CopyTo(serialisedErrorCode); + EndianAwareBitConverter.GetBytes(instance.DataLength).CopyTo(serialisedDataLength); + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Packets/Packet.cs b/NetSharp/NetSharp/Packets/Packet.cs @@ -1,97 +0,0 @@ -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; - ReadOnlyMemory<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 @@ -45,18 +45,6 @@ namespace NetSharp.Packets 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. @@ -177,7 +165,7 @@ namespace NetSharp.Packets /// <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; + return requestToResponseMap.TryGetValue(typeof(TRequest), out Type responsePacketType) ? responsePacketType : default; } /// <summary> @@ -187,7 +175,7 @@ namespace NetSharp.Packets /// <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; + return requestToResponseMap.TryGetValue(requestPacketType, out Type responsePacketType) ? responsePacketType : default; } /// <summary> @@ -283,5 +271,17 @@ namespace NetSharp.Packets RegisterPacketType(requestPacketType, responsePacketType); } } + + /// <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); + } } } \ No newline at end of file diff --git a/NetSharp/NetSharp/Packets/SerialisedPacket.cs b/NetSharp/NetSharp/Packets/SerialisedPacket.cs @@ -0,0 +1,48 @@ +using System; +using NetSharp.Interfaces; + +namespace NetSharp.Packets +{ + public readonly struct SerialisedPacket + { + public static readonly SerialisedPacket Null = new SerialisedPacket(Memory<byte>.Empty, 0); + public readonly Memory<byte> Contents; + public readonly uint Type; + + public SerialisedPacket(Memory<byte> contents, uint type) + { + Contents = contents; + + Type = type; + } + + /// <summary> + /// Serialises the given serialisable packet instance and returns the <see cref="SerialisedPacket"/> instance + /// that was generated. This method invokes <see cref="IPacket.BeforeSerialisation"/>. + /// </summary> + /// <typeparam name="T">The packet type that will be serialised.</typeparam> + /// <param name="serialisable">The packet instance that should be serialised.</param> + /// <returns>The serialised instance.</returns> + public static SerialisedPacket From<T>(T serialisable) where T : class, IPacket, INetworkSerialisable + { + serialisable.BeforeSerialisation(); + return new SerialisedPacket(serialisable.Serialise(), PacketRegistry.GetPacketId<T>()); + } + + /// <summary> + /// Deserialises and returns a packet instance of the given type from the <see cref="SerialisedPacket"/> instance + /// that was given. This method invokes <see cref="IPacket.AfterDeserialisation"/>. + /// </summary> + /// <typeparam name="T">The packet type to which the packet should be deserialised.</typeparam> + /// <param name="instance">The serialised packet instance that should be deserialised.</param> + /// <returns>The deserialised instance.</returns> + public static T To<T>(in SerialisedPacket instance) where T : class, IPacket, INetworkSerialisable, new() + { + T packet = new T(); + packet.Deserialise(instance.Contents); + packet.AfterDeserialisation(); + + return packet; + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Server.cs b/NetSharp/NetSharp/Server.cs @@ -95,7 +95,7 @@ namespace NetSharp /// </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); + private delegate IRequestPacket RawRequestPacketDeserialiser(in SerialisedPacket rawPacket); /// <summary> /// Registers packet handlers for every internal library packet. @@ -104,6 +104,9 @@ namespace NetSharp { TryRegisterSimplePacketHandler((DisconnectPacket packet, EndPoint remoteEndPoint) => { +#if DEBUG + logger.LogMessage($"Received disconnect packet from {remoteEndPoint}"); +#endif OnClientDisconnected(remoteEndPoint); }); @@ -117,7 +120,9 @@ namespace NetSharp TryRegisterComplexPacketHandler((ConnectPacket packet, EndPoint remoteEndPoint) => { OnClientConnected(remoteEndPoint); - +#if DEBUG + logger.LogMessage($"Received connection request from {remoteEndPoint}"); +#endif return new ConnectResponsePacket { RequestPacket = packet }; }); @@ -198,13 +203,12 @@ namespace NetSharp } /// <summary> - /// Deserialises the given <see cref="Packet"/> struct into an <see cref="IRequestPacket"/> implementor. + /// Deserialises the given <see cref="NetworkPacket"/> 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) + protected IRequestPacket? DeserialiseRequestPacket(uint packetType, in SerialisedPacket rawRequestPacket) { if (requestPacketDeserialisers.TryGetValue(packetType, out RawRequestPacketDeserialiser deserialiser)) { @@ -234,7 +238,7 @@ namespace NetSharp } /// <summary> - /// Provides a task that represents the handling of a client. + /// Provides a task that represents the handling of a client. Calls the abstract <see cref="HandleClientAsync"/> method. /// </summary> /// <param name="clientHandlerArgsObj">The object representing the passed <see cref="ClientHandlerArgs"/> instance.</param> protected async Task DoHandleClientAsync(object clientHandlerArgsObj) @@ -245,8 +249,14 @@ namespace NetSharp { await HandleClientAsync(clientHandlerArgs, serverShutdownCancellationTokenSource.Token); } - catch (TaskCanceledException) { logger.LogWarning("Client handling was cancelled via a task cancellation."); } - catch (OperationCanceledException) { logger.LogWarning("Client handling was cancelled via an operation cancellation."); } + catch (TaskCanceledException) + { + logger.LogWarning("Client handling was cancelled via a task cancellation."); + } + catch (OperationCanceledException) + { + logger.LogWarning("Client handling was cancelled via an operation cancellation."); + } catch (Exception ex) { logger.LogException("Exception during client handling", ex); @@ -266,7 +276,7 @@ namespace NetSharp } /// <summary> - /// Handles a new client asynchronously. + /// Handles a client asynchronously. /// </summary> /// <param name="args">The client handler arguments that should be passed to the client handler.</param> /// <param name="cancellationToken">Cancellation token set when the server is shutting down.</param> @@ -280,7 +290,6 @@ namespace NetSharp /// <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) { @@ -341,54 +350,14 @@ namespace NetSharp 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. + /// Attempts to synchronously bind the underlying socket to the given local endpoint. 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 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> + /// <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) => - await TryBindAsync(localEndPoint, Timeout.InfiniteTimeSpan); + protected bool TryBind(EndPoint localEndPoint, TimeSpan timeout) => + TryBindAsync(localEndPoint, timeout).Result; /// <summary> /// Attempts to asynchronously bind the underlying socket to the given local endpoint. Does not block. @@ -399,8 +368,8 @@ namespace NetSharp /// <returns>Whether the binding was successful or not.</returns> protected async Task<bool> TryBindAsync(EndPoint localEndPoint, TimeSpan timeout) { - CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource(timeout); - CancellationTokenSource cts = + using CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource(timeout); + using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCancellationTokenSource.Token, serverShutdownCancellationToken); try @@ -421,11 +390,6 @@ namespace NetSharp logger.LogException($"Socket exception on binding socket to {localEndPoint}:", ex); return false; } - finally - { - cts.Dispose(); - timeoutCancellationTokenSource.Dispose(); - } } /// <summary> @@ -576,14 +540,7 @@ namespace NetSharp { uint packetTypeId = PacketRegistry.GetPacketId<Req>(); - static IRequestPacket PacketDeserialiser(in Packet packet) - { - Req request = new Req(); - request.Deserialise(packet.Buffer); - request.AfterDeserialisation(); - - return request; - } + static IRequestPacket PacketDeserialiser(in SerialisedPacket packet) => SerialisedPacket.To<Req>(packet); IResponsePacket<IRequestPacket> MappedHandlerDelegate(IRequestPacket p, EndPoint ep) { @@ -617,14 +574,7 @@ namespace NetSharp { uint packetTypeId = PacketRegistry.GetPacketId<Req>(); - static IRequestPacket PacketDeserialiser(in Packet packet) - { - Req request = new Req(); - request.Deserialise(packet.Buffer); - request.AfterDeserialisation(); - - return request; - } + static IRequestPacket PacketDeserialiser(in SerialisedPacket packet) => SerialisedPacket.To<Req>(packet); void MappedHandlerDelegate(IRequestPacket p, EndPoint ep) => handlerDelegate((Req)p, ep); diff --git a/NetSharp/NetSharp/Servers/TcpServer.cs b/NetSharp/NetSharp/Servers/TcpServer.cs @@ -29,10 +29,10 @@ namespace NetSharp.Servers do { // receive a single raw packet from the network - Packet rawRequest = await DoReceivePacketAsync(clientHandlerSocket, SocketFlags.None, + SerialisedPacket rawRequest = await DoReceivePacketAsync(clientHandlerSocket, SocketFlags.None, Timeout.InfiniteTimeSpan, cancellationToken); - if (rawRequest.Equals(NullPacket) || + if (rawRequest.Equals(SerialisedPacket.Null) || rawRequest.Type == PacketRegistry.GetPacketId<DisconnectPacket>()) { logger.LogMessage( @@ -44,33 +44,32 @@ namespace NetSharp.Servers Type requestPacketType = PacketRegistry.GetPacketType(rawRequest.Type); // the request packet is only null if no packet handler was registered for it - if (requestPacket == null) continue; + if (requestPacket == default) continue; - logger.LogMessage($"Received {rawRequest.Count} bytes from {remoteEp}"); + logger.LogMessage($"Received {rawRequest.Contents.Length} bytes from {remoteEp}"); - logger.LogMessage($"Received request: {Encoding.UTF8.GetString(rawRequest.Buffer.Span)}"); + logger.LogMessage($"Received request: {Encoding.UTF8.GetString(rawRequest.Contents.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; + if (responsePacket == default) continue; Type? responsePacketType = PacketRegistry.GetResponsePacketType(requestPacketType); - if (responsePacketType == null) + if (responsePacketType == default) { 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); + SerialisedPacket rawResponse = SerialisedPacket.From(responsePacket); + // uint responsePacketTypeId = PacketRegistry.GetPacketId(responsePacketType); + // responsePacket.BeforeSerialisation(); + // SerialisedPacket rawResponse = new SerialisedPacket(responsePacket.Serialise(), responsePacketTypeId); // echo back the processed raw response to the network bool sentCorrectly = await DoSendPacketAsync(clientHandlerSocket, rawResponse, SocketFlags.None, @@ -80,10 +79,9 @@ namespace NetSharp.Servers { logger.LogMessage( $"Could not send response back to client socket: [Remote EP: {remoteEp}]"); - break; } - logger.LogMessage($"Sent {rawResponse.TotalSize} bytes to {remoteEp}"); + logger.LogMessage($"Sent {rawResponse.Contents.Length} bytes to {remoteEp}"); } while (true); } finally @@ -106,7 +104,7 @@ namespace NetSharp.Servers /// <inheritdoc /> public override async Task RunAsync(EndPoint localEndPoint) { - bool bound = await TryBindAsync(localEndPoint); + bool bound = await TryBindAsync(localEndPoint, Timeout.InfiniteTimeSpan); logger.LogMessage($"Is server socket bound: {bound}"); diff --git a/NetSharp/NetSharp/Servers/UdpServer.cs b/NetSharp/NetSharp/Servers/UdpServer.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Net; using System.Net.Sockets; +using System.Text; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; @@ -30,13 +31,13 @@ namespace NetSharp.Servers /// <summary> /// Holds currently connected and active clients, as well as their current received packet queues. /// </summary> - private readonly ConcurrentDictionary<EndPoint, Channel<Packet>> activeClients; + private readonly ConcurrentDictionary<EndPoint, Channel<SerialisedPacket>> activeClients; /// <inheritdoc /> protected override async Task HandleClientAsync(ClientHandlerArgs args, CancellationToken cancellationToken) { EndPoint clientEndPoint = args.ClientEndPoint; - Channel<Packet> clientPacketBuffer = activeClients[clientEndPoint]; + Channel<SerialisedPacket> clientPacketBuffer = activeClients[clientEndPoint]; logger.LogMessage($"Initialised client handler for client socket: [Remote EP: {clientEndPoint}]"); @@ -45,9 +46,9 @@ namespace NetSharp.Servers do { // receive a single raw packet from the network - Packet rawRequest = await clientPacketBuffer.Reader.ReadAsync(cancellationToken); + SerialisedPacket rawRequest = await clientPacketBuffer.Reader.ReadAsync(cancellationToken); - if (rawRequest.Equals(NullPacket) || + if (rawRequest.Equals(SerialisedPacket.Null) || rawRequest.Type == PacketRegistry.GetPacketId<DisconnectPacket>()) { logger.LogMessage( @@ -59,29 +60,32 @@ namespace NetSharp.Servers Type requestPacketType = PacketRegistry.GetPacketType(rawRequest.Type); // the request packet is only null if no packet handler was registered for it - if (requestPacket == null) continue; + if (requestPacket == default) continue; + + logger.LogMessage($"Received {rawRequest.Contents.Length} bytes from {clientEndPoint}"); + + logger.LogMessage($"Received request: {Encoding.UTF8.GetString(rawRequest.Contents.Span)}"); 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; + if (responsePacket == default) continue; Type? responsePacketType = PacketRegistry.GetResponsePacketType(requestPacketType); - if (responsePacketType == null) + if (responsePacketType == default) { 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); + SerialisedPacket rawResponse = SerialisedPacket.From(responsePacket); + // uint responsePacketTypeId = PacketRegistry.GetPacketId(responsePacketType); + // responsePacket.BeforeSerialisation(); + // SerialisedPacket rawResponse = new SerialisedPacket(responsePacket.Serialise(), responsePacketTypeId); // echo back the processed raw response to the network bool sentCorrectly = await DoSendPacketToAsync(socket, clientEndPoint, rawResponse, SocketFlags.None, @@ -91,7 +95,6 @@ namespace NetSharp.Servers { logger.LogWarning( $"Could not send response back to client socket: [Remote EP: {clientEndPoint}]"); - break; } } while (true); } @@ -99,7 +102,7 @@ namespace NetSharp.Servers { logger.LogMessage($"Stopping client handler for client socket: [Remote EP: {clientEndPoint}]"); - if (activeClients.TryRemove(clientEndPoint, out Channel<Packet> packetChannel)) + if (activeClients.TryRemove(clientEndPoint, out Channel<SerialisedPacket> packetChannel)) { packetChannel.Writer.Complete(); logger.LogMessage( @@ -117,7 +120,7 @@ namespace NetSharp.Servers public UdpServer(TimeSpan networkOperationTimeout) : base(SocketType.Dgram, ProtocolType.Udp, SocketOptionManager.Udp, networkOperationTimeout) { - activeClients = new ConcurrentDictionary<EndPoint, Channel<Packet>>(); + activeClients = new ConcurrentDictionary<EndPoint, Channel<SerialisedPacket>>(); } /// <inheritdoc /> @@ -128,7 +131,7 @@ namespace NetSharp.Servers /// <inheritdoc /> public override async Task RunAsync(EndPoint localEndPoint) { - bool bound = await TryBindAsync(localEndPoint); + bool bound = await TryBindAsync(localEndPoint, Timeout.InfiniteTimeSpan); if (!bound) { @@ -142,12 +145,12 @@ namespace NetSharp.Servers while (runServer) { EndPoint nullEndPoint = new IPEndPoint(IPAddress.Any, 0); - (Packet request, TransmissionResult packetResult) = + (SerialisedPacket request, EndPoint remoteEndPoint) = await DoReceivePacketFromAsync(socket, nullEndPoint, SocketFlags.None, Timeout.InfiniteTimeSpan, serverShutdownCancellationToken); - EndPoint clientEndPoint = packetResult.RemoteEndPoint; + EndPoint clientEndPoint = remoteEndPoint; - if (request.Equals(NullPacket) || packetResult.Equals(NullTransmissionResult)) + if (request.Equals(SerialisedPacket.Null)) { continue; } @@ -156,7 +159,7 @@ namespace NetSharp.Servers { ClientHandlerArgs args = ClientHandlerArgs.ForUdpClientHandler(in clientEndPoint); - activeClients.TryAdd(clientEndPoint, Channel.CreateUnbounded<Packet>(clientChannelOptions)); + activeClients.TryAdd(clientEndPoint, Channel.CreateUnbounded<SerialisedPacket>(clientChannelOptions)); await Task.Factory.StartNew(DoHandleClientAsync, args, serverShutdownCancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default); diff --git a/NetSharp/NetSharp/Utils/Constants.cs b/NetSharp/NetSharp/Utils/Constants.cs @@ -9,5 +9,7 @@ /// The default port over which a connection is made. /// </summary> internal const int DefaultPort = 12374; + + internal const int MaximumUdpPacketBytes = 65507; } } \ No newline at end of file diff --git a/NetSharp/NetSharp/Utils/NetworkOperations.cs b/NetSharp/NetSharp/Utils/NetworkOperations.cs @@ -1,8 +1,11 @@ using System; +using System.Collections.Generic; using System.Net; using System.Net.Sockets; -using System.Runtime.CompilerServices; using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.ObjectPool; +using NetSharp.Extensions; using NetSharp.Packets; using NetSharp.Utils.Conversion; @@ -11,6 +14,13 @@ namespace NetSharp.Utils /// <summary> /// Helper class for asynchronously performing common network operations, for both the UDP and TCP protocols. /// </summary> + /// TODO: Implement cancellation support for network operations + /// TODO: Somehow dont run into the exception below + /// [Excep] Socket exception while reading bytes from 0.0.0.0:0: System.Net.Sockets.SocketException (10040): Komunikat wysłany na gniazdo datagramu był większy niż wewnętrzny bufor lub przekraczał inny sieciowy limit albo bufor używany do odbierania datagramów był mniejszy niż sam datagram. + /// at NetSharp.Extensions.SocketTask.GetResult() in G:\Git Repos\EnderRifter\NetSharp\NetSharp\NetSharp\Extensions\SocketExtensions.cs:line 158 + /// at NetSharp.Utils.NetworkOperations.ReadFromAsync(Socket socket, Int32 count, EndPoint remoteEndPoint, SocketFlags socketFlags) in G:\Git Repos\EnderRifter\NetSharp\NetSharp\NetSharp\Utils\NetworkOperations.cs:line 78 + /// at NetSharp.Utils.NetworkOperations.ReadPacketFromAsync(Socket socket, EndPoint remoteEndPoint, SocketFlags socketFlags, CancellationToken cancellationToken) in G:\Git Repos\EnderRifter\NetSharp\NetSharp\NetSharp\Utils\NetworkOperations.cs:line 225 + /// at NetSharp.Connection.DoReceivePacketFromAsync(Socket socket, EndPoint remoteEndPoint, SocketFlags socketFlags, TimeSpan timeout, CancellationToken cancellationToken) in G:\Git Repos\EnderRifter\NetSharp\NetSharp\NetSharp\Connection.cs:line 114 internal static class NetworkOperations { /// <summary> @@ -21,19 +31,37 @@ namespace NetSharp.Utils /// <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> /// <returns>The result of the receive operation.</returns> - private static TransmissionResult Read(Socket socket, int count, SocketFlags socketFlags) + private static Task<TransmissionResult> ReadAsync(Socket socket, int count, SocketFlags socketFlags, CancellationToken cancellationToken = default) { - byte[] byteBuffer = new byte[count]; - int receivedBytesCount = 0; + /* + SocketAsyncEventArgs args = new SocketAsyncEventArgs(); + args.SetBuffer(new byte[count], 0, count); + args.SocketFlags = socketFlags; + SocketTask awaitableTask = new SocketTask(args); - while (count > receivedBytesCount) + while (count > args.BytesTransferred) { - Span<byte> receivedBytes = new Span<byte>(byteBuffer, receivedBytesCount, count - receivedBytesCount); - - receivedBytesCount += socket.Receive(receivedBytes, socketFlags); + await socket.ReceiveAsync(awaitableTask); } - return new TransmissionResult(byteBuffer, receivedBytesCount, socket.RemoteEndPoint); ; + return new TransmissionResult(args.MemoryBuffer, args.BytesTransferred, socket.RemoteEndPoint); + */ + + return Task.Factory.StartNew(() => + { + byte[] byteBuffer = new byte[count]; + int receivedBytesCount = 0; + + while (count > receivedBytesCount) + { + Span<byte> receivedBytes = + new Span<byte>(byteBuffer, receivedBytesCount, count - receivedBytesCount); + + receivedBytesCount += socket.Receive(receivedBytes, socketFlags); + } + + return new TransmissionResult(byteBuffer, receivedBytesCount, socket.RemoteEndPoint); + }, cancellationToken); } /// <summary> @@ -45,20 +73,38 @@ namespace NetSharp.Utils /// <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> /// <returns>The result of the receive operation.</returns> - private static TransmissionResult ReadFrom(Socket socket, int count, EndPoint remoteEndPoint, SocketFlags socketFlags) + private static Task<TransmissionResult> ReadFromAsync(Socket socket, int count, EndPoint remoteEndPoint, SocketFlags socketFlags, CancellationToken cancellationToken = default) { - byte[] byteBuffer = new byte[count]; - EndPoint actualRemoteEndPoint = remoteEndPoint; - int receivedBytesCount = 0; + /* + SocketAsyncEventArgs args = new SocketAsyncEventArgs(); + args.SetBuffer(new byte[count], 0, count); + args.SocketFlags = socketFlags; + args.RemoteEndPoint = remoteEndPoint; + SocketTask awaitableTask = new SocketTask(args); - while (count > receivedBytesCount) + while (count > args.BytesTransferred) { - receivedBytesCount += - socket.ReceiveMessageFrom(byteBuffer, receivedBytesCount, count - receivedBytesCount, - ref socketFlags, ref actualRemoteEndPoint, out IPPacketInformation _); + await socket.ReceiveMessageFromAsync(awaitableTask); } - return new TransmissionResult(byteBuffer, receivedBytesCount, actualRemoteEndPoint); + return new TransmissionResult(args.MemoryBuffer, args.BytesTransferred, args.RemoteEndPoint); + */ + + return Task.Factory.StartNew(() => + { + byte[] byteBuffer = new byte[count]; + EndPoint actualRemoteEndPoint = remoteEndPoint; + int receivedBytesCount = 0; + + while (count > receivedBytesCount) + { + receivedBytesCount += + socket.ReceiveMessageFrom(byteBuffer, receivedBytesCount, count - receivedBytesCount, + ref socketFlags, ref actualRemoteEndPoint, out IPPacketInformation packetInformation); + } + + return new TransmissionResult(byteBuffer, receivedBytesCount, actualRemoteEndPoint); + }, cancellationToken); } /// <summary> @@ -69,17 +115,33 @@ namespace NetSharp.Utils /// <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> - private static void Write(Socket socket, ReadOnlyMemory<byte> buffer, SocketFlags socketFlags) + private static Task WriteAsync(Socket socket, Memory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default) { + /* + SocketAsyncEventArgs args = new SocketAsyncEventArgs(); + args.SetBuffer(buffer); + args.SocketFlags = socketFlags; + SocketTask awaitableTask = new SocketTask(args); + int bytesToSend = buffer.Length; - int sentBytesCount = 0; + while (bytesToSend > args.BytesTransferred) + { + await socket.SendAsync(awaitableTask); + } + */ - while (bytesToSend > sentBytesCount) + return Task.Factory.StartNew(() => { - ReadOnlySpan<byte> bufferSegment = buffer.Span.Slice(sentBytesCount, bytesToSend - sentBytesCount); + int bytesToSend = buffer.Length; + int sentBytesCount = 0; - sentBytesCount += socket.Send(bufferSegment, socketFlags); - } + while (bytesToSend > sentBytesCount) + { + ReadOnlySpan<byte> bufferSegment = buffer.Span.Slice(sentBytesCount, bytesToSend - sentBytesCount); + + sentBytesCount += socket.Send(bufferSegment, socketFlags); + } + }, cancellationToken); } /// <summary> @@ -91,17 +153,34 @@ namespace NetSharp.Utils /// <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> - private static void WriteTo(Socket socket, EndPoint remoteEndPoint, ReadOnlyMemory<byte> buffer, SocketFlags socketFlags) + private static Task WriteToAsync(Socket socket, EndPoint remoteEndPoint, Memory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default) { + /* + SocketAsyncEventArgs args = new SocketAsyncEventArgs(); + args.SetBuffer(buffer); + args.SocketFlags = socketFlags; + args.RemoteEndPoint = remoteEndPoint; + SocketTask awaitableTask = new SocketTask(args); + int bytesToSend = buffer.Length; - int sentBytesCount = 0; + while (bytesToSend > args.BytesTransferred) + { + await socket.SendToAsync(awaitableTask); + } + */ - while (bytesToSend > sentBytesCount) + return Task.Factory.StartNew(() => { - ReadOnlySpan<byte> bufferSegment = buffer.Span.Slice(sentBytesCount, bytesToSend - sentBytesCount); + int bytesToSend = buffer.Length; + int sentBytesCount = 0; - sentBytesCount += socket.SendTo(bufferSegment.ToArray(), socketFlags, remoteEndPoint); - } + while (bytesToSend > sentBytesCount) + { + ReadOnlySpan<byte> bufferSegment = buffer.Span.Slice(sentBytesCount, bytesToSend - sentBytesCount); + + sentBytesCount += socket.SendTo(bufferSegment.ToArray(), socketFlags, remoteEndPoint); + } + }, cancellationToken); } /// <summary> @@ -110,29 +189,68 @@ namespace NetSharp.Utils /// </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> - /// <returns>The read packet.</returns> - internal static Packet ReadPacket(Socket socket, SocketFlags socketFlags) + /// <param name="cancellationToken">The cancellation token that should be observed for the duration of the task.</param> + /// <returns>The read packet, and the endpoint from which it was read.</returns> + internal static async Task<(SerialisedPacket packet, EndPoint remoteEndPoint)> ReadPacketAsync(Socket socket, SocketFlags socketFlags, + CancellationToken cancellationToken = default) { - TransmissionResult packetHeaderResult = Read(socket, Packet.HeaderSize, socketFlags); + List<ReadOnlyMemory<byte>> userDataBuffer = new List<ReadOnlyMemory<byte>>(1); + int receivedBytes = 0; + + EndPoint remoteEndPoint; + NetworkPacket receivedPacket; + uint receivedPacketType; + + do + { + TransmissionResult result = await ReadAsync(socket, NetworkPacket.PacketSize, socketFlags, cancellationToken); + + remoteEndPoint = result.RemoteEndPoint; + receivedPacket = NetworkPacket.Deserialise(result.Buffer); + receivedPacketType = receivedPacket.Header.Type; + + // TODO: try to remove this extra allocation + userDataBuffer.Add(receivedPacket.DataBuffer); + receivedBytes += receivedPacket.Header.DataLength; + } while (receivedPacket.Footer.HasSucceedingPacket); + + Memory<byte> finalBuffer = new byte[receivedBytes]; + int writtenBytes = 0; + + foreach (ReadOnlyMemory<byte> bufferSegment in userDataBuffer) + { + bufferSegment.CopyTo(finalBuffer.Slice(writtenBytes, bufferSegment.Length)); + writtenBytes += bufferSegment.Length; + } + + SerialisedPacket finalPacket = new SerialisedPacket(finalBuffer, receivedPacketType); + + return (finalPacket, remoteEndPoint); + + /* + TransmissionResult packetHeaderResult = + await ReadAsync(socket, NetworkPacket.HeaderSize, socketFlags, cancellationToken); int packetSize = EndianAwareBitConverter.ToInt32(packetHeaderResult.Buffer.Span.Slice(0, sizeof(int))); if (packetSize == 0) { - return Packet.Deserialise(packetHeaderResult.Buffer); + return NetworkPacket.Deserialise(packetHeaderResult.Buffer); } - TransmissionResult packetDataResult = Read(socket, packetSize, socketFlags); + TransmissionResult packetDataResult = + await ReadAsync(socket, packetSize, socketFlags, cancellationToken); - byte[] serialisedPacket = new byte[Packet.HeaderSize + packetSize]; + byte[] serialisedPacket = new byte[NetworkPacket.HeaderSize + packetSize]; - Memory<byte> serialisedPacketHeader = new Memory<byte>(serialisedPacket, 0, Packet.HeaderSize); + Memory<byte> serialisedPacketHeader = new Memory<byte>(serialisedPacket, 0, NetworkPacket.HeaderSize); packetHeaderResult.Buffer.CopyTo(serialisedPacketHeader); - Memory<byte> serialisedPacketData = new Memory<byte>(serialisedPacket, Packet.HeaderSize, packetSize); + Memory<byte> serialisedPacketData = new Memory<byte>(serialisedPacket, NetworkPacket.HeaderSize, packetSize); packetDataResult.Buffer.CopyTo(serialisedPacketData); - return Packet.Deserialise(serialisedPacket); + return NetworkPacket.Deserialise(serialisedPacket); + */ } /// <summary> @@ -142,32 +260,68 @@ namespace NetSharp.Utils /// <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> - /// <returns>The read packet and associated transmission results.</returns> - internal static (Packet packet, TransmissionResult packetResult) ReadPacketFrom( - Socket socket, EndPoint remoteEndPoint, SocketFlags socketFlags) + /// <param name="cancellationToken">The cancellation token that should be observed for the duration of the task.</param> + /// <returns>The read packet, and the endpoint from which it was read.</returns> + internal static async Task<(SerialisedPacket packet, EndPoint remoteEndPoint)> ReadPacketFromAsync( + Socket socket, EndPoint remoteEndPoint, SocketFlags socketFlags, CancellationToken cancellationToken = default) { + List<ReadOnlyMemory<byte>> userDataBuffer = new List<ReadOnlyMemory<byte>>(1); + int receivedBytes = 0; + + NetworkPacket receivedPacket; + uint receivedPacketType; + + do + { + TransmissionResult result = + await ReadFromAsync(socket, NetworkPacket.PacketSize, remoteEndPoint, socketFlags, cancellationToken); + + remoteEndPoint = result.RemoteEndPoint; + receivedPacket = NetworkPacket.Deserialise(result.Buffer); + receivedPacketType = receivedPacket.Header.Type; + + // TODO: try to remove this extra allocation + userDataBuffer.Add(receivedPacket.DataBuffer); + receivedBytes += receivedPacket.Header.DataLength; + } while (receivedPacket.Footer.HasSucceedingPacket); + + Memory<byte> finalBuffer = new byte[receivedBytes]; + int writtenBytes = 0; + + foreach (ReadOnlyMemory<byte> bufferSegment in userDataBuffer) + { + bufferSegment.CopyTo(finalBuffer.Slice(writtenBytes, bufferSegment.Length)); + writtenBytes += bufferSegment.Length; + } + + SerialisedPacket finalPacket = new SerialisedPacket(finalBuffer, receivedPacketType); + + return (finalPacket, remoteEndPoint); + + /* TransmissionResult packetHeaderResult = - ReadFrom(socket, Packet.HeaderSize, remoteEndPoint, socketFlags); + await ReadFromAsync(socket, NetworkPacket.HeaderSize, remoteEndPoint, socketFlags, cancellationToken); int packetSize = EndianAwareBitConverter.ToInt32(packetHeaderResult.Buffer.Span.Slice(0, sizeof(int))); if (packetSize == 0) { - return (Packet.Deserialise(packetHeaderResult.Buffer), packetHeaderResult); + return (NetworkPacket.Deserialise(packetHeaderResult.Buffer), packetHeaderResult); } TransmissionResult packetDataResult = - ReadFrom(socket, packetSize, packetHeaderResult.RemoteEndPoint, socketFlags); + await ReadFromAsync(socket, packetSize, packetHeaderResult.RemoteEndPoint, socketFlags, cancellationToken); - byte[] serialisedPacket = new byte[Packet.HeaderSize + packetSize]; + byte[] serialisedPacket = new byte[NetworkPacket.HeaderSize + packetSize]; - Memory<byte> serialisedPacketHeader = new Memory<byte>(serialisedPacket, 0, Packet.HeaderSize); + Memory<byte> serialisedPacketHeader = new Memory<byte>(serialisedPacket, 0, NetworkPacket.HeaderSize); packetHeaderResult.Buffer.CopyTo(serialisedPacketHeader); - Memory<byte> serialisedPacketData = new Memory<byte>(serialisedPacket, Packet.HeaderSize, packetSize); + Memory<byte> serialisedPacketData = new Memory<byte>(serialisedPacket, NetworkPacket.HeaderSize, packetSize); packetDataResult.Buffer.CopyTo(serialisedPacketData); - return (Packet.Deserialise(serialisedPacket), packetDataResult); + return (NetworkPacket.Deserialise(serialisedPacket), packetDataResult); + */ } /// <summary> @@ -175,10 +329,36 @@ namespace NetSharp.Utils /// The given <see cref="SocketFlags"/> are associated with the write. /// </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="serialisedPacket">The packet that should be written to the network.</param> /// <param name="socketFlags">The socket flags associated with the send operation.</param> - internal static void WritePacket(Socket socket, Packet packet, SocketFlags socketFlags) - => Write(socket, Packet.Serialise(packet), socketFlags); + /// <param name="cancellationToken">The cancellation token that should be observed for the duration of the task.</param> + internal static async Task WritePacketAsync(Socket socket, SerialisedPacket serialisedPacket, SocketFlags socketFlags, + CancellationToken cancellationToken = default) + { + int packetCount = serialisedPacket.Contents.Length / NetworkPacket.PacketSize; + + for (int i = 0; i < packetCount; i++) + { + ReadOnlyMemory<byte> packetDataSegment = + serialisedPacket.Contents.Slice(NetworkPacket.PacketSize * i, NetworkPacket.PacketSize); + + NetworkPacket packet = new NetworkPacket(packetDataSegment, packetDataSegment.Length, + serialisedPacket.Type, NetworkErrorCode.Ok, true); + + await WriteAsync(socket, NetworkPacket.Serialise(packet), socketFlags, cancellationToken); + } + + if (serialisedPacket.Contents.Length % NetworkPacket.PacketSize != 0) + { + ReadOnlyMemory<byte> packetDataSegment = + serialisedPacket.Contents.Slice(NetworkPacket.PacketSize * packetCount, serialisedPacket.Contents.Length % NetworkPacket.PacketSize); + + NetworkPacket packet = new NetworkPacket(packetDataSegment, packetDataSegment.Length, + serialisedPacket.Type, NetworkErrorCode.Ok, true); + + await WriteAsync(socket, NetworkPacket.Serialise(packet), socketFlags, cancellationToken); + } + } /// <summary> /// Writes the given packet to the given remote endpoint, via the given socket. @@ -186,9 +366,35 @@ namespace NetSharp.Utils /// </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="serialisedPacket">The packet that should be written to the remote endpoint.</param> /// <param name="socketFlags">The socket flags associated with the send operation.</param> - internal static void WritePacketTo(Socket socket, EndPoint remoteEndPoint, Packet packet, SocketFlags socketFlags) - => WriteTo(socket, remoteEndPoint, Packet.Serialise(packet), socketFlags); + /// <param name="cancellationToken">The cancellation token that should be observed for the duration of the task.</param> + internal static async Task WritePacketToAsync(Socket socket, EndPoint remoteEndPoint, SerialisedPacket serialisedPacket, SocketFlags socketFlags, + CancellationToken cancellationToken = default) + { + int packetCount = serialisedPacket.Contents.Length / NetworkPacket.PacketSize; + + for (int i = 0; i < packetCount; i++) + { + ReadOnlyMemory<byte> packetDataSegment = + serialisedPacket.Contents.Slice(NetworkPacket.PacketSize * i, NetworkPacket.PacketSize); + + NetworkPacket packet = new NetworkPacket(packetDataSegment, packetDataSegment.Length, + serialisedPacket.Type, NetworkErrorCode.Ok, true); + + await WriteToAsync(socket, remoteEndPoint, NetworkPacket.Serialise(packet), socketFlags, cancellationToken); + } + + if (serialisedPacket.Contents.Length % NetworkPacket.PacketSize != 0) + { + ReadOnlyMemory<byte> packetDataSegment = + serialisedPacket.Contents.Slice(NetworkPacket.PacketSize * packetCount, serialisedPacket.Contents.Length % NetworkPacket.PacketSize); + + NetworkPacket packet = new NetworkPacket(packetDataSegment, packetDataSegment.Length, + serialisedPacket.Type, NetworkErrorCode.Ok, true); + + await WriteToAsync(socket, remoteEndPoint, NetworkPacket.Serialise(packet), socketFlags, cancellationToken); + } + } } } \ No newline at end of file diff --git a/NetSharp/NetSharpExamples/Program.cs b/NetSharp/NetSharpExamples/Program.cs @@ -15,7 +15,7 @@ namespace NetSharpExamples { internal class Program { - private static int newtorkTimeout = 10; + private static int newtorkTimeout = 1_000_000; private static IPAddress serverAddress; private static int serverPort; @@ -60,7 +60,7 @@ namespace NetSharpExamples { TimeSpan socketTimeout = TimeSpan.FromSeconds(newtorkTimeout); - const int clientCount = 10; + const int clientCount = 1; const int sentPacketCount = 1_000_000; static Client ClientFactory() @@ -74,6 +74,8 @@ namespace NetSharpExamples { using Client client = ClientFactory(); + client.ChangeLoggingStream(Console.OpenStandardOutput(), LogLevel.Warn); + if (await client.TryBindAsync(null, null, socketTimeout)) { Console.WriteLine($"Socket bound successfully: {client.SocketOptions.LocalIPEndPoint}"); @@ -99,7 +101,7 @@ namespace NetSharpExamples //Console.WriteLine($"[{j}] Sent message to server."); - //byte[] response = await client.SendBytesWithResponseAsync(message); + //byte[] response = await client.SendBytesWithResponseAsync(message, socketTimeout); //Console.WriteLine($"[{j}] Received response: {Encoding.UTF8.GetString(response)}"); //await Task.Delay(new Random(DateTime.Now.Millisecond).Next(200, 500));