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