NetSharp

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

commit c28d5fabdf15a8f4310c5fd6e8fa443ab574f1a5
parent 219a7a72da500fac5a58a504e61db458fc95610a
Author: Mikolaj Lenczewski <mikolaj.lenczewski308@gmail.com>
Date:   Sun, 12 Apr 2020 14:18:37 +0100

Added some light docs and cancellation support for the SocketClient.ConnectAsync method

Diffstat:
MNetSharp/NetSharp/Sockets/Datagram/DatagramSocketClient.cs | 36++++++++++++++++++++++++++++--------
MNetSharp/NetSharp/Sockets/Datagram/DatagramSocketServer.cs | 42+++++++++++++++++++++++++-----------------
MNetSharp/NetSharp/Sockets/SocketClient.cs | 35+++++++++++++++++++++++++++++------
MNetSharp/NetSharp/Sockets/SocketConnection.cs | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
MNetSharp/NetSharp/Sockets/SocketServer.cs | 18+++++++++++++++---
MNetSharp/NetSharp/Sockets/Stream/StreamSocketClient.cs | 50+++++++++++++++++++++++++++++++++++---------------
MNetSharp/NetSharp/Sockets/Stream/StreamSocketServer.cs | 57+++++++++++++++++++++++++++++++++------------------------
MNetSharp/NetSharpExamples/Program.cs | 8+++-----
8 files changed, 244 insertions(+), 91 deletions(-)

diff --git a/NetSharp/NetSharp/Sockets/Datagram/DatagramSocketClient.cs b/NetSharp/NetSharp/Sockets/Datagram/DatagramSocketClient.cs @@ -1,4 +1,5 @@ -using NetSharp.Utils; +using NetSharp.Packets; +using NetSharp.Utils; using System; using System.Net; @@ -8,16 +9,31 @@ using System.Threading.Tasks; namespace NetSharp.Sockets.Datagram { - //TODO fix memory leak issue + //TODO document + public readonly struct DatagramSocketClientOptions + { + public static readonly DatagramSocketClientOptions Defaults = + new DatagramSocketClientOptions(NetworkPacket.TotalSize); + + public readonly int PacketSize; + + public DatagramSocketClientOptions(int packetSize) + { + PacketSize = packetSize; + } + } + //TODO address the need to handle series of network packets, not just single packets //TODO document class public sealed class DatagramSocketClient : SocketClient { - public DatagramSocketClient(in AddressFamily connectionAddressFamily, in ProtocolType connectionProtocolType) - : base(in connectionAddressFamily, SocketType.Dgram, in connectionProtocolType) + public DatagramSocketClient(in AddressFamily connectionAddressFamily, in ProtocolType connectionProtocolType, + in DatagramSocketClientOptions? clientOptions = null) : base(in connectionAddressFamily, SocketType.Dgram, + in connectionProtocolType, clientOptions?.PacketSize ?? DatagramSocketClientOptions.Defaults.PacketSize) { } + /// <inheritdoc /> protected override SocketAsyncEventArgs CreateTransmissionArgs() { SocketAsyncEventArgs connectionArgs = new SocketAsyncEventArgs(); @@ -27,15 +43,18 @@ namespace NetSharp.Sockets.Datagram 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; @@ -43,6 +62,7 @@ namespace NetSharp.Sockets.Datagram remoteConnectionArgs.Dispose(); } + /// <inheritdoc /> protected override void HandleIoCompleted(object sender, SocketAsyncEventArgs args) { switch (args.LastOperation) @@ -118,7 +138,7 @@ namespace NetSharp.Sockets.Datagram public TransmissionResult ReceiveFrom(ref EndPoint remoteEndPoint, byte[] receiveBuffer, SocketFlags flags = SocketFlags.None) { - int receivedBytes = connection.ReceiveFrom(receiveBuffer, flags, ref remoteEndPoint); + int receivedBytes = Connection.ReceiveFrom(receiveBuffer, flags, ref remoteEndPoint); return new TransmissionResult(in receiveBuffer, in receivedBytes, in remoteEndPoint); } @@ -136,7 +156,7 @@ namespace NetSharp.Sockets.Datagram args.SocketFlags = flags; args.UserToken = new AsyncTransmissionToken(in tcs, in cancellationToken); - if (connection.ReceiveFromAsync(args)) return new ValueTask<TransmissionResult>(tcs.Task); + if (Connection.ReceiveFromAsync(args)) return new ValueTask<TransmissionResult>(tcs.Task); TransmissionResult result = new TransmissionResult(in args); @@ -147,7 +167,7 @@ namespace NetSharp.Sockets.Datagram public TransmissionResult SendTo(EndPoint remoteEndPoint, byte[] sendBuffer, SocketFlags flags = SocketFlags.None) { - int sentBytes = connection.SendTo(sendBuffer, flags, remoteEndPoint); + int sentBytes = Connection.SendTo(sendBuffer, flags, remoteEndPoint); return new TransmissionResult(in sendBuffer, in sentBytes, in remoteEndPoint); } @@ -165,7 +185,7 @@ namespace NetSharp.Sockets.Datagram args.SocketFlags = flags; args.UserToken = new AsyncTransmissionToken(in tcs, in cancellationToken); - if (connection.SendToAsync(args)) return new ValueTask<TransmissionResult>(tcs.Task); + if (Connection.SendToAsync(args)) return new ValueTask<TransmissionResult>(tcs.Task); TransmissionResult result = new TransmissionResult(in args); diff --git a/NetSharp/NetSharp/Sockets/Datagram/DatagramSocketServer.cs b/NetSharp/NetSharp/Sockets/Datagram/DatagramSocketServer.cs @@ -1,4 +1,5 @@ -using NetSharp.Utils; +using NetSharp.Packets; +using NetSharp.Utils; using System; using System.Net; @@ -6,15 +7,13 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; -using NetworkPacket = NetSharp.Packets.NetworkPacket; - namespace NetSharp.Sockets.Datagram { //TODO document public readonly struct DatagramSocketServerOptions { public static readonly DatagramSocketServerOptions Defaults = - new DatagramSocketServerOptions(NetworkPacket.TotalSize, 8); + new DatagramSocketServerOptions(NetworkPacket.TotalSize, Environment.ProcessorCount); public readonly int PacketSize; @@ -28,7 +27,6 @@ namespace NetSharp.Sockets.Datagram } } - //TODO fix memory leak issue //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 @@ -36,13 +34,13 @@ namespace NetSharp.Sockets.Datagram { private static readonly EndPoint AnyRemoteEndPoint = new IPEndPoint(IPAddress.Any, 0); - public readonly DatagramSocketServerOptions ServerOptions; + private readonly DatagramSocketServerOptions serverOptions; public DatagramSocketServer(in AddressFamily connectionAddressFamily, in ProtocolType connectionProtocolType, in DatagramSocketServerOptions? serverOptions = null) : base(in connectionAddressFamily, SocketType.Dgram, - in connectionProtocolType) + in connectionProtocolType, serverOptions?.PacketSize ?? DatagramSocketServerOptions.Defaults.PacketSize) { - ServerOptions = serverOptions ?? DatagramSocketServerOptions.Defaults; + this.serverOptions = serverOptions ?? DatagramSocketServerOptions.Defaults; } private readonly struct SocketOperationToken @@ -55,6 +53,7 @@ namespace NetSharp.Sockets.Datagram } } + /// <inheritdoc /> protected override SocketAsyncEventArgs CreateTransmissionArgs() { SocketAsyncEventArgs connectionArgs = new SocketAsyncEventArgs(); @@ -64,15 +63,18 @@ namespace NetSharp.Sockets.Datagram 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; @@ -80,6 +82,7 @@ namespace NetSharp.Sockets.Datagram remoteConnectionArgs.Dispose(); } + /// <inheritdoc /> protected override void HandleIoCompleted(object sender, SocketAsyncEventArgs args) { switch (args.LastOperation) @@ -110,17 +113,16 @@ namespace NetSharp.Sockets.Datagram receiveArgs.SetBuffer(receiveBufferMemory); receiveArgs.UserToken = new SocketOperationToken(in receiveBuffer); - bool operationPending = connection.ReceiveFromAsync(receiveArgs); + bool operationPending = Connection.ReceiveFromAsync(receiveArgs); - if (!operationPending) - { - SocketAsyncEventArgs newReceiveArgs = TransmissionArgsPool.Rent(); - newReceiveArgs.RemoteEndPoint = AnyRemoteEndPoint; + if (operationPending) return; - ReceiveFrom(newReceiveArgs); // start a new receive from operation immediately, to not drop any packets + SocketAsyncEventArgs newReceiveArgs = TransmissionArgsPool.Rent(); + newReceiveArgs.RemoteEndPoint = AnyRemoteEndPoint; - CompleteReceiveFrom(receiveArgs); - } + ReceiveFrom(newReceiveArgs); // start a new receive from operation immediately, to not drop any packets + + CompleteReceiveFrom(receiveArgs); } private void CompleteReceiveFrom(SocketAsyncEventArgs receiveArgs) @@ -157,7 +159,7 @@ namespace NetSharp.Sockets.Datagram private void SendTo(SocketAsyncEventArgs sendArgs) { - bool operationPending = connection.SendToAsync(sendArgs); + bool operationPending = Connection.SendToAsync(sendArgs); if (!operationPending) { @@ -184,6 +186,12 @@ namespace NetSharp.Sockets.Datagram TransmissionArgsPool.Return(sendArgs); } + public ref readonly DatagramSocketServerOptions ServerOptions + { + get { return ref serverOptions; } + } + + /// <inheritdoc /> public override Task RunAsync(CancellationToken cancellationToken = default) { for (int i = 0; i < ServerOptions.ConcurrentReceiveFromCalls; i++) diff --git a/NetSharp/NetSharp/Sockets/SocketClient.cs b/NetSharp/NetSharp/Sockets/SocketClient.cs @@ -7,7 +7,9 @@ using System.Threading.Tasks; namespace NetSharp.Sockets { - //TODO document class + /// <summary> + /// Abstract base class for clients. + /// </summary> public abstract class SocketClient : SocketConnection { //TODO document @@ -55,16 +57,37 @@ namespace NetSharp.Sockets } } - protected SocketClient(in AddressFamily connectionAddressFamily, in SocketType connectionSocketType, in ProtocolType connectionProtocolType) - : base(in connectionAddressFamily, in connectionSocketType, in connectionProtocolType) + /// <summary> + /// Constructs a new instance of the <see cref="SocketClient"/> class. + /// </summary> + /// <inheritdoc /> + protected SocketClient(in AddressFamily connectionAddressFamily, in SocketType connectionSocketType, + in ProtocolType connectionProtocolType, in int maxPooledBufferLength) : base(in connectionAddressFamily, + in connectionSocketType, in connectionProtocolType, in maxPooledBufferLength) { } + /// <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); + 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>(); @@ -79,9 +102,9 @@ namespace NetSharp.Sockets AsyncCancellationToken cancellationArgs = (AsyncCancellationToken)token; Socket.CancelConnectAsync(cancellationArgs.TransmissionArgs); - }, new AsyncCancellationToken(in connection, in args)); + }, new AsyncCancellationToken(in Connection, in args)); - if (connection.ConnectAsync(args)) return new ValueTask(tcs.Task); + if (Connection.ConnectAsync(args)) return new ValueTask(tcs.Task); TransmissionArgsPool.Return(args); diff --git a/NetSharp/NetSharp/Sockets/SocketConnection.cs b/NetSharp/NetSharp/Sockets/SocketConnection.cs @@ -1,5 +1,4 @@ -using NetSharp.Packets; -using NetSharp.Utils; +using NetSharp.Utils; using System; using System.Buffers; @@ -8,56 +7,120 @@ using System.Net.Sockets; namespace NetSharp.Sockets { - //TODO document class + /// <summary> + /// Abstract base class for clients and servers. + /// </summary> + /// TODO add access to socket options public abstract class SocketConnection : IDisposable { - protected readonly Socket connection; - + /// <summary> + /// The underlying <see cref="Socket"/> which provides access to network operations. + /// </summary> + protected readonly 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. + /// </summary> protected readonly SlimObjectPool<SocketAsyncEventArgs> TransmissionArgsPool; - protected SocketConnection(in AddressFamily connectionAddressFamily, in SocketType connectionSocketType, in ProtocolType connectionProtocolType) + /// <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> + protected internal SocketConnection(in AddressFamily connectionAddressFamily, in SocketType connectionSocketType, + in ProtocolType connectionProtocolType, in int maxPooledBufferLength) { - connection = new Socket(connectionAddressFamily, connectionSocketType, connectionProtocolType); + Connection = new Socket(connectionAddressFamily, connectionSocketType, connectionProtocolType); - BufferPool = ArrayPool<byte>.Create(NetworkPacket.TotalSize, 1000); + BufferPool = ArrayPool<byte>.Create(maxPooledBufferLength, 1000); - TransmissionArgsPool = new SlimObjectPool<SocketAsyncEventArgs>(CreateTransmissionArgs, ResetTransmissionArgs, DestroyTransmissionArgs, CanTransmissionArgsBeReused); + TransmissionArgsPool = new SlimObjectPool<SocketAsyncEventArgs>(CreateTransmissionArgs, + ResetTransmissionArgs, DestroyTransmissionArgs, CanTransmissionArgsBeReused); } + /// <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. + /// </summary> + /// <returns>The configured <see cref="SocketAsyncEventArgs"/> instance.</returns> protected abstract SocketAsyncEventArgs CreateTransmissionArgs(); + /// <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); + /// <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. + /// </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); + /// <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. + /// </summary> + /// <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. + /// </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> protected abstract void HandleIoCompleted(object sender, SocketAsyncEventArgs args); + /// <summary> + /// Binds the underlying socket. + /// </summary> + /// <param name="localEndPoint">The end point to which the socket should be bound.</param> public void Bind(in EndPoint localEndPoint) { - connection.Bind(localEndPoint); + Connection.Bind(localEndPoint); } + /// <summary> + /// Shuts down the underlying socket. + /// </summary> + /// <param name="how">Which socket transmission functions should be shut down on the socket.</param> public void Shutdown(SocketShutdown how) { try { - connection.Shutdown(how); + Connection.Shutdown(how); } 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(); + Connection.Close(); + Connection.Dispose(); } + /// <inheritdoc /> public void Dispose() { Dispose(true); diff --git a/NetSharp/NetSharp/Sockets/SocketServer.cs b/NetSharp/NetSharp/Sockets/SocketServer.cs @@ -4,14 +4,26 @@ using System.Threading.Tasks; namespace NetSharp.Sockets { - //TODO document class + /// <summary> + /// Abstract base class for servers. + /// </summary> public abstract class SocketServer : SocketConnection { - protected SocketServer(in AddressFamily connectionAddressFamily, in SocketType connectionSocketType, in ProtocolType connectionProtocolType) - : base(in connectionAddressFamily, in connectionSocketType, in connectionProtocolType) + /// <summary> + /// Constructs a new instance of the <see cref="SocketServer"/> class. + /// </summary> + /// <inheritdoc /> + protected SocketServer(in AddressFamily connectionAddressFamily, in SocketType connectionSocketType, + in ProtocolType connectionProtocolType, in int maxPooledBufferLength) : base(in connectionAddressFamily, + in connectionSocketType, in connectionProtocolType, in maxPooledBufferLength) { } + /// <summary> + /// Runs the server, handling requests from clients, until the <paramref name="cancellationToken"/> has its cancellation requested. + /// </summary> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> upon whose cancellation the server should shut down.</param> + /// <returns>A <see cref="Task"/> representing the server's execution.</returns> 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 @@ -1,4 +1,5 @@ -using NetSharp.Utils; +using NetSharp.Packets; +using NetSharp.Utils; using System; using System.Net.Sockets; @@ -7,16 +8,31 @@ using System.Threading.Tasks; namespace NetSharp.Sockets.Stream { - //TODO fix memory leak issue + //TODO document + public readonly struct StreamSocketClientOptions + { + public static readonly StreamSocketClientOptions Defaults = + new StreamSocketClientOptions(NetworkPacket.TotalSize); + + public readonly int PacketSize; + + public StreamSocketClientOptions(int packetSize) + { + PacketSize = packetSize; + } + } + //TODO address the need to handle series of network packets, not just single packets //TODO document class public sealed class StreamSocketClient : SocketClient { - public StreamSocketClient(in AddressFamily connectionAddressFamily, in ProtocolType connectionProtocolType) - : base(in connectionAddressFamily, SocketType.Stream, in connectionProtocolType) + public StreamSocketClient(in AddressFamily connectionAddressFamily, in ProtocolType connectionProtocolType, + in StreamSocketClientOptions? clientOptions = null) : base(in connectionAddressFamily, SocketType.Stream, + in connectionProtocolType, clientOptions?.PacketSize ?? StreamSocketClientOptions.Defaults.PacketSize) { } + /// <inheritdoc /> protected override SocketAsyncEventArgs CreateTransmissionArgs() { SocketAsyncEventArgs connectionArgs = new SocketAsyncEventArgs(); @@ -26,15 +42,18 @@ namespace NetSharp.Sockets.Stream 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; @@ -42,6 +61,7 @@ namespace NetSharp.Sockets.Stream remoteConnectionArgs.Dispose(); } + /// <inheritdoc /> protected override void HandleIoCompleted(object sender, SocketAsyncEventArgs args) { switch (args.LastOperation) @@ -51,7 +71,7 @@ namespace NetSharp.Sockets.Stream if (connectToken.CancellationToken.IsCancellationRequested) { - connection.Disconnect(true); + Connection.Disconnect(true); connectToken.CompletionSource.SetCanceled(); } @@ -120,7 +140,7 @@ namespace NetSharp.Sockets.Stream args.SetBuffer(receivedBytes, expectedBytes - receivedBytes); - connection.ReceiveAsync(args); + Connection.ReceiveAsync(args); } else { @@ -172,7 +192,7 @@ namespace NetSharp.Sockets.Stream args.SetBuffer(sentBytes, remainingBytes - sentBytes); - connection.SendAsync(args); + Connection.SendAsync(args); } else { @@ -199,7 +219,7 @@ namespace NetSharp.Sockets.Stream public void Disconnect(bool allowSocketReuse) { - connection.Disconnect(allowSocketReuse); + Connection.Disconnect(allowSocketReuse); } public ValueTask DisconnectAsync(bool allowSocketReuse, CancellationToken cancellationToken = default) @@ -211,7 +231,7 @@ namespace NetSharp.Sockets.Stream args.DisconnectReuseSocket = allowSocketReuse; args.UserToken = new AsyncOperationToken(in tcs, in cancellationToken); - if (connection.DisconnectAsync(args)) return new ValueTask(tcs.Task); + if (Connection.DisconnectAsync(args)) return new ValueTask(tcs.Task); TransmissionArgsPool.Return(args); @@ -220,9 +240,9 @@ namespace NetSharp.Sockets.Stream public TransmissionResult Receive(byte[] buffer, SocketFlags flags = SocketFlags.None) { - int receivedBytes = connection.Receive(buffer, flags); + int receivedBytes = Connection.Receive(buffer, flags); - return new TransmissionResult(in buffer, in receivedBytes, connection.RemoteEndPoint); + return new TransmissionResult(in buffer, in receivedBytes, Connection.RemoteEndPoint); } public ValueTask<TransmissionResult> ReceiveAsync(Memory<byte> receiveBuffer, SocketFlags flags = SocketFlags.None, @@ -237,7 +257,7 @@ namespace NetSharp.Sockets.Stream args.SocketFlags = flags; args.UserToken = new AsyncTransmissionToken(in tcs, in cancellationToken); - if (connection.ReceiveAsync(args)) return new ValueTask<TransmissionResult>(tcs.Task); + if (Connection.ReceiveAsync(args)) return new ValueTask<TransmissionResult>(tcs.Task); TransmissionResult result = new TransmissionResult(in args); @@ -248,9 +268,9 @@ namespace NetSharp.Sockets.Stream public TransmissionResult Send(byte[] buffer, SocketFlags flags = SocketFlags.None) { - int sentBytes = connection.Send(buffer, flags); + int sentBytes = Connection.Send(buffer, flags); - return new TransmissionResult(in buffer, in sentBytes, connection.RemoteEndPoint); + return new TransmissionResult(in buffer, in sentBytes, Connection.RemoteEndPoint); } public ValueTask<TransmissionResult> SendAsync(Memory<byte> sendBuffer, SocketFlags flags = SocketFlags.None, @@ -265,7 +285,7 @@ namespace NetSharp.Sockets.Stream args.SocketFlags = flags; args.UserToken = new AsyncTransmissionToken(in tcs, in cancellationToken); - if (connection.SendToAsync(args)) return new ValueTask<TransmissionResult>(tcs.Task); + if (Connection.SendToAsync(args)) return new ValueTask<TransmissionResult>(tcs.Task); TransmissionResult result = new TransmissionResult(in args); diff --git a/NetSharp/NetSharp/Sockets/Stream/StreamSocketServer.cs b/NetSharp/NetSharp/Sockets/Stream/StreamSocketServer.cs @@ -12,7 +12,7 @@ namespace NetSharp.Sockets.Stream public readonly struct StreamSocketServerOptions { public static readonly StreamSocketServerOptions Defaults = - new StreamSocketServerOptions(NetworkPacket.TotalSize, 8); + new StreamSocketServerOptions(NetworkPacket.TotalSize, Environment.ProcessorCount); public readonly int PacketSize; @@ -26,7 +26,6 @@ namespace NetSharp.Sockets.Stream } } - //TODO fix memory leak issue //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 @@ -51,15 +50,16 @@ namespace NetSharp.Sockets.Stream } } - public readonly StreamSocketServerOptions ServerOptions; + private readonly StreamSocketServerOptions serverOptions; public StreamSocketServer(in AddressFamily connectionAddressFamily, in ProtocolType connectionProtocolType, in StreamSocketServerOptions? serverOptions = null) : base(in connectionAddressFamily, SocketType.Stream, - in connectionProtocolType) + in connectionProtocolType, serverOptions?.PacketSize ?? StreamSocketServerOptions.Defaults.PacketSize) { - ServerOptions = serverOptions ?? StreamSocketServerOptions.Defaults; + this.serverOptions = serverOptions ?? StreamSocketServerOptions.Defaults; } + /// <inheritdoc /> protected override SocketAsyncEventArgs CreateTransmissionArgs() { SocketAsyncEventArgs connectionArgs = new SocketAsyncEventArgs(); @@ -69,15 +69,18 @@ namespace NetSharp.Sockets.Stream 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; @@ -85,6 +88,7 @@ namespace NetSharp.Sockets.Stream remoteConnectionArgs.Dispose(); } + /// <inheritdoc /> protected override void HandleIoCompleted(object sender, SocketAsyncEventArgs args) { switch (args.LastOperation) @@ -112,16 +116,15 @@ namespace NetSharp.Sockets.Stream private void Accept(SocketAsyncEventArgs acceptArgs) { - bool operationPending = connection.AcceptAsync(acceptArgs); + bool operationPending = Connection.AcceptAsync(acceptArgs); - if (!operationPending) - { - SocketAsyncEventArgs newAcceptArgs = TransmissionArgsPool.Rent(); + if (operationPending) return; - Accept(newAcceptArgs); // start a new accept operation to not miss any clients + SocketAsyncEventArgs newAcceptArgs = TransmissionArgsPool.Rent(); - CompleteAccept(acceptArgs); - } + Accept(newAcceptArgs); // start a new accept operation to not miss any clients + + CompleteAccept(acceptArgs); } private void CompleteAccept(SocketAsyncEventArgs connectedClientArgs) @@ -139,11 +142,11 @@ namespace NetSharp.Sockets.Stream { RemoteStreamClientToken clientToken = (RemoteStreamClientToken)clientArgs.UserToken; - byte[] requestBuffer = BufferPool.Rent(ServerOptions.PacketSize); + byte[] requestBuffer = BufferPool.Rent(serverOptions.PacketSize); Memory<byte> requestBufferMemory = new Memory<byte>(requestBuffer); clientToken.RentedBuffer = requestBuffer; - clientArgs.SetBuffer(clientToken.RentedBuffer, 0, ServerOptions.PacketSize); + clientArgs.SetBuffer(clientToken.RentedBuffer, 0, serverOptions.PacketSize); bool operationPending = clientToken.ClientSocket.ReceiveAsync(clientArgs); @@ -159,7 +162,7 @@ namespace NetSharp.Sockets.Stream if (clientArgs.SocketError == SocketError.Success) { - if (clientArgs.BytesTransferred == ServerOptions.PacketSize) + if (clientArgs.BytesTransferred == serverOptions.PacketSize) { // buffer was fully received @@ -168,7 +171,7 @@ namespace NetSharp.Sockets.Stream // TODO implement actual request processing, not just an echo server NetworkPacket response = request; - byte[] responseBuffer = BufferPool.Rent(ServerOptions.PacketSize); + byte[] responseBuffer = BufferPool.Rent(serverOptions.PacketSize); Memory<byte> responseBufferMemory = new Memory<byte>(responseBuffer); NetworkPacket.Serialise(response, responseBufferMemory); @@ -176,17 +179,17 @@ namespace NetSharp.Sockets.Stream BufferPool.Return(receiveToken.RentedBuffer, true); // at this point the request buffer can be returned receiveToken.RentedBuffer = responseBuffer; - clientArgs.SetBuffer(responseBuffer, 0, ServerOptions.PacketSize); + clientArgs.SetBuffer(responseBuffer, 0, serverOptions.PacketSize); Send(clientArgs); } - else if (ServerOptions.PacketSize > clientArgs.BytesTransferred && clientArgs.BytesTransferred > 0) + else if (serverOptions.PacketSize > clientArgs.BytesTransferred && clientArgs.BytesTransferred > 0) { // receive the remaining parts of the buffer int receivedBytes = clientArgs.BytesTransferred; - clientArgs.SetBuffer(receivedBytes, ServerOptions.PacketSize - receivedBytes); + clientArgs.SetBuffer(receivedBytes, serverOptions.PacketSize - receivedBytes); Receive(clientArgs); } @@ -221,7 +224,7 @@ namespace NetSharp.Sockets.Stream if (clientArgs.SocketError == SocketError.Success) { - if (clientArgs.BytesTransferred == ServerOptions.PacketSize) + if (clientArgs.BytesTransferred == serverOptions.PacketSize) { // buffer was fully sent @@ -231,13 +234,13 @@ namespace NetSharp.Sockets.Stream Receive(clientArgs); } - else if (ServerOptions.PacketSize > clientArgs.BytesTransferred && clientArgs.BytesTransferred > 0) + else if (serverOptions.PacketSize > clientArgs.BytesTransferred && clientArgs.BytesTransferred > 0) { // send the remaining parts of the buffer int sentBytes = clientArgs.BytesTransferred; - clientArgs.SetBuffer(sentBytes, ServerOptions.PacketSize - sentBytes); + clientArgs.SetBuffer(sentBytes, serverOptions.PacketSize - sentBytes); Send(clientArgs); } @@ -262,11 +265,17 @@ namespace NetSharp.Sockets.Stream TransmissionArgsPool.Return(clientArgs); } + public ref readonly StreamSocketServerOptions ServerOptions + { + get { return ref serverOptions; } + } + + /// <inheritdoc /> public override async Task RunAsync(CancellationToken cancellationToken = default) { - connection.Listen(100); + Connection.Listen(100); - for (int i = 0; i < ServerOptions.ConcurrentAcceptCalls; i++) + for (int i = 0; i < serverOptions.ConcurrentAcceptCalls; i++) { SocketAsyncEventArgs acceptArgs = TransmissionArgsPool.Rent(); diff --git a/NetSharp/NetSharpExamples/Program.cs b/NetSharp/NetSharpExamples/Program.cs @@ -1,6 +1,7 @@ #define TCP //#undef TCP +using NetSharp.Packets; using NetSharp.Sockets.Datagram; using NetSharp.Sockets.Stream; using NetSharp.Utils; @@ -14,9 +15,6 @@ using System.Net.Sockets; using System.Text; using System.Threading.Tasks; -using NetworkPacket = NetSharp.Packets.NetworkPacket; -using SocketServer = NetSharp.Sockets.SocketServer; - namespace NetSharpExamples { internal class Program @@ -229,9 +227,9 @@ namespace NetSharpExamples try { #if TCP - using SocketServer server = new StreamSocketServer(AddressFamily.InterNetwork, ProtocolType.Tcp); + using StreamSocketServer server = new StreamSocketServer(AddressFamily.InterNetwork, ProtocolType.Tcp); #else - using SocketServer server = new DatagramSocketServer(AddressFamily.InterNetwork, ProtocolType.Udp); + using DatagramSocketServer server = new DatagramSocketServer(AddressFamily.InterNetwork, ProtocolType.Udp); #endif server.Bind(in ServerEndPoint);