NetSharp

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

commit 8c0479a3b8fa784ba1988e8948a359b927664bf3
parent 8f7f63eb48376ec161dec362707e04dcaed88afc
Author: Mikolaj Lenczewski <33129490+EnderRifter@users.noreply.github.com>
Date:   Fri, 28 Feb 2020 14:30:59 +0000

Added instance builders and basic implementation of a packet pipeline.

Diffstat:
MNetSharp/NetSharp/Connection.cs | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
ANetSharp/NetSharp/ConnectionBuilder.cs | 245+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
DNetSharp/NetSharp/ConnectionFactory.cs | 37-------------------------------------
ANetSharp/NetSharp/Extensions/ConnectionBuilderExtensions.cs | 40++++++++++++++++++++++++++++++++++++++++
MNetSharp/NetSharp/Extensions/ConnectionExtensions.cs | 23+++++++++++++++++++++++
MNetSharp/NetSharp/NetSharp.xml | 242++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
MNetSharp/NetSharp/Packets/NetworkPacket.cs | 2+-
MNetSharp/NetSharp/Pipelines/PacketPipeline.cs | 101++++++++++++++++++++++++++++++++-----------------------------------------------
ANetSharp/NetSharp/Pipelines/PacketPipelineBuilder.cs | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MNetSharp/NetSharp/Sockets/SocketAcceptor.cs | 6++++++
MNetSharp/NetSharp/Sockets/SocketReader.cs | 4++++
MNetSharp/NetSharp/Sockets/SocketWriter.cs | 20++++++++++++++++++++
MNetSharp/NetSharpExamples/Program.cs | 72++++++++++++++++++++++++++++++++++++++++++++----------------------------
13 files changed, 830 insertions(+), 148 deletions(-)

diff --git a/NetSharp/NetSharp/Connection.cs b/NetSharp/NetSharp/Connection.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using NetSharp.Logging; using NetSharp.Packets; +using NetSharp.Pipelines; using NetSharp.Sockets; using NetSharp.Utils; @@ -17,7 +18,9 @@ namespace NetSharp private readonly SocketAcceptor acceptor; private readonly ConcurrentDictionary<EndPoint, Connection> connections; private readonly CancellationTokenSource connectionShutdownTokenSource; + private readonly PacketPipeline<Memory<byte>, Memory<byte>, NetworkPacket> incomingPacketPipeline; private readonly SocketReader listener; + private readonly PacketPipeline<NetworkPacket, Memory<byte>, Memory<byte>> outgoingPacketPipeline; private readonly Socket socket; private readonly SocketWriter transmitter; @@ -29,6 +32,41 @@ namespace NetSharp Dispose(false); } + private void AcceptorWork(object shutdownToken) + { + CancellationToken cancellationToken = (CancellationToken)shutdownToken; + + while (!cancellationToken.IsCancellationRequested) + { + } + } + + private void ListenerWork(object shutdownToken) + { + CancellationToken cancellationToken = (CancellationToken)shutdownToken; + + while (!cancellationToken.IsCancellationRequested) + { + } + } + + private void PacketPipelineWork(object shutdownToken) + { + CancellationToken cancellationToken = (CancellationToken)shutdownToken; + + while (!cancellationToken.IsCancellationRequested) + { + } + } + + /// <summary> + /// Lock synchronisation object for the <see cref="logger"/> variable. + /// </summary> + protected readonly object loggerLockObject = new object(); + + /// <summary> + /// Cancellation token which allows observing the shutdown of the server. It is set when <see cref="Shutdown()"/> is called. + /// </summary> protected readonly CancellationToken ShutdownToken; /// <summary> @@ -49,8 +87,10 @@ namespace NetSharp } internal Connection(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, - int objectPoolSize = 10, bool preallocateBuffers = false, Stream? loggingStream = default, - LogLevel minimumLoggedSeverity = LogLevel.Info) + PacketPipeline<Memory<byte>, Memory<byte>, NetworkPacket> incomingPacketPipeline, + PacketPipeline<NetworkPacket, Memory<byte>, Memory<byte>> outgoingPacketPipeline, + int objectPoolSize = 10, bool preallocateBuffers = false, Stream? loggingStream = default, + LogLevel minimumLoggedSeverity = LogLevel.Info) { connectionShutdownTokenSource = new CancellationTokenSource(); ShutdownToken = connectionShutdownTokenSource.Token; @@ -63,6 +103,9 @@ namespace NetSharp connections = new ConcurrentDictionary<EndPoint, Connection>(); + this.incomingPacketPipeline = incomingPacketPipeline; + this.outgoingPacketPipeline = outgoingPacketPipeline; + logger = new Logger(loggingStream ?? Stream.Null, minimumLoggedSeverity); } @@ -73,9 +116,6 @@ namespace NetSharp GC.SuppressFinalize(this); } - public Task<TransmissionResult> ReceiveAsync(Memory<byte> inputBuffer, SocketFlags flags) - => ReceiveAsync(inputBuffer, flags, Timeout.InfiniteTimeSpan); - public Task<TransmissionResult> ReceiveAsync(Memory<byte> inputBuffer, SocketFlags flags, TimeSpan timeout) { using CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource(timeout); @@ -85,9 +125,6 @@ namespace NetSharp return listener.ReceiveAsync(socket, flags, inputBuffer, cts.Token); } - public Task<TransmissionResult> ReceiveFromAsync(EndPoint remoteEndPoint, Memory<byte> inputBuffer, SocketFlags flags) - => ReceiveFromAsync(remoteEndPoint, inputBuffer, flags, Timeout.InfiniteTimeSpan); - public Task<TransmissionResult> ReceiveFromAsync(EndPoint remoteEndPoint, Memory<byte> inputBuffer, SocketFlags flags, TimeSpan timeout) { using CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource(timeout); @@ -97,8 +134,27 @@ namespace NetSharp return listener.ReceiveFromAsync(socket, remoteEndPoint, flags, inputBuffer, cts.Token); } - public Task<int> SendAsync(Memory<byte> outputBuffer, SocketFlags flags) - => SendAsync(outputBuffer, flags, Timeout.InfiniteTimeSpan); + /// <summary> + /// Makes the connection listen for incoming client request packets, and handle them according to registered packet handler delegates. + /// This work can be cancelled by calling <see cref="Shutdown()"/>. + /// </summary> + /// <returns>The task representing the connection work.</returns> + public Task RunAsync() + { + Task acceptorThread = + Task.Factory.StartNew(AcceptorWork, ShutdownToken, ShutdownToken, TaskCreationOptions.LongRunning, + TaskScheduler.Default); + + Task listenerThread = + Task.Factory.StartNew(ListenerWork, ShutdownToken, ShutdownToken, TaskCreationOptions.LongRunning, + TaskScheduler.Default); + + Task packetPipelineThread = + Task.Factory.StartNew(PacketPipelineWork, ShutdownToken, ShutdownToken, TaskCreationOptions.LongRunning, + TaskScheduler.Default); + + return Task.WhenAll(acceptorThread, listenerThread, packetPipelineThread); + } public Task<int> SendAsync(Memory<byte> outputBuffer, SocketFlags flags, TimeSpan timeout) { @@ -109,9 +165,6 @@ namespace NetSharp return transmitter.SendAsync(socket, flags, outputBuffer, cts.Token); } - public Task<int> SendToAsync(EndPoint remoteEndPoint, Memory<byte> outputBuffer, SocketFlags flags) - => SendToAsync(remoteEndPoint, outputBuffer, flags, Timeout.InfiniteTimeSpan); - public Task<int> SendToAsync(EndPoint remoteEndPoint, Memory<byte> outputBuffer, SocketFlags flags, TimeSpan timeout) { using CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource(timeout); @@ -121,9 +174,26 @@ namespace NetSharp return transmitter.SendToAsync(socket, remoteEndPoint, flags, outputBuffer, cts.Token); } + /// <summary> + /// Configures the logger to log messages to the given stream (or to <see cref="Stream.Null"/> if <c>null</c>) and + /// to only log messages that are of severity <paramref name="minimumLoggedSeverity"/> or higher. + /// </summary> + /// <param name="loggingStream">The stream to which messages will be logged.</param> + /// <param name="minimumLoggedSeverity">The minimum severity a message must be to be logged.</param> public void SetLoggingStream(Stream? loggingStream, LogLevel minimumLoggedSeverity = LogLevel.Info) { - logger = new Logger(loggingStream ?? Stream.Null, minimumLoggedSeverity); + lock (loggerLockObject) + { + logger = new Logger(loggingStream ?? Stream.Null, minimumLoggedSeverity); + } + } + + /// <summary> + /// Shuts down the connection, and releases managed and unmanaged resources. + /// </summary> + public void Shutdown() + { + connectionShutdownTokenSource.Cancel(); } /// <summary> diff --git a/NetSharp/NetSharp/ConnectionBuilder.cs b/NetSharp/NetSharp/ConnectionBuilder.cs @@ -0,0 +1,244 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Sockets; +using NetSharp.Logging; +using NetSharp.Packets; +using NetSharp.Pipelines; + +namespace NetSharp +{ + /// <summary> + /// Allows for configuring and subsequently building a <see cref="Connection"/> instance. + /// </summary> + public sealed class ConnectionBuilder + { + private static readonly LoggingSettings DefaultLoggingSettings = new LoggingSettings(Stream.Null, LogLevel.Warn); + private static readonly PoolingSettings DefaultPoolingSettings = new PoolingSettings(10, false); + + private readonly List<Func<Memory<byte>, Memory<byte>>> incomingPipelineStages = + new List<Func<Memory<byte>, Memory<byte>>>(); + + private readonly List<Func<Memory<byte>, Memory<byte>>> outgoingPipelineStages = + new List<Func<Memory<byte>, Memory<byte>>>(); + + private LoggingSettings? loggingSettings; + private PoolingSettings? poolingSettings; + private SocketSettings? socketSettings; + + /// <summary> + /// The number of stages in the currently configured incoming packet pipeline. + /// </summary> + public int IncomingPacketPipelineStageCount + { + get { return incomingPipelineStages.Count; } + } + + /// <summary> + /// The number of stages in the currently configured outgoing packet pipeline. + /// </summary> + public int OutgoingPacketPipelineStageCount + { + get { return outgoingPipelineStages.Count; } + } + + /// <summary> + /// Returns a new <see cref="Connection"/> instance with the current configuration. + /// </summary> + /// <returns>The configured <see cref="Connection"/> instance.</returns> + /// <exception cref="ArgumentNullException"> + /// Thrown when <see cref="WithSocket"/> has not been called. + /// </exception> + public Connection Build() + { + if (socketSettings == null) + { + throw new ArgumentNullException(nameof(socketSettings), $"{nameof(WithSocket)} has not been called."); + } + + PacketPipelineBuilder<Memory<byte>, Memory<byte>, NetworkPacket> incomingPipelineBuilder = + new PacketPipelineBuilder<Memory<byte>, Memory<byte>, NetworkPacket>(); + + incomingPipelineBuilder.WithInputStage(memory => memory); + foreach (Func<Memory<byte>, Memory<byte>> stage in incomingPipelineStages) + { + incomingPipelineBuilder = incomingPipelineBuilder.WithIntermediateStage(stage); + } + + incomingPipelineBuilder.WithOutputStage(NetworkPacket.Deserialise); + + PacketPipelineBuilder<NetworkPacket, Memory<byte>, Memory<byte>> outgoingPipelineBuilder = + new PacketPipelineBuilder<NetworkPacket, Memory<byte>, Memory<byte>>(); + + outgoingPipelineBuilder.WithInputStage(NetworkPacket.Serialise); + foreach (Func<Memory<byte>, Memory<byte>> stage in outgoingPipelineStages) + { + outgoingPipelineBuilder = outgoingPipelineBuilder.WithIntermediateStage(stage); + } + + outgoingPipelineBuilder.WithOutputStage(memory => memory); + + Connection connection = new Connection( + socketSettings.Value.AddressFamily, + socketSettings.Value.SocketType, + socketSettings.Value.ProtocolType, + incomingPipelineBuilder.Build(), + outgoingPipelineBuilder.Build(), + poolingSettings?.ObjectPoolSize ?? DefaultPoolingSettings.ObjectPoolSize, + poolingSettings?.PreallocateBuffers ?? DefaultPoolingSettings.PreallocateBuffers, + loggingSettings?.LoggingStream ?? DefaultLoggingSettings.LoggingStream, + loggingSettings?.MinimumLevel ?? DefaultLoggingSettings.MinimumLevel); + + return connection; + } + + /// <summary> + /// Adds an extra pipeline stage to the currently configured incoming packet pipeline, at the given index. + /// </summary> + /// <param name="transform"> + /// The transformation that should be applied when a packet passes through the pipeline. + /// </param> + /// <param name="index">The position in the pipeline at which to place the transform.</param> + /// <returns>The builder instance for further configuration.</returns> + public ConnectionBuilder WithIncomingPipelineStage(in Func<Memory<byte>, Memory<byte>> transform, int index) + { + incomingPipelineStages.Insert(index, transform); + return this; + } + + /// <summary> + /// Sets the logging settings for the currently configured connection. + /// </summary> + /// <param name="settings">The logging settings to use.</param> + /// <returns>The builder instance for further configuration.</returns> + public ConnectionBuilder WithLogging(LoggingSettings settings) + { + loggingSettings = settings; + return this; + } + + /// <summary> + /// Adds an extra pipeline stage to the currently configured outgoing packet pipeline, at the given index. + /// </summary> + /// <param name="transform"> + /// The transformation that should be applied when a packet passes through the pipeline. + /// </param> + /// <param name="index">The position in the pipeline at which to place the transform.</param> + /// <returns>The builder instance for further configuration.</returns> + public ConnectionBuilder WithOutgoingPipelineStage(in Func<Memory<byte>, Memory<byte>> transform, int index) + { + outgoingPipelineStages.Insert(index, transform); + return this; + } + + /// <summary> + /// Sets the pooling settings for the currently configured connection. + /// </summary> + /// <param name="settings">The pooling settings to use.</param> + /// <returns>The builder instance for further configuration.</returns> + public ConnectionBuilder WithPooling(PoolingSettings settings) + { + poolingSettings = settings; + return this; + } + + /// <summary> + /// Sets the socket settings for the currently configured connection. + /// </summary> + /// <param name="settings">The socket settings to use.</param> + /// <returns>The builder instance for further configuration.</returns> + public ConnectionBuilder WithSocket(SocketSettings settings) + { + socketSettings = settings; + return this; + } + + /// <summary> + /// Holds settings for configuring a connection's logging. + /// </summary> + public readonly struct LoggingSettings + { + /// <summary> + /// The stream to which messages will be logged. + /// </summary> + public readonly Stream LoggingStream; + + /// <summary> + /// The minimum severity that a log message must have to be recorded. + /// </summary> + public readonly LogLevel MinimumLevel; + + /// <summary> + /// Initialises a new instance of the <see cref="LoggingStream"/> struct. + /// </summary> + /// <param name="stream">The stream to which messages will be logged..</param> + /// <param name="minimumLevel">The minimum severity that a log message must have to be recorded.</param> + public LoggingSettings(Stream stream, LogLevel minimumLevel) + { + LoggingStream = stream; + MinimumLevel = minimumLevel; + } + } + + /// <summary> + /// Holds settings for configuring a connection's buffer pooling. + /// </summary> + public readonly struct PoolingSettings + { + /// <summary> + /// The number of objects that will be held in the object pools. + /// </summary> + public readonly int ObjectPoolSize; + + /// <summary> + /// Whether the buffers for receiving messages should be preallocated. + /// </summary> + public readonly bool PreallocateBuffers; + + /// <summary> + /// Initialises a new instance of the <see cref="PoolingSettings"/> struct. + /// </summary> + /// <param name="poolSize">The number of objects that will be held in the object pools.</param> + /// <param name="preallocateBuffers">Whether the buffers for receiving messages should be preallocated.</param> + public PoolingSettings(int poolSize, bool preallocateBuffers) + { + ObjectPoolSize = poolSize; + PreallocateBuffers = preallocateBuffers; + } + } + + /// <summary> + /// Holds settings fo configuring a connection's underlying socket. + /// </summary> + public readonly struct SocketSettings + { + /// <summary> + /// The address family for the socket underlying the connection. + /// </summary> + public readonly AddressFamily AddressFamily; + + /// <summary> + /// The socket type for the socket underlying the connection. + /// </summary> + public readonly ProtocolType ProtocolType; + + /// <summary> + /// The protocol type for the socket underlying the connection. + /// </summary> + public readonly SocketType SocketType; + + /// <summary> + /// Initialises a new instance of the <see cref="SocketSettings"/> struct. + /// </summary> + /// <param name="addressFamily">The address family for the underlying socket.</param> + /// <param name="socketType">The socket type for the underlying socket.</param> + /// <param name="protocolType">The protocol type for the underlying socket.</param> + public SocketSettings(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) + { + AddressFamily = addressFamily; + SocketType = socketType; + ProtocolType = protocolType; + } + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/ConnectionFactory.cs b/NetSharp/NetSharp/ConnectionFactory.cs @@ -1,36 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Net.Sockets; -using NetSharp.Logging; - -namespace NetSharp -{ - /// <summary> - /// Provides methods to construct and configure <see cref="Connection"/> instances. - /// </summary> - public static class ConnectionFactory - { - public static Connection Init(EndPoint localEndPoint, AddressFamily connectionAddressFamily, - SocketType connectionSocketType, ProtocolType connectionProtocolType, TimeSpan timeout, int objectPoolSize = 10, - bool preallocateBuffers = false, Stream? loggingStream = default, LogLevel minimumLoggedSeverity = LogLevel.Info) - { - Connection connection = new Connection(connectionAddressFamily, connectionSocketType, - connectionProtocolType, objectPoolSize, preallocateBuffers, loggingStream, minimumLoggedSeverity); - - connection.TryBind(localEndPoint, timeout); - - return connection; - } - - public static Connection InitTcp(EndPoint remoteEndPoint, int objectPoolSize = 10, bool preallocateBuffers = false, - Stream? loggingStream = default, LogLevel minimumLoggedSeverity = LogLevel.Info) - => Init(remoteEndPoint, AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp, TimeSpan.FromSeconds(1), - objectPoolSize, preallocateBuffers, loggingStream, minimumLoggedSeverity); - - public static Connection InitUdp(EndPoint remoteEndPoint, int objectPoolSize = 10, bool preallocateBuffers = false, - Stream? loggingStream = default, LogLevel minimumLoggedSeverity = LogLevel.Info) - => Init(remoteEndPoint, AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp, TimeSpan.FromSeconds(1), - objectPoolSize, preallocateBuffers, loggingStream, minimumLoggedSeverity); - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp/Extensions/ConnectionBuilderExtensions.cs b/NetSharp/NetSharp/Extensions/ConnectionBuilderExtensions.cs @@ -0,0 +1,39 @@ +using System; +using System.IO; +using System.Net.Sockets; +using NetSharp.Logging; + +namespace NetSharp.Extensions +{ + /// <summary> + /// Provides additional methods and functionality to the <see cref="ConnectionBuilder"/> class. + /// </summary> + public static class ConnectionBuilderExtensions + { + public static ConnectionBuilder AppendIncomingPipelineStage(this ConnectionBuilder instance, + in Func<Memory<byte>, Memory<byte>> transform) + => instance.WithIncomingPipelineStage(transform, instance.IncomingPacketPipelineStageCount); + + public static ConnectionBuilder AppendOutgoingPipelineStage(this ConnectionBuilder instance, + in Func<Memory<byte>, Memory<byte>> transform) + => instance.WithOutgoingPipelineStage(transform, instance.OutgoingPacketPipelineStageCount); + + public static ConnectionBuilder WithLogging(this ConnectionBuilder instance, + Stream loggingStream, LogLevel minimumLogLevel) + => instance.WithLogging(new ConnectionBuilder.LoggingSettings(loggingStream, minimumLogLevel)); + + public static ConnectionBuilder WithPooling(this ConnectionBuilder instance, + int poolSize, bool preallocateBuffers) + => instance.WithPooling(new ConnectionBuilder.PoolingSettings(poolSize, preallocateBuffers)); + + public static ConnectionBuilder WithSocket(this ConnectionBuilder instance, + AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) + => instance.WithSocket(new ConnectionBuilder.SocketSettings(addressFamily, socketType, protocolType)); + + public static ConnectionBuilder WithTcp(this ConnectionBuilder instance) + => instance.WithSocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + public static ConnectionBuilder WithUdp(this ConnectionBuilder instance) + => instance.WithSocket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Extensions/ConnectionExtensions.cs b/NetSharp/NetSharp/Extensions/ConnectionExtensions.cs @@ -12,5 +12,28 @@ namespace NetSharp.Extensions /// </summary> public static class ConnectionExtensions { + public static Task<TransmissionResult> ReceiveAsync(this Connection instance, + Memory<byte> inputBuffer, SocketFlags flags) + => instance.ReceiveAsync(inputBuffer, flags, Timeout.InfiniteTimeSpan); + + public static Task<TransmissionResult> ReceiveFromAsync(this Connection instance, + EndPoint remoteEndPoint, Memory<byte> inputBuffer, SocketFlags flags) + => instance.ReceiveFromAsync(remoteEndPoint, inputBuffer, flags, Timeout.InfiniteTimeSpan); + + public static Task<int> SendAsync(this Connection instance, + Memory<byte> outputBuffer, SocketFlags flags) + => instance.SendAsync(outputBuffer, flags, Timeout.InfiniteTimeSpan); + + public static Task<int> SendToAsync(this Connection instance, + EndPoint remoteEndPoint, Memory<byte> outputBuffer, SocketFlags flags) + => instance.SendToAsync(remoteEndPoint, outputBuffer, flags, Timeout.InfiniteTimeSpan); + + public static bool TryBind(this Connection instance, + EndPoint localEndPoint) + => instance.TryBind(localEndPoint, Timeout.InfiniteTimeSpan); + + public static Task<bool> TryBindAsync(this Connection instance, + EndPoint localEndPoint) + => instance.TryBindAsync(localEndPoint, Timeout.InfiniteTimeSpan); } } \ No newline at end of file diff --git a/NetSharp/NetSharp/NetSharp.xml b/NetSharp/NetSharp/NetSharp.xml @@ -9,6 +9,16 @@ Destroys a <see cref="T:NetSharp.Connection"/> class instance, freeing all managed resources. </summary> </member> + <member name="F:NetSharp.Connection.loggerLockObject"> + <summary> + Lock synchronisation object for the <see cref="F:NetSharp.Connection.logger"/> variable. + </summary> + </member> + <member name="F:NetSharp.Connection.ShutdownToken"> + <summary> + Cancellation token which allows observing the shutdown of the server. It is set when <see cref="M:NetSharp.Connection.Shutdown"/> is called. + </summary> + </member> <member name="F:NetSharp.Connection.logger"> <summary> A logger object allowing for writing debug messages to an output stream. @@ -23,6 +33,26 @@ <member name="M:NetSharp.Connection.Dispose"> <inheritdoc /> </member> + <member name="M:NetSharp.Connection.RunAsync"> + <summary> + Makes the connection listen for incoming client request packets, and handle them according to registered packet handler delegates. + This work can be cancelled by calling <see cref="M:NetSharp.Connection.Shutdown"/>. + </summary> + <returns>The task representing the connection work.</returns> + </member> + <member name="M:NetSharp.Connection.SetLoggingStream(System.IO.Stream,NetSharp.Logging.LogLevel)"> + <summary> + Configures the logger to log messages to the given stream (or to <see cref="F:System.IO.Stream.Null"/> if <c>null</c>) and + to only log messages that are of severity <paramref name="minimumLoggedSeverity"/> or higher. + </summary> + <param name="loggingStream">The stream to which messages will be logged.</param> + <param name="minimumLoggedSeverity">The minimum severity a message must be to be logged.</param> + </member> + <member name="M:NetSharp.Connection.Shutdown"> + <summary> + Shuts down the connection, and releases managed and unmanaged resources. + </summary> + </member> <member name="M:NetSharp.Connection.TryBind(System.Net.EndPoint,System.TimeSpan)"> <summary> Attempts to synchronously bind the underlying socket to the given local endpoint. Blocks. @@ -41,11 +71,143 @@ <param name="timeout">The timeout within which to attempt the binding.</param> <returns>Whether the binding was successful or not.</returns> </member> - <member name="T:NetSharp.ConnectionFactory"> + <member name="T:NetSharp.ConnectionBuilder"> + <summary> + Allows for configuring and subsequently building a <see cref="T:NetSharp.Connection"/> instance. + </summary> + </member> + <member name="P:NetSharp.ConnectionBuilder.IncomingPacketPipelineStageCount"> + <summary> + The number of stages in the currently configured incoming packet pipeline. + </summary> + </member> + <member name="P:NetSharp.ConnectionBuilder.OutgoingPacketPipelineStageCount"> + <summary> + The number of stages in the currently configured outgoing packet pipeline. + </summary> + </member> + <member name="M:NetSharp.ConnectionBuilder.Build"> + <summary> + Returns a new <see cref="T:NetSharp.Connection"/> instance with the current configuration. + </summary> + <returns>The configured <see cref="T:NetSharp.Connection"/> instance.</returns> + <exception cref="T:System.ArgumentNullException"> + Thrown when <see cref="M:NetSharp.ConnectionBuilder.WithSocket(NetSharp.ConnectionBuilder.SocketSettings)"/> has not been called. + </exception> + </member> + <member name="M:NetSharp.ConnectionBuilder.WithIncomingPipelineStage(System.Func{System.Memory{System.Byte},System.Memory{System.Byte}}@,System.Int32)"> + <summary> + Adds an extra pipeline stage to the currently configured incoming packet pipeline, at the given index. + </summary> + <param name="transform"> + The transformation that should be applied when a packet passes through the pipeline. + </param> + <param name="index">The position in the pipeline at which to place the transform.</param> + <returns>The builder instance for further configuration.</returns> + </member> + <member name="M:NetSharp.ConnectionBuilder.WithLogging(NetSharp.ConnectionBuilder.LoggingSettings)"> + <summary> + Sets the logging settings for the currently configured connection. + </summary> + <param name="settings">The logging settings to use.</param> + <returns>The builder instance for further configuration.</returns> + </member> + <member name="M:NetSharp.ConnectionBuilder.WithOutgoingPipelineStage(System.Func{System.Memory{System.Byte},System.Memory{System.Byte}}@,System.Int32)"> + <summary> + Adds an extra pipeline stage to the currently configured outgoing packet pipeline, at the given index. + </summary> + <param name="transform"> + The transformation that should be applied when a packet passes through the pipeline. + </param> + <param name="index">The position in the pipeline at which to place the transform.</param> + <returns>The builder instance for further configuration.</returns> + </member> + <member name="M:NetSharp.ConnectionBuilder.WithPooling(NetSharp.ConnectionBuilder.PoolingSettings)"> + <summary> + Sets the pooling settings for the currently configured connection. + </summary> + <param name="settings">The pooling settings to use.</param> + <returns>The builder instance for further configuration.</returns> + </member> + <member name="M:NetSharp.ConnectionBuilder.WithSocket(NetSharp.ConnectionBuilder.SocketSettings)"> + <summary> + Sets the socket settings for the currently configured connection. + </summary> + <param name="settings">The socket settings to use.</param> + <returns>The builder instance for further configuration.</returns> + </member> + <member name="T:NetSharp.ConnectionBuilder.LoggingSettings"> + <summary> + Holds settings for configuring a connection's logging. + </summary> + </member> + <member name="F:NetSharp.ConnectionBuilder.LoggingSettings.LoggingStream"> + <summary> + The stream to which messages will be logged. + </summary> + </member> + <member name="F:NetSharp.ConnectionBuilder.LoggingSettings.MinimumLevel"> + <summary> + The minimum severity that a log message must have to be recorded. + </summary> + </member> + <member name="M:NetSharp.ConnectionBuilder.LoggingSettings.#ctor(System.IO.Stream,NetSharp.Logging.LogLevel)"> + <summary> + Initialises a new instance of the <see cref="F:NetSharp.ConnectionBuilder.LoggingSettings.LoggingStream"/> struct. + </summary> + <param name="stream">The stream to which messages will be logged..</param> + <param name="minimumLevel">The minimum severity that a log message must have to be recorded.</param> + </member> + <member name="T:NetSharp.ConnectionBuilder.PoolingSettings"> <summary> - Provides methods to construct and configure <see cref="T:NetSharp.Connection"/> instances. + Holds settings for configuring a connection's buffer pooling. </summary> </member> + <member name="F:NetSharp.ConnectionBuilder.PoolingSettings.ObjectPoolSize"> + <summary> + The number of objects that will be held in the object pools. + </summary> + </member> + <member name="F:NetSharp.ConnectionBuilder.PoolingSettings.PreallocateBuffers"> + <summary> + Whether the buffers for receiving messages should be preallocated. + </summary> + </member> + <member name="M:NetSharp.ConnectionBuilder.PoolingSettings.#ctor(System.Int32,System.Boolean)"> + <summary> + Initialises a new instance of the <see cref="T:NetSharp.ConnectionBuilder.PoolingSettings"/> struct. + </summary> + <param name="poolSize">The number of objects that will be held in the object pools.</param> + <param name="preallocateBuffers">Whether the buffers for receiving messages should be preallocated.</param> + </member> + <member name="T:NetSharp.ConnectionBuilder.SocketSettings"> + <summary> + Holds settings fo configuring a connection's underlying socket. + </summary> + </member> + <member name="F:NetSharp.ConnectionBuilder.SocketSettings.AddressFamily"> + <summary> + The address family for the socket underlying the connection. + </summary> + </member> + <member name="F:NetSharp.ConnectionBuilder.SocketSettings.ProtocolType"> + <summary> + The socket type for the socket underlying the connection. + </summary> + </member> + <member name="F:NetSharp.ConnectionBuilder.SocketSettings.SocketType"> + <summary> + The protocol type for the socket underlying the connection. + </summary> + </member> + <member name="M:NetSharp.ConnectionBuilder.SocketSettings.#ctor(System.Net.Sockets.AddressFamily,System.Net.Sockets.SocketType,System.Net.Sockets.ProtocolType)"> + <summary> + Initialises a new instance of the <see cref="T:NetSharp.ConnectionBuilder.SocketSettings"/> struct. + </summary> + <param name="addressFamily">The address family for the underlying socket.</param> + <param name="socketType">The socket type for the underlying socket.</param> + <param name="protocolType">The protocol type for the underlying socket.</param> + </member> <member name="T:NetSharp.Deprecated.Client"> <summary> Provides methods for connecting to and talking with a <see cref="T:NetSharp.Deprecated.IServer"/> instance. @@ -1133,6 +1295,11 @@ <member name="P:NetSharp.Deprecated.UdpSocketOptions.UseLoopback"> <inheritdoc /> </member> + <member name="T:NetSharp.Extensions.ConnectionBuilderExtensions"> + <summary> + Provides additional methods and functionality to the <see cref="T:NetSharp.ConnectionBuilder"/> class. + </summary> + </member> <member name="T:NetSharp.Extensions.ConnectionExtensions"> <summary> Provides additional methods and functionality to the <see cref="T:NetSharp.Connection"/> class. @@ -1727,14 +1894,73 @@ The custom type id that the decorated packet type should have. This overrides the automatically generated id. </summary> </member> - <member name="P:NetSharp.Pipelines.PacketPipelineStage`2.StageInput"> - <inheritdoc /> + <member name="T:NetSharp.Pipelines.PacketPipeline`3"> + <summary> + Represents a pipeline of transformations that packets must undergo. + </summary> + <typeparam name="TInput">The type of packet the pipeline receives.</typeparam> + <typeparam name="TIntermediate">The type of packet the pipeline internally handles.</typeparam> + <typeparam name="TOutput">The type of packet the pipeline outputs.</typeparam> </member> - <member name="P:NetSharp.Pipelines.PacketPipelineStage`2.StageOutput"> - <inheritdoc /> + <member name="M:NetSharp.Pipelines.PacketPipeline`3.ProcessPacket(`0)"> + <summary> + Passes the given packet through the pipeline. + </summary> + <param name="inputPacket">The incoming packet.</param> + <returns>The outgoing transformed packet.</returns> </member> - <member name="M:NetSharp.Pipelines.PacketPipelineStage`2.Process(`0)"> - <inheritdoc /> + <member name="T:NetSharp.Pipelines.PacketPipelineStage`2"> + <summary> + Represents a single transformation applied to a packet traveling through the pipeline. + </summary> + <typeparam name="TInput">The type the transformation takes as input.</typeparam> + <typeparam name="TOutput">The type the transformation produces as output.</typeparam> + </member> + <member name="T:NetSharp.Pipelines.PacketPipelineBuilder`3"> + <summary> + Allows for configuring and subsequently building a <see cref="T:NetSharp.Pipelines.PacketPipeline`3"/> instance. + </summary> + <typeparam name="TInput">The type of packet that will be submitted to the pipeline.</typeparam> + <typeparam name="TIntermediate">The type of packet that will be handled internally by the pipeline.</typeparam> + <typeparam name="TOutput">The type of packet that will be requested from the pipeline.</typeparam> + </member> + <member name="M:NetSharp.Pipelines.PacketPipelineBuilder`3.Build"> + <summary> + Returns the currently configured <see cref="T:NetSharp.Pipelines.PacketPipeline`3"/> instance. + </summary> + <returns>The configured <see cref="T:NetSharp.Pipelines.PacketPipeline`3"/> instance.</returns> + <exception cref="T:System.ArgumentNullException"> + Thrown when either <see cref="M:NetSharp.Pipelines.PacketPipelineBuilder`3.WithInputStage(System.Func{`0,`1}@)"/> or <see cref="M:NetSharp.Pipelines.PacketPipelineBuilder`3.WithOutputStage(System.Func{`1,`2}@)"/> have not been called. + </exception> + </member> + <member name="M:NetSharp.Pipelines.PacketPipelineBuilder`3.WithInputStage(System.Func{`0,`1}@)"> + <summary> + Configures the input stage for the pipeline. + </summary> + <param name="stage"> + The transformation that should be applied to incoming packets, to convert them from the <typeparamref name="TInput"/> + type to the <typeparamref name="TIntermediate"/> type that the pipeline handles internally. + </param> + <returns>The builder instance for further configuration.</returns> + </member> + <member name="M:NetSharp.Pipelines.PacketPipelineBuilder`3.WithIntermediateStage(System.Func{`1,`1}@)"> + <summary> + Adds the given intermediate stage to the pipeline. + </summary> + <param name="stage"> + The transformation that should be applied to packets traveling through the pipeline. + </param> + <returns>The builder instance for further configuration.</returns> + </member> + <member name="M:NetSharp.Pipelines.PacketPipelineBuilder`3.WithOutputStage(System.Func{`1,`2}@)"> + <summary> + Configures the output stage for the pipeline. + </summary> + <param name="stage"> + The transformation that should be applied to outgoing packets, to convert them from the + <typeparamref name="TIntermediate"/> type used internally to the <typeparamref name="TOutput"/> type. + </param> + <returns>The builder instance for further configuration.</returns> </member> <member name="T:NetSharp.Sockets.SocketAcceptor"> <summary> diff --git a/NetSharp/NetSharp/Packets/NetworkPacket.cs b/NetSharp/NetSharp/Packets/NetworkPacket.cs @@ -41,7 +41,7 @@ namespace NetSharp.Packets /// <summary> /// The size of each packet, including its header, footer, and data segment. /// </summary> - public const int PacketSize = 1024; + public const int PacketSize = 4096; /// <summary> /// The data held in this packet. diff --git a/NetSharp/NetSharp/Pipelines/PacketPipeline.cs b/NetSharp/NetSharp/Pipelines/PacketPipeline.cs @@ -1,84 +1,65 @@ using System; using System.Collections.Generic; -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; -using NetSharp.Packets; +using System.Linq; namespace NetSharp.Pipelines { - public interface IPacketPipelineStage<TInput, TOutput> - { - ChannelReader<TInput> StageInput { get; } - - ChannelWriter<TOutput> StageOutput { get; } - - TOutput Process(TInput input); - } - + /// <summary> + /// Represents a pipeline of transformations that packets must undergo. + /// </summary> + /// <typeparam name="TInput">The type of packet the pipeline receives.</typeparam> + /// <typeparam name="TIntermediate">The type of packet the pipeline internally handles.</typeparam> + /// <typeparam name="TOutput">The type of packet the pipeline outputs.</typeparam> // TODO: Implement a packet pipeline, with multiple transform stages to allow encryption, compression, and various other bytewise manipulation stages. - public readonly struct PacketPipeline<TInput, TIntermediate, TOutput> + internal readonly struct PacketPipeline<TInput, TIntermediate, TOutput> { - private readonly IPacketPipelineStage<TIntermediate, TOutput> finalPipelineStage; - private readonly IPacketPipelineStage<TInput, TIntermediate> initialPipelineStage; - private readonly IEnumerable<IPacketPipelineStage<TIntermediate, TIntermediate>> intermediatePipelineStages; - private readonly Channel<TInput> pipelineInput; - private readonly Channel<TOutput> pipelineOutput; - private readonly CancellationToken pipelineShutdownToken; + private readonly PacketPipelineStage<TInput, TIntermediate> pipelineInputStage; + private readonly IReadOnlyCollection<PacketPipelineStage<TIntermediate, TIntermediate>> pipelineIntermediateStages; + private readonly PacketPipelineStage<TIntermediate, TOutput> pipelineOutputStage; - internal PacketPipeline(CancellationToken shutdownToken, - IPacketPipelineStage<TInput, TIntermediate> firstStage, - IPacketPipelineStage<TIntermediate, TOutput> lastStage, - IEnumerable<IPacketPipelineStage<TIntermediate, TIntermediate>> intermediateStages) + internal PacketPipeline( + PacketPipelineStage<TInput, TIntermediate> firstStage, + PacketPipelineStage<TIntermediate, TOutput> lastStage, + IReadOnlyCollection<PacketPipelineStage<TIntermediate, TIntermediate>> intermediateStages) { - pipelineShutdownToken = shutdownToken; - - UnboundedChannelOptions inputOptions = new UnboundedChannelOptions - { - SingleReader = true, - SingleWriter = true, - }; - pipelineInput = Channel.CreateUnbounded<TInput>(inputOptions); - - UnboundedChannelOptions outputOptions = new UnboundedChannelOptions - { - SingleReader = true, - SingleWriter = true, - }; - pipelineOutput = Channel.CreateUnbounded<TOutput>(outputOptions); + pipelineInputStage = firstStage; + pipelineOutputStage = lastStage; - initialPipelineStage = firstStage; - finalPipelineStage = lastStage; - - intermediatePipelineStages = intermediateStages; - foreach (IPacketPipelineStage<TIntermediate, TIntermediate> stage in intermediatePipelineStages) - { - } + pipelineIntermediateStages = intermediateStages; } - public async Task<TOutput> DequeuePacketAsync() + /// <summary> + /// Passes the given packet through the pipeline. + /// </summary> + /// <param name="inputPacket">The incoming packet.</param> + /// <returns>The outgoing transformed packet.</returns> + internal TOutput ProcessPacket(TInput inputPacket) { - return await pipelineOutput.Reader.ReadAsync(pipelineShutdownToken); - } + TIntermediate intermediatePacket = pipelineInputStage.Process(inputPacket); - public async Task EnqueuePacketAsync(TInput pipelineInput) - { - await this.pipelineInput.Writer.WriteAsync(pipelineInput, pipelineShutdownToken); + intermediatePacket = pipelineIntermediateStages.Aggregate(intermediatePacket, (current, stage) => stage.Process(current)); + + return pipelineOutputStage.Process(intermediatePacket); } } - public readonly struct PacketPipelineStage<TInput, TOutput> : IPacketPipelineStage<TInput, TOutput> + /// <summary> + /// Represents a single transformation applied to a packet traveling through the pipeline. + /// </summary> + /// <typeparam name="TInput">The type the transformation takes as input.</typeparam> + /// <typeparam name="TOutput">The type the transformation produces as output.</typeparam> + internal readonly struct PacketPipelineStage<TInput, TOutput> { - /// <inheritdoc /> - public ChannelReader<TInput> StageInput { get; } + private readonly Func<TInput, TOutput> stageDelegate; - /// <inheritdoc /> - public ChannelWriter<TOutput> StageOutput { get; } + internal PacketPipelineStage(in Func<TInput, TOutput> stageProcessingDelegate) + { + stageDelegate = stageProcessingDelegate; + } - /// <inheritdoc /> - public TOutput Process(TInput input) + internal TOutput Process(TInput input) { - throw new NotImplementedException(); + return stageDelegate(input); } } } \ No newline at end of file diff --git a/NetSharp/NetSharp/Pipelines/PacketPipelineBuilder.cs b/NetSharp/NetSharp/Pipelines/PacketPipelineBuilder.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; + +namespace NetSharp.Pipelines +{ + /// <summary> + /// Allows for configuring and subsequently building a <see cref="PacketPipeline{TInput,TIntermediate,TOutput}"/> instance. + /// </summary> + /// <typeparam name="TInput">The type of packet that will be submitted to the pipeline.</typeparam> + /// <typeparam name="TIntermediate">The type of packet that will be handled internally by the pipeline.</typeparam> + /// <typeparam name="TOutput">The type of packet that will be requested from the pipeline.</typeparam> + internal sealed class PacketPipelineBuilder<TInput, TIntermediate, TOutput> + { + private readonly List<PacketPipelineStage<TIntermediate, TIntermediate>> intermediateStages; + + private PacketPipelineStage<TInput, TIntermediate>? inputStage; + private PacketPipelineStage<TIntermediate, TOutput>? outputStage; + + internal PacketPipelineBuilder() + { + intermediateStages = new List<PacketPipelineStage<TIntermediate, TIntermediate>>(); + } + + /// <summary> + /// Returns the currently configured <see cref="PacketPipeline{TInput,TIntermediate,TOutput}"/> instance. + /// </summary> + /// <returns>The configured <see cref="PacketPipeline{TInput,TIntermediate,TOutput}"/> instance.</returns> + /// <exception cref="ArgumentNullException"> + /// Thrown when either <see cref="WithInputStage"/> or <see cref="WithOutputStage"/> have not been called. + /// </exception> + internal PacketPipeline<TInput, TIntermediate, TOutput> Build() + { + if (inputStage == null) + { + throw new ArgumentNullException(nameof(inputStage), $"{nameof(WithInputStage)} has not been called."); + } + + if (outputStage == null) + { + throw new ArgumentNullException(nameof(outputStage), $"{nameof(WithOutputStage)} has not been called."); + } + + return new PacketPipeline<TInput, TIntermediate, TOutput>(inputStage.Value, outputStage.Value, intermediateStages); + } + + /// <summary> + /// Configures the input stage for the pipeline. + /// </summary> + /// <param name="stage"> + /// The transformation that should be applied to incoming packets, to convert them from the <typeparamref name="TInput"/> + /// type to the <typeparamref name="TIntermediate"/> type that the pipeline handles internally. + /// </param> + /// <returns>The builder instance for further configuration.</returns> + internal PacketPipelineBuilder<TInput, TIntermediate, TOutput> WithInputStage(in Func<TInput, TIntermediate> stage) + { + inputStage = new PacketPipelineStage<TInput, TIntermediate>(in stage); + return this; + } + + /// <summary> + /// Adds the given intermediate stage to the pipeline. + /// </summary> + /// <param name="stage"> + /// The transformation that should be applied to packets traveling through the pipeline. + /// </param> + /// <returns>The builder instance for further configuration.</returns> + internal PacketPipelineBuilder<TInput, TIntermediate, TOutput> WithIntermediateStage(in Func<TIntermediate, TIntermediate> stage) + { + intermediateStages.Add(new PacketPipelineStage<TIntermediate, TIntermediate>(in stage)); + return this; + } + + /// <summary> + /// Configures the output stage for the pipeline. + /// </summary> + /// <param name="stage"> + /// The transformation that should be applied to outgoing packets, to convert them from the + /// <typeparamref name="TIntermediate"/> type used internally to the <typeparamref name="TOutput"/> type. + /// </param> + /// <returns>The builder instance for further configuration.</returns> + internal PacketPipelineBuilder<TInput, TIntermediate, TOutput> WithOutputStage(in Func<TIntermediate, TOutput> stage) + { + outputStage = new PacketPipelineStage<TIntermediate, TOutput>(in stage); + return this; + } + } +} +\ No newline at end of file diff --git a/NetSharp/NetSharp/Sockets/SocketAcceptor.cs b/NetSharp/NetSharp/Sockets/SocketAcceptor.cs @@ -177,6 +177,7 @@ namespace NetSharp.Sockets SocketAsyncEventArgs args = acceptAsyncEventArgsPool.Get(); args.UserToken = new AsyncAcceptToken(tcs, cancellationToken); + /* // register cleanup action for when the cancellation token is thrown cancellationToken.Register(() => { @@ -190,6 +191,7 @@ namespace NetSharp.Sockets newArgs.Completed += HandleIOCompleted; acceptAsyncEventArgsPool.Return(newArgs); }); + */ // if the accept operation doesn't complete synchronously, return the awaitable task if (socket.AcceptAsync(args)) return tcs.Task; @@ -215,6 +217,7 @@ namespace NetSharp.Sockets args.RemoteEndPoint = remoteEndPoint; args.UserToken = new AsyncConnectToken(tcs, cancellationToken); + /* // register cleanup action for when the cancellation token is thrown cancellationToken.Register(() => { @@ -228,6 +231,7 @@ namespace NetSharp.Sockets newArgs.Completed += HandleIOCompleted; connectAsyncEventArgsPool.Return(newArgs); }); + */ // if the connect operation doesn't complete synchronously, return the awaitable task if (socket.ConnectAsync(args)) return tcs.Task; @@ -249,6 +253,7 @@ namespace NetSharp.Sockets SocketAsyncEventArgs args = connectAsyncEventArgsPool.Get(); args.UserToken = new AsyncDisconnectToken(tcs, cancellationToken); + /* // register cleanup action for when the cancellation token is thrown cancellationToken.Register(() => { @@ -262,6 +267,7 @@ namespace NetSharp.Sockets newArgs.Completed += HandleIOCompleted; disconnectAsyncEventArgsPool.Return(newArgs); }); + */ // if the disconnect operation doesn't complete synchronously, return the awaitable task if (socket.DisconnectAsync(args)) return tcs.Task; diff --git a/NetSharp/NetSharp/Sockets/SocketReader.cs b/NetSharp/NetSharp/Sockets/SocketReader.cs @@ -145,6 +145,7 @@ namespace NetSharp.Sockets args.SocketFlags = socketFlags; args.UserToken = new AsyncReadToken(rentedReceiveBuffer, inputBuffer, tcs, cancellationToken); + /* // register cleanup action for when the cancellation token is thrown cancellationToken.Register(() => { @@ -160,6 +161,7 @@ namespace NetSharp.Sockets newArgs.Completed += HandleIOCompleted; receiveAsyncEventArgsPool.Return(newArgs); }); + */ // if the receive operation doesn't complete synchronously, returns the awaitable task if (socket.ReceiveAsync(args)) return tcs.Task; @@ -197,6 +199,7 @@ namespace NetSharp.Sockets args.RemoteEndPoint = remoteEndPoint; args.UserToken = new AsyncReadToken(rentedReceiveFromBuffer, inputBuffer, tcs, cancellationToken); + /* // register cleanup action for when the cancellation token is thrown cancellationToken.Register(() => { @@ -212,6 +215,7 @@ namespace NetSharp.Sockets newArgs.Completed += HandleIOCompleted; receiveAsyncEventArgsPool.Return(newArgs); }); + */ // if the receive operation doesn't complete synchronously, returns the awaitable task if (socket.ReceiveFromAsync(args)) return tcs.Task; diff --git a/NetSharp/NetSharp/Sockets/SocketWriter.cs b/NetSharp/NetSharp/Sockets/SocketWriter.cs @@ -134,6 +134,24 @@ namespace NetSharp.Sockets args.SocketFlags = socketFlags; args.UserToken = new AsyncWriteToken(rentedSendBuffer, tcs, cancellationToken); + /* + // register cleanup action for when the cancellation token is thrown + cancellationToken.Register(() => + { + tcs.SetCanceled(); + + sendBufferPool.Return(rentedSendBuffer, true); + + //TODO this is probably a hideous solution. find a better one + args.Completed -= HandleIOCompleted; + args.Dispose(); + + SocketAsyncEventArgs newArgs = new SocketAsyncEventArgs(); + newArgs.Completed += HandleIOCompleted; + sendAsyncEventArgsPool.Return(newArgs); + }); + */ + // if the send operation doesn't complete synchronously, return the awaitable task if (socket.SendAsync(args)) return tcs.Task; @@ -170,6 +188,7 @@ namespace NetSharp.Sockets args.RemoteEndPoint = remoteEndPoint; args.UserToken = new AsyncWriteToken(rentedSendToBuffer, tcs, cancellationToken); + /* // register cleanup action for when the cancellation token is thrown cancellationToken.Register(() => { @@ -185,6 +204,7 @@ namespace NetSharp.Sockets newArgs.Completed += HandleIOCompleted; sendAsyncEventArgsPool.Return(newArgs); }); + */ // if the send operation doesn't complete synchronously, return the awaitable task if (socket.SendToAsync(args)) return tcs.Task; diff --git a/NetSharp/NetSharpExamples/Program.cs b/NetSharp/NetSharpExamples/Program.cs @@ -4,8 +4,10 @@ using System.IO; using System.Net; using System.Net.Sockets; using System.Text; +using System.Threading; using System.Threading.Tasks; using NetSharp; +using NetSharp.Extensions; using NetSharp.Packets; using NetSharp.Utils; @@ -25,19 +27,8 @@ namespace NetSharpExamples serverAddress = IPAddress.Loopback; serverPort = 12374; - Console.WriteLine("Test server (y/n): "); - if (Console.ReadLine()?.ToLower().Equals("y") ?? false) - { - await TestServer(); - - Console.ReadLine(); - } - else - { - await TestClient(); - - Console.ReadLine(); - } + await Task.Factory.StartNew(TestServer); + await Task.Factory.StartNew(TestClient).Result; } private static async Task TestClient() @@ -45,13 +36,14 @@ namespace NetSharpExamples TimeSpan socketTimeout = TimeSpan.FromSeconds(newtorkTimeout); const int clientCount = 10; - const int sentPacketCount = 1_000_000; + const long sentPacketCount = 10_000; EndPoint serverEndPoint = new IPEndPoint(serverAddress, serverPort); + ConnectionBuilder clientBuilder = new ConnectionBuilder().WithUdp(); - static Connection ClientFactory() + Connection ClientFactory() { - return ConnectionFactory.InitUdp(new IPEndPoint(IPAddress.Loopback, 0)); + return clientBuilder.Build(); } Console.WriteLine($"Testing client connections..."); @@ -63,7 +55,9 @@ namespace NetSharpExamples Console.WriteLine($"Starting client {clientId}"); using Connection client = ClientFactory(); - TimeSpan timeout = TimeSpan.FromMilliseconds(1); + client.TryBind(new IPEndPoint(IPAddress.Any, 0)); + //TimeSpan timeout = TimeSpan.FromMilliseconds(100); + Stopwatch stopwatch = new Stopwatch(); byte[] message = Encoding.UTF8.GetBytes("Hello World!"); Memory<byte> messageBuffer = new Memory<byte>(message); @@ -71,18 +65,38 @@ namespace NetSharpExamples byte[] response = new byte[NetworkPacket.PacketSize]; Memory<byte> responseBuffer = new Memory<byte>(response); + long sentPackets = 0, receivedPackets = 0; for (int j = 0; j < sentPacketCount; j++) { - int sentBytes = await client.SendToAsync(serverEndPoint, messageBuffer, SocketFlags.None, timeout); - - Console.WriteLine($"[Client {clientId}] Sent {sentBytes} bytes to {serverEndPoint}"); - - TransmissionResult result = await client.ReceiveFromAsync(serverEndPoint, responseBuffer, SocketFlags.None, timeout); + try + { + stopwatch.Start(); + int sentBytes = await client.SendToAsync(serverEndPoint, messageBuffer, SocketFlags.None); + stopwatch.Stop(); + Interlocked.Increment(ref sentPackets); + + //Console.WriteLine($"[Client {clientId}] Sent {sentBytes} bytes to {serverEndPoint}"); + + stopwatch.Start(); + TransmissionResult result = await client.ReceiveFromAsync(serverEndPoint, responseBuffer, SocketFlags.None); + stopwatch.Stop(); + Interlocked.Increment(ref receivedPackets); + + //Console.WriteLine($"[Client {clientId}] Received {result.Count} bytes from {result.RemoteEndPoint}"); + + //await Task.Delay(10); + } + catch (Exception ex) + { + Console.WriteLine($"[Client {clientId}] Exception: {ex}"); + } + } - Console.WriteLine($"[Client {clientId}] Received {result.Count} bytes from {result.RemoteEndPoint}"); + long millis = stopwatch.ElapsedMilliseconds; + double megabytes = sentPackets * NetworkPacket.DataSegmentSize / 1_000_000.0; - //await Task.Delay(10); - } + Console.WriteLine($"[Client {clientId}] Sent {sentPacketCount} packets to {serverEndPoint} in {millis} milliseconds"); + Console.WriteLine($"[Client {clientId}] Approximate bandwidth: {megabytes / (millis / 1000.0):F3} MBps"); }, i, TaskCreationOptions.LongRunning); } @@ -96,8 +110,10 @@ namespace NetSharpExamples await using Stream serverOutputStream = File.OpenWrite(serverLogFile); EndPoint serverEndPoint = new IPEndPoint(serverAddress, serverPort); + ConnectionBuilder serverBuilder = new ConnectionBuilder().WithUdp(); - using Connection server = ConnectionFactory.InitUdp(serverEndPoint); + using Connection server = serverBuilder.Build(); + server.TryBind(serverEndPoint); //server.SetLoggingStream(Console.OpenStandardOutput(), LogLevel.Info); //server.ChangeLoggingStream(serverOutputStream, LogLevel.Error); @@ -113,11 +129,11 @@ namespace NetSharpExamples TransmissionResult result = await server.ReceiveFromAsync(nullEndPoint, requestBuffer, SocketFlags.None); - Console.WriteLine($"[Server] Received {result.Count} bytes from {result.RemoteEndPoint}"); + //Console.WriteLine($"[Server] Received {result.Count} bytes from {result.RemoteEndPoint}"); int sentBytes = await server.SendToAsync(result.RemoteEndPoint, requestBuffer, SocketFlags.None); - Console.WriteLine($"[Server] Sent {sentBytes} bytes tp {result.RemoteEndPoint}"); + //Console.WriteLine($"[Server] Sent {sentBytes} bytes tp {result.RemoteEndPoint}"); } Console.WriteLine("Server stopped");