NetSharp

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

commit 874aeca6a67305adeaf6c300834be3e2f88a42bd
parent c28d5fabdf15a8f4310c5fd6e8fa443ab574f1a5
Author: Mikolaj Lenczewski <mikolaj.lenczewski308@gmail.com>
Date:   Wed, 22 Apr 2020 21:41:49 +0100

Added benchmark for udp, and added ability to specify a packet handler.

Diffstat:
MNetSharp/NetSharp/NetSharp.xml | 432++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
MNetSharp/NetSharp/Packets/NetworkPacket.cs | 45+++++++++++++++++++++++++--------------------
MNetSharp/NetSharp/Sockets/Datagram/DatagramSocketClient.cs | 35++++++++++++++++++++++++++++++++---
MNetSharp/NetSharp/Sockets/Datagram/DatagramSocketServer.cs | 95++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
MNetSharp/NetSharp/Sockets/SocketClient.cs | 132++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
MNetSharp/NetSharp/Sockets/SocketConnection.cs | 12++++++++++--
MNetSharp/NetSharp/Sockets/SocketServer.cs | 44++++++++++++++++++++++++++++++++++++++++----
MNetSharp/NetSharp/Sockets/Stream/StreamSocketClient.cs | 19++++++++++++++++---
MNetSharp/NetSharp/Sockets/Stream/StreamSocketServer.cs | 52++++++++++++++++++++++++++++++++++++----------------
MNetSharp/NetSharp/Utils/SlimObjectPool.cs | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
DNetSharp/NetSharp/Utils/WaitHandleExtensions.cs | 45---------------------------------------------
ANetSharp/NetSharpExamples/BenchmarkHelper.cs | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ANetSharp/NetSharpExamples/Examples/UdpSocketServerBenchmark.cs | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ANetSharp/NetSharpExamples/INetSharpExample.cs | 10++++++++++
MNetSharp/NetSharpExamples/Program.cs | 20+++++++++++++++++---
15 files changed, 1088 insertions(+), 149 deletions(-)

diff --git a/NetSharp/NetSharp/NetSharp.xml b/NetSharp/NetSharp/NetSharp.xml @@ -4,7 +4,7 @@ <name>NetSharp</name> </assembly> <members> - <member name="M:NetSharp.Packets.NetworkPacket.#ctor(NetSharp.Packets.NetworkPacket.NetworkPacketHeader,System.ReadOnlyMemory{System.Byte},NetSharp.Packets.NetworkPacket.NetworkPacketFooter)"> + <member name="M:NetSharp.Packets.NetworkPacket.#ctor(NetSharp.Packets.NetworkPacketHeader,System.ReadOnlyMemory{System.Byte},NetSharp.Packets.NetworkPacketFooter)"> <summary> Constructs a new instance of the <see cref="T:NetSharp.Packets.NetworkPacket"/> struct. </summary> @@ -15,6 +15,372 @@ Thrown when the given <paramref name="packetDataBuffer"/> exceeds <see cref="F:NetSharp.Packets.NetworkPacket.TotalSize"/> bytes in size. </exception> </member> + <member name="M:NetSharp.Sockets.Datagram.DatagramSocketClient.CreateTransmissionArgs"> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Datagram.DatagramSocketClient.ResetTransmissionArgs(System.Net.Sockets.SocketAsyncEventArgs)"> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Datagram.DatagramSocketClient.CanTransmissionArgsBeReused(System.Net.Sockets.SocketAsyncEventArgs@)"> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Datagram.DatagramSocketClient.DestroyTransmissionArgs(System.Net.Sockets.SocketAsyncEventArgs)"> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Datagram.DatagramSocketClient.HandleIoCompleted(System.Object,System.Net.Sockets.SocketAsyncEventArgs)"> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Datagram.DatagramSocketServer.#ctor(System.Net.Sockets.AddressFamily@,System.Net.Sockets.ProtocolType@,NetSharp.Sockets.SocketServerPacketHandler@,System.Nullable{NetSharp.Sockets.Datagram.DatagramSocketServerOptions}@)"> + <summary> + Constructs a new instance of the <see cref="T:NetSharp.Sockets.Datagram.DatagramSocketServer"/> class. + </summary> + <param name="serverOptions">Additional options to configure the server.</param> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Datagram.DatagramSocketServer.CreateTransmissionArgs"> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Datagram.DatagramSocketServer.ResetTransmissionArgs(System.Net.Sockets.SocketAsyncEventArgs)"> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Datagram.DatagramSocketServer.CanTransmissionArgsBeReused(System.Net.Sockets.SocketAsyncEventArgs@)"> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Datagram.DatagramSocketServer.DestroyTransmissionArgs(System.Net.Sockets.SocketAsyncEventArgs)"> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Datagram.DatagramSocketServer.HandleIoCompleted(System.Object,System.Net.Sockets.SocketAsyncEventArgs)"> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Datagram.DatagramSocketServer.RunAsync(System.Threading.CancellationToken)"> + <inheritdoc /> + </member> + <member name="T:NetSharp.Sockets.SocketClient"> + <summary> + Abstract base class for clients. + </summary> + </member> + <member name="T:NetSharp.Sockets.SocketClient.AsyncTransmissionToken"> + <summary> + A state token for asynchronous network IO operations. + </summary> + </member> + <member name="F:NetSharp.Sockets.SocketClient.AsyncTransmissionToken.CompletionSource"> + <summary> + The completion source which wraps the event-based APM, and provides an awaitable <see cref="T:System.Threading.Tasks.Task"/>. + </summary> + </member> + <member name="F:NetSharp.Sockets.SocketClient.AsyncTransmissionToken.CancellationToken"> + <summary> + The <see cref="T:System.Threading.CancellationToken"/> associated with the network IO operation. + </summary> + </member> + <member name="M:NetSharp.Sockets.SocketClient.AsyncTransmissionToken.#ctor(System.Threading.Tasks.TaskCompletionSource{NetSharp.Utils.TransmissionResult}@,System.Threading.CancellationToken@)"> + <summary> + Constructs a new instance of the <see cref="T:NetSharp.Sockets.SocketClient.AsyncTransmissionToken"/> struct. + </summary> + <param name="completionSource">The completion source to trigger when the IO operation completes.</param> + <param name="cancellationToken">The cancellation token to observe during the operation.</param> + </member> + <member name="T:NetSharp.Sockets.SocketClient.AsyncOperationToken"> + <summary> + A state token for asynchronous socket operations. + </summary> + </member> + <member name="F:NetSharp.Sockets.SocketClient.AsyncOperationToken.CompletionSource"> + <summary> + The completion source which wraps the event-based APM, and provides an awaitable <see cref="T:System.Threading.Tasks.Task"/>. + </summary> + </member> + <member name="F:NetSharp.Sockets.SocketClient.AsyncOperationToken.CancellationToken"> + <summary> + The <see cref="T:System.Threading.CancellationToken"/> associated with the socket operation. + </summary> + </member> + <member name="M:NetSharp.Sockets.SocketClient.AsyncOperationToken.#ctor(System.Threading.Tasks.TaskCompletionSource{System.Boolean}@,System.Threading.CancellationToken@)"> + <summary> + Constructs a new instance of the <see cref="T:NetSharp.Sockets.SocketClient.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> + </member> + <member name="T:NetSharp.Sockets.SocketClient.AsyncTransmissionCancellationToken"> + <summary> + A state token for cancelling asynchronous network IO operations. + </summary> + </member> + <member name="F:NetSharp.Sockets.SocketClient.AsyncTransmissionCancellationToken.Socket"> + <summary> + The socket on which the operation was started. + </summary> + </member> + <member name="F:NetSharp.Sockets.SocketClient.AsyncTransmissionCancellationToken.TransmissionArgs"> + <summary> + The <see cref="T:System.Net.Sockets.SocketAsyncEventArgs"/> instance associated with the network IO operation. + </summary> + </member> + <member name="F:NetSharp.Sockets.SocketClient.AsyncTransmissionCancellationToken.TransmissionArgsPool"> + <summary> + The pool to which the <see cref="F:NetSharp.Sockets.SocketClient.AsyncTransmissionCancellationToken.TransmissionArgs"/> should be returned upon operation cancellation. + </summary> + </member> + <member name="F:NetSharp.Sockets.SocketClient.AsyncTransmissionCancellationToken.CompletionSource"> + <summary> + The completion source associated with the network IO operation. + </summary> + </member> + <member name="M:NetSharp.Sockets.SocketClient.AsyncTransmissionCancellationToken.#ctor(System.Net.Sockets.Socket@,System.Net.Sockets.SocketAsyncEventArgs@,NetSharp.Utils.SlimObjectPool{System.Net.Sockets.SocketAsyncEventArgs}@,System.Threading.Tasks.TaskCompletionSource{NetSharp.Utils.TransmissionResult}@)"> + <summary> + Constructs a new instance of the <see cref="T:NetSharp.Sockets.SocketClient.AsyncTransmissionCancellationToken"/> struct. + </summary> + <param name="socket">The socket on which the operation was started.</param> + <param name="args">The socket event args associated with the operation.</param> + <param name="argsPool">The pool to which the <paramref name="args"/> instance will be returned upon cancellation.</param> + <param name="completionSource">The completion source associated with the operation.</param> + </member> + <member name="T:NetSharp.Sockets.SocketClient.AsyncOperationCancellationToken"> + <summary> + A state token for cancelling asynchronous socket operations. + </summary> + </member> + <member name="F:NetSharp.Sockets.SocketClient.AsyncOperationCancellationToken.Socket"> + <summary> + The socket on which the operation was started. + </summary> + </member> + <member name="F:NetSharp.Sockets.SocketClient.AsyncOperationCancellationToken.TransmissionArgs"> + <summary> + The <see cref="T:System.Net.Sockets.SocketAsyncEventArgs"/> instance associated with the socket operation. + </summary> + </member> + <member name="F:NetSharp.Sockets.SocketClient.AsyncOperationCancellationToken.TransmissionArgsPool"> + <summary> + The pool to which the <see cref="F:NetSharp.Sockets.SocketClient.AsyncOperationCancellationToken.TransmissionArgs"/> should be returned upon operation cancellation. + </summary> + </member> + <member name="F:NetSharp.Sockets.SocketClient.AsyncOperationCancellationToken.CompletionSource"> + <summary> + The completion source associated with the network IO operation. + </summary> + </member> + <member name="M:NetSharp.Sockets.SocketClient.AsyncOperationCancellationToken.#ctor(System.Net.Sockets.Socket@,System.Net.Sockets.SocketAsyncEventArgs@,NetSharp.Utils.SlimObjectPool{System.Net.Sockets.SocketAsyncEventArgs}@,System.Threading.Tasks.TaskCompletionSource{System.Boolean}@)"> + <summary> + Constructs a new instance of the <see cref="T:NetSharp.Sockets.SocketClient.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> + </member> + <member name="M:NetSharp.Sockets.SocketClient.#ctor(System.Net.Sockets.AddressFamily@,System.Net.Sockets.SocketType@,System.Net.Sockets.ProtocolType@,System.Int32@,System.UInt16@)"> + <summary> + Constructs a new instance of the <see cref="T:NetSharp.Sockets.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> + </member> + <member name="M:NetSharp.Sockets.SocketClient.Connect(System.Net.EndPoint@)"> + <summary> + Connects the client to the specified end point. If called on a <see cref="F:System.Net.Sockets.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> + </member> + <member name="M:NetSharp.Sockets.SocketClient.ConnectAsync(System.Net.EndPoint@,System.Threading.CancellationToken)"> + <summary> + Asynchronously connects the client to the specified end point. If called on a + <see cref="F:System.Net.Sockets.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="T:System.Threading.CancellationToken"/> upon whose cancellation the connection attempt should be aborted. + </param> + <returns>A <see cref="T:System.Threading.Tasks.ValueTask"/> representing the connection attempt.</returns> + </member> + <member name="T:NetSharp.Sockets.SocketConnection"> + <summary> + Abstract base class for clients and servers. + </summary> + TODO add access to socket options + </member> + <member name="F:NetSharp.Sockets.SocketConnection.Connection"> + <summary> + The underlying <see cref="T:System.Net.Sockets.Socket"/> which provides access to network operations. + </summary> + </member> + <member name="F:NetSharp.Sockets.SocketConnection.BufferPool"> + <summary> + Pools arrays to function as temporary buffers during network read/write operations. + </summary> + </member> + <member name="F:NetSharp.Sockets.SocketConnection.TransmissionArgsPool"> + <summary> + Pools <see cref="T:System.Net.Sockets.SocketAsyncEventArgs"/> objects for use during network read/write operations and calls + to <see cref="T:System.Net.Sockets.Socket"/>.XXXAsync(<see cref="T:System.Net.Sockets.SocketAsyncEventArgs"/>) methods. + </summary> + </member> + <member name="M:NetSharp.Sockets.SocketConnection.#ctor(System.Net.Sockets.AddressFamily@,System.Net.Sockets.SocketType@,System.Net.Sockets.ProtocolType@,System.Int32@,System.UInt16@)"> + <summary> + Constructs a new instance of the <see cref="T:NetSharp.Sockets.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="F:NetSharp.Sockets.SocketConnection.BufferPool"/>.</param> + <param name="preallocatedTransmissionArgs"> The number of <see cref="T:System.Net.Sockets.SocketAsyncEventArgs"/> objects to initially preallocate.</param> + </member> + <member name="M:NetSharp.Sockets.SocketConnection.CreateTransmissionArgs"> + <summary> + Delegate method used to construct fresh <see cref="T:System.Net.Sockets.SocketAsyncEventArgs"/> instances for use in the + <see cref="F:NetSharp.Sockets.SocketConnection.TransmissionArgsPool"/>. The resulting instance should register <see cref="M:NetSharp.Sockets.SocketConnection.HandleIoCompleted(System.Object,System.Net.Sockets.SocketAsyncEventArgs)"/> + as an event handler for the <see cref="E:System.Net.Sockets.SocketAsyncEventArgs.Completed"/> event. + </summary> + <returns>The configured <see cref="T:System.Net.Sockets.SocketAsyncEventArgs"/> instance.</returns> + </member> + <member name="M:NetSharp.Sockets.SocketConnection.ResetTransmissionArgs(System.Net.Sockets.SocketAsyncEventArgs)"> + <summary> + Delegate method used to reset used <see cref="T:System.Net.Sockets.SocketAsyncEventArgs"/> instances for later reuse by + the <see cref="F:NetSharp.Sockets.SocketConnection.TransmissionArgsPool"/>. + </summary> + <param name="args">The <see cref="T:System.Net.Sockets.SocketAsyncEventArgs"/> instance that should be reset.</param> + </member> + <member name="M:NetSharp.Sockets.SocketConnection.CanTransmissionArgsBeReused(System.Net.Sockets.SocketAsyncEventArgs@)"> + <summary> + Delegate method used to check whether the given used <see cref="T:System.Net.Sockets.SocketAsyncEventArgs"/> instance can be reused + by the <see cref="F:NetSharp.Sockets.SocketConnection.TransmissionArgsPool"/>. If this method returns <c>true</c>, <see cref="M:NetSharp.Sockets.SocketConnection.ResetTransmissionArgs(System.Net.Sockets.SocketAsyncEventArgs)"/> + is called on the given <paramref name="args"/>. Otherwise, <see cref="M:NetSharp.Sockets.SocketConnection.DestroyTransmissionArgs(System.Net.Sockets.SocketAsyncEventArgs)"/> is called. + </summary> + <param name="args">The <see cref="T:System.Net.Sockets.SocketAsyncEventArgs"/> instance to check.</param> + <returns>Whether the given <paramref name="args"/> should be reset and reused, or should be destroyed.</returns> + </member> + <member name="M:NetSharp.Sockets.SocketConnection.DestroyTransmissionArgs(System.Net.Sockets.SocketAsyncEventArgs)"> + <summary> + Delegate method to destroy used <see cref="T:System.Net.Sockets.SocketAsyncEventArgs"/> instances that cannot be reused by the + <see cref="F:NetSharp.Sockets.SocketConnection.TransmissionArgsPool"/>. This method should deregister <see cref="M:NetSharp.Sockets.SocketConnection.HandleIoCompleted(System.Object,System.Net.Sockets.SocketAsyncEventArgs)"/> as an + event handler for the <see cref="E:System.Net.Sockets.SocketAsyncEventArgs.Completed"/> event. + </summary> + <param name="remoteConnectionArgs">The <see cref="T:System.Net.Sockets.SocketAsyncEventArgs"/> which should be destroyed.</param> + </member> + <member name="M:NetSharp.Sockets.SocketConnection.HandleIoCompleted(System.Object,System.Net.Sockets.SocketAsyncEventArgs)"> + <summary> + Delegate method to handle asynchronous network IO completion via the <see cref="E:System.Net.Sockets.SocketAsyncEventArgs.Completed"/> event. + </summary> + <param name="sender">The object which raised the event.</param> + <param name="args">The <see cref="T:System.Net.Sockets.SocketAsyncEventArgs"/> instance associated with the asynchronous network IO.</param> + </member> + <member name="M:NetSharp.Sockets.SocketConnection.Bind(System.Net.EndPoint@)"> + <summary> + Binds the underlying socket. + </summary> + <param name="localEndPoint">The end point to which the socket should be bound.</param> + </member> + <member name="M:NetSharp.Sockets.SocketConnection.Shutdown(System.Net.Sockets.SocketShutdown)"> + <summary> + Shuts down the underlying socket. + </summary> + <param name="how">Which socket transmission functions should be shut down on the socket.</param> + </member> + <member name="M:NetSharp.Sockets.SocketConnection.Dispose(System.Boolean)"> + <summary> + Disposes of managed and unmanaged resources used by the <see cref="T:NetSharp.Sockets.SocketConnection"/> class. + </summary> + <param name="disposing">Whether this call was made by a call to <see cref="M:NetSharp.Sockets.SocketConnection.Dispose"/>.</param> + </member> + <member name="M:NetSharp.Sockets.SocketConnection.Dispose"> + <inheritdoc /> + </member> + <member name="T:NetSharp.Sockets.SocketServerPacketHandler"> + <summary> + Represents a method for serving request packets. This method should not throw any errors. + </summary> + <param name="requestPacket">The request packet received by the server.</param> + <param name="clientEndPoint">The client from which the packet was received.</param> + <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="F:NetSharp.Packets.NetworkPacket.NullPacket"/>. + </returns> + </member> + <member name="T:NetSharp.Sockets.SocketServer"> + <summary> + Abstract base class for servers. + </summary> + </member> + <member name="F:NetSharp.Sockets.SocketServer.PacketHandler"> + <summary> + The packet handler delegate to use to respond to incoming requests. + </summary> + </member> + <member name="M:NetSharp.Sockets.SocketServer.#ctor(System.Net.Sockets.AddressFamily@,System.Net.Sockets.SocketType@,System.Net.Sockets.ProtocolType@,NetSharp.Sockets.SocketServerPacketHandler@,System.Int32@,System.UInt16@)"> + <summary> + Constructs a new instance of the <see cref="T:NetSharp.Sockets.SocketServer"/> class. + </summary> + <param name="connectionAddressFamily">The address family that the underlying connection should use.</param> + <param name="connectionSocketType">The socket type that the underlying connection should use.</param> + <param name="connectionProtocolType">The protocol type that the underlying connection should use.</param> + <param name="packetHandler">The packet handler delegate to use to respond to incoming requests.</param> + <param name="maxPooledBufferLength">The maximum length of a pooled network IO buffer.</param> + <param name="preallocatedTransmissionArgs">The number of transmission args to preallocate.</param> + </member> + <member name="M:NetSharp.Sockets.SocketServer.DefaultPacketHandler(NetSharp.Packets.NetworkPacket@,System.Net.EndPoint@)"> + <summary> + The default request packet handler for servers. Simply echoes back any received packets. + </summary> + <param name="request">The request packet that was received.</param> + <param name="remoteEndPoint">The client from which the packet was received.</param> + <returns>The received packet.</returns> + </member> + <member name="M:NetSharp.Sockets.SocketServer.RunAsync(System.Threading.CancellationToken)"> + <summary> + Runs the server, handling requests from clients, until the <paramref name="cancellationToken"/> has its cancellation requested. + </summary> + <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> upon whose cancellation the server should shut down.</param> + <returns>A <see cref="T:System.Threading.Tasks.Task"/> representing the server's execution.</returns> + </member> + <member name="M:NetSharp.Sockets.Stream.StreamSocketClient.CreateTransmissionArgs"> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Stream.StreamSocketClient.ResetTransmissionArgs(System.Net.Sockets.SocketAsyncEventArgs)"> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Stream.StreamSocketClient.CanTransmissionArgsBeReused(System.Net.Sockets.SocketAsyncEventArgs@)"> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Stream.StreamSocketClient.DestroyTransmissionArgs(System.Net.Sockets.SocketAsyncEventArgs)"> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Stream.StreamSocketClient.HandleIoCompleted(System.Object,System.Net.Sockets.SocketAsyncEventArgs)"> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Stream.StreamSocketServer.#ctor(System.Net.Sockets.AddressFamily@,System.Net.Sockets.ProtocolType@,NetSharp.Sockets.SocketServerPacketHandler@,System.Nullable{NetSharp.Sockets.Stream.StreamSocketServerOptions}@)"> + <summary> + Constructs a new instance of the <see cref="T:NetSharp.Sockets.Stream.StreamSocketServer"/> class. + </summary> + <param name="serverOptions">Additional options to configure the server.</param> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Stream.StreamSocketServer.CreateTransmissionArgs"> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Stream.StreamSocketServer.ResetTransmissionArgs(System.Net.Sockets.SocketAsyncEventArgs)"> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Stream.StreamSocketServer.CanTransmissionArgsBeReused(System.Net.Sockets.SocketAsyncEventArgs@)"> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Stream.StreamSocketServer.DestroyTransmissionArgs(System.Net.Sockets.SocketAsyncEventArgs)"> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Stream.StreamSocketServer.HandleIoCompleted(System.Object,System.Net.Sockets.SocketAsyncEventArgs)"> + <inheritdoc /> + </member> + <member name="M:NetSharp.Sockets.Stream.StreamSocketServer.RunAsync(System.Threading.CancellationToken)"> + <inheritdoc /> + </member> <member name="T:NetSharp.Utils.BiDictionary`2"> <summary> Represents a concurrent two-way dictionary, that can be indexed by either a key or a value. @@ -204,6 +570,70 @@ <member name="M:NetSharp.Utils.Conversion.EndianAwareBitConverter.ToUInt64(System.Span{System.Byte},System.Boolean)"> <inheritdoc cref="M:System.BitConverter.ToUInt64(System.ReadOnlySpan{System.Byte})"/> </member> + <member name="T:NetSharp.Utils.SlimObjectPool`1"> + <summary> + Provides a lightweight implementation of an object pool for classes. + </summary> + <typeparam name="T">The type of item stored in the pool.</typeparam> + </member> + <member name="T:NetSharp.Utils.SlimObjectPool`1.CreateObjectDelegate"> + <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> + </member> + <member name="T:NetSharp.Utils.SlimObjectPool`1.CanRebufferObjectPredicate"> + <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> + </member> + <member name="T:NetSharp.Utils.SlimObjectPool`1.ResetObjectDelegate"> + <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> + </member> + <member name="T:NetSharp.Utils.SlimObjectPool`1.DestroyObjectDelegate"> + <summary> + Delegate method to destroy a used <paramref name="instance"/> which cannot be reused. + </summary> + <param name="instance">The instance to destroy.</param> + </member> + <member name="M:NetSharp.Utils.SlimObjectPool`1.#ctor(NetSharp.Utils.SlimObjectPool{`0}.CreateObjectDelegate@,NetSharp.Utils.SlimObjectPool{`0}.ResetObjectDelegate@,NetSharp.Utils.SlimObjectPool{`0}.DestroyObjectDelegate@,NetSharp.Utils.SlimObjectPool{`0}.CanRebufferObjectPredicate@,System.Collections.Concurrent.IProducerConsumerCollection{`0}@)"> + <summary> + Constructs a new instance of the <see cref="T:NetSharp.Utils.SlimObjectPool`1"/> class. + </summary> + <param name="createDelegate">The delegate method to use to create new pooled object instances.</param> + <param name="resetDelegate">The delegate method to use to reset used pooled object instances.</param> + <param name="destroyDelegate">The delegate method to use to destroy pooled object instances that cannot be reused.</param> + <param name="rebufferPredicate">The delegate method to use to decide whether an instance can be reused.</param> + <param name="baseCollection">The underlying pooled object buffer to use.</param> + </member> + <member name="M:NetSharp.Utils.SlimObjectPool`1.#ctor(NetSharp.Utils.SlimObjectPool{`0}.CreateObjectDelegate@,NetSharp.Utils.SlimObjectPool{`0}.ResetObjectDelegate@,NetSharp.Utils.SlimObjectPool{`0}.DestroyObjectDelegate@,NetSharp.Utils.SlimObjectPool{`0}.CanRebufferObjectPredicate@)"> + <summary> + Constructs a new instance of the <see cref="T:NetSharp.Utils.SlimObjectPool`1"/> class. + </summary> + <param name="createDelegate">The delegate method to use to create new pooled object instances.</param> + <param name="resetDelegate">The delegate method to use to reset used pooled object instances.</param> + <param name="destroyDelegate">The delegate method to use to destroy pooled object instances that cannot be reused.</param> + <param name="rebufferPredicate">The delegate method to use to decide whether an instance can be reused.</param> + </member> + <member name="M:NetSharp.Utils.SlimObjectPool`1.Rent"> + <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> + </member> + <member name="M:NetSharp.Utils.SlimObjectPool`1.Return(`0)"> + <summary> + Returns a previously leased <typeparamref name="T"/> instance to the pool. + </summary> + <param name="instance">The previously leased instance which should be returned.</param> + </member> <member name="T:NetSharp.Utils.TransmissionResult"> <summary> Represents the result of a socket transmission. diff --git a/NetSharp/NetSharp/Packets/NetworkPacket.cs b/NetSharp/NetSharp/Packets/NetworkPacket.cs @@ -2,8 +2,11 @@ namespace NetSharp.Packets { + //TODO document public readonly struct NetworkPacket { + public static NetworkPacket NullPacket = new NetworkPacket(); + public const int TotalSize = HeaderSize + DataSize + FooterSize; public const int HeaderSize = NetworkPacketHeader.TotalSize; @@ -63,33 +66,35 @@ namespace NetSharp.Packets Memory<byte> packetFooter = buffer.Slice(HeaderSize + DataSize, FooterSize); NetworkPacketFooter.Serialise(instance.Footer, packetFooter); } + } - public readonly struct NetworkPacketHeader - { - public const int TotalSize = 0; - - public static NetworkPacketHeader Deserialise(ReadOnlyMemory<byte> buffer) - { - return new NetworkPacketHeader(); - } + //TODO document + public readonly struct NetworkPacketHeader + { + public const int TotalSize = 0; - public static void Serialise(NetworkPacketHeader instance, Memory<byte> buffer) - { - } + public static NetworkPacketHeader Deserialise(ReadOnlyMemory<byte> buffer) + { + return new NetworkPacketHeader(); } - public readonly struct NetworkPacketFooter + public static void Serialise(NetworkPacketHeader instance, Memory<byte> buffer) { - public const int TotalSize = 0; + } + } - public static NetworkPacketFooter Deserialise(ReadOnlyMemory<byte> buffer) - { - return new NetworkPacketFooter(); - } + //TODO document + public readonly struct NetworkPacketFooter + { + public const int TotalSize = 0; - public static void Serialise(NetworkPacketFooter instance, Memory<byte> buffer) - { - } + public static NetworkPacketFooter Deserialise(ReadOnlyMemory<byte> buffer) + { + return new NetworkPacketFooter(); + } + + public static void Serialise(NetworkPacketFooter instance, Memory<byte> buffer) + { } } } \ No newline at end of file diff --git a/NetSharp/NetSharp/Sockets/Datagram/DatagramSocketClient.cs b/NetSharp/NetSharp/Sockets/Datagram/DatagramSocketClient.cs @@ -13,13 +13,17 @@ namespace NetSharp.Sockets.Datagram public readonly struct DatagramSocketClientOptions { public static readonly DatagramSocketClientOptions Defaults = - new DatagramSocketClientOptions(NetworkPacket.TotalSize); + new DatagramSocketClientOptions(NetworkPacket.TotalSize, 0); public readonly int PacketSize; - public DatagramSocketClientOptions(int packetSize) + public readonly ushort PreallocatedTransmissionArgs; + + public DatagramSocketClientOptions(int packetSize, ushort preallocatedTransmissionArgs) { PacketSize = packetSize; + + PreallocatedTransmissionArgs = preallocatedTransmissionArgs; } } @@ -27,10 +31,14 @@ namespace NetSharp.Sockets.Datagram //TODO document class public sealed class DatagramSocketClient : SocketClient { + 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) + in connectionProtocolType, clientOptions?.PacketSize ?? DatagramSocketClientOptions.Defaults.PacketSize, + clientOptions?.PreallocatedTransmissionArgs ?? DatagramSocketClientOptions.Defaults.PreallocatedTransmissionArgs) { + this.clientOptions = clientOptions ?? DatagramSocketClientOptions.Defaults; } /// <inheritdoc /> @@ -136,6 +144,11 @@ namespace NetSharp.Sockets.Datagram } } + public ref readonly DatagramSocketClientOptions ClientOptions + { + get { return ref clientOptions; } + } + public TransmissionResult ReceiveFrom(ref EndPoint remoteEndPoint, byte[] receiveBuffer, SocketFlags flags = SocketFlags.None) { int receivedBytes = Connection.ReceiveFrom(receiveBuffer, flags, ref remoteEndPoint); @@ -156,6 +169,14 @@ namespace NetSharp.Sockets.Datagram args.SocketFlags = flags; args.UserToken = new AsyncTransmissionToken(in tcs, in cancellationToken); + // TODO implement cancellation for client sockets + cancellationToken.Register(token => + { + AsyncTransmissionCancellationToken transmissionCancellationToken = + (AsyncTransmissionCancellationToken) token; + + }, new AsyncTransmissionCancellationToken(in Connection, in args, in TransmissionArgsPool, in tcs)); + if (Connection.ReceiveFromAsync(args)) return new ValueTask<TransmissionResult>(tcs.Task); TransmissionResult result = new TransmissionResult(in args); @@ -185,6 +206,14 @@ namespace NetSharp.Sockets.Datagram args.SocketFlags = flags; args.UserToken = new AsyncTransmissionToken(in tcs, in cancellationToken); + // TODO implement cancellation for client sockets + cancellationToken.Register(token => + { + AsyncTransmissionCancellationToken transmissionCancellationToken = + (AsyncTransmissionCancellationToken)token; + + }, new AsyncTransmissionCancellationToken(in Connection, in args, in TransmissionArgsPool, in tcs)); + if (Connection.SendToAsync(args)) return new ValueTask<TransmissionResult>(tcs.Task); TransmissionResult result = new TransmissionResult(in args); diff --git a/NetSharp/NetSharp/Sockets/Datagram/DatagramSocketServer.cs b/NetSharp/NetSharp/Sockets/Datagram/DatagramSocketServer.cs @@ -1,5 +1,4 @@ using NetSharp.Packets; -using NetSharp.Utils; using System; using System.Net; @@ -13,17 +12,21 @@ namespace NetSharp.Sockets.Datagram public readonly struct DatagramSocketServerOptions { public static readonly DatagramSocketServerOptions Defaults = - new DatagramSocketServerOptions(NetworkPacket.TotalSize, Environment.ProcessorCount); + new DatagramSocketServerOptions(NetworkPacket.TotalSize, Environment.ProcessorCount, 0); public readonly int PacketSize; public readonly int ConcurrentReceiveFromCalls; - public DatagramSocketServerOptions(int packetSize, int concurrentReceiveFromCalls) + public readonly ushort PreallocatedTransmissionArgs; + + public DatagramSocketServerOptions(int packetSize, int concurrentReceiveFromCalls, ushort preallocatedTransmissionArgs) { PacketSize = packetSize; ConcurrentReceiveFromCalls = concurrentReceiveFromCalls; + + PreallocatedTransmissionArgs = preallocatedTransmissionArgs; } } @@ -36,9 +39,18 @@ namespace NetSharp.Sockets.Datagram private readonly DatagramSocketServerOptions serverOptions; + private CancellationToken serverShutdownToken; + + /// <summary> + /// Constructs a new instance of the <see cref="DatagramSocketServer"/> class. + /// </summary> + /// <param name="serverOptions">Additional options to configure the server.</param> + /// <inheritdoc /> public DatagramSocketServer(in AddressFamily connectionAddressFamily, in ProtocolType connectionProtocolType, - in DatagramSocketServerOptions? serverOptions = null) : base(in connectionAddressFamily, SocketType.Dgram, - in connectionProtocolType, serverOptions?.PacketSize ?? DatagramSocketServerOptions.Defaults.PacketSize) + in SocketServerPacketHandler packetHandler, in DatagramSocketServerOptions? serverOptions = null) + : base(in connectionAddressFamily, SocketType.Dgram, in connectionProtocolType, in packetHandler, + serverOptions?.PacketSize ?? DatagramSocketServerOptions.Defaults.PacketSize, + serverOptions?.PreallocatedTransmissionArgs ?? DatagramSocketServerOptions.Defaults.PreallocatedTransmissionArgs) { this.serverOptions = serverOptions ?? DatagramSocketServerOptions.Defaults; } @@ -107,6 +119,13 @@ namespace NetSharp.Sockets.Datagram private void ReceiveFrom(SocketAsyncEventArgs receiveArgs) { + if (serverShutdownToken.IsCancellationRequested) + { + TransmissionArgsPool.Return(receiveArgs); + + return; + } + byte[] receiveBuffer = BufferPool.Rent(ServerOptions.PacketSize); Memory<byte> receiveBufferMemory = new Memory<byte>(receiveBuffer); @@ -129,36 +148,48 @@ namespace NetSharp.Sockets.Datagram { SocketOperationToken receiveToken = (SocketOperationToken)receiveArgs.UserToken; - TransmissionResult receiveResult = new TransmissionResult(in receiveArgs); - -#if DEBUG - lock (typeof(Console)) - { - Console.WriteLine($"[Server] Received {receiveResult.Count} bytes from {receiveResult.RemoteEndPoint}"); - Console.WriteLine($"[Server] <<<< {Encoding.UTF8.GetString(receiveResult.Buffer.Span)}"); - } -#endif + if (receiveArgs.SocketError == SocketError.Success) + { + NetworkPacket request = NetworkPacket.Deserialise(receiveArgs.MemoryBuffer); - NetworkPacket request = NetworkPacket.Deserialise(receiveArgs.MemoryBuffer); + NetworkPacket response = PacketHandler(in request, receiveArgs.RemoteEndPoint); - // TODO implement actual request processing, not just an echo server - NetworkPacket response = request; + if (!response.Equals(NetworkPacket.NullPacket)) + { + byte[] sendBuffer = BufferPool.Rent(ServerOptions.PacketSize); + Memory<byte> sendBufferMemory = new Memory<byte>(sendBuffer); - byte[] sendBuffer = BufferPool.Rent(ServerOptions.PacketSize); - Memory<byte> sendBufferMemory = new Memory<byte>(sendBuffer); + NetworkPacket.Serialise(response, sendBufferMemory); - NetworkPacket.Serialise(response, sendBufferMemory); + receiveArgs.SetBuffer(sendBufferMemory); + receiveArgs.UserToken = new SocketOperationToken(in sendBuffer); - receiveArgs.SetBuffer(sendBufferMemory); - receiveArgs.UserToken = new SocketOperationToken(in sendBuffer); + SendTo(receiveArgs); + } - SendTo(receiveArgs); + BufferPool.Return(receiveToken.RentedBuffer, true); + } + else + { + BufferPool.Return(receiveToken.RentedBuffer, true); - BufferPool.Return(receiveToken.RentedBuffer, true); + TransmissionArgsPool.Return(receiveArgs); + } } private void SendTo(SocketAsyncEventArgs sendArgs) { + if (serverShutdownToken.IsCancellationRequested) + { + SocketOperationToken sendToken = (SocketOperationToken)sendArgs.UserToken; + + BufferPool.Return(sendToken.RentedBuffer, true); + + TransmissionArgsPool.Return(sendArgs); + + return; + } + bool operationPending = Connection.SendToAsync(sendArgs); if (!operationPending) @@ -171,16 +202,6 @@ namespace NetSharp.Sockets.Datagram { SocketOperationToken sendToken = (SocketOperationToken)sendArgs.UserToken; - TransmissionResult sendResult = new TransmissionResult(in sendArgs); - -#if DEBUG - lock (typeof(Console)) - { - Console.WriteLine($"[Server] Sent {sendResult.Count} bytes to {sendResult.RemoteEndPoint}"); - Console.WriteLine($"[Server] >>>> {Encoding.UTF8.GetString(sendResult.Buffer.Span)}"); - } -#endif - BufferPool.Return(sendToken.RentedBuffer, true); TransmissionArgsPool.Return(sendArgs); @@ -194,6 +215,8 @@ namespace NetSharp.Sockets.Datagram /// <inheritdoc /> public override Task RunAsync(CancellationToken cancellationToken = default) { + serverShutdownToken = cancellationToken; + for (int i = 0; i < ServerOptions.ConcurrentReceiveFromCalls; i++) { SocketAsyncEventArgs newReceiveArgs = TransmissionArgsPool.Rent(); @@ -202,7 +225,9 @@ namespace NetSharp.Sockets.Datagram ReceiveFrom(newReceiveArgs); } - return cancellationToken.WaitHandle.WaitOneAsync(CancellationToken.None); + serverShutdownToken.WaitHandle.WaitOne(); + + return Task.CompletedTask; } } } \ No newline at end of file diff --git a/NetSharp/NetSharp/Sockets/SocketClient.cs b/NetSharp/NetSharp/Sockets/SocketClient.cs @@ -12,13 +12,26 @@ namespace NetSharp.Sockets /// </summary> public abstract class SocketClient : SocketConnection { - //TODO document + /// <summary> + /// A state token for asynchronous network IO operations. + /// </summary> protected readonly struct AsyncTransmissionToken { + /// <summary> + /// The completion source which wraps the event-based APM, and provides an awaitable <see cref="Task"/>. + /// </summary> public readonly TaskCompletionSource<TransmissionResult> CompletionSource; + /// <summary> + /// The <see cref="System.Threading.CancellationToken"/> associated with the network IO operation. + /// </summary> public readonly CancellationToken CancellationToken; + /// <summary> + /// Constructs a new instance of the <see cref="AsyncTransmissionToken"/> struct. + /// </summary> + /// <param name="completionSource">The completion source to trigger when the IO operation completes.</param> + /// <param name="cancellationToken">The cancellation token to observe during the operation.</param> public AsyncTransmissionToken(in TaskCompletionSource<TransmissionResult> completionSource, in CancellationToken cancellationToken) { CompletionSource = completionSource; @@ -27,13 +40,26 @@ namespace NetSharp.Sockets } } - //TODO document + /// <summary> + /// A state token for asynchronous socket operations. + /// </summary> protected readonly struct AsyncOperationToken { + /// <summary> + /// The completion source which wraps the event-based APM, and provides an awaitable <see cref="Task"/>. + /// </summary> public readonly TaskCompletionSource<bool> CompletionSource; + /// <summary> + /// The <see cref="System.Threading.CancellationToken"/> associated with the socket operation. + /// </summary> public readonly CancellationToken CancellationToken; + /// <summary> + /// Constructs a new instance of the <see cref="AsyncOperationToken"/> struct. + /// </summary> + /// <param name="completionSource">The completion source to trigger when the socket operation completes.</param> + /// <param name="cancellationToken">The cancellation token to observe during the operation.</param> public AsyncOperationToken(in TaskCompletionSource<bool> completionSource, in CancellationToken cancellationToken) { CompletionSource = completionSource; @@ -42,28 +68,108 @@ namespace NetSharp.Sockets } } - //TODO document - protected readonly struct AsyncCancellationToken + /// <summary> + /// A state token for cancelling asynchronous network IO operations. + /// </summary> + protected readonly struct AsyncTransmissionCancellationToken + { + /// <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. + /// </summary> + 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> + /// 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. + /// </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 AsyncTransmissionCancellationToken(in Socket socket, in SocketAsyncEventArgs args, + in SlimObjectPool<SocketAsyncEventArgs> argsPool, in TaskCompletionSource<TransmissionResult> completionSource) + { + Socket = socket; + + TransmissionArgs = args; + + TransmissionArgsPool = argsPool; + + CompletionSource = completionSource; + } + } + + /// <summary> + /// A state token for cancelling asynchronous socket operations. + /// </summary> + protected readonly struct AsyncOperationCancellationToken { + /// <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; - public AsyncCancellationToken(in Socket socket, in SocketAsyncEventArgs args) + /// <summary> + /// 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<bool> CompletionSource; + + /// <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) { Socket = socket; TransmissionArgs = args; + + TransmissionArgsPool = argsPool; + + CompletionSource = completionSource; } } /// <summary> /// Constructs a new instance of the <see cref="SocketClient"/> class. /// </summary> - /// <inheritdoc /> + /// <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) : base(in connectionAddressFamily, - in connectionSocketType, in connectionProtocolType, in maxPooledBufferLength) + in ProtocolType connectionProtocolType, in int maxPooledBufferLength, in ushort preallocatedTransmissionArgs) + : base(in connectionAddressFamily, in connectionSocketType, in connectionProtocolType, in maxPooledBufferLength, + in preallocatedTransmissionArgs) { } @@ -99,10 +205,14 @@ namespace NetSharp.Sockets cancellationToken.Register(token => { - AsyncCancellationToken cancellationArgs = (AsyncCancellationToken)token; + AsyncOperationCancellationToken operationCancellationToken = (AsyncOperationCancellationToken) token; + + Socket.CancelConnectAsync(operationCancellationToken.TransmissionArgs); + + operationCancellationToken.CompletionSource.SetCanceled(); - Socket.CancelConnectAsync(cancellationArgs.TransmissionArgs); - }, new AsyncCancellationToken(in Connection, in args)); + operationCancellationToken.TransmissionArgsPool.Return(operationCancellationToken.TransmissionArgs); + }, new AsyncOperationCancellationToken(in Connection, in args, in TransmissionArgsPool, in tcs)); if (Connection.ConnectAsync(args)) return new ValueTask(tcs.Task); diff --git a/NetSharp/NetSharp/Sockets/SocketConnection.cs b/NetSharp/NetSharp/Sockets/SocketConnection.cs @@ -16,7 +16,7 @@ namespace NetSharp.Sockets /// <summary> /// The underlying <see cref="Socket"/> which provides access to network operations. /// </summary> - protected readonly Socket Connection; + protected Socket Connection; /// <summary> /// Pools arrays to function as temporary buffers during network read/write operations. @@ -36,8 +36,9 @@ namespace NetSharp.Sockets /// <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, - in ProtocolType connectionProtocolType, in int maxPooledBufferLength) + in ProtocolType connectionProtocolType, in int maxPooledBufferLength, in ushort preallocatedTransmissionArgs) { Connection = new Socket(connectionAddressFamily, connectionSocketType, connectionProtocolType); @@ -45,6 +46,13 @@ namespace NetSharp.Sockets TransmissionArgsPool = new SlimObjectPool<SocketAsyncEventArgs>(CreateTransmissionArgs, ResetTransmissionArgs, DestroyTransmissionArgs, CanTransmissionArgsBeReused); + + for (ushort i = 0; i < preallocatedTransmissionArgs; i++) + { + SocketAsyncEventArgs args = CreateTransmissionArgs(); + + TransmissionArgsPool.Return(args); + } } /// <summary> diff --git a/NetSharp/NetSharp/Sockets/SocketServer.cs b/NetSharp/NetSharp/Sockets/SocketServer.cs @@ -1,22 +1,58 @@ -using System.Net.Sockets; +using System.Net; +using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; +using NetSharp.Packets; namespace NetSharp.Sockets { /// <summary> + /// Represents a method for serving request packets. This method should not throw any errors. + /// </summary> + /// <param name="requestPacket">The request packet received by the server.</param> + /// <param name="clientEndPoint">The client from which the packet was received.</param> + /// <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"/>. + /// </returns> + public delegate NetworkPacket SocketServerPacketHandler(in NetworkPacket requestPacket, in EndPoint clientEndPoint); + + /// <summary> /// Abstract base class for servers. /// </summary> public abstract class SocketServer : SocketConnection { /// <summary> + /// The packet handler delegate to use to respond to incoming requests. + /// </summary> + protected readonly SocketServerPacketHandler PacketHandler; + + /// <summary> /// Constructs a new instance of the <see cref="SocketServer"/> class. /// </summary> - /// <inheritdoc /> + /// <param name="connectionAddressFamily">The address family that the underlying connection should use.</param> + /// <param name="connectionSocketType">The socket type that the underlying connection should use.</param> + /// <param name="connectionProtocolType">The protocol type that the underlying connection should use.</param> + /// <param name="packetHandler">The packet handler delegate to use to respond to incoming requests.</param> + /// <param name="maxPooledBufferLength">The maximum length of a pooled network IO buffer.</param> + /// <param name="preallocatedTransmissionArgs">The number of transmission args to preallocate.</param> protected SocketServer(in AddressFamily connectionAddressFamily, in SocketType connectionSocketType, - in ProtocolType connectionProtocolType, in int maxPooledBufferLength) : base(in connectionAddressFamily, - in connectionSocketType, in connectionProtocolType, in maxPooledBufferLength) + in ProtocolType connectionProtocolType, in SocketServerPacketHandler packetHandler, in int maxPooledBufferLength, + in ushort preallocatedTransmissionArgs) : base(in connectionAddressFamily, in connectionSocketType, + in connectionProtocolType, in maxPooledBufferLength, preallocatedTransmissionArgs) + { + PacketHandler = packetHandler; + } + + /// <summary> + /// The default request packet handler for servers. Simply echoes back any received packets. + /// </summary> + /// <param name="request">The request packet that was received.</param> + /// <param name="remoteEndPoint">The client from which the packet was received.</param> + /// <returns>The received packet.</returns> + public static NetworkPacket DefaultPacketHandler(in NetworkPacket request, in EndPoint remoteEndPoint) { + return request; } /// <summary> diff --git a/NetSharp/NetSharp/Sockets/Stream/StreamSocketClient.cs b/NetSharp/NetSharp/Sockets/Stream/StreamSocketClient.cs @@ -12,13 +12,17 @@ namespace NetSharp.Sockets.Stream public readonly struct StreamSocketClientOptions { public static readonly StreamSocketClientOptions Defaults = - new StreamSocketClientOptions(NetworkPacket.TotalSize); + new StreamSocketClientOptions(NetworkPacket.TotalSize, 0); public readonly int PacketSize; - public StreamSocketClientOptions(int packetSize) + public readonly ushort PreallocatedTransmissionArgs; + + public StreamSocketClientOptions(int packetSize, ushort preallocatedTransmissionArgs) { PacketSize = packetSize; + + PreallocatedTransmissionArgs = preallocatedTransmissionArgs; } } @@ -26,10 +30,14 @@ namespace NetSharp.Sockets.Stream //TODO document class public sealed class StreamSocketClient : SocketClient { + 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) + in connectionProtocolType, clientOptions?.PacketSize ?? StreamSocketClientOptions.Defaults.PacketSize, + clientOptions?.PreallocatedTransmissionArgs ?? StreamSocketClientOptions.Defaults.PreallocatedTransmissionArgs) { + this.clientOptions = clientOptions ?? StreamSocketClientOptions.Defaults; } /// <inheritdoc /> @@ -217,6 +225,11 @@ namespace NetSharp.Sockets.Stream } } + public ref readonly StreamSocketClientOptions ClientOptions + { + get { return ref clientOptions; } + } + public void Disconnect(bool allowSocketReuse) { Connection.Disconnect(allowSocketReuse); diff --git a/NetSharp/NetSharp/Sockets/Stream/StreamSocketServer.cs b/NetSharp/NetSharp/Sockets/Stream/StreamSocketServer.cs @@ -1,5 +1,4 @@ using NetSharp.Packets; -using NetSharp.Utils; using System; using System.Net.Sockets; @@ -12,17 +11,21 @@ namespace NetSharp.Sockets.Stream public readonly struct StreamSocketServerOptions { public static readonly StreamSocketServerOptions Defaults = - new StreamSocketServerOptions(NetworkPacket.TotalSize, Environment.ProcessorCount); + new StreamSocketServerOptions(NetworkPacket.TotalSize, Environment.ProcessorCount, 0); public readonly int PacketSize; public readonly int ConcurrentAcceptCalls; - public StreamSocketServerOptions(int packetSize, int concurrentAcceptCalls) + public readonly ushort PreallocatedTransmissionArgs; + + public StreamSocketServerOptions(int packetSize, int concurrentAcceptCalls, ushort preallocatedTransmissionArgs) { PacketSize = packetSize; ConcurrentAcceptCalls = concurrentAcceptCalls; + + PreallocatedTransmissionArgs = preallocatedTransmissionArgs; } } @@ -52,9 +55,16 @@ namespace NetSharp.Sockets.Stream private readonly StreamSocketServerOptions serverOptions; + /// <summary> + /// Constructs a new instance of the <see cref="StreamSocketServer"/> class. + /// </summary> + /// <param name="serverOptions">Additional options to configure the server.</param> + /// <inheritdoc /> public StreamSocketServer(in AddressFamily connectionAddressFamily, in ProtocolType connectionProtocolType, - in StreamSocketServerOptions? serverOptions = null) : base(in connectionAddressFamily, SocketType.Stream, - in connectionProtocolType, serverOptions?.PacketSize ?? StreamSocketServerOptions.Defaults.PacketSize) + in SocketServerPacketHandler packetHandler, in StreamSocketServerOptions? serverOptions = null) + : base(in connectionAddressFamily, SocketType.Stream, in connectionProtocolType, in packetHandler, + serverOptions?.PacketSize ?? StreamSocketServerOptions.Defaults.PacketSize, + serverOptions?.PreallocatedTransmissionArgs ?? StreamSocketServerOptions.Defaults.PreallocatedTransmissionArgs) { this.serverOptions = serverOptions ?? StreamSocketServerOptions.Defaults; } @@ -168,20 +178,28 @@ namespace NetSharp.Sockets.Stream NetworkPacket request = NetworkPacket.Deserialise(receiveToken.RentedBuffer); - // TODO implement actual request processing, not just an echo server - NetworkPacket response = request; + NetworkPacket response = PacketHandler(in request, clientArgs.RemoteEndPoint); - byte[] responseBuffer = BufferPool.Rent(serverOptions.PacketSize); - Memory<byte> responseBufferMemory = new Memory<byte>(responseBuffer); + if (!response.Equals(NetworkPacket.NullPacket)) + { + byte[] responseBuffer = BufferPool.Rent(serverOptions.PacketSize); + Memory<byte> responseBufferMemory = new Memory<byte>(responseBuffer); - NetworkPacket.Serialise(response, responseBufferMemory); + NetworkPacket.Serialise(response, responseBufferMemory); - BufferPool.Return(receiveToken.RentedBuffer, true); // at this point the request buffer can be returned + BufferPool.Return(receiveToken.RentedBuffer, true); // at this point the request buffer can be returned - receiveToken.RentedBuffer = responseBuffer; - clientArgs.SetBuffer(responseBuffer, 0, serverOptions.PacketSize); + receiveToken.RentedBuffer = responseBuffer; + clientArgs.SetBuffer(responseBuffer, 0, serverOptions.PacketSize); - Send(clientArgs); + Send(clientArgs); + } + else + { + BufferPool.Return(receiveToken.RentedBuffer, true); // at this point the request buffer can be returned + + Receive(clientArgs); + } } else if (serverOptions.PacketSize > clientArgs.BytesTransferred && clientArgs.BytesTransferred > 0) { @@ -271,7 +289,7 @@ namespace NetSharp.Sockets.Stream } /// <inheritdoc /> - public override async Task RunAsync(CancellationToken cancellationToken = default) + public override Task RunAsync(CancellationToken cancellationToken = default) { Connection.Listen(100); @@ -282,7 +300,9 @@ namespace NetSharp.Sockets.Stream Accept(acceptArgs); } - await cancellationToken.WaitHandle.WaitOneAsync(CancellationToken.None); + cancellationToken.WaitHandle.WaitOne(); + + return Task.CompletedTask; } } } \ No newline at end of file diff --git a/NetSharp/NetSharp/Utils/SlimObjectPool.cs b/NetSharp/NetSharp/Utils/SlimObjectPool.cs @@ -2,15 +2,37 @@ namespace NetSharp.Utils { - //TODO document + /// <summary> + /// Provides a lightweight implementation of an object pool for classes. + /// </summary> + /// <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 CreateObjectDelegate createObjectDelegate; @@ -18,10 +40,19 @@ namespace NetSharp.Utils private readonly ResetObjectDelegate resetObjectDelegate; private readonly DestroyObjectDelegate destroyObjectDelegate; - private readonly ConcurrentQueue<T> objectBuffer; + private readonly IProducerConsumerCollection<T> objectBuffer; + /// <summary> + /// Constructs a new instance of the <see cref="SlimObjectPool{T}"/> class. + /// </summary> + /// <param name="createDelegate">The delegate method to use to create new pooled object instances.</param> + /// <param name="resetDelegate">The delegate method to use to reset used pooled object instances.</param> + /// <param name="destroyDelegate">The delegate method to use to destroy pooled object instances that cannot be reused.</param> + /// <param name="rebufferPredicate">The delegate method to use to decide whether an instance can be reused.</param> + /// <param name="baseCollection">The underlying pooled object buffer to use.</param> public SlimObjectPool(in CreateObjectDelegate createDelegate, in ResetObjectDelegate resetDelegate, - in DestroyObjectDelegate destroyDelegate, in CanRebufferObjectPredicate rebufferPredicate) + in DestroyObjectDelegate destroyDelegate, in CanRebufferObjectPredicate rebufferPredicate, + in IProducerConsumerCollection<T> baseCollection) { createObjectDelegate = createDelegate; @@ -31,21 +62,42 @@ namespace NetSharp.Utils canObjectBeRebufferedPredicate = rebufferPredicate; - objectBuffer = new ConcurrentQueue<T>(); + objectBuffer = baseCollection; + } + + /// <summary> + /// Constructs a new instance of the <see cref="SlimObjectPool{T}"/> class. + /// </summary> + /// <param name="createDelegate">The delegate method to use to create new pooled object instances.</param> + /// <param name="resetDelegate">The delegate method to use to reset used pooled object instances.</param> + /// <param name="destroyDelegate">The delegate method to use to destroy pooled object instances that cannot be reused.</param> + /// <param name="rebufferPredicate">The delegate method to use to decide whether an instance can be reused.</param> + public SlimObjectPool(in CreateObjectDelegate createDelegate, in ResetObjectDelegate resetDelegate, + in DestroyObjectDelegate destroyDelegate, in CanRebufferObjectPredicate rebufferPredicate) + : this(in createDelegate, in resetDelegate, in destroyDelegate, in rebufferPredicate, new ConcurrentBag<T>()) + { } + /// <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> public T Rent() { - return objectBuffer.TryDequeue(out T result) ? result : createObjectDelegate(); + return objectBuffer.TryTake(out T result) ? result : createObjectDelegate(); } + /// <summary> + /// Returns a previously leased <typeparamref name="T"/> instance to the pool. + /// </summary> + /// <param name="instance">The previously leased instance which should be returned.</param> public void Return(T instance) { if (canObjectBeRebufferedPredicate(instance)) { resetObjectDelegate(instance); - objectBuffer.Enqueue(instance); + objectBuffer.TryAdd(instance); } else { diff --git a/NetSharp/NetSharp/Utils/WaitHandleExtensions.cs b/NetSharp/NetSharp/Utils/WaitHandleExtensions.cs @@ -1,44 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace NetSharp.Utils -{ - public static class WaitHandleExtensions - { - public static Task<bool> WaitOneAsync(this WaitHandle instance, TimeSpan timeout, CancellationToken cancellationToken = default) - { - TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(); - - RegisteredWaitHandle? registeredHandle = default; - CancellationTokenRegistration tokenRegistration = default; - - try - { - registeredHandle = ThreadPool.RegisterWaitForSingleObject( - instance, - (state, timedOut) => ((TaskCompletionSource<bool>)state).TrySetResult(!timedOut), - tcs, - timeout, - true); - - tokenRegistration = cancellationToken.Register( - state => ((TaskCompletionSource<bool>)state).TrySetCanceled(), - tcs); - - return tcs.Task; - } - finally - { - registeredHandle?.Unregister(null); - tokenRegistration.Dispose(); - } - } - - public static Task<bool> WaitOneAsync(this WaitHandle instance, int timeoutMs, CancellationToken cancellationToken = default) - => instance.WaitOneAsync(TimeSpan.FromMilliseconds(timeoutMs), cancellationToken); - - public static Task<bool> WaitOneAsync(this WaitHandle instance, CancellationToken cancellationToken = default) - => instance.WaitOneAsync(Timeout.InfiniteTimeSpan, cancellationToken); - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharpExamples/BenchmarkHelper.cs b/NetSharp/NetSharpExamples/BenchmarkHelper.cs @@ -0,0 +1,104 @@ +using System; +using System.Diagnostics; + +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 long minRttMs = int.MaxValue, maxRttMs = int.MinValue; + + public void StartRttStopwatch() + { + rttStopwatch.Start(); + } + + public void StopRttStopwatch() + { + rttStopwatch.Stop(); + } + + public void ResetRttStopwatch() + { + rttStopwatch.Reset(); + } + + public void UpdateRttStats(int clientId) + { + minRttTicks = rttStopwatch.ElapsedTicks < minRttTicks + ? rttStopwatch.ElapsedTicks + : minRttTicks; + + minRttMs = rttStopwatch.ElapsedMilliseconds < minRttMs + ? rttStopwatch.ElapsedMilliseconds + : minRttMs; + + maxRttTicks = rttStopwatch.ElapsedTicks > maxRttTicks + ? rttStopwatch.ElapsedTicks + : maxRttTicks; + + maxRttMs = rttStopwatch.ElapsedMilliseconds > maxRttMs + ? rttStopwatch.ElapsedMilliseconds + : maxRttMs; + } + + public long RttTicks + { + get { return rttStopwatch.ElapsedTicks; } + } + + public long RttMs + { + get { return rttStopwatch.ElapsedMilliseconds; } + } + + public void PrintRttStats(int clientId) + { + lock (typeof(Console)) + { + Console.WriteLine($"[Client {clientId}] Min RTT: {minRttTicks} ticks, {minRttMs} ms"); + Console.WriteLine($"[Client {clientId}] Max RTT: {maxRttTicks} ticks, {maxRttMs} ms"); + } + } + + public void StartBandwidthStopwatch() + { + bandwidthStopwatch.Start(); + } + + public void StopBandwidthStopwatch() + { + bandwidthStopwatch.Stop(); + } + + public void ResetBandwidthStopwatch() + { + bandwidthStopwatch.Reset(); + } + + public void PrintBandwidthStats(int clientId, long sentPacketCount, long packetSize) + { + long millis = bandwidthStopwatch.ElapsedMilliseconds; + double megabytes = sentPacketCount * packetSize / 1_000_000.0; + double bandwidth = megabytes / (millis / 1000.0); + + 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 double CalcBandwidth(long sentPacketCount, long packetSize) + { + long millis = bandwidthStopwatch.ElapsedMilliseconds; + double megabytes = sentPacketCount * packetSize / 1_000_000.0; + double bandwidth = megabytes / (millis / 1000.0); + + return bandwidth; + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharpExamples/Examples/UdpSocketServerBenchmark.cs b/NetSharp/NetSharpExamples/Examples/UdpSocketServerBenchmark.cs @@ -0,0 +1,126 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NetSharp.Packets; +using NetSharp.Sockets; +using NetSharp.Sockets.Datagram; + +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; + + 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($"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); + + 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/INetSharpExample.cs b/NetSharp/NetSharpExamples/INetSharpExample.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace NetSharpExamples +{ + public interface INetSharpExample + { + Task RunAsync(); + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharpExamples/Program.cs b/NetSharp/NetSharpExamples/Program.cs @@ -1,5 +1,5 @@ #define TCP -//#undef TCP +#undef TCP using NetSharp.Packets; using NetSharp.Sockets.Datagram; @@ -14,6 +14,8 @@ using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; +using NetSharp.Sockets; +using NetSharpExamples.Examples; namespace NetSharpExamples { @@ -28,13 +30,17 @@ namespace NetSharpExamples { Console.WriteLine("Hello World!"); + INetSharpExample udpSocketServerBenchmark = new UdpSocketServerBenchmark(); + await udpSocketServerBenchmark.RunAsync(); + /* Console.WriteLine("Starting socket server test..."); Task serverTest = TestSocketServer(); Console.WriteLine("Starting socket client test..."); Task clientTest = TestSocketClient(); await clientTest; + */ Console.ReadLine(); } @@ -227,9 +233,17 @@ namespace NetSharpExamples try { #if TCP - using StreamSocketServer server = new StreamSocketServer(AddressFamily.InterNetwork, ProtocolType.Tcp); + StreamSocketServerOptions serverOptions = new StreamSocketServerOptions(NetworkPacket.TotalSize, + Environment.ProcessorCount, (ushort) Environment.ProcessorCount); + + using StreamSocketServer server = new StreamSocketServer(AddressFamily.InterNetwork, + ProtocolType.Tcp, SocketServer.DefaultPacketHandler, serverOptions); #else - using DatagramSocketServer server = new DatagramSocketServer(AddressFamily.InterNetwork, ProtocolType.Udp); + DatagramSocketServerOptions serverOptions = new DatagramSocketServerOptions(NetworkPacket.TotalSize, + Environment.ProcessorCount, (ushort) Environment.ProcessorCount); + + using DatagramSocketServer server = new DatagramSocketServer(AddressFamily.InterNetwork, + ProtocolType.Udp, SocketServer.DefaultPacketHandler, serverOptions); #endif server.Bind(in ServerEndPoint);