NetSharp

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

commit f30b7d469c349902f22371419655e350df558bd5
parent 502cd5d5e0d41a210e5ca4405b14ee05cc6ecbc7
Author: Mikolaj Lenczewski <mikolaj.lenczewski308@gmail.com>
Date:   Thu, 23 Apr 2020 18:23:22 +0100

Made network packets not throw exceptions. Playing around with doc comment settings.

Diffstat:
MNetSharp/NetSharp/Packets/NetworkPacket.cs | 162++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
MNetSharp/NetSharp/Sockets/Datagram/DatagramSocketClient.cs | 132++++++++++++++++++++++++++++++++++++++++++-------------------------------------
MNetSharp/NetSharp/Sockets/Datagram/DatagramSocketServer.cs | 22+++++++++++-----------
MNetSharp/NetSharp/Sockets/SocketClient.cs | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
MNetSharp/NetSharp/Sockets/SocketConnection.cs | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
MNetSharp/NetSharp/Sockets/SocketServer.cs | 62+++++++++++++++++++++++++++++++++++++++++++-------------------
MNetSharp/NetSharp/Sockets/Stream/StreamSocketClient.cs | 307+++++++++++++++++++++++++++++++++++++++++++------------------------------------
MNetSharp/NetSharp/Sockets/Stream/StreamSocketServer.cs | 39++++++++++++++++++++-------------------
MNetSharp/NetSharp/Utils/BiDictionary.cs | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
MNetSharp/NetSharp/Utils/SlimObjectPool.cs | 68+++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
MNetSharp/NetSharp/Utils/TransmissionResult.cs | 16++++++++++++----
MNetSharp/NetSharpExamples/Benchmarks/TcpSocketServerBenchmark.cs | 102++++++++++++++++++++++++++++++-------------------------------------------------
MNetSharp/NetSharpExamples/Benchmarks/UdpSocketServerBenchmark.cs | 102++++++++++++++++++++++++++++++-------------------------------------------------
MNetSharp/NetSharpExamples/Examples/TcpSocketClientExample.cs | 6+++---
MNetSharp/NetSharpExamples/Examples/TcpSocketServerExample.cs | 2+-
MNetSharp/NetSharpExamples/Examples/UdpSocketClientExample.cs | 6+++---
MNetSharp/NetSharpExamples/Examples/UdpSocketServerExample.cs | 2+-
17 files changed, 828 insertions(+), 517 deletions(-)

diff --git a/NetSharp/NetSharp/Packets/NetworkPacket.cs b/NetSharp/NetSharp/Packets/NetworkPacket.cs @@ -1,49 +1,96 @@ using System; +using System.Runtime.CompilerServices; namespace NetSharp.Packets { - //TODO document + /// <summary> + /// Represents a raw packet sent across the network. + /// </summary> public readonly struct NetworkPacket { + /// <summary> + /// The size in bytes of the packet data segment. + /// </summary> public const int DataSize = 8192; + /// <summary> + /// The size in bytes of the packet footer segment. + /// </summary> public const int FooterSize = NetworkPacketFooter.TotalSize; + /// <summary> + /// The size in bytes of the packet header segment. + /// </summary> public const int HeaderSize = NetworkPacketHeader.TotalSize; + /// <summary> + /// The total size of the packet in bytes. + /// </summary> public const int TotalSize = HeaderSize + DataSize + FooterSize; + /// <summary> + /// Represents an empty packet. + /// </summary> public static NetworkPacket NullPacket = new NetworkPacket(); + /// <summary> + /// The data held by this packet instance. + /// </summary> public readonly ReadOnlyMemory<byte> Data; + /// <summary> + /// The footer for this packet instance, holding additional metadata. + /// </summary> public readonly NetworkPacketFooter Footer; + /// <summary> + /// The header for this packet instance, holding additional metadata. + /// </summary> public readonly NetworkPacketHeader Header; /// <summary> /// Constructs a new instance of the <see cref="NetworkPacket" /> struct. /// </summary> - /// <param name="packetHeader">The header for this packet.</param> - /// <param name="packetDataBuffer">The data that should be stored in the packet.</param> - /// <param name="packetFooter">The footer for this packet.</param> - /// <exception cref="ArgumentException"> - /// Thrown when the given <paramref name="packetDataBuffer" /> exceeds <see cref="TotalSize" /> bytes in size. - /// </exception> + /// <param name="packetHeader"> + /// The header for this packet. + /// </param> + /// <param name="packetDataBuffer"> + /// The data that should be stored in the packet. + /// </param> + /// <param name="packetFooter"> + /// The footer for this packet. + /// </param> private NetworkPacket(NetworkPacketHeader packetHeader, ReadOnlyMemory<byte> packetDataBuffer, NetworkPacketFooter packetFooter) { - if (packetDataBuffer.Length > DataSize) - { - throw new ArgumentException( - $"Given buffer exceeds {TotalSize} bytes, and cannot fit into a network packet", - nameof(packetDataBuffer)); - } + Header = packetHeader; Data = packetDataBuffer; + + Footer = packetFooter; } - public static NetworkPacket Deserialise(ReadOnlyMemory<byte> buffer) + /// <summary> + /// Deserialises the serialised packet in the given memory buffer into a new <see cref="NetworkPacket" /> instance. + /// </summary> + /// <param name="buffer"> + /// The memory buffer to read the serialised packet instance from. + /// </param> + /// <param name="instance"> + /// The deserialised instance. + /// </param> + /// <returns> + /// Whether the deserialisation attempt was successful. The <paramref name="instance" /> will be equal to <see cref="NullPacket" /> if the + /// attempt fails. + /// </returns> + public static bool Deserialise(ReadOnlyMemory<byte> buffer, out NetworkPacket instance) { + if (buffer.Length != TotalSize) + { + instance = NullPacket; + + return false; + } + ReadOnlyMemory<byte> serialisedPacketHeader = buffer.Slice(0, HeaderSize); NetworkPacketHeader packetHeader = NetworkPacketHeader.Deserialise(serialisedPacketHeader); @@ -52,11 +99,30 @@ namespace NetSharp.Packets ReadOnlyMemory<byte> serialisedPacketFooter = buffer.Slice(HeaderSize + DataSize, FooterSize); NetworkPacketFooter packetFooter = NetworkPacketFooter.Deserialise(serialisedPacketFooter); - return new NetworkPacket(packetHeader, packetDataBuffer, packetFooter); + instance = new NetworkPacket(packetHeader, packetDataBuffer, packetFooter); + + return true; } - public static void Serialise(NetworkPacket instance, Memory<byte> buffer) + /// <summary> + /// Serialises the given <see cref="NetworkPacket" /> instance into the given memory buffer. + /// </summary> + /// <param name="instance"> + /// The packet instance which should be serialised. + /// </param> + /// <param name="buffer"> + /// The memory buffer to write the serialised packet instance to. <see cref="TotalSize" /> bytes will be written into this buffer on success. + /// </param> + /// <returns> + /// Whether the serialisation attempt was successful. No bytes are written to the <paramref name="buffer" /> if the attempt fails. + /// </returns> + public static bool Serialise(NetworkPacket instance, Memory<byte> buffer) { + if (buffer.Length < TotalSize) + { + return false; + } + Memory<byte> packetHeader = buffer.Slice(0, HeaderSize); NetworkPacketHeader.Serialise(instance.Header, packetHeader); @@ -65,35 +131,87 @@ namespace NetSharp.Packets Memory<byte> packetFooter = buffer.Slice(HeaderSize + DataSize, FooterSize); NetworkPacketFooter.Serialise(instance.Footer, packetFooter); + + return true; } } - //TODO document + /// <summary> + /// Represents the footer of a <see cref="NetworkPacket" />, holding additional metadata. + /// </summary> public readonly struct NetworkPacketFooter { + /// <summary> + /// The total size of the packet footer, in bytes. + /// </summary> public const int TotalSize = 0; - public static NetworkPacketFooter Deserialise(ReadOnlyMemory<byte> buffer) + /// <summary> + /// Deserialises the serialised packet footer in the given memory buffer into a new <see cref="NetworkPacketFooter" /> instance. + /// </summary> + /// <param name="buffer"> + /// The memory buffer to read the serialised packet footer instance from. + /// </param> + /// <returns> + /// The deserialised instance. + /// </returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static NetworkPacketFooter Deserialise(ReadOnlyMemory<byte> buffer) { return new NetworkPacketFooter(); } - public static void Serialise(NetworkPacketFooter instance, Memory<byte> buffer) + /// <summary> + /// Serialises the given <see cref="NetworkPacketFooter" /> instance into the given memory buffer. + /// </summary> + /// <param name="instance"> + /// The packet footer instance which should be serialised. + /// </param> + /// <param name="buffer"> + /// The memory buffer to write the serialised packet footer instance to. + /// </param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Serialise(NetworkPacketFooter instance, Memory<byte> buffer) { } } - //TODO document + /// <summary> + /// Represents the header of a <see cref="NetworkPacket" />, holding additional metadata. + /// </summary> public readonly struct NetworkPacketHeader { + /// <summary> + /// The total size of the packet header, in bytes. + /// </summary> public const int TotalSize = 0; - public static NetworkPacketHeader Deserialise(ReadOnlyMemory<byte> buffer) + /// <summary> + /// Deserialises the serialised packet header in the given memory buffer into a new <see cref="NetworkPacketHeader" /> instance. + /// </summary> + /// <param name="buffer"> + /// The memory buffer to read the serialised packet header instance from. + /// </param> + /// <returns> + /// The deserialised instance. + /// </returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static NetworkPacketHeader Deserialise(ReadOnlyMemory<byte> buffer) { return new NetworkPacketHeader(); } - public static void Serialise(NetworkPacketHeader instance, Memory<byte> buffer) + /// <summary> + /// Serialises the given <see cref="NetworkPacketHeader" /> instance into the given memory buffer. + /// </summary> + /// <param name="instance"> + /// The packet header instance which should be serialised. + /// </param> + /// <param name="buffer"> + /// The memory buffer to write the serialised packet header instance to. + /// </param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Serialise(NetworkPacketHeader instance, Memory<byte> buffer) { } } diff --git a/NetSharp/NetSharp/Sockets/Datagram/DatagramSocketClient.cs b/NetSharp/NetSharp/Sockets/Datagram/DatagramSocketClient.cs @@ -13,16 +13,12 @@ namespace NetSharp.Sockets.Datagram public readonly struct DatagramSocketClientOptions { public static readonly DatagramSocketClientOptions Defaults = - new DatagramSocketClientOptions(NetworkPacket.TotalSize, 0); - - public readonly int PacketSize; + new DatagramSocketClientOptions(0); public readonly ushort PreallocatedTransmissionArgs; - public DatagramSocketClientOptions(int packetSize, ushort preallocatedTransmissionArgs) + public DatagramSocketClientOptions(ushort preallocatedTransmissionArgs) { - PacketSize = packetSize; - PreallocatedTransmissionArgs = preallocatedTransmissionArgs; } } @@ -34,9 +30,8 @@ namespace NetSharp.Sockets.Datagram private readonly DatagramSocketClientOptions clientOptions; public DatagramSocketClient(in AddressFamily connectionAddressFamily, in ProtocolType connectionProtocolType, - in DatagramSocketClientOptions? clientOptions = null) : base(in connectionAddressFamily, SocketType.Dgram, - in connectionProtocolType, clientOptions?.PacketSize ?? DatagramSocketClientOptions.Defaults.PacketSize, - clientOptions?.PreallocatedTransmissionArgs ?? DatagramSocketClientOptions.Defaults.PreallocatedTransmissionArgs) + in DatagramSocketClientOptions? clientOptions = null) : base(in connectionAddressFamily, SocketType.Dgram, in connectionProtocolType, + NetworkPacket.TotalSize, clientOptions?.PreallocatedTransmissionArgs ?? DatagramSocketClientOptions.Defaults.PreallocatedTransmissionArgs) { this.clientOptions = clientOptions ?? DatagramSocketClientOptions.Defaults; } @@ -46,6 +41,70 @@ namespace NetSharp.Sockets.Datagram get { return ref clientOptions; } } + private void CompleteConnect(SocketAsyncEventArgs args) + { + AsyncOperationToken connectToken = (AsyncOperationToken)args.UserToken; + + if (connectToken.CancellationToken.IsCancellationRequested) + { + connectToken.CompletionSource.SetCanceled(); + } + else if (args.SocketError == SocketError.Success) + { + connectToken.CompletionSource.SetResult(true); + } + else + { + connectToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); + } + + TransmissionArgsPool.Return(args); + } + + private void CompleteReceiveFrom(SocketAsyncEventArgs args) + { + AsyncTransmissionToken receiveToken = (AsyncTransmissionToken)args.UserToken; + + if (receiveToken.CancellationToken.IsCancellationRequested) + { + receiveToken.CompletionSource.SetCanceled(); + } + else if (args.SocketError == SocketError.Success) + { + TransmissionResult result = new TransmissionResult(in args); + + receiveToken.CompletionSource.SetResult(result); + } + else + { + receiveToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); + } + + TransmissionArgsPool.Return(args); + } + + private void CompleteSendTo(SocketAsyncEventArgs args) + { + AsyncTransmissionToken sendToken = (AsyncTransmissionToken)args.UserToken; + + if (sendToken.CancellationToken.IsCancellationRequested) + { + sendToken.CompletionSource.SetCanceled(); + } + else if (args.SocketError == SocketError.Success) + { + TransmissionResult result = new TransmissionResult(in args); + + sendToken.CompletionSource.SetResult(result); + } + else + { + sendToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); + } + + TransmissionArgsPool.Return(args); + } + /// <inheritdoc /> protected override bool CanTransmissionArgsBeReused(in SocketAsyncEventArgs args) { @@ -76,66 +135,17 @@ namespace NetSharp.Sockets.Datagram switch (args.LastOperation) { case SocketAsyncOperation.Connect: - AsyncOperationToken connectToken = (AsyncOperationToken)args.UserToken; - - if (connectToken.CancellationToken.IsCancellationRequested) - { - connectToken.CompletionSource.SetCanceled(); - } - else if (args.SocketError == SocketError.Success) - { - connectToken.CompletionSource.SetResult(true); - } - else - { - connectToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); - } - - TransmissionArgsPool.Return(args); + CompleteConnect(args); break; case SocketAsyncOperation.ReceiveFrom: - AsyncTransmissionToken receiveToken = (AsyncTransmissionToken)args.UserToken; - - if (receiveToken.CancellationToken.IsCancellationRequested) - { - receiveToken.CompletionSource.SetCanceled(); - } - else if (args.SocketError == SocketError.Success) - { - TransmissionResult result = new TransmissionResult(in args); - - receiveToken.CompletionSource.SetResult(result); - } - else - { - receiveToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); - } - - TransmissionArgsPool.Return(args); + CompleteReceiveFrom(args); break; case SocketAsyncOperation.SendTo: - AsyncTransmissionToken sendToken = (AsyncTransmissionToken)args.UserToken; - - if (sendToken.CancellationToken.IsCancellationRequested) - { - sendToken.CompletionSource.SetCanceled(); - } - else if (args.SocketError == SocketError.Success) - { - TransmissionResult result = new TransmissionResult(in args); - - sendToken.CompletionSource.SetResult(result); - } - else - { - sendToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); - } - - TransmissionArgsPool.Return(args); + CompleteSendTo(args); break; diff --git a/NetSharp/NetSharp/Sockets/Datagram/DatagramSocketServer.cs b/NetSharp/NetSharp/Sockets/Datagram/DatagramSocketServer.cs @@ -12,16 +12,13 @@ namespace NetSharp.Sockets.Datagram public readonly struct DatagramSocketServerOptions { public static readonly DatagramSocketServerOptions Defaults = - new DatagramSocketServerOptions(NetworkPacket.TotalSize, Environment.ProcessorCount, 0); + new DatagramSocketServerOptions(Environment.ProcessorCount, 0); public readonly int ConcurrentReceiveFromCalls; - public readonly int PacketSize; public readonly ushort PreallocatedTransmissionArgs; - public DatagramSocketServerOptions(int packetSize, int concurrentReceiveFromCalls, ushort preallocatedTransmissionArgs) + public DatagramSocketServerOptions(int concurrentReceiveFromCalls, ushort preallocatedTransmissionArgs) { - PacketSize = packetSize; - ConcurrentReceiveFromCalls = concurrentReceiveFromCalls; PreallocatedTransmissionArgs = preallocatedTransmissionArgs; @@ -41,13 +38,14 @@ namespace NetSharp.Sockets.Datagram /// <summary> /// Constructs a new instance of the <see cref="DatagramSocketServer" /> class. /// </summary> - /// <param name="serverOptions">Additional options to configure the server.</param> + /// <param name="serverOptions"> + /// Additional options to configure the server. + /// </param> /// <inheritdoc /> public DatagramSocketServer(in AddressFamily connectionAddressFamily, in ProtocolType connectionProtocolType, in SocketServerPacketHandler packetHandler, in DatagramSocketServerOptions? serverOptions = null) : base(in connectionAddressFamily, SocketType.Dgram, in connectionProtocolType, in packetHandler, - serverOptions?.PacketSize ?? DatagramSocketServerOptions.Defaults.PacketSize, - serverOptions?.PreallocatedTransmissionArgs ?? DatagramSocketServerOptions.Defaults.PreallocatedTransmissionArgs) + NetworkPacket.TotalSize, serverOptions?.PreallocatedTransmissionArgs ?? DatagramSocketServerOptions.Defaults.PreallocatedTransmissionArgs) { this.serverOptions = serverOptions ?? DatagramSocketServerOptions.Defaults; } @@ -63,13 +61,13 @@ namespace NetSharp.Sockets.Datagram if (receiveArgs.SocketError == SocketError.Success) { - NetworkPacket request = NetworkPacket.Deserialise(receiveArgs.MemoryBuffer); + NetworkPacket.Deserialise(receiveArgs.MemoryBuffer, out NetworkPacket request); NetworkPacket response = PacketHandler(in request, receiveArgs.RemoteEndPoint); if (!response.Equals(NetworkPacket.NullPacket)) { - byte[] sendBuffer = BufferPool.Rent(ServerOptions.PacketSize); + byte[] sendBuffer = BufferPool.Rent(NetworkPacket.TotalSize); Memory<byte> sendBufferMemory = new Memory<byte>(sendBuffer); NetworkPacket.Serialise(response, sendBufferMemory); @@ -108,7 +106,7 @@ namespace NetSharp.Sockets.Datagram return; } - byte[] receiveBuffer = BufferPool.Rent(ServerOptions.PacketSize); + byte[] receiveBuffer = BufferPool.Rent(NetworkPacket.TotalSize); Memory<byte> receiveBufferMemory = new Memory<byte>(receiveBuffer); receiveArgs.SetBuffer(receiveBufferMemory); @@ -183,10 +181,12 @@ namespace NetSharp.Sockets.Datagram ReceiveFrom(newReceiveArgs); // start a new receive from operation immediately, to not drop any packets CompleteReceiveFrom(args); + break; case SocketAsyncOperation.SendTo: CompleteSendTo(args); + break; default: diff --git a/NetSharp/NetSharp/Sockets/SocketClient.cs b/NetSharp/NetSharp/Sockets/SocketClient.cs @@ -15,24 +15,34 @@ namespace NetSharp.Sockets /// <summary> /// Constructs a new instance of the <see cref="SocketClient" /> class. /// </summary> - /// <param name="connectionAddressFamily">The address family that the underlying connection should use.</param> - /// <param name="connectionSocketType">The socket type that the underlying connection should use.</param> - /// <param name="connectionProtocolType">The protocol type that the underlying connection should use.</param> - /// <param name="maxPooledBufferLength">The maximum length of a pooled network IO buffer.</param> - /// <param name="preallocatedTransmissionArgs">The number of transmission args to preallocate.</param> - protected SocketClient(in AddressFamily connectionAddressFamily, in SocketType connectionSocketType, - in ProtocolType connectionProtocolType, in int maxPooledBufferLength, in ushort preallocatedTransmissionArgs) - : base(in connectionAddressFamily, in connectionSocketType, in connectionProtocolType, in maxPooledBufferLength, - in preallocatedTransmissionArgs) + /// <param name="connectionAddressFamily"> + /// The address family that the underlying connection should use. + /// </param> + /// <param name="connectionSocketType"> + /// The socket type that the underlying connection should use. + /// </param> + /// <param name="connectionProtocolType"> + /// The protocol type that the underlying connection should use. + /// </param> + /// <param name="pooledBufferMaxSize"> + /// The maximum size in bytes of buffers held in the buffer pool. + /// </param> + /// <param name="preallocatedTransmissionArgs"> + /// The number of transmission args to preallocate. + /// </param> + protected SocketClient(in AddressFamily connectionAddressFamily, in SocketType connectionSocketType, in ProtocolType connectionProtocolType, + in int pooledBufferMaxSize, in ushort preallocatedTransmissionArgs) : base(in connectionAddressFamily, in connectionSocketType, + in connectionProtocolType, pooledBufferMaxSize, preallocatedTransmissionArgs) { } /// <summary> /// Connects the client to the specified end point. If called on a <see cref="SocketType.Dgram" />-based client, this method configures the - /// default remote host, and the client will ignore any packets not coming from this default host (i.e the given <paramref - /// name="remoteEndPoint" />). + /// default remote host, and the client will ignore any packets not coming from this default host (i.e the given <paramref name="remoteEndPoint" />). /// </summary> - /// <param name="remoteEndPoint">The remote end point which to which to connect the client.</param> + /// <param name="remoteEndPoint"> + /// The remote end point which to which to connect the client. + /// </param> public void Connect(in EndPoint remoteEndPoint) { Connection.Connect(remoteEndPoint); @@ -40,12 +50,17 @@ namespace NetSharp.Sockets /// <summary> /// Asynchronously connects the client to the specified end point. If called on a <see cref="SocketType.Dgram" />-based client, this method - /// configures the default remote host, and the client will ignore any packets not coming from this default host (i.e the given <paramref - /// name="remoteEndPoint" />). + /// configures the default remote host, and the client will ignore any packets not coming from this default host (i.e the given <paramref name="remoteEndPoint" />). /// </summary> - /// <param name="remoteEndPoint">The remote end point which to which to connect the client.</param> - /// <param name="cancellationToken">The <see cref="CancellationToken" /> upon whose cancellation the connection attempt should be aborted.</param> - /// <returns>A <see cref="ValueTask" /> representing the connection attempt.</returns> + /// <param name="remoteEndPoint"> + /// The remote end point which to which to connect the client. + /// </param> + /// <param name="cancellationToken"> + /// The <see cref="CancellationToken" /> upon whose cancellation the connection attempt should be aborted. + /// </param> + /// <returns> + /// A <see cref="ValueTask" /> representing the connection attempt. + /// </returns> public ValueTask ConnectAsync(in EndPoint remoteEndPoint, CancellationToken cancellationToken = default) { TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(); @@ -101,10 +116,18 @@ namespace NetSharp.Sockets /// <summary> /// Constructs a new instance of the <see cref="AsyncOperationCancellationToken" /> struct. /// </summary> - /// <param name="socket">The socket on which the operation was started.</param> - /// <param name="args">The socket event args associated with the operation.</param> - /// <param name="argsPool">The pool to which the <paramref name="args" /> instance will be returned upon cancellation.</param> - /// <param name="completionSource">The completion source associated with the operation.</param> + /// <param name="socket"> + /// The socket on which the operation was started. + /// </param> + /// <param name="args"> + /// The socket event args associated with the operation. + /// </param> + /// <param name="argsPool"> + /// The pool to which the <paramref name="args" /> instance will be returned upon cancellation. + /// </param> + /// <param name="completionSource"> + /// The completion source associated with the operation. + /// </param> public AsyncOperationCancellationToken(in Socket socket, in SocketAsyncEventArgs args, in SlimObjectPool<SocketAsyncEventArgs> argsPool, in TaskCompletionSource<bool> completionSource) { @@ -136,8 +159,12 @@ namespace NetSharp.Sockets /// <summary> /// Constructs a new instance of the <see cref="AsyncOperationToken" /> struct. /// </summary> - /// <param name="completionSource">The completion source to trigger when the socket operation completes.</param> - /// <param name="cancellationToken">The cancellation token to observe during the operation.</param> + /// <param name="completionSource"> + /// The completion source to trigger when the socket operation completes. + /// </param> + /// <param name="cancellationToken"> + /// The cancellation token to observe during the operation. + /// </param> public AsyncOperationToken(in TaskCompletionSource<bool> completionSource, in CancellationToken cancellationToken) { CompletionSource = completionSource; @@ -174,10 +201,18 @@ namespace NetSharp.Sockets /// <summary> /// Constructs a new instance of the <see cref="AsyncTransmissionCancellationToken" /> struct. /// </summary> - /// <param name="socket">The socket on which the operation was started.</param> - /// <param name="args">The socket event args associated with the operation.</param> - /// <param name="argsPool">The pool to which the <paramref name="args" /> instance will be returned upon cancellation.</param> - /// <param name="completionSource">The completion source associated with the operation.</param> + /// <param name="socket"> + /// The socket on which the operation was started. + /// </param> + /// <param name="args"> + /// The socket event args associated with the operation. + /// </param> + /// <param name="argsPool"> + /// The pool to which the <paramref name="args" /> instance will be returned upon cancellation. + /// </param> + /// <param name="completionSource"> + /// The completion source associated with the operation. + /// </param> public AsyncTransmissionCancellationToken(in Socket socket, in SocketAsyncEventArgs args, in SlimObjectPool<SocketAsyncEventArgs> argsPool, in TaskCompletionSource<TransmissionResult> completionSource) { @@ -209,8 +244,12 @@ namespace NetSharp.Sockets /// <summary> /// Constructs a new instance of the <see cref="AsyncTransmissionToken" /> struct. /// </summary> - /// <param name="completionSource">The completion source to trigger when the IO operation completes.</param> - /// <param name="cancellationToken">The cancellation token to observe during the operation.</param> + /// <param name="completionSource"> + /// The completion source to trigger when the IO operation completes. + /// </param> + /// <param name="cancellationToken"> + /// The cancellation token to observe during the operation. + /// </param> public AsyncTransmissionToken(in TaskCompletionSource<TransmissionResult> completionSource, in CancellationToken cancellationToken) { CompletionSource = completionSource; diff --git a/NetSharp/NetSharp/Sockets/SocketConnection.cs b/NetSharp/NetSharp/Sockets/SocketConnection.cs @@ -19,8 +19,8 @@ namespace NetSharp.Sockets protected readonly ArrayPool<byte> BufferPool; /// <summary> - /// Pools <see cref="SocketAsyncEventArgs" /> objects for use during network read/write operations and calls to <see cref="Socket" - /// />.XXXAsync( <see cref="SocketAsyncEventArgs" />) methods. + /// Pools <see cref="SocketAsyncEventArgs" /> objects for use during network read/write operations and calls to + /// <see cref="Socket" />.XXXAsync( <see cref="SocketAsyncEventArgs" />) methods. /// </summary> protected readonly SlimObjectPool<SocketAsyncEventArgs> TransmissionArgsPool; @@ -32,17 +32,27 @@ namespace NetSharp.Sockets /// <summary> /// Constructs a new instance of the <see cref="SocketConnection" /> class. /// </summary> - /// <param name="connectionAddressFamily">The address family for the underlying socket.</param> - /// <param name="connectionSocketType">The socket type for the underlying socket.</param> - /// <param name="connectionProtocolType">The protocol type for the underlying socket.</param> - /// <param name="maxPooledBufferLength">The maximum size of the buffers stored in the <see cref="BufferPool" />.</param> - /// <param name="preallocatedTransmissionArgs">The number of <see cref="SocketAsyncEventArgs" /> objects to initially preallocate.</param> + /// <param name="connectionAddressFamily"> + /// The address family for the underlying socket. + /// </param> + /// <param name="connectionSocketType"> + /// The socket type for the underlying socket. + /// </param> + /// <param name="connectionProtocolType"> + /// The protocol type for the underlying socket. + /// </param> + /// <param name="pooledBufferMaxSize"> + /// The maximum size in bytes of buffers held in the buffer pool. + /// </param> + /// <param name="preallocatedTransmissionArgs"> + /// The number of <see cref="SocketAsyncEventArgs" /> objects to initially preallocate. + /// </param> private protected SocketConnection(in AddressFamily connectionAddressFamily, in SocketType connectionSocketType, - in ProtocolType connectionProtocolType, in int maxPooledBufferLength, in ushort preallocatedTransmissionArgs) + in ProtocolType connectionProtocolType, in int pooledBufferMaxSize, in ushort preallocatedTransmissionArgs) { Connection = new Socket(connectionAddressFamily, connectionSocketType, connectionProtocolType); - BufferPool = ArrayPool<byte>.Create(maxPooledBufferLength, 1000); + BufferPool = ArrayPool<byte>.Create(pooledBufferMaxSize, 1000); TransmissionArgsPool = new SlimObjectPool<SocketAsyncEventArgs>(CreateTransmissionArgs, ResetTransmissionArgs, DestroyTransmissionArgs, CanTransmissionArgsBeReused); @@ -65,34 +75,44 @@ namespace NetSharp.Sockets } /// <summary> - /// Delegate method used to check whether the given used <see cref="SocketAsyncEventArgs" /> instance can be reused by the <see - /// cref="TransmissionArgsPool" />. If this method returns <c>true</c>, <see cref="ResetTransmissionArgs" /> is called on the given <paramref - /// name="args" />. Otherwise, <see cref="DestroyTransmissionArgs" /> is called. + /// Delegate method used to check whether the given used <see cref="SocketAsyncEventArgs" /> instance can be reused by the + /// <see cref="TransmissionArgsPool" />. If this method returns <c>true</c>, <see cref="ResetTransmissionArgs" /> is called on the given + /// <paramref name="args" />. Otherwise, <see cref="DestroyTransmissionArgs" /> is called. /// </summary> - /// <param name="args">The <see cref="SocketAsyncEventArgs" /> instance to check.</param> - /// <returns>Whether the given <paramref name="args" /> should be reset and reused, or should be destroyed.</returns> + /// <param name="args"> + /// The <see cref="SocketAsyncEventArgs" /> instance to check. + /// </param> + /// <returns> + /// Whether the given <paramref name="args" /> should be reset and reused, or should be destroyed. + /// </returns> protected abstract bool CanTransmissionArgsBeReused(in SocketAsyncEventArgs args); /// <summary> /// Delegate method used to construct fresh <see cref="SocketAsyncEventArgs" /> instances for use in the <see cref="TransmissionArgsPool" />. - /// The resulting instance should register <see cref="HandleIoCompleted" /> as an event handler for the <see - /// cref="SocketAsyncEventArgs.Completed" /> event. + /// The resulting instance should register <see cref="HandleIoCompleted" /> as an event handler for the + /// <see cref="SocketAsyncEventArgs.Completed" /> event. /// </summary> - /// <returns>The configured <see cref="SocketAsyncEventArgs" /> instance.</returns> + /// <returns> + /// The configured <see cref="SocketAsyncEventArgs" /> instance. + /// </returns> protected abstract SocketAsyncEventArgs CreateTransmissionArgs(); /// <summary> - /// Delegate method to destroy used <see cref="SocketAsyncEventArgs" /> instances that cannot be reused by the <see - /// cref="TransmissionArgsPool" />. This method should deregister <see cref="HandleIoCompleted" /> as an event handler for the <see - /// cref="SocketAsyncEventArgs.Completed" /> event. + /// Delegate method to destroy used <see cref="SocketAsyncEventArgs" /> instances that cannot be reused by the + /// <see cref="TransmissionArgsPool" />. This method should deregister <see cref="HandleIoCompleted" /> as an event handler for the + /// <see cref="SocketAsyncEventArgs.Completed" /> event. /// </summary> - /// <param name="remoteConnectionArgs">The <see cref="SocketAsyncEventArgs" /> which should be destroyed.</param> + /// <param name="remoteConnectionArgs"> + /// The <see cref="SocketAsyncEventArgs" /> which should be destroyed. + /// </param> protected abstract void DestroyTransmissionArgs(SocketAsyncEventArgs remoteConnectionArgs); /// <summary> /// Disposes of managed and unmanaged resources used by the <see cref="SocketConnection" /> class. /// </summary> - /// <param name="disposing">Whether this call was made by a call to <see cref="Dispose()" />.</param> + /// <param name="disposing"> + /// Whether this call was made by a call to <see cref="Dispose()" />. + /// </param> protected virtual void Dispose(bool disposing) { if (!disposing) return; @@ -104,14 +124,20 @@ namespace NetSharp.Sockets /// <summary> /// Delegate method to handle asynchronous network IO completion via the <see cref="SocketAsyncEventArgs.Completed" /> event. /// </summary> - /// <param name="sender">The object which raised the event.</param> - /// <param name="args">The <see cref="SocketAsyncEventArgs" /> instance associated with the asynchronous network IO.</param> + /// <param name="sender"> + /// The object which raised the event. + /// </param> + /// <param name="args"> + /// The <see cref="SocketAsyncEventArgs" /> instance associated with the asynchronous network IO. + /// </param> protected abstract void HandleIoCompleted(object sender, SocketAsyncEventArgs args); /// <summary> /// Delegate method used to reset used <see cref="SocketAsyncEventArgs" /> instances for later reuse by the <see cref="TransmissionArgsPool" />. /// </summary> - /// <param name="args">The <see cref="SocketAsyncEventArgs" /> instance that should be reset.</param> + /// <param name="args"> + /// The <see cref="SocketAsyncEventArgs" /> instance that should be reset. + /// </param> protected abstract void ResetTransmissionArgs(SocketAsyncEventArgs args); /// <inheritdoc cref="Socket.SetSocketOption(SocketOptionLevel,SocketOptionName,bool)" /> @@ -141,7 +167,9 @@ namespace NetSharp.Sockets /// <summary> /// Binds the underlying socket. /// </summary> - /// <param name="localEndPoint">The end point to which the socket should be bound.</param> + /// <param name="localEndPoint"> + /// The end point to which the socket should be bound. + /// </param> public void Bind(in EndPoint localEndPoint) { Connection.Bind(localEndPoint); @@ -175,7 +203,9 @@ namespace NetSharp.Sockets /// <summary> /// Shuts down the underlying socket. /// </summary> - /// <param name="how">Which socket transmission functions should be shut down on the socket.</param> + /// <param name="how"> + /// Which socket transmission functions should be shut down on the socket. + /// </param> public void Shutdown(SocketShutdown how) { try diff --git a/NetSharp/NetSharp/Sockets/SocketServer.cs b/NetSharp/NetSharp/Sockets/SocketServer.cs @@ -10,11 +10,14 @@ namespace NetSharp.Sockets /// <summary> /// Represents a method for serving request packets. This method should not throw any errors. /// </summary> - /// <param name="requestPacket">The request packet received by the server.</param> - /// <param name="clientEndPoint">The client from which the packet was received.</param> + /// <param name="requestPacket"> + /// The request packet received by the server. + /// </param> + /// <param name="clientEndPoint"> + /// The client from which the packet was received. + /// </param> /// <returns> - /// The response packet which should be sent out to the client. If no packet should be sent out, this method must return <see - /// cref="NetworkPacket.NullPacket" />. + /// The response packet which should be sent out to the client. If no packet should be sent out, this method must return <see cref="NetworkPacket.NullPacket" />. /// </returns> public delegate NetworkPacket SocketServerPacketHandler(in NetworkPacket requestPacket, in EndPoint clientEndPoint); @@ -31,16 +34,27 @@ namespace NetSharp.Sockets /// <summary> /// Constructs a new instance of the <see cref="SocketServer" /> class. /// </summary> - /// <param name="connectionAddressFamily">The address family that the underlying connection should use.</param> - /// <param name="connectionSocketType">The socket type that the underlying connection should use.</param> - /// <param name="connectionProtocolType">The protocol type that the underlying connection should use.</param> - /// <param name="packetHandler">The packet handler delegate to use to respond to incoming requests.</param> - /// <param name="maxPooledBufferLength">The maximum length of a pooled network IO buffer.</param> - /// <param name="preallocatedTransmissionArgs">The number of transmission args to preallocate.</param> - protected SocketServer(in AddressFamily connectionAddressFamily, in SocketType connectionSocketType, - in ProtocolType connectionProtocolType, in SocketServerPacketHandler packetHandler, in int maxPooledBufferLength, - in ushort preallocatedTransmissionArgs) : base(in connectionAddressFamily, in connectionSocketType, - in connectionProtocolType, in maxPooledBufferLength, preallocatedTransmissionArgs) + /// <param name="connectionAddressFamily"> + /// The address family that the underlying connection should use. + /// </param> + /// <param name="connectionSocketType"> + /// The socket type that the underlying connection should use. + /// </param> + /// <param name="connectionProtocolType"> + /// The protocol type that the underlying connection should use. + /// </param> + /// <param name="packetHandler"> + /// The packet handler delegate to use to respond to incoming requests. + /// </param> + /// <param name="pooledBufferMaxSize"> + /// The maximum size in bytes of buffers held in the buffer pool. + /// </param> + /// <param name="preallocatedTransmissionArgs"> + /// The number of transmission args to preallocate. + /// </param> + protected SocketServer(in AddressFamily connectionAddressFamily, in SocketType connectionSocketType, in ProtocolType connectionProtocolType, + in SocketServerPacketHandler packetHandler, in int pooledBufferMaxSize, in ushort preallocatedTransmissionArgs) + : base(in connectionAddressFamily, in connectionSocketType, in connectionProtocolType, pooledBufferMaxSize, preallocatedTransmissionArgs) { PacketHandler = packetHandler; } @@ -48,9 +62,15 @@ namespace NetSharp.Sockets /// <summary> /// The default request packet handler for servers. Simply echoes back any received packets. /// </summary> - /// <param name="request">The request packet that was received.</param> - /// <param name="remoteEndPoint">The client from which the packet was received.</param> - /// <returns>The received packet.</returns> + /// <param name="request"> + /// The request packet that was received. + /// </param> + /// <param name="remoteEndPoint"> + /// The client from which the packet was received. + /// </param> + /// <returns> + /// The received packet. + /// </returns> public static NetworkPacket DefaultPacketHandler(in NetworkPacket request, in EndPoint remoteEndPoint) { return request; @@ -59,8 +79,12 @@ namespace NetSharp.Sockets /// <summary> /// Runs the server, handling requests from clients, until the <paramref name="cancellationToken" /> has its cancellation requested. /// </summary> - /// <param name="cancellationToken">The <see cref="CancellationToken" /> upon whose cancellation the server should shut down.</param> - /// <returns>A <see cref="Task" /> representing the server's execution.</returns> + /// <param name="cancellationToken"> + /// The <see cref="CancellationToken" /> upon whose cancellation the server should shut down. + /// </param> + /// <returns> + /// A <see cref="Task" /> representing the server's execution. + /// </returns> public abstract Task RunAsync(CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/NetSharp/NetSharp/Sockets/Stream/StreamSocketClient.cs b/NetSharp/NetSharp/Sockets/Stream/StreamSocketClient.cs @@ -12,16 +12,12 @@ namespace NetSharp.Sockets.Stream public readonly struct StreamSocketClientOptions { public static readonly StreamSocketClientOptions Defaults = - new StreamSocketClientOptions(NetworkPacket.TotalSize, 0); - - public readonly int PacketSize; + new StreamSocketClientOptions(0); public readonly ushort PreallocatedTransmissionArgs; - public StreamSocketClientOptions(int packetSize, ushort preallocatedTransmissionArgs) + public StreamSocketClientOptions(ushort preallocatedTransmissionArgs) { - PacketSize = packetSize; - PreallocatedTransmissionArgs = preallocatedTransmissionArgs; } } @@ -33,9 +29,8 @@ namespace NetSharp.Sockets.Stream private readonly StreamSocketClientOptions clientOptions; public StreamSocketClient(in AddressFamily connectionAddressFamily, in ProtocolType connectionProtocolType, - in StreamSocketClientOptions? clientOptions = null) : base(in connectionAddressFamily, SocketType.Stream, - in connectionProtocolType, clientOptions?.PacketSize ?? StreamSocketClientOptions.Defaults.PacketSize, - clientOptions?.PreallocatedTransmissionArgs ?? StreamSocketClientOptions.Defaults.PreallocatedTransmissionArgs) + in StreamSocketClientOptions? clientOptions = null) : base(in connectionAddressFamily, SocketType.Stream, in connectionProtocolType, + NetworkPacket.TotalSize, clientOptions?.PreallocatedTransmissionArgs ?? StreamSocketClientOptions.Defaults.PreallocatedTransmissionArgs) { this.clientOptions = clientOptions ?? StreamSocketClientOptions.Defaults; } @@ -45,178 +40,198 @@ namespace NetSharp.Sockets.Stream get { return ref clientOptions; } } - /// <inheritdoc /> - protected override bool CanTransmissionArgsBeReused(in SocketAsyncEventArgs args) + private void CompleteConnect(SocketAsyncEventArgs args) { - return true; - } + AsyncOperationToken connectToken = (AsyncOperationToken)args.UserToken; - /// <inheritdoc /> - protected override SocketAsyncEventArgs CreateTransmissionArgs() - { - SocketAsyncEventArgs connectionArgs = new SocketAsyncEventArgs(); + if (connectToken.CancellationToken.IsCancellationRequested) + { + Connection.Disconnect(true); - connectionArgs.Completed += HandleIoCompleted; + connectToken.CompletionSource.SetCanceled(); + } + else if (args.SocketError == SocketError.Success) + { + connectToken.CompletionSource.SetResult(true); + } + else + { + connectToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); + } - return connectionArgs; + TransmissionArgsPool.Return(args); } - /// <inheritdoc /> - protected override void DestroyTransmissionArgs(SocketAsyncEventArgs remoteConnectionArgs) + private void CompleteDisconnect(SocketAsyncEventArgs args) { - remoteConnectionArgs.Completed -= HandleIoCompleted; + AsyncOperationToken disconnectToken = (AsyncOperationToken)args.UserToken; - remoteConnectionArgs.Dispose(); + if (disconnectToken.CancellationToken.IsCancellationRequested) + { + disconnectToken.CompletionSource.SetCanceled(); + } + else if (args.SocketError == SocketError.Success) + { + disconnectToken.CompletionSource.SetResult(true); + } + else + { + disconnectToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); + } + + TransmissionArgsPool.Return(args); } - /// <inheritdoc /> - protected override void HandleIoCompleted(object sender, SocketAsyncEventArgs args) + private void CompleteReceive(SocketAsyncEventArgs args) { - switch (args.LastOperation) + AsyncTransmissionToken receiveToken = (AsyncTransmissionToken)args.UserToken; + + if (receiveToken.CancellationToken.IsCancellationRequested) { - case SocketAsyncOperation.Connect: - AsyncOperationToken connectToken = (AsyncOperationToken)args.UserToken; - - if (connectToken.CancellationToken.IsCancellationRequested) - { - Connection.Disconnect(true); - - connectToken.CompletionSource.SetCanceled(); - } - else if (args.SocketError == SocketError.Success) - { - connectToken.CompletionSource.SetResult(true); - } - else - { - connectToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); - } + receiveToken.CompletionSource.SetCanceled(); - TransmissionArgsPool.Return(args); + TransmissionArgsPool.Return(args); + } + else if (args.SocketError == SocketError.Success) + { + Memory<byte> transmissionBuffer = args.MemoryBuffer; + int expectedBytes = transmissionBuffer.Length; - break; + if (args.BytesTransferred == expectedBytes) + { + // buffer was fully received - case SocketAsyncOperation.Disconnect: - AsyncOperationToken disconnectToken = (AsyncOperationToken)args.UserToken; - - if (disconnectToken.CancellationToken.IsCancellationRequested) - { - disconnectToken.CompletionSource.SetCanceled(); - } - else if (args.SocketError == SocketError.Success) - { - disconnectToken.CompletionSource.SetResult(true); - } - else - { - disconnectToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); - } + TransmissionResult result = new TransmissionResult(in args); + + receiveToken.CompletionSource.SetResult(result); TransmissionArgsPool.Return(args); + } + else if (expectedBytes > args.BytesTransferred && args.BytesTransferred > 0) + { + // receive the remaining parts of the buffer - break; + int receivedBytes = args.BytesTransferred; - case SocketAsyncOperation.Receive: - AsyncTransmissionToken receiveToken = (AsyncTransmissionToken)args.UserToken; + args.SetBuffer(receivedBytes, expectedBytes - receivedBytes); - if (receiveToken.CancellationToken.IsCancellationRequested) - { - receiveToken.CompletionSource.SetCanceled(); + Connection.ReceiveAsync(args); + } + else + { + // no bytes were received, remote socket is dead - TransmissionArgsPool.Return(args); - } - else if (args.SocketError == SocketError.Success) - { - Memory<byte> transmissionBuffer = args.MemoryBuffer; - int expectedBytes = transmissionBuffer.Length; + receiveToken.CompletionSource.SetException(new SocketException((int)SocketError.HostDown)); - if (args.BytesTransferred == expectedBytes) - { - // buffer was fully received + TransmissionArgsPool.Return(args); + } + } + else + { + receiveToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); - TransmissionResult result = new TransmissionResult(in args); + TransmissionArgsPool.Return(args); + } + } - receiveToken.CompletionSource.SetResult(result); + private void CompleteSend(SocketAsyncEventArgs args) + { + AsyncTransmissionToken sendToken = (AsyncTransmissionToken)args.UserToken; - TransmissionArgsPool.Return(args); - } - else if (expectedBytes > args.BytesTransferred && args.BytesTransferred > 0) - { - // receive the remaining parts of the buffer + if (sendToken.CancellationToken.IsCancellationRequested) + { + sendToken.CompletionSource.SetCanceled(); - int receivedBytes = args.BytesTransferred; + TransmissionArgsPool.Return(args); + } + else if (args.SocketError == SocketError.Success) + { + Memory<byte> transmissionBuffer = args.MemoryBuffer; + int remainingBytes = transmissionBuffer.Length; - args.SetBuffer(receivedBytes, expectedBytes - receivedBytes); + if (args.BytesTransferred == remainingBytes) + { + // buffer was fully sent - Connection.ReceiveAsync(args); - } - else - { - // no bytes were received, remote socket is dead + TransmissionResult result = new TransmissionResult(in args); - receiveToken.CompletionSource.SetException(new SocketException((int)SocketError.HostDown)); + sendToken.CompletionSource.SetResult(result); - TransmissionArgsPool.Return(args); - } - } - else - { - receiveToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); + TransmissionArgsPool.Return(args); + } + else if (remainingBytes > args.BytesTransferred && args.BytesTransferred > 0) + { + // send the remaining parts of the buffer - TransmissionArgsPool.Return(args); - } + int sentBytes = args.BytesTransferred; - break; + args.SetBuffer(sentBytes, remainingBytes - sentBytes); - case SocketAsyncOperation.Send: - AsyncTransmissionToken sendToken = (AsyncTransmissionToken)args.UserToken; + Connection.SendAsync(args); + } + else + { + // no bytes were sent, remote socket is dead - if (sendToken.CancellationToken.IsCancellationRequested) - { - sendToken.CompletionSource.SetCanceled(); + sendToken.CompletionSource.SetException(new SocketException((int)SocketError.HostDown)); - TransmissionArgsPool.Return(args); - } - else if (args.SocketError == SocketError.Success) - { - Memory<byte> transmissionBuffer = args.MemoryBuffer; - int remainingBytes = transmissionBuffer.Length; + TransmissionArgsPool.Return(args); + } + } + else + { + sendToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); - if (args.BytesTransferred == remainingBytes) - { - // buffer was fully sent + TransmissionArgsPool.Return(args); + } + } - TransmissionResult result = new TransmissionResult(in args); + /// <inheritdoc /> + protected override bool CanTransmissionArgsBeReused(in SocketAsyncEventArgs args) + { + return true; + } - sendToken.CompletionSource.SetResult(result); + /// <inheritdoc /> + protected override SocketAsyncEventArgs CreateTransmissionArgs() + { + SocketAsyncEventArgs connectionArgs = new SocketAsyncEventArgs(); - TransmissionArgsPool.Return(args); - } - else if (remainingBytes > args.BytesTransferred && args.BytesTransferred > 0) - { - // send the remaining parts of the buffer + connectionArgs.Completed += HandleIoCompleted; - int sentBytes = args.BytesTransferred; + return connectionArgs; + } - args.SetBuffer(sentBytes, remainingBytes - sentBytes); + /// <inheritdoc /> + protected override void DestroyTransmissionArgs(SocketAsyncEventArgs remoteConnectionArgs) + { + remoteConnectionArgs.Completed -= HandleIoCompleted; + + remoteConnectionArgs.Dispose(); + } + + /// <inheritdoc /> + protected override void HandleIoCompleted(object sender, SocketAsyncEventArgs args) + { + switch (args.LastOperation) + { + case SocketAsyncOperation.Connect: + CompleteConnect(args); - Connection.SendAsync(args); - } - else - { - // no bytes were sent, remote socket is dead + break; + + case SocketAsyncOperation.Disconnect: + CompleteDisconnect(args); - sendToken.CompletionSource.SetException(new SocketException((int)SocketError.HostDown)); + break; + + case SocketAsyncOperation.Receive: + CompleteReceive(args); - TransmissionArgsPool.Return(args); - } - } - else - { - sendToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); + break; - TransmissionArgsPool.Return(args); - } + case SocketAsyncOperation.Send: + CompleteSend(args); break; @@ -253,9 +268,15 @@ namespace NetSharp.Sockets.Stream public TransmissionResult Receive(byte[] buffer, SocketFlags flags = SocketFlags.None) { - int receivedBytes = Connection.Receive(buffer, flags); + int bytesToReceive = buffer.Length; + int bytesReceived = 0; + + do + { + bytesReceived += Connection.Receive(buffer, bytesReceived, bytesToReceive - bytesReceived, flags); + } while (bytesReceived != 0 && bytesReceived < bytesToReceive); - return new TransmissionResult(in buffer, in receivedBytes, Connection.RemoteEndPoint); + return new TransmissionResult(in buffer, in bytesReceived, Connection.RemoteEndPoint); } public ValueTask<TransmissionResult> ReceiveAsync(Memory<byte> receiveBuffer, SocketFlags flags = SocketFlags.None, @@ -281,9 +302,15 @@ namespace NetSharp.Sockets.Stream public TransmissionResult Send(byte[] buffer, SocketFlags flags = SocketFlags.None) { - int sentBytes = Connection.Send(buffer, flags); + int bytesToSend = buffer.Length; + int bytesSent = 0; + + do + { + bytesSent += Connection.Send(buffer, bytesSent, bytesToSend - bytesSent, flags); + } while (bytesSent != 0 && bytesSent < bytesToSend); - return new TransmissionResult(in buffer, in sentBytes, Connection.RemoteEndPoint); + return new TransmissionResult(in buffer, in bytesSent, Connection.RemoteEndPoint); } public ValueTask<TransmissionResult> SendAsync(Memory<byte> sendBuffer, SocketFlags flags = SocketFlags.None, diff --git a/NetSharp/NetSharp/Sockets/Stream/StreamSocketServer.cs b/NetSharp/NetSharp/Sockets/Stream/StreamSocketServer.cs @@ -11,16 +11,13 @@ namespace NetSharp.Sockets.Stream public readonly struct StreamSocketServerOptions { public static readonly StreamSocketServerOptions Defaults = - new StreamSocketServerOptions(NetworkPacket.TotalSize, Environment.ProcessorCount, 0); + new StreamSocketServerOptions(Environment.ProcessorCount, 0); public readonly int ConcurrentAcceptCalls; - public readonly int PacketSize; public readonly ushort PreallocatedTransmissionArgs; - public StreamSocketServerOptions(int packetSize, int concurrentAcceptCalls, ushort preallocatedTransmissionArgs) + public StreamSocketServerOptions(int concurrentAcceptCalls, ushort preallocatedTransmissionArgs) { - PacketSize = packetSize; - ConcurrentAcceptCalls = concurrentAcceptCalls; PreallocatedTransmissionArgs = preallocatedTransmissionArgs; @@ -36,13 +33,14 @@ namespace NetSharp.Sockets.Stream /// <summary> /// Constructs a new instance of the <see cref="StreamSocketServer" /> class. /// </summary> - /// <param name="serverOptions">Additional options to configure the server.</param> + /// <param name="serverOptions"> + /// Additional options to configure the server. + /// </param> /// <inheritdoc /> public StreamSocketServer(in AddressFamily connectionAddressFamily, in ProtocolType connectionProtocolType, in SocketServerPacketHandler packetHandler, in StreamSocketServerOptions? serverOptions = null) : base(in connectionAddressFamily, SocketType.Stream, in connectionProtocolType, in packetHandler, - serverOptions?.PacketSize ?? StreamSocketServerOptions.Defaults.PacketSize, - serverOptions?.PreallocatedTransmissionArgs ?? StreamSocketServerOptions.Defaults.PreallocatedTransmissionArgs) + NetworkPacket.TotalSize, serverOptions?.PreallocatedTransmissionArgs ?? StreamSocketServerOptions.Defaults.PreallocatedTransmissionArgs) { this.serverOptions = serverOptions ?? StreamSocketServerOptions.Defaults; } @@ -90,17 +88,17 @@ namespace NetSharp.Sockets.Stream if (clientArgs.SocketError == SocketError.Success) { - if (clientArgs.BytesTransferred == serverOptions.PacketSize) + if (clientArgs.BytesTransferred == NetworkPacket.TotalSize) { // buffer was fully received - NetworkPacket request = NetworkPacket.Deserialise(receiveToken.RentedBuffer); + NetworkPacket.Deserialise(receiveToken.RentedBuffer, out NetworkPacket request); NetworkPacket response = PacketHandler(in request, clientArgs.RemoteEndPoint); if (!response.Equals(NetworkPacket.NullPacket)) { - byte[] responseBuffer = BufferPool.Rent(serverOptions.PacketSize); + byte[] responseBuffer = BufferPool.Rent(NetworkPacket.TotalSize); Memory<byte> responseBufferMemory = new Memory<byte>(responseBuffer); NetworkPacket.Serialise(response, responseBufferMemory); @@ -108,7 +106,7 @@ namespace NetSharp.Sockets.Stream BufferPool.Return(receiveToken.RentedBuffer, true); // at this point the request buffer can be returned receiveToken.RentedBuffer = responseBuffer; - clientArgs.SetBuffer(responseBuffer, 0, serverOptions.PacketSize); + clientArgs.SetBuffer(responseBuffer, 0, NetworkPacket.TotalSize); Send(clientArgs); } @@ -119,13 +117,13 @@ namespace NetSharp.Sockets.Stream Receive(clientArgs); } } - else if (serverOptions.PacketSize > clientArgs.BytesTransferred && clientArgs.BytesTransferred > 0) + else if (NetworkPacket.TotalSize > clientArgs.BytesTransferred && clientArgs.BytesTransferred > 0) { // receive the remaining parts of the buffer int receivedBytes = clientArgs.BytesTransferred; - clientArgs.SetBuffer(receivedBytes, serverOptions.PacketSize - receivedBytes); + clientArgs.SetBuffer(receivedBytes, NetworkPacket.TotalSize - receivedBytes); Receive(clientArgs); } @@ -148,7 +146,7 @@ namespace NetSharp.Sockets.Stream if (clientArgs.SocketError == SocketError.Success) { - if (clientArgs.BytesTransferred == serverOptions.PacketSize) + if (clientArgs.BytesTransferred == NetworkPacket.TotalSize) { // buffer was fully sent @@ -158,13 +156,13 @@ namespace NetSharp.Sockets.Stream Receive(clientArgs); } - else if (serverOptions.PacketSize > clientArgs.BytesTransferred && clientArgs.BytesTransferred > 0) + else if (NetworkPacket.TotalSize > clientArgs.BytesTransferred && clientArgs.BytesTransferred > 0) { // send the remaining parts of the buffer int sentBytes = clientArgs.BytesTransferred; - clientArgs.SetBuffer(sentBytes, serverOptions.PacketSize - sentBytes); + clientArgs.SetBuffer(sentBytes, NetworkPacket.TotalSize - sentBytes); Send(clientArgs); } @@ -185,11 +183,11 @@ namespace NetSharp.Sockets.Stream { RemoteStreamClientToken clientToken = (RemoteStreamClientToken)clientArgs.UserToken; - byte[] requestBuffer = BufferPool.Rent(serverOptions.PacketSize); + byte[] requestBuffer = BufferPool.Rent(NetworkPacket.TotalSize); Memory<byte> requestBufferMemory = new Memory<byte>(requestBuffer); clientToken.RentedBuffer = requestBuffer; - clientArgs.SetBuffer(clientToken.RentedBuffer, 0, serverOptions.PacketSize); + clientArgs.SetBuffer(clientToken.RentedBuffer, 0, NetworkPacket.TotalSize); bool operationPending = clientToken.ClientSocket.ReceiveAsync(clientArgs); @@ -246,14 +244,17 @@ namespace NetSharp.Sockets.Stream Accept(newAcceptArgs); // start a new accept operation to not miss any clients CompleteAccept(args); + break; case SocketAsyncOperation.Receive: CompleteReceive(args); + break; case SocketAsyncOperation.Send: CompleteSend(args); + break; default: diff --git a/NetSharp/NetSharp/Utils/BiDictionary.cs b/NetSharp/NetSharp/Utils/BiDictionary.cs @@ -5,8 +5,12 @@ namespace NetSharp.Utils /// <summary> /// Represents a concurrent two-way dictionary, that can be indexed by either a key or a value. /// </summary> - /// <typeparam name="K">The type of key that will be stored.</typeparam> - /// <typeparam name="V">The type of value that will be stored.</typeparam> + /// <typeparam name="K"> + /// The type of key that will be stored. + /// </typeparam> + /// <typeparam name="V"> + /// The type of value that will be stored. + /// </typeparam> public class BiDictionary<K, V> { /// <summary> @@ -32,8 +36,12 @@ namespace NetSharp.Utils /// <summary> /// Indexes this instance with the given value. /// </summary> - /// <param name="index">The value whose key to get or set.</param> - /// <returns>The fetched key.</returns> + /// <param name="index"> + /// The value whose key to get or set. + /// </param> + /// <returns> + /// The fetched key. + /// </returns> public K this[V index] { get @@ -49,8 +57,12 @@ namespace NetSharp.Utils /// <summary> /// Indexes this instance with the given key. /// </summary> - /// <param name="index">The key whose value to get or set.</param> - /// <returns>The fetched value.</returns> + /// <param name="index"> + /// The key whose value to get or set. + /// </param> + /// <returns> + /// The fetched value. + /// </returns> public V this[K index] { get @@ -75,23 +87,37 @@ namespace NetSharp.Utils /// <summary> /// Whether this instance contains the given key. /// </summary> - /// <param name="key">The key to check.</param> - /// <returns>Whether the given key was found.</returns> + /// <param name="key"> + /// The key to check. + /// </param> + /// <returns> + /// Whether the given key was found. + /// </returns> public bool ContainsKey(in K key) => keyToValueMap.ContainsKey(key); /// <summary> /// Whether this instance contains the given value. /// </summary> - /// <param name="value">The value to check.</param> - /// <returns>Whether the given value was found.</returns> + /// <param name="value"> + /// The value to check. + /// </param> + /// <returns> + /// Whether the given value was found. + /// </returns> public bool ContainsValue(in V value) => valueToKeyMap.ContainsKey(value); /// <summary> /// Attempts to set the key associated with the given value. /// </summary> - /// <param name="value">The value whose key to set.</param> - /// <param name="key">The new value for the value's associated key.</param> - /// <returns>Whether the new key was correctly set.</returns> + /// <param name="value"> + /// The value whose key to set. + /// </param> + /// <param name="key"> + /// The new value for the value's associated key. + /// </param> + /// <returns> + /// Whether the new key was correctly set. + /// </returns> public void SetOrUpdateKey(V value, K key) { valueToKeyMap.AddOrUpdate(value, key, (v, k) => key); @@ -102,9 +128,15 @@ namespace NetSharp.Utils /// <summary> /// Attempts to set the value associated with the given key. /// </summary> - /// <param name="key">The key whose value to set.</param> - /// <param name="value">The new value for the key's associated value.</param> - /// <returns>Whether the new value was correctly set.</returns> + /// <param name="key"> + /// The key whose value to set. + /// </param> + /// <param name="value"> + /// The new value for the key's associated value. + /// </param> + /// <returns> + /// Whether the new value was correctly set. + /// </returns> public void SetOrUpdateValue(K key, V value) { keyToValueMap.AddOrUpdate(key, value, (k, v) => value); @@ -115,9 +147,15 @@ namespace NetSharp.Utils /// <summary> /// Attempts to remove the key associated with the given value. /// </summary> - /// <param name="value">The value whose key to remove.</param> - /// <param name="key">The old key value.</param> - /// <returns>Whether the given value had a valid key associated with it.</returns> + /// <param name="value"> + /// The value whose key to remove. + /// </param> + /// <param name="key"> + /// The old key value. + /// </param> + /// <returns> + /// Whether the given value had a valid key associated with it. + /// </returns> public bool TryClearKey(in V value, out K key) { bool clearedValue = valueToKeyMap.TryRemove(value, out key); @@ -130,9 +168,15 @@ namespace NetSharp.Utils /// <summary> /// Attempts to remove the value associated with the given key. /// </summary> - /// <param name="key">The key whose value to remove.</param> - /// <param name="value">The old value.</param> - /// <returns>Whether the given key had a valid valid associated with it.</returns> + /// <param name="key"> + /// The key whose value to remove. + /// </param> + /// <param name="value"> + /// The old value. + /// </param> + /// <returns> + /// Whether the given key had a valid valid associated with it. + /// </returns> public bool TryClearValue(in K key, out V value) { bool clearedKey = keyToValueMap.TryRemove(key, out value); @@ -145,9 +189,15 @@ namespace NetSharp.Utils /// <summary> /// Attempts to get the key associated with the given value. /// </summary> - /// <param name="value">The value whose key to get.</param> - /// <param name="key">The returned key.</param> - /// <returns>Whether the given value has a valid key associated with it.</returns> + /// <param name="value"> + /// The value whose key to get. + /// </param> + /// <param name="key"> + /// The returned key. + /// </param> + /// <returns> + /// Whether the given value has a valid key associated with it. + /// </returns> public bool TryGetKey(in V value, out K key) { return valueToKeyMap.TryGetValue(value, out key); @@ -156,9 +206,15 @@ namespace NetSharp.Utils /// <summary> /// Attempts to get the value associated with the given key. /// </summary> - /// <param name="key">The key whose value to get.</param> - /// <param name="value">The returned value.</param> - /// <returns>Whether the given key as a valid value associated with it.</returns> + /// <param name="key"> + /// The key whose value to get. + /// </param> + /// <param name="value"> + /// The returned value. + /// </param> + /// <returns> + /// Whether the given key as a valid value associated with it. + /// </returns> public bool TryGetValue(in K key, out V value) { return keyToValueMap.TryGetValue(key, out value); @@ -167,9 +223,15 @@ namespace NetSharp.Utils /// <summary> /// Attempts to set the key associated with the given value. /// </summary> - /// <param name="value">The value whose key to set.</param> - /// <param name="key">The key which should be set for the given value.</param> - /// <returns>Whether the given value was successfully set.</returns> + /// <param name="value"> + /// The value whose key to set. + /// </param> + /// <param name="key"> + /// The key which should be set for the given value. + /// </param> + /// <returns> + /// Whether the given value was successfully set. + /// </returns> public bool TrySetKey(in V value, in K key) { K newKey = key; @@ -184,9 +246,15 @@ namespace NetSharp.Utils /// <summary> /// Attempts to set the value associated with the given key. /// </summary> - /// <param name="key">The key whose value to set.</param> - /// <param name="value">The value which should be set for the given key.</param> - /// <returns>Whether the given key was successfully set.</returns> + /// <param name="key"> + /// The key whose value to set. + /// </param> + /// <param name="value"> + /// The value which should be set for the given key. + /// </param> + /// <returns> + /// Whether the given key was successfully set. + /// </returns> public bool TrySetValue(in K key, in V value) { V newValue = value; diff --git a/NetSharp/NetSharp/Utils/SlimObjectPool.cs b/NetSharp/NetSharp/Utils/SlimObjectPool.cs @@ -5,7 +5,9 @@ namespace NetSharp.Utils /// <summary> /// Provides a lightweight implementation of an object pool for classes. /// </summary> - /// <typeparam name="T">The type of item stored in the pool.</typeparam> + /// <typeparam name="T"> + /// The type of item stored in the pool. + /// </typeparam> public class SlimObjectPool<T> where T : class { private readonly CanRebufferObjectPredicate canObjectBeRebufferedPredicate; @@ -21,11 +23,21 @@ namespace NetSharp.Utils /// <summary> /// Constructs a new instance of the <see cref="SlimObjectPool{T}" /> class. /// </summary> - /// <param name="createDelegate">The delegate method to use to create new pooled object instances.</param> - /// <param name="resetDelegate">The delegate method to use to reset used pooled object instances.</param> - /// <param name="destroyDelegate">The delegate method to use to destroy pooled object instances that cannot be reused.</param> - /// <param name="rebufferPredicate">The delegate method to use to decide whether an instance can be reused.</param> - /// <param name="baseCollection">The underlying pooled object buffer to use.</param> + /// <param name="createDelegate"> + /// The delegate method to use to create new pooled object instances. + /// </param> + /// <param name="resetDelegate"> + /// The delegate method to use to reset used pooled object instances. + /// </param> + /// <param name="destroyDelegate"> + /// The delegate method to use to destroy pooled object instances that cannot be reused. + /// </param> + /// <param name="rebufferPredicate"> + /// The delegate method to use to decide whether an instance can be reused. + /// </param> + /// <param name="baseCollection"> + /// The underlying pooled object buffer to use. + /// </param> public SlimObjectPool(in CreateObjectDelegate createDelegate, in ResetObjectDelegate resetDelegate, in DestroyObjectDelegate destroyDelegate, in CanRebufferObjectPredicate rebufferPredicate, in IProducerConsumerCollection<T> baseCollection) @@ -44,10 +56,18 @@ namespace NetSharp.Utils /// <summary> /// Constructs a new instance of the <see cref="SlimObjectPool{T}" /> class. /// </summary> - /// <param name="createDelegate">The delegate method to use to create new pooled object instances.</param> - /// <param name="resetDelegate">The delegate method to use to reset used pooled object instances.</param> - /// <param name="destroyDelegate">The delegate method to use to destroy pooled object instances that cannot be reused.</param> - /// <param name="rebufferPredicate">The delegate method to use to decide whether an instance can be reused.</param> + /// <param name="createDelegate"> + /// The delegate method to use to create new pooled object instances. + /// </param> + /// <param name="resetDelegate"> + /// The delegate method to use to reset used pooled object instances. + /// </param> + /// <param name="destroyDelegate"> + /// The delegate method to use to destroy pooled object instances that cannot be reused. + /// </param> + /// <param name="rebufferPredicate"> + /// The delegate method to use to decide whether an instance can be reused. + /// </param> public SlimObjectPool(in CreateObjectDelegate createDelegate, in ResetObjectDelegate resetDelegate, in DestroyObjectDelegate destroyDelegate, in CanRebufferObjectPredicate rebufferPredicate) : this(in createDelegate, in resetDelegate, in destroyDelegate, in rebufferPredicate, new ConcurrentBag<T>()) @@ -58,32 +78,44 @@ namespace NetSharp.Utils /// Delegate method to check whether the given <paramref name="instance" /> can and should be placed back into the pool. If <c>true</c> is /// returned, the <paramref name="instance" /> is reset and placed back into the pool. Otherwise, the instance is destroyed. /// </summary> - /// <param name="instance">The instance to check.</param> - /// <returns>Whether the given instance should be placed back into the pool.</returns> + /// <param name="instance"> + /// The instance to check. + /// </param> + /// <returns> + /// Whether the given instance should be placed back into the pool. + /// </returns> public delegate bool CanRebufferObjectPredicate(in T instance); /// <summary> /// Delegate method for creating fresh <typeparamref name="T" /> instances to be stored in the pool. /// </summary> - /// <returns>A configured <typeparamref name="T" /> instance.</returns> + /// <returns> + /// A configured <typeparamref name="T" /> instance. + /// </returns> public delegate T CreateObjectDelegate(); /// <summary> /// Delegate method to destroy a used <paramref name="instance" /> which cannot be reused. /// </summary> - /// <param name="instance">The instance to destroy.</param> + /// <param name="instance"> + /// The instance to destroy. + /// </param> public delegate void DestroyObjectDelegate(T instance); /// <summary> /// Delegate method to reset a used <paramref name="instance" /> before placing it back into the pool. /// </summary> - /// <param name="instance">The instance which should be reset.</param> + /// <param name="instance"> + /// The instance which should be reset. + /// </param> public delegate void ResetObjectDelegate(T instance); /// <summary> /// Leases a new <typeparamref name="T" /> instance from the pool, and returns it. /// </summary> - /// <returns>The <typeparamref name="T" /> instance which was fetched from the pool.</returns> + /// <returns> + /// The <typeparamref name="T" /> instance which was fetched from the pool. + /// </returns> public T Rent() { return objectBuffer.TryTake(out T result) ? result : createObjectDelegate(); @@ -92,7 +124,9 @@ namespace NetSharp.Utils /// <summary> /// Returns a previously leased <typeparamref name="T" /> instance to the pool. /// </summary> - /// <param name="instance">The previously leased instance which should be returned.</param> + /// <param name="instance"> + /// The previously leased instance which should be returned. + /// </param> public void Return(T instance) { if (canObjectBeRebufferedPredicate(instance)) diff --git a/NetSharp/NetSharp/Utils/TransmissionResult.cs b/NetSharp/NetSharp/Utils/TransmissionResult.cs @@ -27,7 +27,9 @@ namespace NetSharp.Utils /// <summary> /// Initialises a new instance of the <see cref="TransmissionResult" /> struct. /// </summary> - /// <param name="args">The socket arguments associated with the transmission.</param> + /// <param name="args"> + /// The socket arguments associated with the transmission. + /// </param> internal TransmissionResult(in SocketAsyncEventArgs args) { Buffer = args.MemoryBuffer; @@ -38,9 +40,15 @@ namespace NetSharp.Utils /// <summary> /// Initialises a new instance of the <see cref="TransmissionResult" /> struct. /// </summary> - /// <param name="buffer">The buffer associated with the transmission.</param> - /// <param name="count">The number of bytes written to or read from the buffer.</param> - /// <param name="remoteEndPoint">The remote end point associated with the transmission.</param> + /// <param name="buffer"> + /// The buffer associated with the transmission. + /// </param> + /// <param name="count"> + /// The number of bytes written to or read from the buffer. + /// </param> + /// <param name="remoteEndPoint"> + /// The remote end point associated with the transmission. + /// </param> internal TransmissionResult(in byte[] buffer, in int count, in EndPoint remoteEndPoint) { Buffer = buffer; diff --git a/NetSharp/NetSharpExamples/Benchmarks/TcpSocketServerBenchmark.cs b/NetSharp/NetSharpExamples/Benchmarks/TcpSocketServerBenchmark.cs @@ -20,46 +20,6 @@ namespace NetSharpExamples.Benchmarks private double[] ClientBandwidths; - /// <inheritdoc /> - public async Task RunAsync() - { - CancellationTokenSource serverCts = new CancellationTokenSource(); - - int clientCount = Environment.ProcessorCount / 2; - - Console.WriteLine($"TCP Server Benchmark started!"); - - StreamSocketServerOptions serverOptions = new StreamSocketServerOptions(NetworkPacket.TotalSize, - clientCount, (ushort)clientCount); - - StreamSocketServer server = new StreamSocketServer(AddressFamily.InterNetwork, ProtocolType.Tcp, - SocketServer.DefaultPacketHandler, serverOptions); - - server.Bind(ServerEndPoint); - - Task serverTask = Task.Factory.StartNew(() => - { - server.RunAsync(serverCts.Token).GetAwaiter().GetResult(); - }, TaskCreationOptions.LongRunning); - - ClientBandwidths = new double[clientCount]; - Task[] clientTasks = new Task[clientCount]; - for (int i = 0; i < clientTasks.Length; i++) - { - clientTasks[i] = Task.Factory.StartNew(BenchmarkClientTask, i, TaskCreationOptions.LongRunning); - } - - await Task.WhenAll(clientTasks); - - Console.WriteLine($"Total estimated bandwidth: {ClientBandwidths.Sum():F5}"); - - serverCts.Cancel(); - - await serverTask; - - Console.WriteLine($"TCP Server Benchmark finished!"); - } - private Task BenchmarkClientTask(object idObj) { int id = (int)idObj; @@ -101,14 +61,6 @@ namespace NetSharpExamples.Benchmarks break; } -#if DEBUG - lock (typeof(Console)) - { - Console.WriteLine($"[Client {id}, Packet {i}] Sent {sentBytes} bytes to {remoteEndPoint}"); - Console.WriteLine($"[Client {id}, Packet {i}] >>>> {Encoding.UTF8.GetString(sendBuffer)}"); - } -#endif - int totalReceived = 0; do { @@ -124,23 +76,8 @@ namespace NetSharpExamples.Benchmarks benchmarkHelper.StopRttStopwatch(); benchmarkHelper.StopBandwidthStopwatch(); -#if DEBUG - lock (typeof(Console)) - { - Console.WriteLine($"[Client {id}, Packet {i}] Received {receivedBytes} bytes from {remoteEndPoint}"); - Console.WriteLine($"[Client {id}, Packet {i}] <<<< {Encoding.UTF8.GetString(receiveBuffer)}"); - } -#endif - benchmarkHelper.UpdateRttStats(id); - /* - lock (typeof(Console)) - { - Console.WriteLine($"[Client {id}] Current RTT: {benchmarkHelper.RttTicks} ticks, {benchmarkHelper.RttMs} ms"); - } - */ - benchmarkHelper.ResetRttStopwatch(); } @@ -154,5 +91,44 @@ namespace NetSharpExamples.Benchmarks return Task.CompletedTask; } + + /// <inheritdoc /> + public async Task RunAsync() + { + CancellationTokenSource serverCts = new CancellationTokenSource(); + + int clientCount = Environment.ProcessorCount / 2; + + Console.WriteLine($"TCP Server Benchmark started!"); + + StreamSocketServerOptions serverOptions = new StreamSocketServerOptions(clientCount, (ushort)clientCount); + + StreamSocketServer server = new StreamSocketServer(AddressFamily.InterNetwork, ProtocolType.Tcp, + SocketServer.DefaultPacketHandler, serverOptions); + + server.Bind(ServerEndPoint); + + Task serverTask = Task.Factory.StartNew(() => + { + server.RunAsync(serverCts.Token).GetAwaiter().GetResult(); + }, TaskCreationOptions.LongRunning); + + ClientBandwidths = new double[clientCount]; + Task[] clientTasks = new Task[clientCount]; + for (int i = 0; i < clientTasks.Length; i++) + { + clientTasks[i] = Task.Factory.StartNew(BenchmarkClientTask, i, TaskCreationOptions.LongRunning); + } + + await Task.WhenAll(clientTasks); + + Console.WriteLine($"Total estimated bandwidth: {ClientBandwidths.Sum():F5}"); + + serverCts.Cancel(); + + await serverTask; + + Console.WriteLine($"TCP Server Benchmark finished!"); + } } } \ No newline at end of file diff --git a/NetSharp/NetSharpExamples/Benchmarks/UdpSocketServerBenchmark.cs b/NetSharp/NetSharpExamples/Benchmarks/UdpSocketServerBenchmark.cs @@ -20,46 +20,6 @@ namespace NetSharpExamples.Benchmarks private double[] ClientBandwidths; - /// <inheritdoc /> - public async Task RunAsync() - { - CancellationTokenSource serverCts = new CancellationTokenSource(); - - int clientCount = Environment.ProcessorCount / 2; - - Console.WriteLine($"UDP Server Benchmark started!"); - - DatagramSocketServerOptions serverOptions = new DatagramSocketServerOptions(NetworkPacket.TotalSize, - clientCount, (ushort)clientCount); - - DatagramSocketServer server = new DatagramSocketServer(AddressFamily.InterNetwork, ProtocolType.Udp, - SocketServer.DefaultPacketHandler, serverOptions); - - server.Bind(ServerEndPoint); - - Task serverTask = Task.Factory.StartNew(() => - { - server.RunAsync(serverCts.Token).GetAwaiter().GetResult(); - }, TaskCreationOptions.LongRunning); - - ClientBandwidths = new double[clientCount]; - Task[] clientTasks = new Task[clientCount]; - for (int i = 0; i < clientTasks.Length; i++) - { - clientTasks[i] = Task.Factory.StartNew(BenchmarkClientTask, i, TaskCreationOptions.LongRunning); - } - - await Task.WhenAll(clientTasks); - - Console.WriteLine($"Total estimated bandwidth: {ClientBandwidths.Sum():F5}"); - - serverCts.Cancel(); - - await serverTask; - - Console.WriteLine($"UDP Server Benchmark finished!"); - } - private Task BenchmarkClientTask(object idObj) { int id = (int)idObj; @@ -89,35 +49,12 @@ namespace NetSharpExamples.Benchmarks benchmarkHelper.StartRttStopwatch(); int sentBytes = clientSocket.SendTo(sendBuffer, remoteEndPoint); -#if DEBUG - lock (typeof(Console)) - { - Console.WriteLine($"[Client {id}, Packet {i}] Sent {sentBytes} bytes to {remoteEndPoint}"); - Console.WriteLine($"[Client {id}, Packet {i}] >>>> {Encoding.UTF8.GetString(sendBuffer)}"); - } -#endif - int receivedBytes = clientSocket.ReceiveFrom(receiveBuffer, ref remoteEndPoint); benchmarkHelper.StopRttStopwatch(); benchmarkHelper.StopBandwidthStopwatch(); -#if DEBUG - lock (typeof(Console)) - { - Console.WriteLine($"[Client {id}, Packet {i}] Received {receivedBytes} bytes from {remoteEndPoint}"); - Console.WriteLine($"[Client {id}, Packet {i}] <<<< {Encoding.UTF8.GetString(receiveBuffer)}"); - } -#endif - benchmarkHelper.UpdateRttStats(id); - /* - lock (typeof(Console)) - { - Console.WriteLine($"[Client {id}] Current RTT: {benchmarkHelper.RttTicks} ticks, {benchmarkHelper.RttMs} ms"); - } - */ - benchmarkHelper.ResetRttStopwatch(); } @@ -128,5 +65,44 @@ namespace NetSharpExamples.Benchmarks return Task.CompletedTask; } + + /// <inheritdoc /> + public async Task RunAsync() + { + CancellationTokenSource serverCts = new CancellationTokenSource(); + + int clientCount = Environment.ProcessorCount / 2; + + Console.WriteLine($"UDP Server Benchmark started!"); + + DatagramSocketServerOptions serverOptions = new DatagramSocketServerOptions(clientCount, (ushort)clientCount); + + DatagramSocketServer server = new DatagramSocketServer(AddressFamily.InterNetwork, ProtocolType.Udp, + SocketServer.DefaultPacketHandler, serverOptions); + + server.Bind(ServerEndPoint); + + Task serverTask = Task.Factory.StartNew(() => + { + server.RunAsync(serverCts.Token).GetAwaiter().GetResult(); + }, TaskCreationOptions.LongRunning); + + ClientBandwidths = new double[clientCount]; + Task[] clientTasks = new Task[clientCount]; + for (int i = 0; i < clientTasks.Length; i++) + { + clientTasks[i] = Task.Factory.StartNew(BenchmarkClientTask, i, TaskCreationOptions.LongRunning); + } + + await Task.WhenAll(clientTasks); + + Console.WriteLine($"Total estimated bandwidth: {ClientBandwidths.Sum():F5}"); + + serverCts.Cancel(); + + await serverTask; + + Console.WriteLine($"UDP Server Benchmark finished!"); + } } } \ No newline at end of file diff --git a/NetSharp/NetSharpExamples/Examples/TcpSocketClientExample.cs b/NetSharp/NetSharpExamples/Examples/TcpSocketClientExample.cs @@ -15,13 +15,13 @@ namespace NetSharpExamples.Examples /// <inheritdoc /> public async Task RunAsync() { - StreamSocketClientOptions clientOptions = new StreamSocketClientOptions(NetworkPacket.TotalSize, 2); + StreamSocketClientOptions clientOptions = new StreamSocketClientOptions(2); using StreamSocketClient client = new StreamSocketClient(AddressFamily.InterNetwork, ProtocolType.Tcp, clientOptions); Encoding dataEncoding = UdpSocketServerExample.ServerEncoding; - byte[] sendBuffer = new byte[clientOptions.PacketSize]; - byte[] receiveBuffer = new byte[clientOptions.PacketSize]; + byte[] sendBuffer = new byte[NetworkPacket.TotalSize]; + byte[] receiveBuffer = new byte[NetworkPacket.TotalSize]; EndPoint remoteEndPoint = TcpSocketServerExample.ServerEndPoint; diff --git a/NetSharp/NetSharpExamples/Examples/TcpSocketServerExample.cs b/NetSharp/NetSharpExamples/Examples/TcpSocketServerExample.cs @@ -33,7 +33,7 @@ namespace NetSharpExamples.Examples public Task RunAsync() { StreamSocketServerOptions serverOptions = - new StreamSocketServerOptions(NetworkPacket.TotalSize, Environment.ProcessorCount, 2); + new StreamSocketServerOptions(Environment.ProcessorCount, 2); using StreamSocketServer server = new StreamSocketServer(AddressFamily.InterNetwork, ProtocolType.Tcp, ServerPacketHandler, serverOptions); diff --git a/NetSharp/NetSharpExamples/Examples/UdpSocketClientExample.cs b/NetSharp/NetSharpExamples/Examples/UdpSocketClientExample.cs @@ -15,13 +15,13 @@ namespace NetSharpExamples.Examples /// <inheritdoc /> public async Task RunAsync() { - DatagramSocketClientOptions clientOptions = new DatagramSocketClientOptions(NetworkPacket.TotalSize, 2); + DatagramSocketClientOptions clientOptions = new DatagramSocketClientOptions(2); using DatagramSocketClient client = new DatagramSocketClient(AddressFamily.InterNetwork, ProtocolType.Udp, clientOptions); Encoding dataEncoding = UdpSocketServerExample.ServerEncoding; - byte[] sendBuffer = new byte[clientOptions.PacketSize]; - byte[] receiveBuffer = new byte[clientOptions.PacketSize]; + byte[] sendBuffer = new byte[NetworkPacket.TotalSize]; + byte[] receiveBuffer = new byte[NetworkPacket.TotalSize]; EndPoint remoteEndPoint = UdpSocketServerExample.ServerEndPoint; diff --git a/NetSharp/NetSharpExamples/Examples/UdpSocketServerExample.cs b/NetSharp/NetSharpExamples/Examples/UdpSocketServerExample.cs @@ -33,7 +33,7 @@ namespace NetSharpExamples.Examples public Task RunAsync() { DatagramSocketServerOptions serverOptions = - new DatagramSocketServerOptions(NetworkPacket.TotalSize, Environment.ProcessorCount, 2); + new DatagramSocketServerOptions(Environment.ProcessorCount, 2); using DatagramSocketServer server = new DatagramSocketServer(AddressFamily.InterNetwork, ProtocolType.Udp, ServerPacketHandler, serverOptions);