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:
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);