NetSharp

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

commit 502cd5d5e0d41a210e5ca4405b14ee05cc6ecbc7
parent 98b1736ce0dc86bf8b7ae43518050fd53b802034
Author: Mikolaj Lenczewski <mikolaj.lenczewski308@gmail.com>
Date:   Thu, 23 Apr 2020 17:14:08 +0100

Added socket option support and local end point access to SocketConnection. Also added further examples. Cleaned solution fully as well.

Diffstat:
MNetSharp/NetSharp/Packets/NetworkPacket.cs | 32++++++++++++++++----------------
MNetSharp/NetSharp/Sockets/Datagram/DatagramSocketClient.cs | 30+++++++++++++++---------------
MNetSharp/NetSharp/Sockets/Datagram/DatagramSocketServer.cs | 173+++++++++++++++++++++++++++++++++++++++----------------------------------------
MNetSharp/NetSharp/Sockets/SocketClient.cs | 234+++++++++++++++++++++++++++++++++++++++----------------------------------------
MNetSharp/NetSharp/Sockets/SocketConnection.cs | 153++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
MNetSharp/NetSharp/Sockets/SocketServer.cs | 12++++++------
MNetSharp/NetSharp/Sockets/Stream/StreamSocketClient.cs | 30+++++++++++++++---------------
MNetSharp/NetSharp/Sockets/Stream/StreamSocketServer.cs | 219+++++++++++++++++++++++++++++++++++++++----------------------------------------
MNetSharp/NetSharp/Utils/BiDictionary.cs | 6+++---
MNetSharp/NetSharp/Utils/Conversion/EndianAwareBitConverter.cs | 42+++++++++++++++++++++---------------------
MNetSharp/NetSharp/Utils/SlimObjectPool.cs | 68+++++++++++++++++++++++++++++++++++---------------------------------
MNetSharp/NetSharp/Utils/TransmissionResult.cs | 34+++++++++++++++++-----------------
MNetSharp/NetSharpExamples/BenchmarkHelper.cs | 101+++++++++++++++++++++++++++++++++++++++----------------------------------------
ANetSharp/NetSharpExamples/Benchmarks/TcpSocketServerBenchmark.cs | 159+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ANetSharp/NetSharpExamples/Benchmarks/UdpSocketServerBenchmark.cs | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ANetSharp/NetSharpExamples/Examples/TcpSocketClientExample.cs | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
DNetSharp/NetSharpExamples/Examples/TcpSocketServerBenchmark.cs | 158-------------------------------------------------------------------------------
MNetSharp/NetSharpExamples/Examples/TcpSocketServerExample.cs | 32+++++++++++++++++++-------------
ANetSharp/NetSharpExamples/Examples/UdpSocketClientExample.cs | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
DNetSharp/NetSharpExamples/Examples/UdpSocketServerBenchmark.cs | 132-------------------------------------------------------------------------------
MNetSharp/NetSharpExamples/Examples/UdpSocketServerExample.cs | 32+++++++++++++++++++-------------
MNetSharp/NetSharpExamples/INetSharpExample.cs | 6++++++
MNetSharp/NetSharpExamples/Program.cs | 2+-
23 files changed, 1058 insertions(+), 863 deletions(-)

diff --git a/NetSharp/NetSharp/Packets/NetworkPacket.cs b/NetSharp/NetSharp/Packets/NetworkPacket.cs @@ -5,30 +5,30 @@ namespace NetSharp.Packets //TODO document public readonly struct NetworkPacket { - public static NetworkPacket NullPacket = new NetworkPacket(); + public const int DataSize = 8192; - public const int TotalSize = HeaderSize + DataSize + FooterSize; + public const int FooterSize = NetworkPacketFooter.TotalSize; public const int HeaderSize = NetworkPacketHeader.TotalSize; - public const int FooterSize = NetworkPacketFooter.TotalSize; + public const int TotalSize = HeaderSize + DataSize + FooterSize; - public const int DataSize = 8192; + public static NetworkPacket NullPacket = new NetworkPacket(); public readonly ReadOnlyMemory<byte> Data; - public readonly NetworkPacketHeader Header; - public readonly NetworkPacketFooter Footer; + public readonly NetworkPacketHeader Header; + /// <summary> - /// Constructs a new instance of the <see cref="NetworkPacket"/> struct. + /// 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. + /// Thrown when the given <paramref name="packetDataBuffer" /> exceeds <see cref="TotalSize" /> bytes in size. /// </exception> private NetworkPacket(NetworkPacketHeader packetHeader, ReadOnlyMemory<byte> packetDataBuffer, NetworkPacketFooter packetFooter) { @@ -69,31 +69,31 @@ namespace NetSharp.Packets } //TODO document - public readonly struct NetworkPacketHeader + public readonly struct NetworkPacketFooter { public const int TotalSize = 0; - public static NetworkPacketHeader Deserialise(ReadOnlyMemory<byte> buffer) + public static NetworkPacketFooter Deserialise(ReadOnlyMemory<byte> buffer) { - return new NetworkPacketHeader(); + return new NetworkPacketFooter(); } - public static void Serialise(NetworkPacketHeader instance, Memory<byte> buffer) + public static void Serialise(NetworkPacketFooter instance, Memory<byte> buffer) { } } //TODO document - public readonly struct NetworkPacketFooter + public readonly struct NetworkPacketHeader { public const int TotalSize = 0; - public static NetworkPacketFooter Deserialise(ReadOnlyMemory<byte> buffer) + public static NetworkPacketHeader Deserialise(ReadOnlyMemory<byte> buffer) { - return new NetworkPacketFooter(); + return new NetworkPacketHeader(); } - public static void Serialise(NetworkPacketFooter instance, Memory<byte> buffer) + public static void Serialise(NetworkPacketHeader instance, Memory<byte> buffer) { } } diff --git a/NetSharp/NetSharp/Sockets/Datagram/DatagramSocketClient.cs b/NetSharp/NetSharp/Sockets/Datagram/DatagramSocketClient.cs @@ -34,32 +34,32 @@ 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, clientOptions?.PacketSize ?? DatagramSocketClientOptions.Defaults.PacketSize, + clientOptions?.PreallocatedTransmissionArgs ?? DatagramSocketClientOptions.Defaults.PreallocatedTransmissionArgs) { this.clientOptions = clientOptions ?? DatagramSocketClientOptions.Defaults; } - /// <inheritdoc /> - protected override SocketAsyncEventArgs CreateTransmissionArgs() + public ref readonly DatagramSocketClientOptions ClientOptions { - SocketAsyncEventArgs connectionArgs = new SocketAsyncEventArgs(); - - connectionArgs.Completed += HandleIoCompleted; - - return connectionArgs; + get { return ref clientOptions; } } /// <inheritdoc /> - protected override void ResetTransmissionArgs(SocketAsyncEventArgs args) + protected override bool CanTransmissionArgsBeReused(in SocketAsyncEventArgs args) { + return true; } /// <inheritdoc /> - protected override bool CanTransmissionArgsBeReused(in SocketAsyncEventArgs args) + protected override SocketAsyncEventArgs CreateTransmissionArgs() { - return true; + SocketAsyncEventArgs connectionArgs = new SocketAsyncEventArgs(); + + connectionArgs.Completed += HandleIoCompleted; + + return connectionArgs; } /// <inheritdoc /> @@ -144,9 +144,9 @@ namespace NetSharp.Sockets.Datagram } } - public ref readonly DatagramSocketClientOptions ClientOptions + /// <inheritdoc /> + protected override void ResetTransmissionArgs(SocketAsyncEventArgs args) { - get { return ref clientOptions; } } public TransmissionResult ReceiveFrom(ref EndPoint remoteEndPoint, byte[] receiveBuffer, SocketFlags flags = SocketFlags.None) diff --git a/NetSharp/NetSharp/Sockets/Datagram/DatagramSocketServer.cs b/NetSharp/NetSharp/Sockets/Datagram/DatagramSocketServer.cs @@ -14,10 +14,8 @@ namespace NetSharp.Sockets.Datagram public static readonly DatagramSocketServerOptions Defaults = new DatagramSocketServerOptions(NetworkPacket.TotalSize, Environment.ProcessorCount, 0); - public readonly int PacketSize; - public readonly int ConcurrentReceiveFromCalls; - + public readonly int PacketSize; public readonly ushort PreallocatedTransmissionArgs; public DatagramSocketServerOptions(int packetSize, int concurrentReceiveFromCalls, ushort preallocatedTransmissionArgs) @@ -31,7 +29,6 @@ namespace NetSharp.Sockets.Datagram } //TODO address the need to handle series of network packets, not just single packets - //TODO allow for the server to do more than just echo packets //TODO document class public sealed class DatagramSocketServer : SocketServer { @@ -42,7 +39,7 @@ namespace NetSharp.Sockets.Datagram private CancellationToken serverShutdownToken; /// <summary> - /// Constructs a new instance of the <see cref="DatagramSocketServer"/> class. + /// Constructs a new instance of the <see cref="DatagramSocketServer" /> class. /// </summary> /// <param name="serverOptions">Additional options to configure the server.</param> /// <inheritdoc /> @@ -55,66 +52,51 @@ namespace NetSharp.Sockets.Datagram this.serverOptions = serverOptions ?? DatagramSocketServerOptions.Defaults; } - private readonly struct SocketOperationToken + public ref readonly DatagramSocketServerOptions ServerOptions { - public readonly byte[] RentedBuffer; - - public SocketOperationToken(in byte[] rentedBuffer) - { - RentedBuffer = rentedBuffer; - } + get { return ref serverOptions; } } - /// <inheritdoc /> - protected override SocketAsyncEventArgs CreateTransmissionArgs() + private void CompleteReceiveFrom(SocketAsyncEventArgs receiveArgs) { - SocketAsyncEventArgs connectionArgs = new SocketAsyncEventArgs(); + SocketOperationToken receiveToken = (SocketOperationToken)receiveArgs.UserToken; - connectionArgs.Completed += HandleIoCompleted; + if (receiveArgs.SocketError == SocketError.Success) + { + NetworkPacket request = NetworkPacket.Deserialise(receiveArgs.MemoryBuffer); - return connectionArgs; - } + NetworkPacket response = PacketHandler(in request, receiveArgs.RemoteEndPoint); - /// <inheritdoc /> - protected override void ResetTransmissionArgs(SocketAsyncEventArgs args) - { - } + if (!response.Equals(NetworkPacket.NullPacket)) + { + byte[] sendBuffer = BufferPool.Rent(ServerOptions.PacketSize); + Memory<byte> sendBufferMemory = new Memory<byte>(sendBuffer); - /// <inheritdoc /> - protected override bool CanTransmissionArgsBeReused(in SocketAsyncEventArgs args) - { - return true; - } + NetworkPacket.Serialise(response, sendBufferMemory); - /// <inheritdoc /> - protected override void DestroyTransmissionArgs(SocketAsyncEventArgs remoteConnectionArgs) - { - remoteConnectionArgs.Completed -= HandleIoCompleted; + receiveArgs.SetBuffer(sendBufferMemory); + receiveArgs.UserToken = new SocketOperationToken(in sendBuffer); - remoteConnectionArgs.Dispose(); - } + SendTo(receiveArgs); + } - /// <inheritdoc /> - protected override void HandleIoCompleted(object sender, SocketAsyncEventArgs args) - { - switch (args.LastOperation) + BufferPool.Return(receiveToken.RentedBuffer, true); + } + else { - case SocketAsyncOperation.ReceiveFrom: - SocketAsyncEventArgs newReceiveArgs = TransmissionArgsPool.Rent(); - newReceiveArgs.RemoteEndPoint = AnyRemoteEndPoint; + BufferPool.Return(receiveToken.RentedBuffer, true); - ReceiveFrom(newReceiveArgs); // start a new receive from operation immediately, to not drop any packets + TransmissionArgsPool.Return(receiveArgs); + } + } - CompleteReceiveFrom(args); - break; + private void CompleteSendTo(SocketAsyncEventArgs sendArgs) + { + SocketOperationToken sendToken = (SocketOperationToken)sendArgs.UserToken; - case SocketAsyncOperation.SendTo: - CompleteSendTo(args); - break; + BufferPool.Return(sendToken.RentedBuffer, true); - default: - throw new NotSupportedException($"{nameof(HandleIoCompleted)} doesn't support {args.LastOperation}"); - } + TransmissionArgsPool.Return(sendArgs); } private void ReceiveFrom(SocketAsyncEventArgs receiveArgs) @@ -144,39 +126,6 @@ namespace NetSharp.Sockets.Datagram CompleteReceiveFrom(receiveArgs); } - private void CompleteReceiveFrom(SocketAsyncEventArgs receiveArgs) - { - SocketOperationToken receiveToken = (SocketOperationToken)receiveArgs.UserToken; - - if (receiveArgs.SocketError == SocketError.Success) - { - NetworkPacket request = NetworkPacket.Deserialise(receiveArgs.MemoryBuffer); - - NetworkPacket response = PacketHandler(in request, receiveArgs.RemoteEndPoint); - - if (!response.Equals(NetworkPacket.NullPacket)) - { - byte[] sendBuffer = BufferPool.Rent(ServerOptions.PacketSize); - Memory<byte> sendBufferMemory = new Memory<byte>(sendBuffer); - - NetworkPacket.Serialise(response, sendBufferMemory); - - receiveArgs.SetBuffer(sendBufferMemory); - receiveArgs.UserToken = new SocketOperationToken(in sendBuffer); - - SendTo(receiveArgs); - } - - BufferPool.Return(receiveToken.RentedBuffer, true); - } - else - { - BufferPool.Return(receiveToken.RentedBuffer, true); - - TransmissionArgsPool.Return(receiveArgs); - } - } - private void SendTo(SocketAsyncEventArgs sendArgs) { if (serverShutdownToken.IsCancellationRequested) @@ -198,18 +147,56 @@ namespace NetSharp.Sockets.Datagram } } - private void CompleteSendTo(SocketAsyncEventArgs sendArgs) + /// <inheritdoc /> + protected override bool CanTransmissionArgsBeReused(in SocketAsyncEventArgs args) { - SocketOperationToken sendToken = (SocketOperationToken)sendArgs.UserToken; + return true; + } - BufferPool.Return(sendToken.RentedBuffer, true); + /// <inheritdoc /> + protected override SocketAsyncEventArgs CreateTransmissionArgs() + { + SocketAsyncEventArgs connectionArgs = new SocketAsyncEventArgs(); - TransmissionArgsPool.Return(sendArgs); + connectionArgs.Completed += HandleIoCompleted; + + return connectionArgs; } - public ref readonly DatagramSocketServerOptions ServerOptions + /// <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.ReceiveFrom: + SocketAsyncEventArgs newReceiveArgs = TransmissionArgsPool.Rent(); + newReceiveArgs.RemoteEndPoint = AnyRemoteEndPoint; + + ReceiveFrom(newReceiveArgs); // start a new receive from operation immediately, to not drop any packets + + CompleteReceiveFrom(args); + break; + + case SocketAsyncOperation.SendTo: + CompleteSendTo(args); + break; + + default: + throw new NotSupportedException($"{nameof(HandleIoCompleted)} doesn't support {args.LastOperation}"); + } + } + + /// <inheritdoc /> + protected override void ResetTransmissionArgs(SocketAsyncEventArgs args) { - get { return ref serverOptions; } } /// <inheritdoc /> @@ -229,5 +216,15 @@ namespace NetSharp.Sockets.Datagram return Task.CompletedTask; } + + private readonly struct SocketOperationToken + { + public readonly byte[] RentedBuffer; + + public SocketOperationToken(in byte[] rentedBuffer) + { + RentedBuffer = rentedBuffer; + } + } } } \ No newline at end of file diff --git a/NetSharp/NetSharp/Sockets/SocketClient.cs b/NetSharp/NetSharp/Sockets/SocketClient.cs @@ -13,30 +13,108 @@ namespace NetSharp.Sockets public abstract class SocketClient : SocketConnection { /// <summary> - /// A state token for asynchronous network IO operations. + /// Constructs a new instance of the <see cref="SocketClient" /> class. /// </summary> - protected readonly struct AsyncTransmissionToken + /// <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) + { + } + + /// <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" />). + /// </summary> + /// <param name="remoteEndPoint">The remote end point which to which to connect the client.</param> + public void Connect(in EndPoint remoteEndPoint) + { + Connection.Connect(remoteEndPoint); + } + + /// <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" />). + /// </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> + public ValueTask ConnectAsync(in EndPoint remoteEndPoint, CancellationToken cancellationToken = default) + { + TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(); + + SocketAsyncEventArgs args = TransmissionArgsPool.Rent(); + + args.RemoteEndPoint = remoteEndPoint; + args.UserToken = new AsyncOperationToken(in tcs, in cancellationToken); + + cancellationToken.Register(token => + { + AsyncOperationCancellationToken operationCancellationToken = (AsyncOperationCancellationToken)token; + + Socket.CancelConnectAsync(operationCancellationToken.TransmissionArgs); + + operationCancellationToken.CompletionSource.SetCanceled(); + + operationCancellationToken.TransmissionArgsPool.Return(operationCancellationToken.TransmissionArgs); + }, new AsyncOperationCancellationToken(in Connection, in args, in TransmissionArgsPool, in tcs)); + + if (Connection.ConnectAsync(args)) return new ValueTask(tcs.Task); + + TransmissionArgsPool.Return(args); + + return new ValueTask(); + } + + /// <summary> + /// A state token for cancelling asynchronous socket operations. + /// </summary> + protected readonly struct AsyncOperationCancellationToken { /// <summary> - /// The completion source which wraps the event-based APM, and provides an awaitable <see cref="Task"/>. + /// The completion source associated with the network IO operation. /// </summary> - public readonly TaskCompletionSource<TransmissionResult> CompletionSource; + public readonly TaskCompletionSource<bool> CompletionSource; /// <summary> - /// The <see cref="System.Threading.CancellationToken"/> associated with the network IO operation. + /// The socket on which the operation was started. /// </summary> - public readonly CancellationToken CancellationToken; + public readonly Socket Socket; /// <summary> - /// Constructs a new instance of the <see cref="AsyncTransmissionToken"/> struct. + /// The <see cref="SocketAsyncEventArgs" /> instance associated with the socket operation. /// </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> - public AsyncTransmissionToken(in TaskCompletionSource<TransmissionResult> completionSource, in CancellationToken cancellationToken) + public readonly SocketAsyncEventArgs TransmissionArgs; + + /// <summary> + /// The pool to which the <see cref="TransmissionArgs" /> should be returned upon operation cancellation. + /// </summary> + public readonly SlimObjectPool<SocketAsyncEventArgs> TransmissionArgsPool; + + /// <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> + public AsyncOperationCancellationToken(in Socket socket, in SocketAsyncEventArgs args, + in SlimObjectPool<SocketAsyncEventArgs> argsPool, in TaskCompletionSource<bool> completionSource) { - CompletionSource = completionSource; + Socket = socket; - CancellationToken = cancellationToken; + TransmissionArgs = args; + + TransmissionArgsPool = argsPool; + + CompletionSource = completionSource; } } @@ -46,17 +124,17 @@ namespace NetSharp.Sockets protected readonly struct AsyncOperationToken { /// <summary> - /// The completion source which wraps the event-based APM, and provides an awaitable <see cref="Task"/>. + /// The <see cref="System.Threading.CancellationToken" /> associated with the socket operation. /// </summary> - public readonly TaskCompletionSource<bool> CompletionSource; + public readonly CancellationToken CancellationToken; /// <summary> - /// The <see cref="System.Threading.CancellationToken"/> associated with the socket operation. + /// The completion source which wraps the event-based APM, and provides an awaitable <see cref="Task" />. /// </summary> - public readonly CancellationToken CancellationToken; + public readonly TaskCompletionSource<bool> CompletionSource; /// <summary> - /// Constructs a new instance of the <see cref="AsyncOperationToken"/> struct. + /// 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> @@ -74,31 +152,31 @@ namespace NetSharp.Sockets protected readonly struct AsyncTransmissionCancellationToken { /// <summary> + /// The completion source associated with the network IO operation. + /// </summary> + public readonly TaskCompletionSource<TransmissionResult> CompletionSource; + + /// <summary> /// The socket on which the operation was started. /// </summary> public readonly Socket Socket; /// <summary> - /// The <see cref="SocketAsyncEventArgs"/> instance associated with the network IO operation. + /// The <see cref="SocketAsyncEventArgs" /> instance associated with the network IO operation. /// </summary> public readonly SocketAsyncEventArgs TransmissionArgs; /// <summary> - /// The pool to which the <see cref="TransmissionArgs"/> should be returned upon operation cancellation. + /// The pool to which the <see cref="TransmissionArgs" /> should be returned upon operation cancellation. /// </summary> public readonly SlimObjectPool<SocketAsyncEventArgs> TransmissionArgsPool; /// <summary> - /// The completion source associated with the network IO operation. - /// </summary> - public readonly TaskCompletionSource<TransmissionResult> CompletionSource; - - /// <summary> - /// Constructs a new instance of the <see cref="AsyncTransmissionCancellationToken"/> struct. + /// 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="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) @@ -114,111 +192,31 @@ namespace NetSharp.Sockets } /// <summary> - /// A state token for cancelling asynchronous socket operations. + /// A state token for asynchronous network IO operations. /// </summary> - protected readonly struct AsyncOperationCancellationToken + protected readonly struct AsyncTransmissionToken { /// <summary> - /// The socket on which the operation was started. - /// </summary> - public readonly Socket Socket; - - /// <summary> - /// The <see cref="SocketAsyncEventArgs"/> instance associated with the socket operation. - /// </summary> - public readonly SocketAsyncEventArgs TransmissionArgs; - - /// <summary> - /// The pool to which the <see cref="TransmissionArgs"/> should be returned upon operation cancellation. + /// The <see cref="System.Threading.CancellationToken" /> associated with the network IO operation. /// </summary> - public readonly SlimObjectPool<SocketAsyncEventArgs> TransmissionArgsPool; + public readonly CancellationToken CancellationToken; /// <summary> - /// The completion source associated with the network IO operation. + /// The completion source which wraps the event-based APM, and provides an awaitable <see cref="Task" />. /// </summary> - public readonly TaskCompletionSource<bool> CompletionSource; + public readonly TaskCompletionSource<TransmissionResult> CompletionSource; /// <summary> - /// Constructs a new instance of the <see cref="AsyncOperationCancellationToken"/> struct. + /// Constructs a new instance of the <see cref="AsyncTransmissionToken" /> 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> - public AsyncOperationCancellationToken(in Socket socket, in SocketAsyncEventArgs args, - in SlimObjectPool<SocketAsyncEventArgs> argsPool, in TaskCompletionSource<bool> completionSource) + /// <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) { - Socket = socket; - - TransmissionArgs = args; - - TransmissionArgsPool = argsPool; - CompletionSource = completionSource; - } - } - /// <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) - { - } - - /// <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"/>). - /// </summary> - /// <param name="remoteEndPoint">The remote end point which to which to connect the client.</param> - public void Connect(in EndPoint remoteEndPoint) - { - Connection.Connect(remoteEndPoint); - } - - /// <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"/>). - /// </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> - public ValueTask ConnectAsync(in EndPoint remoteEndPoint, CancellationToken cancellationToken = default) - { - TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(); - - SocketAsyncEventArgs args = TransmissionArgsPool.Rent(); - - args.RemoteEndPoint = remoteEndPoint; - args.UserToken = new AsyncOperationToken(in tcs, in cancellationToken); - - cancellationToken.Register(token => - { - AsyncOperationCancellationToken operationCancellationToken = (AsyncOperationCancellationToken)token; - - Socket.CancelConnectAsync(operationCancellationToken.TransmissionArgs); - - operationCancellationToken.CompletionSource.SetCanceled(); - - operationCancellationToken.TransmissionArgsPool.Return(operationCancellationToken.TransmissionArgs); - }, new AsyncOperationCancellationToken(in Connection, in args, in TransmissionArgsPool, in tcs)); - - if (Connection.ConnectAsync(args)) return new ValueTask(tcs.Task); - - TransmissionArgsPool.Return(args); - - return new ValueTask(); + CancellationToken = cancellationToken; + } } } } \ No newline at end of file diff --git a/NetSharp/NetSharp/Sockets/SocketConnection.cs b/NetSharp/NetSharp/Sockets/SocketConnection.cs @@ -10,34 +10,34 @@ namespace NetSharp.Sockets /// <summary> /// Abstract base class for clients and servers. /// </summary> - /// TODO add access to socket options + /// TODO implement better protections for accessing socket options public abstract class SocketConnection : IDisposable { /// <summary> - /// The underlying <see cref="Socket"/> which provides access to network operations. - /// </summary> - protected Socket Connection; - - /// <summary> /// Pools arrays to function as temporary buffers during network read/write operations. /// </summary> 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; /// <summary> - /// Constructs a new instance of the <see cref="SocketConnection"/> class. + /// The underlying <see cref="Socket" /> which provides access to network operations. + /// </summary> + protected Socket Connection; + + /// <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> - protected internal SocketConnection(in AddressFamily connectionAddressFamily, in SocketType connectionSocketType, + /// <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> + private protected SocketConnection(in AddressFamily connectionAddressFamily, in SocketType connectionSocketType, in ProtocolType connectionProtocolType, in int maxPooledBufferLength, in ushort preallocatedTransmissionArgs) { Connection = new Socket(connectionAddressFamily, connectionSocketType, connectionProtocolType); @@ -57,45 +57,88 @@ namespace NetSharp.Sockets } /// <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 local endpoint to which the underlying <see cref="Socket" /> is bound. /// </summary> - /// <returns>The configured <see cref="SocketAsyncEventArgs"/> instance.</returns> - protected abstract SocketAsyncEventArgs CreateTransmissionArgs(); + public EndPoint LocalEndPoint + { + get { return Connection.LocalEndPoint; } + } /// <summary> - /// Delegate method used to reset used <see cref="SocketAsyncEventArgs"/> instances for later reuse by - /// the <see cref="TransmissionArgsPool"/>. + /// 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 that should be reset.</param> - protected abstract void ResetTransmissionArgs(SocketAsyncEventArgs args); + /// <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 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 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. /// </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> - protected abstract bool CanTransmissionArgsBeReused(in SocketAsyncEventArgs args); + /// <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> - /// Delegate method to handle asynchronous network IO completion via the <see cref="SocketAsyncEventArgs.Completed"/> event. + /// 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> + protected virtual void Dispose(bool disposing) + { + if (!disposing) return; + + Connection.Close(); + Connection.Dispose(); + } + + /// <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="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> + protected abstract void ResetTransmissionArgs(SocketAsyncEventArgs args); + + /// <inheritdoc cref="Socket.SetSocketOption(SocketOptionLevel,SocketOptionName,bool)" /> + protected void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, bool optionValue) + { + Connection.SetSocketOption(optionLevel, optionName, optionValue); + } + + /// <inheritdoc cref="Socket.SetSocketOption(SocketOptionLevel,SocketOptionName,byte[])" /> + protected void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, byte[] optionValue) + { + Connection.SetSocketOption(optionLevel, optionName, optionValue); + } + + /// <inheritdoc cref="Socket.SetSocketOption(SocketOptionLevel,SocketOptionName,int)" /> + protected void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, int optionValue) + { + Connection.SetSocketOption(optionLevel, optionName, optionValue); + } + + /// <inheritdoc cref="Socket.SetSocketOption(SocketOptionLevel,SocketOptionName,object)" /> + protected void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, object optionValue) + { + Connection.SetSocketOption(optionLevel, optionName, optionValue); + } + + /// <summary> /// Binds the underlying socket. /// </summary> /// <param name="localEndPoint">The end point to which the socket should be bound.</param> @@ -104,6 +147,31 @@ namespace NetSharp.Sockets Connection.Bind(localEndPoint); } + /// <inheritdoc /> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <inheritdoc cref="Socket.GetSocketOption(SocketOptionLevel,SocketOptionName)" /> + public object GetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName) + { + return Connection.GetSocketOption(optionLevel, optionName); + } + + /// <inheritdoc cref="Socket.GetSocketOption(SocketOptionLevel,SocketOptionName,int)" /> + public byte[] GetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, int optionLength) + { + return Connection.GetSocketOption(optionLevel, optionName, optionLength); + } + + /// <inheritdoc cref="Socket.GetSocketOption(SocketOptionLevel,SocketOptionName,byte[])" /> + public void GetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, byte[] optionValue) + { + Connection.GetSocketOption(optionLevel, optionName, optionValue); + } + /// <summary> /// Shuts down the underlying socket. /// </summary> @@ -116,24 +184,5 @@ namespace NetSharp.Sockets } catch (SocketException) { } } - - /// <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> - protected virtual void Dispose(bool disposing) - { - if (!disposing) return; - - Connection.Close(); - Connection.Dispose(); - } - - /// <inheritdoc /> - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } } } \ No newline at end of file diff --git a/NetSharp/NetSharp/Sockets/SocketServer.cs b/NetSharp/NetSharp/Sockets/SocketServer.cs @@ -13,8 +13,8 @@ namespace NetSharp.Sockets /// <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); @@ -29,7 +29,7 @@ namespace NetSharp.Sockets protected readonly SocketServerPacketHandler PacketHandler; /// <summary> - /// Constructs a new instance of the <see cref="SocketServer"/> class. + /// 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> @@ -57,10 +57,10 @@ namespace NetSharp.Sockets } /// <summary> - /// Runs the server, handling requests from clients, until the <paramref name="cancellationToken"/> has its cancellation requested. + /// 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 @@ -33,32 +33,32 @@ 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, clientOptions?.PacketSize ?? StreamSocketClientOptions.Defaults.PacketSize, + clientOptions?.PreallocatedTransmissionArgs ?? StreamSocketClientOptions.Defaults.PreallocatedTransmissionArgs) { this.clientOptions = clientOptions ?? StreamSocketClientOptions.Defaults; } - /// <inheritdoc /> - protected override SocketAsyncEventArgs CreateTransmissionArgs() + public ref readonly StreamSocketClientOptions ClientOptions { - SocketAsyncEventArgs connectionArgs = new SocketAsyncEventArgs(); - - connectionArgs.Completed += HandleIoCompleted; - - return connectionArgs; + get { return ref clientOptions; } } /// <inheritdoc /> - protected override void ResetTransmissionArgs(SocketAsyncEventArgs args) + protected override bool CanTransmissionArgsBeReused(in SocketAsyncEventArgs args) { + return true; } /// <inheritdoc /> - protected override bool CanTransmissionArgsBeReused(in SocketAsyncEventArgs args) + protected override SocketAsyncEventArgs CreateTransmissionArgs() { - return true; + SocketAsyncEventArgs connectionArgs = new SocketAsyncEventArgs(); + + connectionArgs.Completed += HandleIoCompleted; + + return connectionArgs; } /// <inheritdoc /> @@ -225,9 +225,9 @@ namespace NetSharp.Sockets.Stream } } - public ref readonly StreamSocketClientOptions ClientOptions + /// <inheritdoc /> + protected override void ResetTransmissionArgs(SocketAsyncEventArgs args) { - get { return ref clientOptions; } } public void Disconnect(bool allowSocketReuse) diff --git a/NetSharp/NetSharp/Sockets/Stream/StreamSocketServer.cs b/NetSharp/NetSharp/Sockets/Stream/StreamSocketServer.cs @@ -13,10 +13,8 @@ namespace NetSharp.Sockets.Stream public static readonly StreamSocketServerOptions Defaults = new StreamSocketServerOptions(NetworkPacket.TotalSize, Environment.ProcessorCount, 0); - public readonly int PacketSize; - public readonly int ConcurrentAcceptCalls; - + public readonly int PacketSize; public readonly ushort PreallocatedTransmissionArgs; public StreamSocketServerOptions(int packetSize, int concurrentAcceptCalls, ushort preallocatedTransmissionArgs) @@ -30,33 +28,13 @@ namespace NetSharp.Sockets.Stream } //TODO address the need to handle series of network packets, not just single packets - //TODO allow for the server to do more than just echo packets //TODO document class public sealed class StreamSocketServer : SocketServer { - private class RemoteStreamClientToken : IDisposable - { - public readonly Socket ClientSocket; - - public byte[]? RentedBuffer; - - public RemoteStreamClientToken(in Socket clientSocket) - { - ClientSocket = clientSocket; - } - - public void Dispose() - { - ClientSocket.Shutdown(SocketShutdown.Both); - ClientSocket.Close(); - ClientSocket.Dispose(); - } - } - private readonly StreamSocketServerOptions serverOptions; /// <summary> - /// Constructs a new instance of the <see cref="StreamSocketServer"/> class. + /// Constructs a new instance of the <see cref="StreamSocketServer" /> class. /// </summary> /// <param name="serverOptions">Additional options to configure the server.</param> /// <inheritdoc /> @@ -69,59 +47,9 @@ namespace NetSharp.Sockets.Stream this.serverOptions = serverOptions ?? StreamSocketServerOptions.Defaults; } - /// <inheritdoc /> - protected override SocketAsyncEventArgs CreateTransmissionArgs() - { - SocketAsyncEventArgs connectionArgs = new SocketAsyncEventArgs(); - - connectionArgs.Completed += HandleIoCompleted; - - return connectionArgs; - } - - /// <inheritdoc /> - protected override void ResetTransmissionArgs(SocketAsyncEventArgs args) - { - } - - /// <inheritdoc /> - protected override bool CanTransmissionArgsBeReused(in SocketAsyncEventArgs args) - { - return true; - } - - /// <inheritdoc /> - protected override void DestroyTransmissionArgs(SocketAsyncEventArgs remoteConnectionArgs) - { - remoteConnectionArgs.Completed -= HandleIoCompleted; - - remoteConnectionArgs.Dispose(); - } - - /// <inheritdoc /> - protected override void HandleIoCompleted(object sender, SocketAsyncEventArgs args) + public ref readonly StreamSocketServerOptions ServerOptions { - switch (args.LastOperation) - { - case SocketAsyncOperation.Accept: - SocketAsyncEventArgs newAcceptArgs = TransmissionArgsPool.Rent(); - - 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: - throw new NotSupportedException($"{nameof(HandleIoCompleted)} doesn't support {args.LastOperation}"); - } + get { return ref serverOptions; } } private void Accept(SocketAsyncEventArgs acceptArgs) @@ -137,6 +65,14 @@ namespace NetSharp.Sockets.Stream CompleteAccept(acceptArgs); } + private void CloseClientSocket(SocketAsyncEventArgs clientArgs) + { + RemoteStreamClientToken clientToken = (RemoteStreamClientToken)clientArgs.UserToken; + clientToken.Dispose(); + + TransmissionArgsPool.Return(clientArgs); + } + private void CompleteAccept(SocketAsyncEventArgs connectedClientArgs) { Socket clientSocket = connectedClientArgs.AcceptSocket; @@ -148,24 +84,6 @@ namespace NetSharp.Sockets.Stream Receive(connectedClientArgs); } - private void Receive(SocketAsyncEventArgs clientArgs) - { - RemoteStreamClientToken clientToken = (RemoteStreamClientToken)clientArgs.UserToken; - - byte[] requestBuffer = BufferPool.Rent(serverOptions.PacketSize); - Memory<byte> requestBufferMemory = new Memory<byte>(requestBuffer); - - clientToken.RentedBuffer = requestBuffer; - clientArgs.SetBuffer(clientToken.RentedBuffer, 0, serverOptions.PacketSize); - - bool operationPending = clientToken.ClientSocket.ReceiveAsync(clientArgs); - - if (!operationPending) - { - CompleteReceive(clientArgs); - } - } - private void CompleteReceive(SocketAsyncEventArgs clientArgs) { RemoteStreamClientToken receiveToken = (RemoteStreamClientToken)clientArgs.UserToken; @@ -224,18 +142,6 @@ namespace NetSharp.Sockets.Stream } } - private void Send(SocketAsyncEventArgs clientArgs) - { - RemoteStreamClientToken clientToken = (RemoteStreamClientToken)clientArgs.UserToken; - - bool operationPending = clientToken.ClientSocket.SendAsync(clientArgs); - - if (!operationPending) - { - CompleteSend(clientArgs); - } - } - private void CompleteSend(SocketAsyncEventArgs clientArgs) { RemoteStreamClientToken sendToken = (RemoteStreamClientToken)clientArgs.UserToken; @@ -275,17 +181,89 @@ namespace NetSharp.Sockets.Stream } } - private void CloseClientSocket(SocketAsyncEventArgs clientArgs) + private void Receive(SocketAsyncEventArgs clientArgs) { RemoteStreamClientToken clientToken = (RemoteStreamClientToken)clientArgs.UserToken; - clientToken.Dispose(); - TransmissionArgsPool.Return(clientArgs); + byte[] requestBuffer = BufferPool.Rent(serverOptions.PacketSize); + Memory<byte> requestBufferMemory = new Memory<byte>(requestBuffer); + + clientToken.RentedBuffer = requestBuffer; + clientArgs.SetBuffer(clientToken.RentedBuffer, 0, serverOptions.PacketSize); + + bool operationPending = clientToken.ClientSocket.ReceiveAsync(clientArgs); + + if (!operationPending) + { + CompleteReceive(clientArgs); + } } - public ref readonly StreamSocketServerOptions ServerOptions + private void Send(SocketAsyncEventArgs clientArgs) + { + RemoteStreamClientToken clientToken = (RemoteStreamClientToken)clientArgs.UserToken; + + bool operationPending = clientToken.ClientSocket.SendAsync(clientArgs); + + if (!operationPending) + { + CompleteSend(clientArgs); + } + } + + /// <inheritdoc /> + protected override bool CanTransmissionArgsBeReused(in SocketAsyncEventArgs args) + { + return true; + } + + /// <inheritdoc /> + protected override SocketAsyncEventArgs CreateTransmissionArgs() + { + SocketAsyncEventArgs connectionArgs = new SocketAsyncEventArgs(); + + connectionArgs.Completed += HandleIoCompleted; + + return connectionArgs; + } + + /// <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.Accept: + SocketAsyncEventArgs newAcceptArgs = TransmissionArgsPool.Rent(); + + 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: + throw new NotSupportedException($"{nameof(HandleIoCompleted)} doesn't support {args.LastOperation}"); + } + } + + /// <inheritdoc /> + protected override void ResetTransmissionArgs(SocketAsyncEventArgs args) { - get { return ref serverOptions; } } /// <inheritdoc /> @@ -304,5 +282,24 @@ namespace NetSharp.Sockets.Stream return Task.CompletedTask; } + + private class RemoteStreamClientToken : IDisposable + { + public readonly Socket ClientSocket; + + public byte[]? RentedBuffer; + + public RemoteStreamClientToken(in Socket clientSocket) + { + ClientSocket = clientSocket; + } + + public void Dispose() + { + ClientSocket.Shutdown(SocketShutdown.Both); + ClientSocket.Close(); + ClientSocket.Dispose(); + } + } } } \ No newline at end of file diff --git a/NetSharp/NetSharp/Utils/BiDictionary.cs b/NetSharp/NetSharp/Utils/BiDictionary.cs @@ -20,7 +20,7 @@ namespace NetSharp.Utils private readonly ConcurrentDictionary<V, K> valueToKeyMap; /// <summary> - /// Initialises a new instance of the <see cref="BiDictionary{K,V}"/> class. + /// Initialises a new instance of the <see cref="BiDictionary{K,V}" /> class. /// </summary> public BiDictionary() { @@ -64,7 +64,7 @@ namespace NetSharp.Utils } /// <summary> - /// Clears this instance's <see cref="keyToValueMap"/> and <see cref="valueToKeyMap"/>. + /// Clears this instance's <see cref="keyToValueMap" /> and <see cref="valueToKeyMap" />. /// </summary> public void Clear() { @@ -103,7 +103,7 @@ namespace NetSharp.Utils /// 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> + /// <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) { diff --git a/NetSharp/NetSharp/Utils/Conversion/EndianAwareBitConverter.cs b/NetSharp/NetSharp/Utils/Conversion/EndianAwareBitConverter.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; namespace NetSharp.Utils.Conversion { /// <summary> - /// Wraps the <see cref="BitConverter"/> class to provide conversion that is endian-aware. + /// Wraps the <see cref="BitConverter" /> class to provide conversion that is endian-aware. /// </summary> public static class EndianAwareBitConverter { @@ -22,140 +22,140 @@ namespace NetSharp.Utils.Conversion return bytes; } - /// <inheritdoc cref="BitConverter.GetBytes(bool)"/> + /// <inheritdoc cref="BitConverter.GetBytes(bool)" /> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span<byte> GetBytes(bool value, bool littleEndian = false) { return ReverseAsNeeded(BitConverter.GetBytes(value), littleEndian); } - /// <inheritdoc cref="BitConverter.GetBytes(char)"/> + /// <inheritdoc cref="BitConverter.GetBytes(char)" /> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span<byte> GetBytes(char value, bool littleEndian = false) { return ReverseAsNeeded(BitConverter.GetBytes(value), littleEndian); } - /// <inheritdoc cref="BitConverter.GetBytes(double)"/> + /// <inheritdoc cref="BitConverter.GetBytes(double)" /> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span<byte> GetBytes(double value, bool littleEndian = false) { return ReverseAsNeeded(BitConverter.GetBytes(value), littleEndian); } - /// <inheritdoc cref="BitConverter.GetBytes(float)"/> + /// <inheritdoc cref="BitConverter.GetBytes(float)" /> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span<byte> GetBytes(float value, bool littleEndian = false) { return ReverseAsNeeded(BitConverter.GetBytes(value), littleEndian); } - /// <inheritdoc cref="BitConverter.GetBytes(int)"/> + /// <inheritdoc cref="BitConverter.GetBytes(int)" /> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span<byte> GetBytes(int value, bool littleEndian = false) { return ReverseAsNeeded(BitConverter.GetBytes(value), littleEndian); } - /// <inheritdoc cref="BitConverter.GetBytes(long)"/> + /// <inheritdoc cref="BitConverter.GetBytes(long)" /> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span<byte> GetBytes(long value, bool littleEndian = false) { return ReverseAsNeeded(BitConverter.GetBytes(value), littleEndian); } - /// <inheritdoc cref="BitConverter.GetBytes(short)"/> + /// <inheritdoc cref="BitConverter.GetBytes(short)" /> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span<byte> GetBytes(short value, bool littleEndian = false) { return ReverseAsNeeded(BitConverter.GetBytes(value), littleEndian); } - /// <inheritdoc cref="BitConverter.GetBytes(uint)"/> + /// <inheritdoc cref="BitConverter.GetBytes(uint)" /> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span<byte> GetBytes(uint value, bool littleEndian = false) { return ReverseAsNeeded(BitConverter.GetBytes(value), littleEndian); } - /// <inheritdoc cref="BitConverter.GetBytes(ulong)"/> + /// <inheritdoc cref="BitConverter.GetBytes(ulong)" /> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span<byte> GetBytes(ulong value, bool littleEndian = false) { return ReverseAsNeeded(BitConverter.GetBytes(value), littleEndian); } - /// <inheritdoc cref="BitConverter.GetBytes(ushort)"/> + /// <inheritdoc cref="BitConverter.GetBytes(ushort)" /> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span<byte> GetBytes(ushort value, bool littleEndian = false) { return ReverseAsNeeded(BitConverter.GetBytes(value), littleEndian); } - /// <inheritdoc cref="BitConverter.ToBoolean(ReadOnlySpan{byte})"/> + /// <inheritdoc cref="BitConverter.ToBoolean(ReadOnlySpan{byte})" /> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool ToBoolean(Span<byte> bytes, bool littleEndian = false) { return BitConverter.ToBoolean(ReverseAsNeeded(bytes, littleEndian)); } - /// <inheritdoc cref="BitConverter.ToChar(ReadOnlySpan{byte})"/> + /// <inheritdoc cref="BitConverter.ToChar(ReadOnlySpan{byte})" /> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static char ToChar(Span<byte> bytes, bool littleEndian = false) { return BitConverter.ToChar(ReverseAsNeeded(bytes, littleEndian)); } - /// <inheritdoc cref="BitConverter.ToDouble(ReadOnlySpan{byte})"/> + /// <inheritdoc cref="BitConverter.ToDouble(ReadOnlySpan{byte})" /> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double ToDouble(Span<byte> bytes, bool littleEndian = false) { return BitConverter.ToDouble(ReverseAsNeeded(bytes, littleEndian)); } - /// <inheritdoc cref="BitConverter.ToInt16(ReadOnlySpan{byte})"/> + /// <inheritdoc cref="BitConverter.ToInt16(ReadOnlySpan{byte})" /> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static short ToInt16(Span<byte> bytes, bool littleEndian = false) { return BitConverter.ToInt16(ReverseAsNeeded(bytes, littleEndian)); } - /// <inheritdoc cref="BitConverter.ToInt32(ReadOnlySpan{byte})"/> + /// <inheritdoc cref="BitConverter.ToInt32(ReadOnlySpan{byte})" /> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int ToInt32(Span<byte> bytes, bool littleEndian = false) { return BitConverter.ToInt32(ReverseAsNeeded(bytes, littleEndian)); } - /// <inheritdoc cref="BitConverter.ToInt64(ReadOnlySpan{byte})"/> + /// <inheritdoc cref="BitConverter.ToInt64(ReadOnlySpan{byte})" /> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long ToInt64(Span<byte> bytes, bool littleEndian = false) { return BitConverter.ToInt64(ReverseAsNeeded(bytes, littleEndian)); } - /// <inheritdoc cref="BitConverter.ToSingle(ReadOnlySpan{byte})"/> + /// <inheritdoc cref="BitConverter.ToSingle(ReadOnlySpan{byte})" /> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float ToSingle(Span<byte> bytes, bool littleEndian = false) { return BitConverter.ToSingle(ReverseAsNeeded(bytes, littleEndian)); } - /// <inheritdoc cref="BitConverter.ToUInt16(ReadOnlySpan{byte})"/> + /// <inheritdoc cref="BitConverter.ToUInt16(ReadOnlySpan{byte})" /> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ushort ToUInt16(byte[] bytes, bool littleEndian = false) { return BitConverter.ToUInt16(ReverseAsNeeded(bytes, littleEndian)); } - /// <inheritdoc cref="BitConverter.ToUInt32(ReadOnlySpan{byte})"/> + /// <inheritdoc cref="BitConverter.ToUInt32(ReadOnlySpan{byte})" /> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint ToUInt32(Span<byte> bytes, bool littleEndian = false) { return BitConverter.ToUInt32(ReverseAsNeeded(bytes, littleEndian)); } - /// <inheritdoc cref="BitConverter.ToUInt64(ReadOnlySpan{byte})"/> + /// <inheritdoc cref="BitConverter.ToUInt64(ReadOnlySpan{byte})" /> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong ToUInt64(Span<byte> bytes, bool littleEndian = false) { diff --git a/NetSharp/NetSharp/Utils/SlimObjectPool.cs b/NetSharp/NetSharp/Utils/SlimObjectPool.cs @@ -8,42 +8,18 @@ namespace NetSharp.Utils /// <typeparam name="T">The type of item stored in the pool.</typeparam> public class SlimObjectPool<T> where T : class { - /// <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> - public delegate T CreateObjectDelegate(); - - /// <summary> - /// 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> - public delegate bool CanRebufferObjectPredicate(in 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> - public delegate void ResetObjectDelegate(T instance); - - /// <summary> - /// Delegate method to destroy a used <paramref name="instance"/> which cannot be reused. - /// </summary> - /// <param name="instance">The instance to destroy.</param> - public delegate void DestroyObjectDelegate(T instance); + private readonly CanRebufferObjectPredicate canObjectBeRebufferedPredicate; private readonly CreateObjectDelegate createObjectDelegate; - private readonly CanRebufferObjectPredicate canObjectBeRebufferedPredicate; - private readonly ResetObjectDelegate resetObjectDelegate; + private readonly DestroyObjectDelegate destroyObjectDelegate; private readonly IProducerConsumerCollection<T> objectBuffer; + private readonly ResetObjectDelegate resetObjectDelegate; + /// <summary> - /// Constructs a new instance of the <see cref="SlimObjectPool{T}"/> class. + /// 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> @@ -66,7 +42,7 @@ namespace NetSharp.Utils } /// <summary> - /// Constructs a new instance of the <see cref="SlimObjectPool{T}"/> class. + /// 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> @@ -79,16 +55,42 @@ namespace NetSharp.Utils } /// <summary> - /// Leases a new <typeparamref name="T"/> instance from the pool, and returns it. + /// 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> + 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> + 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> + 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> + 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(); } /// <summary> - /// Returns a previously leased <typeparamref name="T"/> instance to the pool. + /// Returns a previously leased <typeparamref name="T" /> instance to the pool. /// </summary> /// <param name="instance">The previously leased instance which should be returned.</param> public void Return(T instance) diff --git a/NetSharp/NetSharp/Utils/TransmissionResult.cs b/NetSharp/NetSharp/Utils/TransmissionResult.cs @@ -10,7 +10,22 @@ namespace NetSharp.Utils public readonly struct TransmissionResult { /// <summary> - /// Initialises a new instance of the <see cref="TransmissionResult"/> struct. + /// The byte buffer that was transmitted across the network. + /// </summary> + public readonly Memory<byte> Buffer; + + /// <summary> + /// The number of bytes that were transmitted across the network. + /// </summary> + public readonly int Count; + + /// <summary> + /// The remote endpoint to which the buffer was transmitted. + /// </summary> + public readonly EndPoint RemoteEndPoint; + + /// <summary> + /// Initialises a new instance of the <see cref="TransmissionResult" /> struct. /// </summary> /// <param name="args">The socket arguments associated with the transmission.</param> internal TransmissionResult(in SocketAsyncEventArgs args) @@ -21,7 +36,7 @@ namespace NetSharp.Utils } /// <summary> - /// Initialises a new instance of the <see cref="TransmissionResult"/> struct. + /// 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> @@ -32,20 +47,5 @@ namespace NetSharp.Utils Count = count; RemoteEndPoint = remoteEndPoint; } - - /// <summary> - /// The byte buffer that was transmitted across the network. - /// </summary> - public readonly Memory<byte> Buffer; - - /// <summary> - /// The number of bytes that were transmitted across the network. - /// </summary> - public readonly int Count; - - /// <summary> - /// The remote endpoint to which the buffer was transmitted. - /// </summary> - public readonly EndPoint RemoteEndPoint; } } \ No newline at end of file diff --git a/NetSharp/NetSharpExamples/BenchmarkHelper.cs b/NetSharp/NetSharpExamples/BenchmarkHelper.cs @@ -5,54 +5,41 @@ namespace NetSharpExamples { public class BenchmarkHelper { - private readonly Stopwatch rttStopwatch = new Stopwatch(); private readonly Stopwatch bandwidthStopwatch = new Stopwatch(); - - private long minRttTicks = int.MaxValue, maxRttTicks = int.MinValue; + private readonly Stopwatch rttStopwatch = new Stopwatch(); private long minRttMs = int.MaxValue, maxRttMs = int.MinValue; + private long minRttTicks = int.MaxValue, maxRttTicks = int.MinValue; - public void StartRttStopwatch() - { - rttStopwatch.Start(); - } - - public void StopRttStopwatch() + public long RttMs { - rttStopwatch.Stop(); + get { return rttStopwatch.ElapsedMilliseconds; } } - public void ResetRttStopwatch() + public long RttTicks { - rttStopwatch.Reset(); + get { return rttStopwatch.ElapsedTicks; } } - public void UpdateRttStats(int clientId) + public double CalcBandwidth(long sentPacketCount, long packetSize) { - minRttTicks = rttStopwatch.ElapsedTicks < minRttTicks - ? rttStopwatch.ElapsedTicks - : minRttTicks; - - minRttMs = rttStopwatch.ElapsedMilliseconds < minRttMs - ? rttStopwatch.ElapsedMilliseconds - : minRttMs; - - maxRttTicks = rttStopwatch.ElapsedTicks > maxRttTicks - ? rttStopwatch.ElapsedTicks - : maxRttTicks; + long millis = bandwidthStopwatch.ElapsedMilliseconds; + double megabytes = sentPacketCount * packetSize / 1_000_000.0; + double bandwidth = megabytes / (millis / 1000.0); - maxRttMs = rttStopwatch.ElapsedMilliseconds > maxRttMs - ? rttStopwatch.ElapsedMilliseconds - : maxRttMs; + return bandwidth; } - public long RttTicks + public void PrintBandwidthStats(int clientId, long sentPacketCount, long packetSize) { - get { return rttStopwatch.ElapsedTicks; } - } + long millis = bandwidthStopwatch.ElapsedMilliseconds; + double megabytes = sentPacketCount * packetSize / 1_000_000.0; + double bandwidth = megabytes / (millis / 1000.0); - public long RttMs - { - get { return rttStopwatch.ElapsedMilliseconds; } + lock (typeof(Console)) + { + Console.WriteLine($"[Client {clientId}] Sent {sentPacketCount} packets (of size {packetSize}) in {millis} milliseconds"); + Console.WriteLine($"[Client {clientId}] Approximate bandwidth: {bandwidth:F3} MBps"); + } } public void PrintRttStats(int clientId) @@ -64,41 +51,53 @@ namespace NetSharpExamples } } + public void ResetBandwidthStopwatch() + { + bandwidthStopwatch.Reset(); + } + + public void ResetRttStopwatch() + { + rttStopwatch.Reset(); + } + public void StartBandwidthStopwatch() { bandwidthStopwatch.Start(); } + public void StartRttStopwatch() + { + rttStopwatch.Start(); + } + public void StopBandwidthStopwatch() { bandwidthStopwatch.Stop(); } - public void ResetBandwidthStopwatch() + public void StopRttStopwatch() { - bandwidthStopwatch.Reset(); + rttStopwatch.Stop(); } - public void PrintBandwidthStats(int clientId, long sentPacketCount, long packetSize) + public void UpdateRttStats(int clientId) { - long millis = bandwidthStopwatch.ElapsedMilliseconds; - double megabytes = sentPacketCount * packetSize / 1_000_000.0; - double bandwidth = megabytes / (millis / 1000.0); + minRttTicks = rttStopwatch.ElapsedTicks < minRttTicks + ? rttStopwatch.ElapsedTicks + : minRttTicks; - lock (typeof(Console)) - { - Console.WriteLine($"[Client {clientId}] Sent {sentPacketCount} packets (of size {packetSize}) in {millis} milliseconds"); - Console.WriteLine($"[Client {clientId}] Approximate bandwidth: {bandwidth:F3} MBps"); - } - } + minRttMs = rttStopwatch.ElapsedMilliseconds < minRttMs + ? rttStopwatch.ElapsedMilliseconds + : minRttMs; - public double CalcBandwidth(long sentPacketCount, long packetSize) - { - long millis = bandwidthStopwatch.ElapsedMilliseconds; - double megabytes = sentPacketCount * packetSize / 1_000_000.0; - double bandwidth = megabytes / (millis / 1000.0); + maxRttTicks = rttStopwatch.ElapsedTicks > maxRttTicks + ? rttStopwatch.ElapsedTicks + : maxRttTicks; - return bandwidth; + maxRttMs = rttStopwatch.ElapsedMilliseconds > maxRttMs + ? rttStopwatch.ElapsedMilliseconds + : maxRttMs; } } } \ No newline at end of file diff --git a/NetSharp/NetSharpExamples/Benchmarks/TcpSocketServerBenchmark.cs b/NetSharp/NetSharpExamples/Benchmarks/TcpSocketServerBenchmark.cs @@ -0,0 +1,158 @@ +using NetSharp.Packets; +using NetSharp.Sockets; +using NetSharp.Sockets.Stream; + +using System; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace NetSharpExamples.Benchmarks +{ + public class TcpSocketServerBenchmark : INetSharpExample + { + private const int PacketCount = 1_000_000; + + private static readonly EndPoint ServerEndPoint = new IPEndPoint(IPAddress.Loopback, 12348); + + 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; + + BenchmarkHelper benchmarkHelper = new BenchmarkHelper(); + + Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + clientSocket.Bind(new IPEndPoint(IPAddress.Any, 0)); + clientSocket.Connect(ServerEndPoint); + + byte[] sendBuffer = new byte[NetworkPacket.TotalSize]; + byte[] receiveBuffer = new byte[NetworkPacket.TotalSize]; + + EndPoint remoteEndPoint = ServerEndPoint; + + lock (typeof(Console)) + { + Console.WriteLine($"[Client {id}] Starting client; sending messages to {remoteEndPoint}"); + } + + for (int i = 0; i < PacketCount; i++) + { + byte[] packetBuffer = Encoding.UTF8.GetBytes($"[Client {id}] Hello World! (Packet {i})"); + packetBuffer.CopyTo(sendBuffer, 0); + + benchmarkHelper.StartBandwidthStopwatch(); + benchmarkHelper.StartRttStopwatch(); + + int totalSent = 0; + do + { + totalSent += clientSocket.Send(sendBuffer, totalSent, sendBuffer.Length - totalSent, + SocketFlags.None); + } while (totalSent != 0 && totalSent != sendBuffer.Length); + + if (totalSent == 0) + { + 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 + { + totalReceived += clientSocket.Receive(receiveBuffer, totalReceived, + receiveBuffer.Length - totalReceived, SocketFlags.None); + } while (totalReceived != 0 && totalReceived != sendBuffer.Length); + + if (totalReceived == 0) + { + break; + } + + 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(); + } + + clientSocket.Disconnect(true); + clientSocket.Close(); + + benchmarkHelper.PrintBandwidthStats(id, PacketCount, NetworkPacket.TotalSize); + benchmarkHelper.PrintRttStats(id); + + ClientBandwidths[id] = benchmarkHelper.CalcBandwidth(PacketCount, NetworkPacket.TotalSize); + + return Task.CompletedTask; + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharpExamples/Benchmarks/UdpSocketServerBenchmark.cs b/NetSharp/NetSharpExamples/Benchmarks/UdpSocketServerBenchmark.cs @@ -0,0 +1,132 @@ +using NetSharp.Packets; +using NetSharp.Sockets; +using NetSharp.Sockets.Datagram; + +using System; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace NetSharpExamples.Benchmarks +{ + public class UdpSocketServerBenchmark : INetSharpExample + { + private const int PacketCount = 1_000_000; + + private static readonly EndPoint ServerEndPoint = new IPEndPoint(IPAddress.Loopback, 12347); + + 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; + + BenchmarkHelper benchmarkHelper = new BenchmarkHelper(); + + Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + + clientSocket.Bind(new IPEndPoint(IPAddress.Any, 0)); + + byte[] sendBuffer = new byte[NetworkPacket.TotalSize]; + byte[] receiveBuffer = new byte[NetworkPacket.TotalSize]; + + EndPoint remoteEndPoint = ServerEndPoint; + + lock (typeof(Console)) + { + Console.WriteLine($"[Client {id}] Starting client; sending messages to {remoteEndPoint}"); + } + + for (int i = 0; i < PacketCount; i++) + { + byte[] packetBuffer = Encoding.UTF8.GetBytes($"[Client {id}] Hello World! (Packet {i})"); + packetBuffer.CopyTo(sendBuffer, 0); + + benchmarkHelper.StartBandwidthStopwatch(); + 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(); + } + + benchmarkHelper.PrintBandwidthStats(id, PacketCount, NetworkPacket.TotalSize); + benchmarkHelper.PrintRttStats(id); + + ClientBandwidths[id] = benchmarkHelper.CalcBandwidth(PacketCount, NetworkPacket.TotalSize); + + return Task.CompletedTask; + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharpExamples/Examples/TcpSocketClientExample.cs b/NetSharp/NetSharpExamples/Examples/TcpSocketClientExample.cs @@ -0,0 +1,68 @@ +using NetSharp.Packets; +using NetSharp.Sockets.Stream; +using NetSharp.Utils; + +using System; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace NetSharpExamples.Examples +{ + public class TcpSocketClientExample : INetSharpExample + { + /// <inheritdoc /> + public async Task RunAsync() + { + StreamSocketClientOptions clientOptions = new StreamSocketClientOptions(NetworkPacket.TotalSize, 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]; + + EndPoint remoteEndPoint = TcpSocketServerExample.ServerEndPoint; + + client.Connect(in remoteEndPoint); + + /* a cancellable asynchronous version also exists. + client.ConnectAsync(in remoteEndPoint, CancellationToken.None); + */ + while (true) + { + string data = $"Hello World from {client.LocalEndPoint}!"; + dataEncoding.GetBytes(data).CopyTo(sendBuffer, 0); + + TransmissionResult sendResult = + client.Send(sendBuffer, SocketFlags.None); + + /* a cancellable asynchronous version also exists. use only when necessary due to the inherent performance penalty of async operations + TransmissionResult sendResult = + await client.SendAsync(sendBuffer, SocketFlags.None, CancellationToken.None); + */ + + // lock is not necessary, but means that console output is clean and not interleaved + lock (typeof(Console)) + { + Console.WriteLine($"[Client] Sent request with contents \'{data}\' to {remoteEndPoint}"); + } + + TransmissionResult receiveResult = + client.Receive(receiveBuffer, SocketFlags.None); + + /* a cancellable asynchronous version also exists. use only when necessary due to the inherent performance penalty of async operations + TransmissionResult receiveResult = + await client.ReceiveAsync(receiveBuffer, SocketFlags.None, CancellationToken.None); + */ + + // lock is not necessary, but means that console output is clean and not interleaved + lock (typeof(Console)) + { + Console.WriteLine($"[Client] Received response with contents \'{dataEncoding.GetString(receiveBuffer)}\' from {remoteEndPoint}"); + } + } + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharpExamples/Examples/TcpSocketServerBenchmark.cs b/NetSharp/NetSharpExamples/Examples/TcpSocketServerBenchmark.cs @@ -1,157 +0,0 @@ -using NetSharp.Packets; -using NetSharp.Sockets; -using NetSharp.Sockets.Stream; - -using System; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace NetSharpExamples.Examples -{ - public class TcpSocketServerBenchmark : INetSharpExample - { - private const int PacketCount = 1_000_000; - - private static readonly EndPoint ServerEndPoint = new IPEndPoint(IPAddress.Loopback, 12348); - - private double[] ClientBandwidths; - - 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; - - BenchmarkHelper benchmarkHelper = new BenchmarkHelper(); - - Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - - clientSocket.Bind(new IPEndPoint(IPAddress.Any, 0)); - clientSocket.Connect(ServerEndPoint); - - byte[] sendBuffer = new byte[NetworkPacket.TotalSize]; - byte[] receiveBuffer = new byte[NetworkPacket.TotalSize]; - - EndPoint remoteEndPoint = ServerEndPoint; - - lock (typeof(Console)) - { - Console.WriteLine($"[Client {id}] Starting client; sending messages to {remoteEndPoint}"); - } - - for (int i = 0; i < PacketCount; i++) - { - byte[] packetBuffer = Encoding.UTF8.GetBytes($"[Client {id}] Hello World! (Packet {i})"); - packetBuffer.CopyTo(sendBuffer, 0); - - benchmarkHelper.StartBandwidthStopwatch(); - benchmarkHelper.StartRttStopwatch(); - - int totalSent = 0; - do - { - totalSent += clientSocket.Send(sendBuffer, totalSent, sendBuffer.Length - totalSent, - SocketFlags.None); - } while (totalSent != 0 && totalSent != sendBuffer.Length); - - if (totalSent == 0) - { - 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 - { - totalReceived += clientSocket.Receive(receiveBuffer, totalReceived, - receiveBuffer.Length - totalReceived, SocketFlags.None); - } while (totalReceived != 0 && totalReceived != sendBuffer.Length); - - if (totalReceived == 0) - { - break; - } - - 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(); - } - - clientSocket.Disconnect(true); - clientSocket.Close(); - - benchmarkHelper.PrintBandwidthStats(id, PacketCount, NetworkPacket.TotalSize); - benchmarkHelper.PrintRttStats(id); - - ClientBandwidths[id] = benchmarkHelper.CalcBandwidth(PacketCount, NetworkPacket.TotalSize); - - return Task.CompletedTask; - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharpExamples/Examples/TcpSocketServerExample.cs b/NetSharp/NetSharpExamples/Examples/TcpSocketServerExample.cs @@ -12,29 +12,35 @@ namespace NetSharpExamples.Examples { public class TcpSocketServerExample : INetSharpExample { - public Task RunAsync() - { - StreamSocketServerOptions serverOptions = - new StreamSocketServerOptions(NetworkPacket.TotalSize, Environment.ProcessorCount, 2); - - using StreamSocketServer server = - new StreamSocketServer(AddressFamily.InterNetwork, ProtocolType.Tcp, ServerPacketHandler, serverOptions); - - return server.RunAsync(CancellationToken.None); // we run forever. alternatively, pass in a cancellation token to ensure that the server terminates - } + public static readonly Encoding ServerEncoding = Encoding.UTF8; + public static readonly EndPoint ServerEndPoint = new IPEndPoint(IPAddress.Loopback, 12348); public static NetworkPacket ServerPacketHandler(in NetworkPacket request, in EndPoint remoteEndPoint) { // lock is not necessary, but means that console output is clean and not interleaved lock (typeof(Console)) { - Console.WriteLine($"[Server] Received request with contents \'{Encoding.UTF8.GetString(request.Data.Span)}\' from {remoteEndPoint}"); + Console.WriteLine($"[Server] Received request with contents \'{ServerEncoding.GetString(request.Data.Span)}\' from {remoteEndPoint}"); Console.WriteLine($"[Server] Echoing back request to {remoteEndPoint}"); } - // we echo back the request, but we could just as easily send back a new packet. - // if we would not want to send back any response, we need to return NetworkPacket.NullPacket + // we echo back the request, but we could just as easily send back a new packet. if we would not want to send back any response, we need + // to return NetworkPacket.NullPacket return request; } + + /// <inheritdoc /> + public Task RunAsync() + { + StreamSocketServerOptions serverOptions = + new StreamSocketServerOptions(NetworkPacket.TotalSize, Environment.ProcessorCount, 2); + + using StreamSocketServer server = + new StreamSocketServer(AddressFamily.InterNetwork, ProtocolType.Tcp, ServerPacketHandler, serverOptions); + + server.Bind(in ServerEndPoint); + + return server.RunAsync(CancellationToken.None); // we run forever. alternatively, pass in a cancellation token to ensure that the server terminates + } } } \ No newline at end of file diff --git a/NetSharp/NetSharpExamples/Examples/UdpSocketClientExample.cs b/NetSharp/NetSharpExamples/Examples/UdpSocketClientExample.cs @@ -0,0 +1,63 @@ +using NetSharp.Packets; +using NetSharp.Sockets.Datagram; +using NetSharp.Utils; + +using System; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace NetSharpExamples.Examples +{ + public class UdpSocketClientExample : INetSharpExample + { + /// <inheritdoc /> + public async Task RunAsync() + { + DatagramSocketClientOptions clientOptions = new DatagramSocketClientOptions(NetworkPacket.TotalSize, 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]; + + EndPoint remoteEndPoint = UdpSocketServerExample.ServerEndPoint; + + while (true) + { + string data = $"Hello World from {client.LocalEndPoint}!"; + dataEncoding.GetBytes(data).CopyTo(sendBuffer, 0); + + TransmissionResult sendResult = + client.SendTo(remoteEndPoint, sendBuffer, SocketFlags.None); + + /* a cancellable asynchronous version also exists. use only when necessary due to the inherent performance penalty of async operations + TransmissionResult sendResult = + await client.SendToAsync(remoteEndPoint, sendBuffer, SocketFlags.None, CancellationToken.None); + */ + + // lock is not necessary, but means that console output is clean and not interleaved + lock (typeof(Console)) + { + Console.WriteLine($"[Client] Sent request with contents \'{data}\' to {remoteEndPoint}"); + } + + TransmissionResult receiveResult = + client.ReceiveFrom(ref remoteEndPoint, receiveBuffer, SocketFlags.None); + + /* a cancellable asynchronous version also exists. use only when necessary due to the inherent performance penalty of async operations + TransmissionResult receiveResult = + await client.ReceiveFromAsync(remoteEndPoint, receiveBuffer, SocketFlags.None, CancellationToken.None); + */ + + // lock is not necessary, but means that console output is clean and not interleaved + lock (typeof(Console)) + { + Console.WriteLine($"[Client] Received response with contents \'{dataEncoding.GetString(receiveBuffer)}\' from {remoteEndPoint}"); + } + } + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharpExamples/Examples/UdpSocketServerBenchmark.cs b/NetSharp/NetSharpExamples/Examples/UdpSocketServerBenchmark.cs @@ -1,131 +0,0 @@ -using NetSharp.Packets; -using NetSharp.Sockets; -using NetSharp.Sockets.Datagram; - -using System; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace NetSharpExamples.Examples -{ - public class UdpSocketServerBenchmark : INetSharpExample - { - private const int PacketCount = 1_000_000; - - private static readonly EndPoint ServerEndPoint = new IPEndPoint(IPAddress.Loopback, 12347); - - private double[] ClientBandwidths; - - 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; - - BenchmarkHelper benchmarkHelper = new BenchmarkHelper(); - - Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - - clientSocket.Bind(new IPEndPoint(IPAddress.Any, 0)); - - byte[] sendBuffer = new byte[NetworkPacket.TotalSize]; - byte[] receiveBuffer = new byte[NetworkPacket.TotalSize]; - - EndPoint remoteEndPoint = ServerEndPoint; - - lock (typeof(Console)) - { - Console.WriteLine($"[Client {id}] Starting client; sending messages to {remoteEndPoint}"); - } - - for (int i = 0; i < PacketCount; i++) - { - byte[] packetBuffer = Encoding.UTF8.GetBytes($"[Client {id}] Hello World! (Packet {i})"); - packetBuffer.CopyTo(sendBuffer, 0); - - benchmarkHelper.StartBandwidthStopwatch(); - 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(); - } - - benchmarkHelper.PrintBandwidthStats(id, PacketCount, NetworkPacket.TotalSize); - benchmarkHelper.PrintRttStats(id); - - ClientBandwidths[id] = benchmarkHelper.CalcBandwidth(PacketCount, NetworkPacket.TotalSize); - - return Task.CompletedTask; - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharpExamples/Examples/UdpSocketServerExample.cs b/NetSharp/NetSharpExamples/Examples/UdpSocketServerExample.cs @@ -12,29 +12,35 @@ namespace NetSharpExamples.Examples { public class UdpSocketServerExample : INetSharpExample { - public Task RunAsync() - { - DatagramSocketServerOptions serverOptions = - new DatagramSocketServerOptions(NetworkPacket.TotalSize, Environment.ProcessorCount, 2); - - using DatagramSocketServer server = - new DatagramSocketServer(AddressFamily.InterNetwork, ProtocolType.Udp, ServerPacketHandler, serverOptions); - - return server.RunAsync(CancellationToken.None); // we run forever. alternatively, pass in a cancellation token to ensure that the server terminates - } + public static readonly Encoding ServerEncoding = Encoding.UTF8; + public static readonly EndPoint ServerEndPoint = new IPEndPoint(IPAddress.Loopback, 12347); public static NetworkPacket ServerPacketHandler(in NetworkPacket request, in EndPoint remoteEndPoint) { // lock is not necessary, but means that console output is clean and not interleaved lock (typeof(Console)) { - Console.WriteLine($"[Server] Received request with contents \'{Encoding.UTF8.GetString(request.Data.Span)}\' from {remoteEndPoint}"); + Console.WriteLine($"[Server] Received request with contents \'{ServerEncoding.GetString(request.Data.Span)}\' from {remoteEndPoint}"); Console.WriteLine($"[Server] Echoing back request to {remoteEndPoint}"); } - // we echo back the request, but we could just as easily send back a new packet. - // if we would not want to send back any response, we need to return NetworkPacket.NullPacket + // we echo back the request, but we could just as easily send back a new packet. if we would not want to send back any response, we need + // to return NetworkPacket.NullPacket return request; } + + /// <inheritdoc /> + public Task RunAsync() + { + DatagramSocketServerOptions serverOptions = + new DatagramSocketServerOptions(NetworkPacket.TotalSize, Environment.ProcessorCount, 2); + + using DatagramSocketServer server = + new DatagramSocketServer(AddressFamily.InterNetwork, ProtocolType.Udp, ServerPacketHandler, serverOptions); + + server.Bind(in ServerEndPoint); + + return server.RunAsync(CancellationToken.None); // we run forever. alternatively, pass in a cancellation token to ensure that the server terminates + } } } \ No newline at end of file diff --git a/NetSharp/NetSharpExamples/INetSharpExample.cs b/NetSharp/NetSharpExamples/INetSharpExample.cs @@ -2,8 +2,14 @@ namespace NetSharpExamples { + /// <summary> + /// Defines an example program. + /// </summary> public interface INetSharpExample { + /// <summary> + /// Runs the example asynchronously. + /// </summary> Task RunAsync(); } } \ No newline at end of file diff --git a/NetSharp/NetSharpExamples/Program.cs b/NetSharp/NetSharpExamples/Program.cs @@ -1,4 +1,4 @@ -using NetSharpExamples.Examples; +using NetSharpExamples.Benchmarks; using System; using System.Threading.Tasks;