commit a4d3c32780db721b39a7758631263c71634b7a6e parent c99bb22307a46534908cf8aaeda464330be6a588 Author: Mikolaj Lenczewski <mikolaj.lenczewski308@gmail.com> Date: Sat, 12 Dec 2020 23:38:56 +0000 Rewrote stream connection. Now getting stack overflows due to recursion, where before there was no problem and the same architecture. Need to somehow convert to iteration (how tf do i get event-driven stuff in a for loop?!?) Diffstat:
62 files changed, 2074 insertions(+), 4618 deletions(-)
diff --git a/NetSharp/NetSharp.Benchmarks/AssemblyAttributes.cs b/NetSharp/NetSharp.Benchmarks/AssemblyAttributes.cs @@ -1,5 +1,5 @@ -using System.Resources; +using System; +using System.Resources; - - -[assembly: NeutralResourcesLanguage("en")] -\ No newline at end of file +[assembly: CLSCompliant(false)] +[assembly: NeutralResourcesLanguage("en")] diff --git a/NetSharp/NetSharp.Benchmarks/BenchmarkHelper.cs b/NetSharp/NetSharp.Benchmarks/BenchmarkHelper.cs @@ -1,97 +0,0 @@ -using System; -using System.Diagnostics; - -namespace NetSharp.Benchmarks -{ - public sealed class BenchmarkHelper - { - private readonly Stopwatch stopwatch = new Stopwatch(); - private long lastTicksSnapshot = 0, lastMsSnapshot = 0; - private long minRttMs = int.MaxValue, maxRttMs = int.MinValue; - private long minRttTicks = int.MaxValue, maxRttTicks = int.MinValue; - - public BenchmarkHelper() - { - stopwatch.Reset(); - } - - public long RttMs => stopwatch.ElapsedMilliseconds; - - public long RttTicks => stopwatch.ElapsedTicks; - - public static double CalcBandwidth(long elapsedMilliseconds, long sentPacketCount, long sentPacketSize, long bandwidthDownscalingFactor = 1_000_000) - { - double bytes = sentPacketCount * sentPacketSize / bandwidthDownscalingFactor; - double bandwidth = bytes / (elapsedMilliseconds / 1000.0); - - return bandwidth; - } - - public double CalcBandwidth(long sentPacketCount, long packetSize) - { - long millis = stopwatch.ElapsedMilliseconds; - double bytes = sentPacketCount * packetSize; - double bandwidth = (bytes / 1_000_000) / (millis / 1000.0); - - return bandwidth; - } - - public void PrintBandwidthStats(int clientId, long sentPacketCount, long packetSize) - { - long millis = stopwatch.ElapsedMilliseconds; - double megabytes = sentPacketCount * packetSize / 1_000_000.0; - double bandwidth = megabytes / (millis / 1000.0); - - Console.WriteLine($"[Client {clientId}] Sent {sentPacketCount} packets (of size {packetSize} bytes; {megabytes / 1000} gigabytes [one-way]) in {millis} milliseconds"); - Console.WriteLine($"[Client {clientId}] Approximate bandwidth: {bandwidth:F3} MBps"); - } - - public void PrintRttStats(int clientId) - { - Console.WriteLine($"[Client {clientId}] Min RTT: {minRttTicks} ticks, {minRttMs} ms"); - Console.WriteLine($"[Client {clientId}] Max RTT: {maxRttTicks} ticks, {maxRttMs} ms"); - } - - public void ResetStopwatch() - { - lastTicksSnapshot = 0; - lastMsSnapshot = 0; - - stopwatch.Reset(); - } - - public void SnapshotRttStats() - { - long elapsedTicksSnapshot = stopwatch.ElapsedTicks, elapsedMsSnapshot = stopwatch.ElapsedMilliseconds; - - minRttTicks = elapsedTicksSnapshot - lastTicksSnapshot < minRttTicks - ? elapsedTicksSnapshot - lastTicksSnapshot - : minRttTicks; - - minRttMs = elapsedMsSnapshot - lastMsSnapshot < minRttMs - ? elapsedMsSnapshot - lastMsSnapshot - : minRttMs; - - maxRttTicks = elapsedTicksSnapshot - lastTicksSnapshot > maxRttTicks - ? elapsedTicksSnapshot - lastTicksSnapshot - : maxRttTicks; - - maxRttMs = elapsedMsSnapshot - lastMsSnapshot > maxRttMs - ? elapsedMsSnapshot - lastMsSnapshot - : maxRttMs; - - lastTicksSnapshot = elapsedTicksSnapshot; - lastMsSnapshot = elapsedMsSnapshot; - } - - public void StartStopwatch() - { - stopwatch.Start(); - } - - public void StopStopwatch() - { - stopwatch.Stop(); - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Benchmarks/Benchmarks/Datagram Network Connection Benchmarks/CombinedMultiClientRawDatagramBenchmark.cs b/NetSharp/NetSharp.Benchmarks/Benchmarks/Datagram Network Connection Benchmarks/CombinedMultiClientRawDatagramBenchmark.cs @@ -1,131 +0,0 @@ -using System; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; - -using NetSharp.Raw.Datagram; - -namespace NetSharp.Benchmarks.Benchmarks.Datagram_Network_Connection_Benchmarks -{ - internal class CombinedMultiClientRawDatagramBenchmark : INetSharpBenchmark - { - private readonly ManualResetEventSlim ServerReadyEvent = new ManualResetEventSlim(false); - private double[] ClientBandwidths; - private volatile EndPoint _serverEndPoint = null; - - public string Name => "Combined Raw Datagram Network Reader/Writer (Multiple Clients) Benchmark"; - - private static bool RequestHandler(EndPoint remoteEndPoint, in ReadOnlyMemory<byte> requestBuffer, int receivedRequestBytes, - in Memory<byte> responseBuffer) - { - requestBuffer.CopyTo(responseBuffer); - - return true; - } - - private Task BenchmarkClientTask(object idObj) - { - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - try - { - int id = (int)idObj; - - EndPoint defaultRemoteEndPoint = new IPEndPoint(IPAddress.Any, 0); - - BenchmarkHelper benchmarkHelper = new BenchmarkHelper(); - - byte[] sendBuffer = new byte[Program.Constants.PacketSize]; - byte[] receiveBuffer = new byte[Program.Constants.PacketSize]; - - EndPoint remoteEndPoint = _serverEndPoint; - - using RawDatagramNetworkWriter writer = new RawDatagramNetworkWriter(ref rawSocket, defaultRemoteEndPoint, Program.Constants.PacketSize); - - lock (typeof(Console)) - { - Console.WriteLine($"[Client {id}] Starting client at {rawSocket.LocalEndPoint}; sending messages to {remoteEndPoint}"); - } - - benchmarkHelper.ResetStopwatch(); - - ServerReadyEvent.Wait(); - - for (int i = 0; i < Program.Constants.PacketCount; i++) - { - byte[] packetBuffer = Program.Constants.ServerEncoding.GetBytes($"[Client {id}] Hello World! (Packet {i})"); - packetBuffer.CopyTo(sendBuffer, 0); - - benchmarkHelper.StartStopwatch(); - int sendResult = writer.Write(remoteEndPoint, sendBuffer); - - int receiveResult = writer.Read(ref remoteEndPoint, receiveBuffer); - benchmarkHelper.StopStopwatch(); - - benchmarkHelper.SnapshotRttStats(); - } - - lock (typeof(Console)) - { - benchmarkHelper.PrintBandwidthStats(id, Program.Constants.PacketCount, Program.Constants.PacketSize); - benchmarkHelper.PrintRttStats(id); - } - - ClientBandwidths[id] = benchmarkHelper.CalcBandwidth(Program.Constants.PacketCount, Program.Constants.PacketSize); - } - catch (Exception ex) - { - Console.WriteLine("Client exception: {0}", ex); - } - - rawSocket.Close(); - rawSocket.Dispose(); - - return Task.CompletedTask; - } - - public async Task RunAsync() - { - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - _serverEndPoint = rawSocket.LocalEndPoint; - - try - { - ClientBandwidths = new double[Program.Constants.ClientCount]; - Task[] clientTasks = new Task[Program.Constants.ClientCount]; - for (int i = 0; i < clientTasks.Length; i++) - { - clientTasks[i] = Task.Factory.StartNew(BenchmarkClientTask, i, TaskCreationOptions.LongRunning); - } - - EndPoint defaultRemoteEndPoint = new IPEndPoint(IPAddress.Any, 0); - - using RawDatagramNetworkReader reader = new RawDatagramNetworkReader(ref rawSocket, RequestHandler, defaultRemoteEndPoint, Program.Constants.PacketSize); - reader.Start(Program.Constants.ClientCount); - - ServerReadyEvent.Set(); - - await Task.WhenAll(clientTasks); - - Console.WriteLine($"Total estimated bandwidth: {ClientBandwidths.Sum():F3}"); - - reader.Shutdown(); - } - catch (Exception ex) - { - Console.WriteLine("Server exception: {0}", ex); - } - - rawSocket.Close(); - rawSocket.Dispose(); - - ServerReadyEvent.Reset(); - _serverEndPoint = null; - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Benchmarks/Benchmarks/Datagram Network Connection Benchmarks/CombinedSingleClientRawDatagramBenchmark.cs b/NetSharp/NetSharp.Benchmarks/Benchmarks/Datagram Network Connection Benchmarks/CombinedSingleClientRawDatagramBenchmark.cs @@ -1,127 +0,0 @@ -using System; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; - -using NetSharp.Raw.Datagram; - -namespace NetSharp.Benchmarks.Benchmarks.Datagram_Network_Connection_Benchmarks -{ - internal class CombinedSingleClientRawDatagramBenchmark : INetSharpBenchmark - { - private readonly ManualResetEventSlim ServerReadyEvent = new ManualResetEventSlim(false); - private double[] ClientBandwidths; - private volatile EndPoint _serverEndPoint = null; - - public string Name => "Combined Raw Datagram Network Reader/Writer (Single Client) Benchmark"; - - private static bool RequestHandler(EndPoint remoteEndPoint, in ReadOnlyMemory<byte> requestBuffer, int receivedRequestBytes, - in Memory<byte> responseBuffer) - { - requestBuffer.CopyTo(responseBuffer); - - return true; - } - - private Task BenchmarkClientTask(object idObj) - { - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - try - { - int id = (int)idObj; - - EndPoint defaultRemoteEndPoint = new IPEndPoint(IPAddress.Any, 0); - - BenchmarkHelper benchmarkHelper = new BenchmarkHelper(); - - byte[] sendBuffer = new byte[Program.Constants.PacketSize]; - byte[] receiveBuffer = new byte[Program.Constants.PacketSize]; - - EndPoint remoteEndPoint = _serverEndPoint; - - using RawDatagramNetworkWriter writer = new RawDatagramNetworkWriter(ref rawSocket, defaultRemoteEndPoint, Program.Constants.PacketSize); - - lock (typeof(Console)) - { - Console.WriteLine($"[Client {id}] Starting client at {rawSocket.LocalEndPoint}; sending messages to {remoteEndPoint}"); - } - - benchmarkHelper.ResetStopwatch(); - - ServerReadyEvent.Wait(); - - for (int i = 0; i < Program.Constants.PacketCount; i++) - { - byte[] packetBuffer = Program.Constants.ServerEncoding.GetBytes($"[Client {id}] Hello World! (Packet {i})"); - packetBuffer.CopyTo(sendBuffer, 0); - - benchmarkHelper.StartStopwatch(); - int sendResult = writer.Write(remoteEndPoint, sendBuffer); - - int receiveResult = writer.Read(ref remoteEndPoint, receiveBuffer); - benchmarkHelper.StopStopwatch(); - - benchmarkHelper.SnapshotRttStats(); - } - - lock (typeof(Console)) - { - benchmarkHelper.PrintBandwidthStats(id, Program.Constants.PacketCount, Program.Constants.PacketSize); - benchmarkHelper.PrintRttStats(id); - } - - ClientBandwidths[id] = benchmarkHelper.CalcBandwidth(Program.Constants.PacketCount, Program.Constants.PacketSize); - } - catch (Exception ex) - { - Console.WriteLine("Client exception: {0}", ex); - } - - rawSocket.Close(); - rawSocket.Dispose(); - - return Task.CompletedTask; - } - - public async Task RunAsync() - { - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - _serverEndPoint = rawSocket.LocalEndPoint; - - try - { - ClientBandwidths = new double[1]; - Task[] clientTasks = new Task[] { Task.Factory.StartNew(BenchmarkClientTask, 0, TaskCreationOptions.LongRunning) }; - - EndPoint defaultRemoteEndPoint = new IPEndPoint(IPAddress.Any, 0); - - using RawDatagramNetworkReader reader = new RawDatagramNetworkReader(ref rawSocket, RequestHandler, defaultRemoteEndPoint, Program.Constants.PacketSize); - reader.Start(1); - - ServerReadyEvent.Set(); - - await Task.WhenAll(clientTasks); - - Console.WriteLine($"Total estimated bandwidth: {ClientBandwidths.Sum():F3}"); - - reader.Shutdown(); - } - catch (Exception ex) - { - Console.WriteLine("Server exception: {0}", ex); - } - - rawSocket.Close(); - rawSocket.Dispose(); - - ServerReadyEvent.Reset(); - _serverEndPoint = null; - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Benchmarks/Benchmarks/Datagram Network Connection Benchmarks/RawDatagramNetworkReaderMultiClientBenchmark.cs b/NetSharp/NetSharp.Benchmarks/Benchmarks/Datagram Network Connection Benchmarks/RawDatagramNetworkReaderMultiClientBenchmark.cs @@ -1,128 +0,0 @@ -using System; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; - -using NetSharp.Raw.Datagram; - -namespace NetSharp.Benchmarks.Benchmarks.Datagram_Network_Connection_Benchmarks -{ - internal class RawDatagramNetworkReaderMultiClientBenchmark : INetSharpBenchmark - { - private readonly ManualResetEventSlim ServerReadyEvent = new ManualResetEventSlim(false); - private double[] ClientBandwidths; - private volatile EndPoint _serverEndPoint = null; - - /// <inheritdoc /> - public string Name => "Raw Datagram Network Reader (Multiple Clients) Benchmark"; - - private static bool RequestHandler(EndPoint remoteEndPoint, in ReadOnlyMemory<byte> requestBuffer, int receivedRequestBytes, - in Memory<byte> responseBuffer) - { - requestBuffer.CopyTo(responseBuffer); - - return true; - } - - private Task BenchmarkClientTask(object idObj) - { - using Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - try - { - int id = (int)idObj; - - BenchmarkHelper benchmarkHelper = new BenchmarkHelper(); - - byte[] sendBuffer = new byte[Program.Constants.PacketSize]; - byte[] receiveBuffer = new byte[Program.Constants.PacketSize]; - - EndPoint remoteEndPoint = _serverEndPoint; - - lock (typeof(Console)) - { - Console.WriteLine($"[Client {id}] Starting client at {rawSocket.LocalEndPoint}; sending messages to {remoteEndPoint}"); - } - - benchmarkHelper.ResetStopwatch(); - - ServerReadyEvent.Wait(); - - for (int i = 0; i < Program.Constants.PacketCount; i++) - { - byte[] packetBuffer = Program.Constants.ServerEncoding.GetBytes($"[Client {id}] Hello World! (Packet {i})"); - packetBuffer.CopyTo(sendBuffer, 0); - - benchmarkHelper.StartStopwatch(); - int sentBytes = rawSocket.SendTo(sendBuffer, remoteEndPoint); - - int receivedBytes = rawSocket.ReceiveFrom(receiveBuffer, ref remoteEndPoint); - benchmarkHelper.StopStopwatch(); - - benchmarkHelper.SnapshotRttStats(); - } - - lock (typeof(Console)) - { - benchmarkHelper.PrintBandwidthStats(id, Program.Constants.PacketCount, Program.Constants.PacketSize); - benchmarkHelper.PrintRttStats(id); - } - - ClientBandwidths[id] = benchmarkHelper.CalcBandwidth(Program.Constants.PacketCount, Program.Constants.PacketSize); - } - catch (Exception ex) - { - Console.WriteLine("Client exception: {0}", ex); - } - - rawSocket.Close(); - - return Task.CompletedTask; - } - - /// <inheritdoc /> - public async Task RunAsync() - { - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - _serverEndPoint = rawSocket.LocalEndPoint; - - try - { - ClientBandwidths = new double[Program.Constants.ClientCount]; - Task[] clientTasks = new Task[Program.Constants.ClientCount]; - for (int i = 0; i < clientTasks.Length; i++) - { - clientTasks[i] = Task.Factory.StartNew(BenchmarkClientTask, i, TaskCreationOptions.LongRunning); - } - - EndPoint defaultRemoteEndPoint = new IPEndPoint(IPAddress.Any, 0); - - using RawDatagramNetworkReader reader = new RawDatagramNetworkReader(ref rawSocket, RequestHandler, defaultRemoteEndPoint, Program.Constants.PacketSize); - reader.Start(Program.Constants.ClientCount); - - ServerReadyEvent.Set(); - - await Task.WhenAll(clientTasks); - - Console.WriteLine($"Total estimated bandwidth: {ClientBandwidths.Sum():F3}"); - - reader.Shutdown(); - } - catch (Exception ex) - { - Console.WriteLine("Server exception: {0}", ex); - } - - rawSocket.Close(); - rawSocket.Dispose(); - - ServerReadyEvent.Reset(); - _serverEndPoint = null; - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Benchmarks/Benchmarks/Datagram Network Connection Benchmarks/RawDatagramNetworkReaderSingleClientBenchmark.cs b/NetSharp/NetSharp.Benchmarks/Benchmarks/Datagram Network Connection Benchmarks/RawDatagramNetworkReaderSingleClientBenchmark.cs @@ -1,124 +0,0 @@ -using System; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; - -using NetSharp.Raw.Datagram; - -namespace NetSharp.Benchmarks.Benchmarks.Datagram_Network_Connection_Benchmarks -{ - internal class RawDatagramNetworkReaderSingleClientBenchmark : INetSharpBenchmark - { - private readonly ManualResetEventSlim ServerReadyEvent = new ManualResetEventSlim(false); - private double[] ClientBandwidths; - private volatile EndPoint _serverEndPoint = null; - - /// <inheritdoc /> - public string Name => "Raw Datagram Network Reader (Single Client) Benchmark"; - - private static bool RequestHandler(EndPoint remoteEndPoint, in ReadOnlyMemory<byte> requestBuffer, int receivedRequestBytes, - in Memory<byte> responseBuffer) - { - requestBuffer.CopyTo(responseBuffer); - - return true; - } - - private Task BenchmarkClientTask(object idObj) - { - using Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - try - { - int id = (int)idObj; - - BenchmarkHelper benchmarkHelper = new BenchmarkHelper(); - - byte[] sendBuffer = new byte[Program.Constants.PacketSize]; - byte[] receiveBuffer = new byte[Program.Constants.PacketSize]; - - EndPoint remoteEndPoint = _serverEndPoint; - - lock (typeof(Console)) - { - Console.WriteLine($"[Client {id}] Starting client at {rawSocket.LocalEndPoint}; sending messages to {remoteEndPoint}"); - } - - benchmarkHelper.ResetStopwatch(); - - ServerReadyEvent.Wait(); - - for (int i = 0; i < Program.Constants.PacketCount; i++) - { - byte[] packetBuffer = Program.Constants.ServerEncoding.GetBytes($"[Client {id}] Hello World! (Packet {i})"); - packetBuffer.CopyTo(sendBuffer, 0); - - benchmarkHelper.StartStopwatch(); - int sentBytes = rawSocket.SendTo(sendBuffer, remoteEndPoint); - - int receivedBytes = rawSocket.ReceiveFrom(receiveBuffer, ref remoteEndPoint); - benchmarkHelper.StopStopwatch(); - - benchmarkHelper.SnapshotRttStats(); - } - - lock (typeof(Console)) - { - benchmarkHelper.PrintBandwidthStats(id, Program.Constants.PacketCount, Program.Constants.PacketSize); - benchmarkHelper.PrintRttStats(id); - } - - ClientBandwidths[id] = benchmarkHelper.CalcBandwidth(Program.Constants.PacketCount, Program.Constants.PacketSize); - } - catch (Exception ex) - { - Console.WriteLine("Client exception: {0}", ex); - } - - rawSocket.Close(); - - return Task.CompletedTask; - } - - /// <inheritdoc /> - public async Task RunAsync() - { - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - _serverEndPoint = rawSocket.LocalEndPoint; - - try - { - ClientBandwidths = new double[1]; - Task[] clientTasks = new Task[] { Task.Factory.StartNew(BenchmarkClientTask, 0, TaskCreationOptions.LongRunning) }; - - EndPoint defaultRemoteEndPoint = new IPEndPoint(IPAddress.Any, 0); - - using RawDatagramNetworkReader reader = new RawDatagramNetworkReader(ref rawSocket, RequestHandler, defaultRemoteEndPoint, Program.Constants.PacketSize); - reader.Start(1); - - ServerReadyEvent.Set(); - - await Task.WhenAll(clientTasks); - - Console.WriteLine($"Total estimated bandwidth: {ClientBandwidths.Sum():F3}"); - - reader.Shutdown(); - } - catch (Exception ex) - { - Console.WriteLine("Server exception: {0}", ex); - } - - rawSocket.Close(); - rawSocket.Dispose(); - - ServerReadyEvent.Reset(); - _serverEndPoint = null; - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Benchmarks/Benchmarks/Datagram Network Connection Benchmarks/RawDatagramNetworkWriterAsyncBenchmark.cs b/NetSharp/NetSharp.Benchmarks/Benchmarks/Datagram Network Connection Benchmarks/RawDatagramNetworkWriterAsyncBenchmark.cs @@ -1,112 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; - -using NetSharp.Raw.Datagram; - -namespace NetSharp.Benchmarks.Benchmarks.Datagram_Network_Connection_Benchmarks -{ - internal class RawDatagramNetworkWriterAsyncBenchmark : INetSharpBenchmark - { - private readonly ManualResetEventSlim ServerReadyEvent = new ManualResetEventSlim(false); - private volatile EndPoint _serverEndPoint = null; - - /// <inheritdoc /> - public string Name => "Raw Datagram Network Writer Benchmark (Asynchronous)"; - - private Task ServerTask() - { - using Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - _serverEndPoint = rawSocket.LocalEndPoint; - - try - { - byte[] transmissionBuffer = new byte[Program.Constants.PacketSize]; - - EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); - - ServerReadyEvent.Set(); - - for (int i = 0; i < Program.Constants.PacketCount; i++) - { - rawSocket.ReceiveFrom(transmissionBuffer, ref remoteEndPoint); - - rawSocket.SendTo(transmissionBuffer, remoteEndPoint); - } - } - catch (Exception ex) - { - Console.WriteLine("Server exception: {0}", ex); - } - - rawSocket.Close(); - - return Task.CompletedTask; - } - - /// <inheritdoc /> - public async Task RunAsync() - { - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - try - { - EndPoint defaultRemoteEndPoint = new IPEndPoint(IPAddress.Any, 0); - - BenchmarkHelper benchmarkHelper = new BenchmarkHelper(); - - byte[] sendBuffer = new byte[Program.Constants.PacketSize]; - byte[] receiveBuffer = new byte[Program.Constants.PacketSize]; - - using RawDatagramNetworkWriter writer = new RawDatagramNetworkWriter(ref rawSocket, defaultRemoteEndPoint, Program.Constants.PacketSize); - - Task serverTask = Task.Factory.StartNew(ServerTask, TaskCreationOptions.LongRunning); - - benchmarkHelper.ResetStopwatch(); - - ServerReadyEvent.Wait(); - - EndPoint remoteEndPoint = _serverEndPoint; - - lock (typeof(Console)) - { - Console.WriteLine($"[Client {0}] Starting client at {rawSocket.LocalEndPoint}; sending messages to {remoteEndPoint}"); - } - - for (int i = 0; i < Program.Constants.PacketCount; i++) - { - byte[] packetBuffer = Program.Constants.ServerEncoding.GetBytes($"[Client 0] Hello World! (Packet {i})"); - packetBuffer.CopyTo(sendBuffer, 0); - - benchmarkHelper.StartStopwatch(); - int sendResult = await writer.WriteAsync(remoteEndPoint, sendBuffer); - - int receiveResult = await writer.ReadAsync(remoteEndPoint, receiveBuffer); - benchmarkHelper.StopStopwatch(); - - benchmarkHelper.SnapshotRttStats(); - } - - benchmarkHelper.PrintBandwidthStats(0, Program.Constants.PacketCount, Program.Constants.PacketSize); - benchmarkHelper.PrintRttStats(0); - - serverTask.GetAwaiter().GetResult(); - } - catch (Exception ex) - { - Console.WriteLine("Client exception: {0}", ex); - } - - rawSocket.Close(); - rawSocket.Dispose(); - - ServerReadyEvent.Reset(); - _serverEndPoint = null; - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Benchmarks/Benchmarks/Datagram Network Connection Benchmarks/RawDatagramNetworkWriterSyncBenchmark.cs b/NetSharp/NetSharp.Benchmarks/Benchmarks/Datagram Network Connection Benchmarks/RawDatagramNetworkWriterSyncBenchmark.cs @@ -1,114 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; - -using NetSharp.Raw.Datagram; - -namespace NetSharp.Benchmarks.Benchmarks.Datagram_Network_Connection_Benchmarks -{ - internal class RawDatagramNetworkWriterSyncBenchmark : INetSharpBenchmark - { - private readonly ManualResetEventSlim ServerReadyEvent = new ManualResetEventSlim(false); - private volatile EndPoint _serverEndPoint = null; - - /// <inheritdoc /> - public string Name => "Raw Datagram Network Writer Benchmark (Synchronous)"; - - private Task ServerTask() - { - using Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - _serverEndPoint = rawSocket.LocalEndPoint; - - try - { - byte[] transmissionBuffer = new byte[Program.Constants.PacketSize]; - - EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); - - ServerReadyEvent.Set(); - - for (int i = 0; i < Program.Constants.PacketCount; i++) - { - rawSocket.ReceiveFrom(transmissionBuffer, ref remoteEndPoint); - - rawSocket.SendTo(transmissionBuffer, remoteEndPoint); - } - } - catch (Exception ex) - { - Console.WriteLine("Server exception: {0}", ex); - } - - rawSocket.Close(); - - return Task.CompletedTask; - } - - /// <inheritdoc /> - public Task RunAsync() - { - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - try - { - EndPoint defaultRemoteEndPoint = new IPEndPoint(IPAddress.Any, 0); - - BenchmarkHelper benchmarkHelper = new BenchmarkHelper(); - - byte[] sendBuffer = new byte[Program.Constants.PacketSize]; - byte[] receiveBuffer = new byte[Program.Constants.PacketSize]; - - using RawDatagramNetworkWriter writer = new RawDatagramNetworkWriter(ref rawSocket, defaultRemoteEndPoint, Program.Constants.PacketSize); - - Task serverTask = Task.Factory.StartNew(ServerTask, TaskCreationOptions.LongRunning); - - benchmarkHelper.ResetStopwatch(); - - ServerReadyEvent.Wait(); - - EndPoint remoteEndPoint = _serverEndPoint; - - lock (typeof(Console)) - { - Console.WriteLine($"[Client {0}] Starting client at {rawSocket.LocalEndPoint}; sending messages to {remoteEndPoint}"); - } - - for (int i = 0; i < Program.Constants.PacketCount; i++) - { - byte[] packetBuffer = Program.Constants.ServerEncoding.GetBytes($"[Client 0] Hello World! (Packet {i})"); - packetBuffer.CopyTo(sendBuffer, 0); - - benchmarkHelper.StartStopwatch(); - int sendResult = writer.Write(remoteEndPoint, sendBuffer); - - int receiveResult = writer.Read(ref remoteEndPoint, receiveBuffer); - benchmarkHelper.StopStopwatch(); - - benchmarkHelper.SnapshotRttStats(); - } - - benchmarkHelper.PrintBandwidthStats(0, Program.Constants.PacketCount, Program.Constants.PacketSize); - benchmarkHelper.PrintRttStats(0); - - serverTask.GetAwaiter().GetResult(); - } - catch (Exception ex) - { - Console.WriteLine("Client exception: {0}", ex); - } - - rawSocket.Close(); - rawSocket.Dispose(); - - ServerReadyEvent.Reset(); - _serverEndPoint = null; - - return Task.CompletedTask; - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Benchmarks/Benchmarks/RawStreamConnectionBenchmark.cs b/NetSharp/NetSharp.Benchmarks/Benchmarks/RawStreamConnectionBenchmark.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; + +namespace NetSharp.Benchmarks.Benchmarks +{ + public class RawStreamConnectionBenchmark : INetSharpBenchmark + { + /// <inheritdoc /> + public string Name { get; } = "Raw Stream Connection Benchmark"; + + /// <inheritdoc /> + public Task RunAsync() + { + return Task.CompletedTask; + } + } +} diff --git a/NetSharp/NetSharp.Benchmarks/Benchmarks/Stream Network Connection Benchmarks/CombinedMultiClientRawStreamBenchmark.cs b/NetSharp/NetSharp.Benchmarks/Benchmarks/Stream Network Connection Benchmarks/CombinedMultiClientRawStreamBenchmark.cs @@ -1,138 +0,0 @@ -using System; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; - -using NetSharp.Raw.Stream; - -namespace NetSharp.Benchmarks.Benchmarks.Stream_Network_Connection_Benchmarks -{ - internal class CombinedMultiClientRawStreamBenchmark : INetSharpBenchmark - { - private readonly ManualResetEventSlim ServerReadyEvent = new ManualResetEventSlim(false); - private double[] ClientBandwidths; - private volatile EndPoint _serverEndPoint = null; - - public string Name => "Combined Raw Stream Network Reader/Writer (Multiple Clients) Benchmark"; - - private static bool RequestHandler(EndPoint remoteEndPoint, in ReadOnlyMemory<byte> requestBuffer, int receivedRequestBytes, - in Memory<byte> responseBuffer) - { - requestBuffer.CopyTo(responseBuffer); - - return true; - } - - private Task BenchmarkClientTask(object idObj) - { - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - rawSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - try - { - int id = (int)idObj; - - EndPoint defaultRemoteEndPoint = new IPEndPoint(IPAddress.Any, 0); - - BenchmarkHelper benchmarkHelper = new BenchmarkHelper(); - - byte[] sendBuffer = new byte[Program.Constants.PacketSize]; - byte[] receiveBuffer = new byte[Program.Constants.PacketSize]; - - using RawStreamNetworkWriter writer = new RawStreamNetworkWriter(ref rawSocket, defaultRemoteEndPoint, Program.Constants.PacketSize); - - benchmarkHelper.ResetStopwatch(); - - ServerReadyEvent.Wait(); - EndPoint remoteEndPoint = _serverEndPoint; - rawSocket.Connect(_serverEndPoint); - - lock (typeof(Console)) - { - Console.WriteLine($"[Client {id}] Starting client at {rawSocket.LocalEndPoint}; sending messages to {remoteEndPoint}"); - } - - for (int i = 0; i < Program.Constants.PacketCount; i++) - { - byte[] packetBuffer = Program.Constants.ServerEncoding.GetBytes($"[Client {id}] Hello World! (Packet {i})"); - packetBuffer.CopyTo(sendBuffer, 0); - - benchmarkHelper.StartStopwatch(); - int sendResult = writer.Write(remoteEndPoint, sendBuffer); - - int receiveResult = writer.Read(ref remoteEndPoint, receiveBuffer); - benchmarkHelper.StopStopwatch(); - - benchmarkHelper.SnapshotRttStats(); - } - - lock (typeof(Console)) - { - benchmarkHelper.PrintBandwidthStats(id, Program.Constants.PacketCount, Program.Constants.PacketSize); - benchmarkHelper.PrintRttStats(id); - } - - ClientBandwidths[id] = benchmarkHelper.CalcBandwidth(Program.Constants.PacketCount, Program.Constants.PacketSize); - - rawSocket.Disconnect(false); - rawSocket.Shutdown(SocketShutdown.Both); - } - catch (Exception ex) - { - Console.WriteLine("Client exception: {0}", ex); - } - - rawSocket.Close(); - rawSocket.Dispose(); - - return Task.CompletedTask; - } - - public async Task RunAsync() - { - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - rawSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - _serverEndPoint = rawSocket.LocalEndPoint; - - try - { - ClientBandwidths = new double[Program.Constants.ClientCount]; - Task[] clientTasks = new Task[Program.Constants.ClientCount]; - for (int i = 0; i < clientTasks.Length; i++) - { - clientTasks[i] = Task.Factory.StartNew(BenchmarkClientTask, i, TaskCreationOptions.LongRunning); - } - - EndPoint defaultEndPoint = new IPEndPoint(IPAddress.Any, 0); - - rawSocket.Listen(Program.Constants.ClientCount); - - using RawStreamNetworkReader reader = new RawStreamNetworkReader(ref rawSocket, RequestHandler, defaultEndPoint, Program.Constants.PacketSize); - reader.Start(Program.Constants.ClientCount); - - ServerReadyEvent.Set(); - - await Task.WhenAll(clientTasks); - - Console.WriteLine($"Total estimated bandwidth: {ClientBandwidths.Sum():F3}"); - - reader.Shutdown(); - } - catch (Exception ex) - { - Console.WriteLine("Server exception: {0}", ex); - } - - rawSocket.Close(); - rawSocket.Dispose(); - - ServerReadyEvent.Reset(); - _serverEndPoint = null; - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Benchmarks/Benchmarks/Stream Network Connection Benchmarks/CombinedSingleClientRawStreamBenchmark.cs b/NetSharp/NetSharp.Benchmarks/Benchmarks/Stream Network Connection Benchmarks/CombinedSingleClientRawStreamBenchmark.cs @@ -1,133 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; - -using NetSharp.Raw.Stream; - -namespace NetSharp.Benchmarks.Benchmarks.Stream_Network_Connection_Benchmarks -{ - internal class CombinedSingleClientRawStreamBenchmark : INetSharpBenchmark - { - private readonly ManualResetEventSlim ServerReadyEvent = new ManualResetEventSlim(false); - private double[] ClientBandwidths; - private volatile EndPoint _serverEndPoint = null; - - public string Name => "Combined Raw Stream Network Reader/Writer (Single Client) Benchmark"; - - private static bool RequestHandler(EndPoint remoteEndPoint, in ReadOnlyMemory<byte> requestBuffer, int receivedRequestBytes, - in Memory<byte> responseBuffer) - { - requestBuffer.CopyTo(responseBuffer); - - return true; - } - - private Task BenchmarkClientTask(object idObj) - { - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - rawSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - try - { - int id = (int)idObj; - - EndPoint defaultRemoteEndPoint = new IPEndPoint(IPAddress.Any, 0); - - BenchmarkHelper benchmarkHelper = new BenchmarkHelper(); - - byte[] sendBuffer = new byte[Program.Constants.PacketSize]; - byte[] receiveBuffer = new byte[Program.Constants.PacketSize]; - - using RawStreamNetworkWriter writer = new RawStreamNetworkWriter(ref rawSocket, defaultRemoteEndPoint, Program.Constants.PacketSize); - - benchmarkHelper.ResetStopwatch(); - - ServerReadyEvent.Wait(); - EndPoint remoteEndPoint = _serverEndPoint; - rawSocket.Connect(_serverEndPoint); - - lock (typeof(Console)) - { - Console.WriteLine($"[Client {id}] Starting client at {rawSocket.LocalEndPoint}; sending messages to {remoteEndPoint}"); - } - - for (int i = 0; i < Program.Constants.PacketCount; i++) - { - byte[] packetBuffer = Program.Constants.ServerEncoding.GetBytes($"[Client {id}] Hello World! (Packet {i})"); - packetBuffer.CopyTo(sendBuffer, 0); - - benchmarkHelper.StartStopwatch(); - int sendResult = writer.Write(remoteEndPoint, sendBuffer); - - int receiveResult = writer.Read(ref remoteEndPoint, receiveBuffer); - benchmarkHelper.StopStopwatch(); - - benchmarkHelper.SnapshotRttStats(); - } - - lock (typeof(Console)) - { - benchmarkHelper.PrintBandwidthStats(id, Program.Constants.PacketCount, Program.Constants.PacketSize); - benchmarkHelper.PrintRttStats(id); - } - - ClientBandwidths[id] = benchmarkHelper.CalcBandwidth(Program.Constants.PacketCount, Program.Constants.PacketSize); - - rawSocket.Disconnect(false); - rawSocket.Shutdown(SocketShutdown.Both); - } - catch (Exception ex) - { - Console.WriteLine("Client exception: {0}", ex); - } - - rawSocket.Close(); - rawSocket.Dispose(); - - return Task.CompletedTask; - } - - public async Task RunAsync() - { - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - rawSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - _serverEndPoint = rawSocket.LocalEndPoint; - - try - { - ClientBandwidths = new double[1]; - Task[] clientTasks = new Task[] { Task.Factory.StartNew(BenchmarkClientTask, 0, TaskCreationOptions.LongRunning) }; - - EndPoint defaultEndPoint = new IPEndPoint(IPAddress.Any, 0); - - rawSocket.Listen(1); - - using RawStreamNetworkReader reader = new RawStreamNetworkReader(ref rawSocket, RequestHandler, defaultEndPoint, Program.Constants.PacketSize); - reader.Start(1); - - ServerReadyEvent.Set(); - - await Task.WhenAll(clientTasks); - - Console.WriteLine($"Total estimated bandwidth: {ClientBandwidths[0]:F3}"); - - reader.Shutdown(); - } - catch (Exception ex) - { - Console.WriteLine("Server exception: {0}", ex); - } - - rawSocket.Close(); - rawSocket.Dispose(); - - ServerReadyEvent.Reset(); - _serverEndPoint = null; - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Benchmarks/Benchmarks/Stream Network Connection Benchmarks/RawStreamNetworkReaderMultiClientBenchmark.cs b/NetSharp/NetSharp.Benchmarks/Benchmarks/Stream Network Connection Benchmarks/RawStreamNetworkReaderMultiClientBenchmark.cs @@ -1,160 +0,0 @@ -using System; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; - -using NetSharp.Raw.Stream; - -namespace NetSharp.Benchmarks.Benchmarks.Stream_Network_Connection_Benchmarks -{ - internal class RawStreamNetworkReaderMultiClientBenchmark : INetSharpBenchmark - { - private readonly ManualResetEventSlim ServerReadyEvent = new ManualResetEventSlim(false); - private double[] ClientBandwidths; - private volatile EndPoint _serverEndPoint = null; - - /// <inheritdoc /> - public string Name => "Raw Stream Network Reader (Multiple Clients) Benchmark"; - - private static bool RequestHandler(EndPoint remoteEndPoint, in ReadOnlyMemory<byte> requestBuffer, int receivedRequestBytes, - in Memory<byte> responseBuffer) - { - requestBuffer.CopyTo(responseBuffer); - - return true; - } - - private Task BenchmarkClientTask(object idObj) - { - using Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true); - clientSocket.Bind(Program.Constants.DefaultEndPoint); - - try - { - int id = (int)idObj; - - BenchmarkHelper benchmarkHelper = new BenchmarkHelper(); - - byte[] sendBuffer = new byte[Program.Constants.PacketSize + RawStreamPacketHeader.TotalSize]; - byte[] receiveBuffer = new byte[Program.Constants.PacketSize + RawStreamPacketHeader.TotalSize]; - byte[] packetBuffer = new byte[Program.Constants.PacketSize]; - - benchmarkHelper.ResetStopwatch(); - - ServerReadyEvent.Wait(); - EndPoint remoteEndPoint = _serverEndPoint; - clientSocket.Connect(remoteEndPoint); - - lock (typeof(Console)) - { - Console.WriteLine($"[Client {id}] Starting client at {clientSocket.LocalEndPoint}; sending messages to {remoteEndPoint}"); - } - - for (int i = 0; i < Program.Constants.PacketCount; i++) - { - Program.Constants.ServerEncoding.GetBytes($"[Client {id}] Hello World! (Packet {i})").CopyTo(packetBuffer, 0); - - RawStreamPacketHeader streamPacketHeader = new RawStreamPacketHeader(Program.Constants.PacketSize); - RawStreamPacket.Serialise(sendBuffer, in streamPacketHeader, packetBuffer); - - benchmarkHelper.StartStopwatch(); - - int totalSent = 0; - do - { - totalSent += clientSocket.Send(sendBuffer, totalSent, sendBuffer.Length - totalSent, - SocketFlags.None); - } while (totalSent != 0 && totalSent < sendBuffer.Length); - - if (totalSent == 0) - { - break; - } - - int totalReceived = 0; - do - { - totalReceived += clientSocket.Receive(receiveBuffer, totalReceived, receiveBuffer.Length - totalReceived, - SocketFlags.None); - } while (totalReceived != 0 && totalReceived < receiveBuffer.Length); - - if (totalReceived == 0) - { - break; - } - - benchmarkHelper.StopStopwatch(); - - benchmarkHelper.SnapshotRttStats(); - } - - lock (typeof(Console)) - { - benchmarkHelper.PrintBandwidthStats(id, Program.Constants.PacketCount, Program.Constants.PacketSize); - benchmarkHelper.PrintRttStats(id); - } - - ClientBandwidths[id] = benchmarkHelper.CalcBandwidth(Program.Constants.PacketCount, Program.Constants.PacketSize); - - clientSocket.Shutdown(SocketShutdown.Both); - } - catch (Exception ex) - { - Console.WriteLine("Client exception: {0}", ex); - } - - clientSocket.Close(); - - return Task.CompletedTask; - } - - /// <inheritdoc /> - public async Task RunAsync() - { - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - rawSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - _serverEndPoint = rawSocket.LocalEndPoint; - - try - { - ClientBandwidths = new double[Program.Constants.ClientCount]; - Task[] clientTasks = new Task[Program.Constants.ClientCount]; - for (int i = 0; i < clientTasks.Length; i++) - { - clientTasks[i] = Task.Factory.StartNew(BenchmarkClientTask, i, TaskCreationOptions.LongRunning); - } - - EndPoint defaultEndPoint = new IPEndPoint(IPAddress.Any, 0); - - rawSocket.Listen(Program.Constants.ClientCount); - - using RawStreamNetworkReader reader = new RawStreamNetworkReader(ref rawSocket, RequestHandler, defaultEndPoint, Program.Constants.PacketSize); - reader.Start(Program.Constants.ClientCount); - - ServerReadyEvent.Set(); - - await Task.WhenAll(clientTasks); - - Console.WriteLine($"Total estimated bandwidth: {ClientBandwidths.Sum():F3}"); - - reader.Shutdown(); - } - catch (Exception ex) - { - Console.WriteLine("Server exception: {0}", ex); - throw; - } - - rawSocket.Close(); - rawSocket.Dispose(); - - ServerReadyEvent.Reset(); - _serverEndPoint = null; - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Benchmarks/Benchmarks/Stream Network Connection Benchmarks/RawStreamNetworkReaderSingleClientBenchmark.cs b/NetSharp/NetSharp.Benchmarks/Benchmarks/Stream Network Connection Benchmarks/RawStreamNetworkReaderSingleClientBenchmark.cs @@ -1,154 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; - -using NetSharp.Raw.Stream; - -namespace NetSharp.Benchmarks.Benchmarks.Stream_Network_Connection_Benchmarks -{ - internal class RawStreamNetworkReaderSingleClientBenchmark : INetSharpBenchmark - { - private readonly ManualResetEventSlim ServerReadyEvent = new ManualResetEventSlim(false); - private double[] ClientBandwidths; - private volatile EndPoint _serverEndPoint = null; - - /// <inheritdoc /> - public string Name => "Raw Stream Network Reader (Single Client) Benchmark"; - - private static bool RequestHandler(EndPoint remoteEndPoint, in ReadOnlyMemory<byte> requestBuffer, int receivedRequestBytes, - in Memory<byte> responseBuffer) - { - requestBuffer.CopyTo(responseBuffer); - - return true; - } - - private Task BenchmarkClientTask(object idObj) - { - using Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true); - clientSocket.Bind(Program.Constants.DefaultEndPoint); - - try - { - int id = (int)idObj; - - BenchmarkHelper benchmarkHelper = new BenchmarkHelper(); - - byte[] sendBuffer = new byte[Program.Constants.PacketSize + RawStreamPacketHeader.TotalSize]; - byte[] receiveBuffer = new byte[Program.Constants.PacketSize + RawStreamPacketHeader.TotalSize]; - byte[] packetBuffer = new byte[Program.Constants.PacketSize]; - - benchmarkHelper.ResetStopwatch(); - - ServerReadyEvent.Wait(); - EndPoint remoteEndPoint = _serverEndPoint; - clientSocket.Connect(remoteEndPoint); - - lock (typeof(Console)) - { - Console.WriteLine($"[Client {id}] Starting client at {clientSocket.LocalEndPoint}; sending messages to {remoteEndPoint}"); - } - - for (int i = 0; i < Program.Constants.PacketCount; i++) - { - Program.Constants.ServerEncoding.GetBytes($"[Client {id}] Hello World! (Packet {i})").CopyTo(packetBuffer, 0); - - RawStreamPacketHeader streamPacketHeader = new RawStreamPacketHeader(Program.Constants.PacketSize); - RawStreamPacket.Serialise(sendBuffer, in streamPacketHeader, packetBuffer); - - benchmarkHelper.StartStopwatch(); - - int totalSent = 0; - do - { - totalSent += clientSocket.Send(sendBuffer, totalSent, sendBuffer.Length - totalSent, - SocketFlags.None); - } while (totalSent != 0 && totalSent < sendBuffer.Length); - - if (totalSent == 0) - { - break; - } - - int totalReceived = 0; - do - { - totalReceived += clientSocket.Receive(receiveBuffer, totalReceived, receiveBuffer.Length - totalReceived, - SocketFlags.None); - } while (totalReceived != 0 && totalReceived < receiveBuffer.Length); - - if (totalReceived == 0) - { - break; - } - - benchmarkHelper.StopStopwatch(); - - benchmarkHelper.SnapshotRttStats(); - } - - lock (typeof(Console)) - { - benchmarkHelper.PrintBandwidthStats(id, Program.Constants.PacketCount, Program.Constants.PacketSize); - benchmarkHelper.PrintRttStats(id); - } - - ClientBandwidths[id] = benchmarkHelper.CalcBandwidth(Program.Constants.PacketCount, Program.Constants.PacketSize); - - clientSocket.Shutdown(SocketShutdown.Both); - } - catch (Exception ex) - { - Console.WriteLine("Client exception: {0}", ex); - } - - clientSocket.Close(); - - return Task.CompletedTask; - } - - /// <inheritdoc /> - public async Task RunAsync() - { - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - rawSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - _serverEndPoint = rawSocket.LocalEndPoint; - - try - { - ClientBandwidths = new double[1]; - Task[] clientTasks = new Task[] { Task.Factory.StartNew(BenchmarkClientTask, 0, TaskCreationOptions.LongRunning) }; - - EndPoint defaultEndPoint = new IPEndPoint(IPAddress.Any, 0); - - rawSocket.Listen(1); - - using RawStreamNetworkReader reader = new RawStreamNetworkReader(ref rawSocket, RequestHandler, defaultEndPoint, Program.Constants.PacketSize); - reader.Start(1); - - ServerReadyEvent.Set(); - - await clientTasks[0]; - - Console.WriteLine($"Total estimated bandwidth: {ClientBandwidths[0]:F3}"); - - reader.Shutdown(); - } - catch (Exception ex) - { - Console.WriteLine("Server exception: {0}", ex); - } - - rawSocket.Close(); - rawSocket.Dispose(); - - ServerReadyEvent.Reset(); - _serverEndPoint = null; - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Benchmarks/Benchmarks/Stream Network Connection Benchmarks/RawStreamNetworkWriterAsyncBenchmark.cs b/NetSharp/NetSharp.Benchmarks/Benchmarks/Stream Network Connection Benchmarks/RawStreamNetworkWriterAsyncBenchmark.cs @@ -1,143 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; - -using NetSharp.Raw.Stream; - -namespace NetSharp.Benchmarks.Benchmarks.Stream_Network_Connection_Benchmarks -{ - internal class RawStreamNetworkWriterAsyncBenchmark : INetSharpBenchmark - { - private readonly ManualResetEventSlim ServerReadyEvent = new ManualResetEventSlim(false); - private volatile EndPoint _serverEndPoint = null; - - /// <inheritdoc /> - public string Name => "Raw Stream Network Writer Benchmark (Asynchronous)"; - - private Task ServerTask() - { - using Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - rawSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - _serverEndPoint = rawSocket.LocalEndPoint; - - try - { - // all the headers should have the same packet size, so will fit in the transmission buffer - RawStreamPacketHeader archetypalHeader = new RawStreamPacketHeader(Program.Constants.PacketSize); - byte[] transmissionBuffer = new byte[RawStreamPacket.TotalPacketSize(in archetypalHeader)]; - - rawSocket.Listen(1); - - ServerReadyEvent.Set(); - - using Socket clientSocket = rawSocket.Accept(); - clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true); - - for (int i = 0; i < Program.Constants.PacketCount; i++) - { - int expectedBytes = transmissionBuffer.Length; - - int receivedBytes = 0; - do - { - receivedBytes += clientSocket.Receive(transmissionBuffer, receivedBytes, expectedBytes - receivedBytes, SocketFlags.None); - } while (receivedBytes < expectedBytes && receivedBytes > 0); - - if (receivedBytes == 0) - { - break; - } - - int sentBytes = 0; - do - { - sentBytes += clientSocket.Send(transmissionBuffer, sentBytes, expectedBytes - sentBytes, SocketFlags.None); - } while (sentBytes < expectedBytes && sentBytes > 0); - - if (sentBytes == 0) - { - break; - } - } - - clientSocket.Disconnect(false); - clientSocket.Shutdown(SocketShutdown.Both); - clientSocket.Close(); - } - catch (Exception ex) - { - Console.WriteLine("Server exception: {0}", ex); - } - - rawSocket.Close(); - - return Task.CompletedTask; - } - - /// <inheritdoc /> - public async Task RunAsync() - { - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - rawSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - try - { - EndPoint defaultRemoteEndPoint = new IPEndPoint(IPAddress.Any, 0); - - BenchmarkHelper benchmarkHelper = new BenchmarkHelper(); - - byte[] sendBuffer = new byte[Program.Constants.PacketSize]; - byte[] receiveBuffer = new byte[Program.Constants.PacketSize]; - - using RawStreamNetworkWriter writer = new RawStreamNetworkWriter(ref rawSocket, defaultRemoteEndPoint, Program.Constants.PacketSize); - - Task serverTask = Task.Factory.StartNew(ServerTask, TaskCreationOptions.LongRunning); - - benchmarkHelper.ResetStopwatch(); - - ServerReadyEvent.Wait(); - EndPoint remoteEndPoint = _serverEndPoint; - rawSocket.Connect(remoteEndPoint); - - lock (typeof(Console)) - { - Console.WriteLine($"[Client {0}] Starting client at {rawSocket.LocalEndPoint}; sending messages to {remoteEndPoint}"); - } - - for (int i = 0; i < Program.Constants.PacketCount; i++) - { - byte[] packetBuffer = Program.Constants.ServerEncoding.GetBytes($"[Client 0] Hello World! (Packet {i})"); - packetBuffer.CopyTo(sendBuffer, 0); - - benchmarkHelper.StartStopwatch(); - int sendResult = await writer.WriteAsync(remoteEndPoint, sendBuffer); - - int receiveResult = await writer.ReadAsync(remoteEndPoint, receiveBuffer); - benchmarkHelper.StopStopwatch(); - - benchmarkHelper.SnapshotRttStats(); - } - - benchmarkHelper.PrintBandwidthStats(0, Program.Constants.PacketCount, Program.Constants.PacketSize); - benchmarkHelper.PrintRttStats(0); - - rawSocket.Shutdown(SocketShutdown.Both); - } - catch (Exception ex) - { - Console.WriteLine("Client exception: {0}", ex); - } - - rawSocket.Close(); - rawSocket.Dispose(); - - ServerReadyEvent.Reset(); - _serverEndPoint = null; - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Benchmarks/Benchmarks/Stream Network Connection Benchmarks/RawStreamNetworkWriterSyncBenchmark.cs b/NetSharp/NetSharp.Benchmarks/Benchmarks/Stream Network Connection Benchmarks/RawStreamNetworkWriterSyncBenchmark.cs @@ -1,145 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; - -using NetSharp.Raw.Stream; - -namespace NetSharp.Benchmarks.Benchmarks.Stream_Network_Connection_Benchmarks -{ - internal class RawStreamNetworkWriterSyncBenchmark : INetSharpBenchmark - { - private readonly ManualResetEventSlim ServerReadyEvent = new ManualResetEventSlim(false); - private volatile EndPoint _serverEndPoint = null; - - /// <inheritdoc /> - public string Name => "Raw Stream Network Writer Benchmark (Synchronous)"; - - private Task ServerTask() - { - using Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - rawSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - _serverEndPoint = rawSocket.LocalEndPoint; - - try - { - // all the headers should have the same packet size, so will fit in the transmission buffer - RawStreamPacketHeader archetypalHeader = new RawStreamPacketHeader(Program.Constants.PacketSize); - byte[] transmissionBuffer = new byte[RawStreamPacket.TotalPacketSize(in archetypalHeader)]; - - rawSocket.Listen(1); - - ServerReadyEvent.Set(); - - using Socket clientSocket = rawSocket.Accept(); - clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true); - - for (int i = 0; i < Program.Constants.PacketCount; i++) - { - int expectedBytes = transmissionBuffer.Length; - - int receivedBytes = 0; - do - { - receivedBytes += clientSocket.Receive(transmissionBuffer, receivedBytes, expectedBytes - receivedBytes, SocketFlags.None); - } while (receivedBytes < expectedBytes && receivedBytes > 0); - - if (receivedBytes == 0) - { - break; - } - - int sentBytes = 0; - do - { - sentBytes += clientSocket.Send(transmissionBuffer, sentBytes, expectedBytes - sentBytes, SocketFlags.None); - } while (sentBytes < expectedBytes && sentBytes > 0); - - if (sentBytes == 0) - { - break; - } - } - - clientSocket.Disconnect(false); - clientSocket.Shutdown(SocketShutdown.Both); - clientSocket.Close(); - } - catch (Exception ex) - { - Console.WriteLine("Server exception: {0}", ex); - } - - rawSocket.Close(); - - return Task.CompletedTask; - } - - /// <inheritdoc /> - public Task RunAsync() - { - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - rawSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true); - rawSocket.Bind(Program.Constants.DefaultEndPoint); - - try - { - EndPoint defaultRemoteEndPoint = new IPEndPoint(IPAddress.Any, 0); - - BenchmarkHelper benchmarkHelper = new BenchmarkHelper(); - - byte[] sendBuffer = new byte[Program.Constants.PacketSize]; - byte[] receiveBuffer = new byte[Program.Constants.PacketSize]; - - using RawStreamNetworkWriter writer = new RawStreamNetworkWriter(ref rawSocket, defaultRemoteEndPoint, Program.Constants.PacketSize); - - Task serverTask = Task.Factory.StartNew(ServerTask, TaskCreationOptions.LongRunning); - - benchmarkHelper.ResetStopwatch(); - - ServerReadyEvent.Wait(); - EndPoint remoteEndPoint = _serverEndPoint; - rawSocket.Connect(remoteEndPoint); - - lock (typeof(Console)) - { - Console.WriteLine($"[Client {0}] Starting client at {rawSocket.LocalEndPoint}; sending messages to {remoteEndPoint}"); - } - - for (int i = 0; i < Program.Constants.PacketCount; i++) - { - byte[] packetBuffer = Program.Constants.ServerEncoding.GetBytes($"[Client 0] Hello World! (Packet {i})"); - packetBuffer.CopyTo(sendBuffer, 0); - - benchmarkHelper.StartStopwatch(); - int sendResult = writer.Write(remoteEndPoint, sendBuffer); - - int receiveResult = writer.Read(ref remoteEndPoint, receiveBuffer); - benchmarkHelper.StopStopwatch(); - - benchmarkHelper.SnapshotRttStats(); - } - - benchmarkHelper.PrintBandwidthStats(0, Program.Constants.PacketCount, Program.Constants.PacketSize); - benchmarkHelper.PrintRttStats(0); - - rawSocket.Shutdown(SocketShutdown.Both); - } - catch (Exception ex) - { - Console.WriteLine("Client exception: {0}", ex); - } - - rawSocket.Close(); - rawSocket.Dispose(); - - ServerReadyEvent.Reset(); - _serverEndPoint = null; - - return Task.CompletedTask; - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Benchmarks/INetSharpBenchmark.cs b/NetSharp/NetSharp.Benchmarks/INetSharpBenchmark.cs @@ -17,4 +17,4 @@ namespace NetSharp.Benchmarks /// </summary> Task RunAsync(); } -} -\ No newline at end of file +} diff --git a/NetSharp/NetSharp.Benchmarks/Program.cs b/NetSharp/NetSharp.Benchmarks/Program.cs @@ -7,6 +7,16 @@ using System.Text; namespace NetSharp.Benchmarks { + public static class Constants + { + public const int PacketSize = 8192, PacketCount = 1_000_000, ClientCount = 10; + + public static readonly EndPoint ClientEndPoint = new IPEndPoint(IPAddress.Any, 0); + public static readonly EndPoint DefaultEndPoint = new IPEndPoint(IPAddress.Loopback, 0); + public static readonly Encoding ServerEncoding = Encoding.UTF8; + public static readonly EndPoint ServerEndPoint = new IPEndPoint(IPAddress.Loopback, 12345); + } + internal class Program { private static readonly List<INetSharpBenchmark> Benchmarks = new List<INetSharpBenchmark>(); @@ -23,36 +33,18 @@ namespace NetSharp.Benchmarks } } - private static void ResetBenchmarks() - { - Benchmarks.Clear(); - - foreach (Type type in Assembly.GetCallingAssembly().GetTypes()) - { - if (type.IsAbstract) - { - continue; - } - - Type[] interfaces = type.GetInterfaces(); - if (interfaces.Contains(typeof(INetSharpBenchmark))) - { - Benchmarks.Add((INetSharpBenchmark)Activator.CreateInstance(type)); - } - } - } - private static void PickBenchmark() { const string allBenchmarkIdentifier = "XX"; while (true) { - Console.WriteLine("Available Examples:"); + Console.WriteLine("Available Benchmarks:"); for (int i = 0; i < Benchmarks.Count; i++) { Console.WriteLine($"\t{i:D2} - {Benchmarks[i].Name}"); } + Console.WriteLine($"\t{allBenchmarkIdentifier} - Run All Benchmarks"); Console.Write("> "); @@ -97,6 +89,25 @@ namespace NetSharp.Benchmarks Console.WriteLine(); } + private static void ResetBenchmarks() + { + Benchmarks.Clear(); + + foreach (Type type in Assembly.GetCallingAssembly().GetTypes()) + { + if (type.IsAbstract) + { + continue; + } + + Type[] interfaces = type.GetInterfaces(); + if (interfaces.Contains(typeof(INetSharpBenchmark))) + { + Benchmarks.Add((INetSharpBenchmark)Activator.CreateInstance(type)); + } + } + } + private static void RunAllBenchmarks() { foreach (INetSharpBenchmark benchmark in Benchmarks) @@ -108,17 +119,5 @@ namespace NetSharp.Benchmarks Console.WriteLine(); } } - - public static class Constants - { -#if DEBUG - // Packet counts of over 10_000 can take a long time, depending on the packet size and number of clients - public const int PacketSize = 4096, PacketCount = 1_000_000, ClientCount = 10; -#else // RELEASE - public const int PacketSize = 8192, PacketCount = 1_000_000, ClientCount = 10; -#endif - public static readonly Encoding ServerEncoding = Encoding.UTF8; - public static readonly EndPoint DefaultEndPoint = new IPEndPoint(IPAddress.Loopback, 0); - } } -} -\ No newline at end of file +} diff --git a/NetSharp/NetSharp.Examples/AssemblyAttributes.cs b/NetSharp/NetSharp.Examples/AssemblyAttributes.cs @@ -1,5 +1,5 @@ -using System.Resources; +using System; +using System.Resources; - - -[assembly: NeutralResourcesLanguage("en")] -\ No newline at end of file +[assembly: CLSCompliant(false)] +[assembly: NeutralResourcesLanguage("en")] diff --git a/NetSharp/NetSharp.Examples/Examples/Datagram Network Connection Examples/DatagramNetworkReaderExample.cs b/NetSharp/NetSharp.Examples/Examples/Datagram Network Connection Examples/DatagramNetworkReaderExample.cs @@ -1,53 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Threading.Tasks; - -using NetSharp.Raw.Datagram; - -namespace NetSharp.Examples.Examples.Datagram_Network_Connection_Examples -{ - internal class DatagramNetworkReaderExample : INetSharpExample - { - private const int PacketSize = 8192, ExpectedClientCount = 8; - - /// <inheritdoc /> - public string Name { get; } = "Datagram Network Reader Example"; - - private static bool RequestHandler(EndPoint remoteEndPoint, in ReadOnlyMemory<byte> requestBuffer, int receivedRequestBytes, - in Memory<byte> responseBuffer) - { - lock (typeof(Console)) - { - string request = Program.Constants.ServerEncoding.GetString(requestBuffer.Span).Trim('\0'); - - Console.WriteLine($"[Server] Received request \'{request}\' ({receivedRequestBytes} bytes) from {remoteEndPoint}"); - Console.WriteLine($"[Server] Sending response \'{request}\' ({receivedRequestBytes} bytes) to {remoteEndPoint}"); - } - - return requestBuffer.TryCopyTo(responseBuffer); - } - - /// <inheritdoc /> - public Task RunAsync() - { - EndPoint defaultEndPoint = new IPEndPoint(IPAddress.Any, 0); - - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - rawSocket.Bind(Program.Constants.ServerEndPoint); - - using RawDatagramNetworkReader reader = new RawDatagramNetworkReader(ref rawSocket, RequestHandler, defaultEndPoint, PacketSize, 100); - reader.Start(ExpectedClientCount); - - Console.WriteLine($"Started datagram server at {Program.Constants.ServerEndPoint}! Enter any key to stop the server..."); - Console.ReadLine(); - - reader.Shutdown(); - - rawSocket.Close(); - rawSocket.Dispose(); - - return Task.CompletedTask; - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Examples/Examples/Datagram Network Connection Examples/DatagramNetworkWriterAsyncExample.cs b/NetSharp/NetSharp.Examples/Examples/Datagram Network Connection Examples/DatagramNetworkWriterAsyncExample.cs @@ -1,69 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Threading.Tasks; - -using NetSharp.Raw.Datagram; - -namespace NetSharp.Examples.Examples.Datagram_Network_Connection_Examples -{ - internal class DatagramNetworkWriterAsyncExample : INetSharpExample - { - private const int PacketSize = 8192; - - /// <inheritdoc /> - public string Name { get; } = "Datagram Network Writer Example (Asynchronous)"; - - /// <inheritdoc /> - public async Task RunAsync() - { - EndPoint defaultEndPoint = new IPEndPoint(IPAddress.Any, 0); - - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - rawSocket.Bind(Program.Constants.ClientEndPoint); - - using RawDatagramNetworkWriter writer = new RawDatagramNetworkWriter(ref rawSocket, defaultEndPoint, PacketSize); - - byte[] transmissionBuffer = new byte[PacketSize]; - - EndPoint remoteEndPoint = Program.Constants.ServerEndPoint; - - try - { - while (true) - { - Console.Write("Input to send to server: "); - string userInput = Console.ReadLine(); - - if (!Program.Constants.ServerEncoding.GetBytes(userInput).AsMemory().TryCopyTo(transmissionBuffer)) - { - Console.WriteLine("Given input is too large. Please try again!"); - continue; - } - - int sent = await writer.WriteAsync(remoteEndPoint, transmissionBuffer); - - lock (typeof(Console)) - { - Console.WriteLine($"Sent {sent} bytes to {remoteEndPoint}!"); - } - - Array.Clear(transmissionBuffer, 0, transmissionBuffer.Length); - - int received = await writer.ReadAsync(remoteEndPoint, transmissionBuffer); - - lock (typeof(Console)) - { - Console.WriteLine($"Received {received} bytes from {remoteEndPoint}!"); - Console.WriteLine(Program.Constants.ServerEncoding.GetString(transmissionBuffer)); - } - } - } - finally - { - rawSocket.Close(); - rawSocket.Dispose(); - } - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Examples/Examples/Datagram Network Connection Examples/DatagramNetworkWriterSyncExample.cs b/NetSharp/NetSharp.Examples/Examples/Datagram Network Connection Examples/DatagramNetworkWriterSyncExample.cs @@ -1,69 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Threading.Tasks; - -using NetSharp.Raw.Datagram; - -namespace NetSharp.Examples.Examples.Datagram_Network_Connection_Examples -{ - internal class DatagramNetworkWriterSyncExample : INetSharpExample - { - private const int PacketSize = 8192; - - /// <inheritdoc /> - public string Name { get; } = "Datagram Network Writer Example (Synchronous)"; - - /// <inheritdoc /> - public Task RunAsync() - { - EndPoint defaultEndPoint = new IPEndPoint(IPAddress.Any, 0); - - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - rawSocket.Bind(Program.Constants.ClientEndPoint); - - using RawDatagramNetworkWriter writer = new RawDatagramNetworkWriter(ref rawSocket, defaultEndPoint, PacketSize); - - byte[] transmissionBuffer = new byte[PacketSize]; - - EndPoint remoteEndPoint = Program.Constants.ServerEndPoint; - - try - { - while (true) - { - Console.Write("Input to send to server: "); - string userInput = Console.ReadLine(); - - if (!Program.Constants.ServerEncoding.GetBytes(userInput).AsMemory().TryCopyTo(transmissionBuffer)) - { - Console.WriteLine("Given input is too large. Please try again!"); - continue; - } - - int sent = writer.Write(remoteEndPoint, transmissionBuffer); - - lock (typeof(Console)) - { - Console.WriteLine($"Sent {sent} bytes to {remoteEndPoint}!"); - } - - Array.Clear(transmissionBuffer, 0, transmissionBuffer.Length); - - int received = writer.Read(ref remoteEndPoint, transmissionBuffer); - - lock (typeof(Console)) - { - Console.WriteLine($"Received {received} bytes from {remoteEndPoint}!"); - Console.WriteLine(Program.Constants.ServerEncoding.GetString(transmissionBuffer)); - } - } - } - finally - { - rawSocket.Close(); - rawSocket.Dispose(); - } - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Examples/Examples/Raw Stream Examples/RawStreamClient.cs b/NetSharp/NetSharp.Examples/Examples/Raw Stream Examples/RawStreamClient.cs @@ -0,0 +1,44 @@ +using System; +using System.Net.Sockets; +using System.Threading.Tasks; + +using NetSharp.Raw.Stream; + +namespace NetSharp.Examples.Examples.Raw_Stream_Examples +{ + public class RawStreamClient : INetSharpExample + { + /// <inheritdoc /> + public string Name { get; } = "Raw Stream Client"; + + /// <inheritdoc /> + public Task RunAsync() + { + using RawStreamConnection client = new RawStreamConnection(ProtocolType.Tcp, Constants.DefaultEndPoint); + client.Bind(Constants.ClientEndPoint); + Console.WriteLine($"[Client] Bound to {client.LocalEndPoint}"); + + client.ConnectAsync(Constants.ServerEndPoint).GetAwaiter().GetResult(); + Console.WriteLine($"[Client] Connected to {client.RemoteEndPoint}"); + + byte[] packet = new byte[Constants.PacketSize]; + byte[] message = Constants.ServerEncoding.GetBytes("Hello World!"); + + int sentBytes; + do + { + message.CopyTo(packet, 0); + + sentBytes = client.SendAsync(0, packet).GetAwaiter().GetResult(); + Console.WriteLine($"[Client] Sent {sentBytes} bytes to {client.RemoteEndPoint}"); + } while (sentBytes > 0); + + client.DisconnectAsync().GetAwaiter().GetResult(); + Console.WriteLine($"[Client] Disconnected from {client.RemoteEndPoint}"); + + client.Close(); + + return Task.CompletedTask; + } + } +} diff --git a/NetSharp/NetSharp.Examples/Examples/Raw Stream Examples/RawStreamExample.cs b/NetSharp/NetSharp.Examples/Examples/Raw Stream Examples/RawStreamExample.cs @@ -0,0 +1,60 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; + +using NetSharp.Raw; +using NetSharp.Raw.Stream; + +namespace NetSharp.Examples.Examples.Raw_Stream_Examples +{ + public class RawStreamExample : INetSharpExample + { + /// <inheritdoc /> + public string Name { get; } = "Raw Stream Example"; + + /// <inheritdoc /> + public Task RunAsync() + { + using RawStreamConnection server = new RawStreamConnection(ProtocolType.Tcp, Constants.DefaultEndPoint); + server.Bind(Constants.ServerEndPoint); + Console.WriteLine($"[Server] Bound to {server.LocalEndPoint}"); + + static void PacketHandler( + EndPoint remoteEndPoint, + in RawPacketHeader header, + in ReadOnlyMemory<byte> data, + IRawStreamWriter writer) + { + Console.WriteLine( + $"[Server] Received {header.DataLength} bytes with type {header.Type} from {remoteEndPoint}"); + } + + server.RegisterHandler(0, PacketHandler); + server.Start(); + + Console.WriteLine("[Server] Started listening for network connections..."); + + using RawStreamConnection client = new RawStreamConnection(ProtocolType.Tcp, Constants.DefaultEndPoint); + client.Bind(Constants.ClientEndPoint); + Console.WriteLine($"[Client] Bound to {client.LocalEndPoint}"); + + client.ConnectAsync(Constants.ServerEndPoint).GetAwaiter().GetResult(); + Console.WriteLine($"[Client] Connected to {client.RemoteEndPoint}"); + + byte[] packet = { 42 }; + int sentBytes = client.SendAsync(0, packet).GetAwaiter().GetResult(); + Console.WriteLine($"[Client] Sent {sentBytes} bytes to {client.RemoteEndPoint}"); + + client.DisconnectAsync().GetAwaiter().GetResult(); + Console.WriteLine($"[Client] Disconnected from {client.RemoteEndPoint}"); + + client.Close(); + + server.DeregisterHandler(0, PacketHandler); + server.Close(); + + return Task.CompletedTask; + } + } +} diff --git a/NetSharp/NetSharp.Examples/Examples/Raw Stream Examples/RawStreamServer.cs b/NetSharp/NetSharp.Examples/Examples/Raw Stream Examples/RawStreamServer.cs @@ -0,0 +1,46 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; + +using NetSharp.Raw; +using NetSharp.Raw.Stream; + +namespace NetSharp.Examples.Examples.Raw_Stream_Examples +{ + public class RawStreamServer : INetSharpExample + { + /// <inheritdoc /> + public string Name { get; } = "Raw Stream Server"; + + /// <inheritdoc /> + public Task RunAsync() + { + using RawStreamConnection server = new RawStreamConnection(ProtocolType.Tcp, Constants.DefaultEndPoint); + server.Bind(Constants.ServerEndPoint); + Console.WriteLine($"[Server] Bound to {server.LocalEndPoint}"); + + static void PacketHandler( + EndPoint remoteEndPoint, + in RawPacketHeader header, + in ReadOnlyMemory<byte> data, + IRawStreamWriter writer) + { + Console.WriteLine( + $"[Server] Received {header.DataLength} bytes with type {header.Type} from {remoteEndPoint}"); + } + + server.RegisterHandler(0, PacketHandler); + server.Start(); + + Console.WriteLine("[Server] Started listening for network connections!"); + Console.WriteLine("[Server] Press enter to stop the server..."); + Console.ReadLine(); + + server.DeregisterHandler(0, PacketHandler); + server.Close(); + + return Task.CompletedTask; + } + } +} diff --git a/NetSharp/NetSharp.Examples/Examples/Stream Network Connection Examples/RawStreamChatServer.cs b/NetSharp/NetSharp.Examples/Examples/Stream Network Connection Examples/RawStreamChatServer.cs @@ -1,115 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; - -using NetSharp.Raw.Stream; - -namespace NetSharp.Examples.Examples.Stream_Network_Connection_Examples -{ - internal class RawStreamChatServer : INetSharpExample - { - private static readonly int ChatPacketSize = 8192; - private static readonly ushort InitialClientCount = 4; - private static readonly ManualResetEventSlim serverStartedEvent = new ManualResetEventSlim(false); - - public string Name => "Raw Stream Chat Server"; - - private static async Task ClientTask() - { - Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - clientSocket.Bind(Program.Constants.ClientEndPoint); - clientSocket.Connect(Program.Constants.ServerEndPoint); - - EndPoint defaultEndPoint = new IPEndPoint(IPAddress.Any, 0); - using RawStreamNetworkWriter client = new RawStreamNetworkWriter(ref clientSocket, defaultEndPoint, ChatPacketSize); - - byte[] transmissionBuffer = new byte[ChatPacketSize]; - - try - { - while (true) - { - Console.Write("Enter string to send to the server: "); - string request = Console.ReadLine(); - if (!Program.Constants.ServerEncoding.GetBytes(request).AsMemory().TryCopyTo(transmissionBuffer)) - { - Console.WriteLine("Could not copy message to transmission buffer. Please try again!"); - continue; - } - - int sentBytes = await client.WriteAsync(Program.Constants.ServerEndPoint, transmissionBuffer); - - Console.WriteLine($"[{clientSocket.LocalEndPoint}] Sent {sentBytes} bytes to server:"); - Console.WriteLine($"\t{request}"); - - Array.Clear(transmissionBuffer, 0, transmissionBuffer.Length); - - int receivedBytes = await client.ReadAsync(Program.Constants.ServerEndPoint, transmissionBuffer); - - string response = Program.Constants.ServerEncoding.GetString(transmissionBuffer); - - Console.WriteLine($"[{clientSocket.LocalEndPoint}] Received {receivedBytes} bytes from server!"); - Console.WriteLine($"\t{response}"); - } - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - finally - { - clientSocket.Shutdown(SocketShutdown.Both); - clientSocket.Disconnect(true); - - clientSocket.Close(); - clientSocket.Dispose(); - } - } - - private static bool ServerPacketHandler(EndPoint remoteEndPoint, in ReadOnlyMemory<byte> requestBuffer, int receivedRequestBytes, in Memory<byte> responseBuffer) - { - lock (typeof(Console)) - { - string request = Program.Constants.ServerEncoding.GetString(requestBuffer.Span).Trim('\0'); - - Console.WriteLine($"[Server] Received request \'{request}\' ({receivedRequestBytes} bytes) from {remoteEndPoint}"); - Console.WriteLine($"[Server] Sending response \'{request}\' ({receivedRequestBytes} bytes) to {remoteEndPoint}"); - } - - return requestBuffer.TryCopyTo(responseBuffer); - } - - private static Task ServerTask() - { - Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - - serverSocket.Bind(Program.Constants.ServerEndPoint); - serverSocket.Listen(InitialClientCount); - - EndPoint defaultEndPoint = new IPEndPoint(IPAddress.Any, 0); - using RawStreamNetworkReader server = new RawStreamNetworkReader(ref serverSocket, ServerPacketHandler, defaultEndPoint, ChatPacketSize); - - Console.WriteLine("[Server] Starting server..."); - - server.Start(InitialClientCount); - - Console.WriteLine($"[Server] Started up on {Program.Constants.ServerEndPoint}!"); - serverStartedEvent.Set(); - - while (true) - { - } - } - - public async Task RunAsync() - { - Task serverTask = Task.Factory.StartNew(ServerTask); - - serverStartedEvent.Wait(); - - await Task.Factory.StartNew(ClientTask).GetAwaiter().GetResult(); - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Examples/Examples/Stream Network Connection Examples/StreamNetworkReaderExample.cs b/NetSharp/NetSharp.Examples/Examples/Stream Network Connection Examples/StreamNetworkReaderExample.cs @@ -1,54 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Threading.Tasks; - -using NetSharp.Raw.Stream; - -namespace NetSharp.Examples.Examples.Stream_Network_Connection_Examples -{ - internal class StreamNetworkReaderExample : INetSharpExample - { - private const int PacketSize = 8192, ExpectedClientCount = 8; - - /// <inheritdoc /> - public string Name { get; } = "Raw Stream Network Reader Example"; - - private static bool RequestHandler(EndPoint remoteEndPoint, in ReadOnlyMemory<byte> requestBuffer, int receivedRequestBytes, - in Memory<byte> responseBuffer) - { - lock (typeof(Console)) - { - string request = Program.Constants.ServerEncoding.GetString(requestBuffer.Span).Trim('\0'); - - Console.WriteLine($"[Server] Received request \'{request}\' ({receivedRequestBytes} bytes) from {remoteEndPoint}"); - Console.WriteLine($"[Server] Sending response \'{request}\' ({receivedRequestBytes} bytes) to {remoteEndPoint}"); - } - - return requestBuffer.TryCopyTo(responseBuffer); - } - - /// <inheritdoc /> - public Task RunAsync() - { - EndPoint defaultEndPoint = new IPEndPoint(IPAddress.Any, 0); - - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - rawSocket.Bind(Program.Constants.ServerEndPoint); - rawSocket.Listen(ExpectedClientCount); - - using RawStreamNetworkReader reader = new RawStreamNetworkReader(ref rawSocket, RequestHandler, defaultEndPoint, PacketSize, 100); - reader.Start(ExpectedClientCount); - - Console.WriteLine($"Started stream server at {Program.Constants.ServerEndPoint}! Enter any key to stop the server..."); - Console.ReadLine(); - - reader.Shutdown(); - - rawSocket.Close(); - rawSocket.Dispose(); - - return Task.CompletedTask; - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Examples/Examples/Stream Network Connection Examples/StreamNetworkWriterAsyncExample.cs b/NetSharp/NetSharp.Examples/Examples/Stream Network Connection Examples/StreamNetworkWriterAsyncExample.cs @@ -1,77 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Threading.Tasks; - -using NetSharp.Raw.Stream; - -namespace NetSharp.Examples.Examples.Stream_Network_Connection_Examples -{ - internal class StreamNetworkWriterAsyncExample : INetSharpExample - { - private const int PacketSize = 8192; - - /// <inheritdoc /> - public string Name { get; } = "Raw Stream Network Writer Example (Asynchronous)"; - - /// <inheritdoc /> - public async Task RunAsync() - { - EndPoint defaultEndPoint = new IPEndPoint(IPAddress.Any, 0); - - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - rawSocket.Bind(Program.Constants.ClientEndPoint); - rawSocket.Connect(Program.Constants.ServerEndPoint); - - using RawStreamNetworkWriter writer = new RawStreamNetworkWriter(ref rawSocket, defaultEndPoint, PacketSize); - - byte[] transmissionBuffer = new byte[PacketSize]; - - EndPoint remoteEndPoint = Program.Constants.ServerEndPoint; - - try - { - while (true) - { - Console.Write("Input to send to server: "); - string userInput = Console.ReadLine(); - - if (!Program.Constants.ServerEncoding.GetBytes(userInput).AsMemory().TryCopyTo(transmissionBuffer)) - { - Console.WriteLine("Given input is too large. Please try again!"); - continue; - } - - int sent = await writer.WriteAsync(remoteEndPoint, transmissionBuffer); - - lock (typeof(Console)) - { - Console.WriteLine($"Sent {sent} bytes to {remoteEndPoint}!"); - } - - Array.Clear(transmissionBuffer, 0, transmissionBuffer.Length); - - int received = await writer.ReadAsync(remoteEndPoint, transmissionBuffer); - - lock (typeof(Console)) - { - Console.WriteLine($"Received {received} bytes from {remoteEndPoint}!"); - Console.WriteLine(Program.Constants.ServerEncoding.GetString(transmissionBuffer)); - } - } - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - finally - { - rawSocket.Shutdown(SocketShutdown.Both); - rawSocket.Disconnect(true); - - rawSocket.Close(); - rawSocket.Dispose(); - } - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Examples/Examples/Stream Network Connection Examples/StreamNetworkWriterSyncExample.cs b/NetSharp/NetSharp.Examples/Examples/Stream Network Connection Examples/StreamNetworkWriterSyncExample.cs @@ -1,79 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Threading.Tasks; - -using NetSharp.Raw.Stream; - -namespace NetSharp.Examples.Examples.Stream_Network_Connection_Examples -{ - internal class StreamNetworkWriterSyncExample : INetSharpExample - { - private const int PacketSize = 8192; - - /// <inheritdoc /> - public string Name { get; } = "Raw Stream Network Writer Example (Synchronous)"; - - /// <inheritdoc /> - public Task RunAsync() - { - EndPoint defaultEndPoint = new IPEndPoint(IPAddress.Any, 0); - - Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - rawSocket.Bind(Program.Constants.ClientEndPoint); - rawSocket.Connect(Program.Constants.ServerEndPoint); - - using RawStreamNetworkWriter writer = new RawStreamNetworkWriter(ref rawSocket, defaultEndPoint, PacketSize); - - byte[] transmissionBuffer = new byte[PacketSize]; - - EndPoint remoteEndPoint = Program.Constants.ServerEndPoint; - - try - { - while (true) - { - Console.Write("Input to send to server: "); - string userInput = Console.ReadLine(); - - if (!Program.Constants.ServerEncoding.GetBytes(userInput).AsMemory().TryCopyTo(transmissionBuffer)) - { - Console.WriteLine("Given input is too large. Please try again!"); - continue; - } - - int sent = writer.Write(remoteEndPoint, transmissionBuffer); - - lock (typeof(Console)) - { - Console.WriteLine($"Sent {sent} bytes to {remoteEndPoint}!"); - } - - Array.Clear(transmissionBuffer, 0, transmissionBuffer.Length); - - int received = writer.Read(ref remoteEndPoint, transmissionBuffer); - - lock (typeof(Console)) - { - Console.WriteLine($"Received {received} bytes from {remoteEndPoint}!"); - Console.WriteLine(Program.Constants.ServerEncoding.GetString(transmissionBuffer)); - } - } - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - finally - { - rawSocket.Shutdown(SocketShutdown.Both); - rawSocket.Disconnect(true); - - rawSocket.Close(); - rawSocket.Dispose(); - } - - return Task.CompletedTask; - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Examples/INetSharpExample.cs b/NetSharp/NetSharp.Examples/INetSharpExample.cs @@ -17,4 +17,4 @@ namespace NetSharp.Examples /// </summary> Task RunAsync(); } -} -\ No newline at end of file +} diff --git a/NetSharp/NetSharp.Examples/NetSharp.Examples.csproj b/NetSharp/NetSharp.Examples/NetSharp.Examples.csproj @@ -1,4 +1,4 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> @@ -8,4 +8,5 @@ <ItemGroup> <ProjectReference Include="..\NetSharp\NetSharp.csproj" /> </ItemGroup> -</Project> -\ No newline at end of file + +</Project> diff --git a/NetSharp/NetSharp.Examples/NetSharpExamples.xml b/NetSharp/NetSharp.Examples/NetSharpExamples.xml @@ -1,59 +0,0 @@ -<?xml version="1.0"?> -<doc> - <assembly> - <name>NetSharp.Examples</name> - </assembly> - <members> - <member name="P:NetSharp.Examples.Examples.Datagram_Network_Connection_Examples.DatagramNetworkReaderExample.Name"> - <inheritdoc /> - </member> - <member name="M:NetSharp.Examples.Examples.Datagram_Network_Connection_Examples.DatagramNetworkReaderExample.RunAsync"> - <inheritdoc /> - </member> - <member name="P:NetSharp.Examples.Examples.Datagram_Network_Connection_Examples.DatagramNetworkWriterAsyncExample.Name"> - <inheritdoc /> - </member> - <member name="M:NetSharp.Examples.Examples.Datagram_Network_Connection_Examples.DatagramNetworkWriterAsyncExample.RunAsync"> - <inheritdoc /> - </member> - <member name="P:NetSharp.Examples.Examples.Datagram_Network_Connection_Examples.DatagramNetworkWriterSyncExample.Name"> - <inheritdoc /> - </member> - <member name="M:NetSharp.Examples.Examples.Datagram_Network_Connection_Examples.DatagramNetworkWriterSyncExample.RunAsync"> - <inheritdoc /> - </member> - <member name="P:NetSharp.Examples.Examples.Stream_Network_Connection_Examples.StreamNetworkReaderExample.Name"> - <inheritdoc /> - </member> - <member name="M:NetSharp.Examples.Examples.Stream_Network_Connection_Examples.StreamNetworkReaderExample.RunAsync"> - <inheritdoc /> - </member> - <member name="P:NetSharp.Examples.Examples.Stream_Network_Connection_Examples.StreamNetworkWriterAsyncExample.Name"> - <inheritdoc /> - </member> - <member name="M:NetSharp.Examples.Examples.Stream_Network_Connection_Examples.StreamNetworkWriterAsyncExample.RunAsync"> - <inheritdoc /> - </member> - <member name="P:NetSharp.Examples.Examples.Stream_Network_Connection_Examples.StreamNetworkWriterSyncExample.Name"> - <inheritdoc /> - </member> - <member name="M:NetSharp.Examples.Examples.Stream_Network_Connection_Examples.StreamNetworkWriterSyncExample.RunAsync"> - <inheritdoc /> - </member> - <member name="T:NetSharp.Examples.INetSharpExample"> - <summary> - Defines an example program. - </summary> - </member> - <member name="P:NetSharp.Examples.INetSharpExample.Name"> - <summary> - The name of the example. - </summary> - </member> - <member name="M:NetSharp.Examples.INetSharpExample.RunAsync"> - <summary> - Runs the example asynchronously. - </summary> - </member> - </members> -</doc> diff --git a/NetSharp/NetSharp.Examples/Program.cs b/NetSharp/NetSharp.Examples/Program.cs @@ -7,54 +7,50 @@ using System.Text; namespace NetSharp.Examples { - internal class Program + public static class Constants { - private static readonly List<INetSharpExample> Examples; - - static Program() - { - Examples = new List<INetSharpExample>(); + public const int PacketSize = 8192, PacketCount = 1_000_000, ClientCount = 10; - foreach (Type type in Assembly.GetCallingAssembly().GetTypes()) - { - if (type.IsAbstract) - { - continue; - } + public static readonly EndPoint ClientEndPoint = new IPEndPoint(IPAddress.Any, 0); + public static readonly EndPoint DefaultEndPoint = new IPEndPoint(IPAddress.Loopback, 0); + public static readonly Encoding ServerEncoding = Encoding.UTF8; + public static readonly EndPoint ServerEndPoint = new IPEndPoint(IPAddress.Loopback, 12345); + } - Type[] interfaces = type.GetInterfaces(); - if (interfaces.Contains(typeof(INetSharpExample))) - { - Examples.Add((INetSharpExample)Activator.CreateInstance(type)); - } - } - } + internal class Program + { + private static readonly List<INetSharpExample> Examples = new List<INetSharpExample>(); private static void Main() { while (true) { + ResetBenchmarks(); + GC.Collect(); - PickExample(); + PickBenchmark(); } } - private static void PickExample() + private static void PickBenchmark() { - Console.WriteLine("Available Examples:"); - for (int i = 0; i < Examples.Count; i++) - { - Console.WriteLine($"\t{i:D2} - {Examples[i].Name}"); - } + const string allBenchmarkIdentifier = "XX"; while (true) { + Console.WriteLine("Available Examples:"); + for (int i = 0; i < Examples.Count; i++) + { + Console.WriteLine($"\t{i:D2} - {Examples[i].Name}"); + } + Console.Write("> "); try { string rawInput = Console.ReadLine()?.ToLowerInvariant() ?? "x"; + int choice = int.Parse(rawInput); if (choice < 0 || choice >= Examples.Count) @@ -84,14 +80,23 @@ namespace NetSharp.Examples Console.WriteLine(); } - public static class Constants + private static void ResetBenchmarks() { - private const int DefaultExamplePort = 44232; - private static readonly IPAddress DefaultExampleAddress = IPAddress.Loopback; + Examples.Clear(); - public static readonly EndPoint ClientEndPoint = new IPEndPoint(DefaultExampleAddress, 0); - public static readonly Encoding ServerEncoding = Encoding.UTF8; - public static readonly EndPoint ServerEndPoint = new IPEndPoint(DefaultExampleAddress, DefaultExamplePort); + foreach (Type type in Assembly.GetCallingAssembly().GetTypes()) + { + if (type.IsAbstract) + { + continue; + } + + Type[] interfaces = type.GetInterfaces(); + if (interfaces.Contains(typeof(INetSharpExample))) + { + Examples.Add((INetSharpExample)Activator.CreateInstance(type)); + } + } } } -} -\ No newline at end of file +} diff --git a/NetSharp/NetSharp.Tests/AssemblyAttributes.cs b/NetSharp/NetSharp.Tests/AssemblyAttributes.cs @@ -1,5 +1,9 @@ -using System.Resources; +using System; +using System.Resources; +using JetBrains.dotMemoryUnit; - -[assembly: NeutralResourcesLanguage("en")] -\ No newline at end of file +[assembly: CLSCompliant(false)] +[assembly: NeutralResourcesLanguage("en")] +[assembly: DotMemoryUnit(FailIfRunWithoutSupport = false)] +[assembly: SuppressXUnitOutputException] diff --git a/NetSharp/NetSharp.Tests/NetSharp.Tests.csproj b/NetSharp/NetSharp.Tests/NetSharp.Tests.csproj @@ -6,7 +6,8 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" /> + <PackageReference Include="JetBrains.DotMemoryUnit" Version="3.1.20200127.214830" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> <PrivateAssets>all</PrivateAssets> diff --git a/NetSharp/NetSharp.Tests/Program.cs b/NetSharp/NetSharp.Tests/Program.cs @@ -1,26 +0,0 @@ -using System.Net; -using System.Text; - -using Xunit; - -namespace NetSharp.Tests -{ - public class Program - { - [Fact] - public void HelloTests() - { - Assert.True(true); - } - - public static class Constants - { - private const int DefaultExamplePort = 44233; - private static readonly IPAddress DefaultExampleAddress = IPAddress.Loopback; - - public static readonly EndPoint ClientEndPoint = new IPEndPoint(DefaultExampleAddress, 0); - public static readonly Encoding ServerEncoding = Encoding.UTF8; - public static readonly EndPoint ServerEndPoint = new IPEndPoint(DefaultExampleAddress, DefaultExamplePort); - } - } -} -\ No newline at end of file diff --git a/NetSharp/NetSharp.Tests/RawDatagramConnectionTests.cs b/NetSharp/NetSharp.Tests/RawDatagramConnectionTests.cs @@ -0,0 +1,48 @@ +using System.Net; +using System.Net.Sockets; + +using JetBrains.dotMemoryUnit; + +using NetSharp.Raw.Datagram; + +using Xunit; + +namespace NetSharp.Tests +{ + public class RawDatagramConnectionTests + { + private static readonly IPEndPoint ClientLocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0); + private static readonly IPEndPoint ServerLocalEndPoint = new IPEndPoint(IPAddress.Loopback, 12345); + + [Fact] + public void DisposesCleanly() + { + static void Instantiate() + { + using RawDatagramConnection conn = ConnectionFactory(); + + conn.Bind(ServerLocalEndPoint); + conn.Start(); + + conn.Close(); + } + + Instantiate(); + + AssertDisposedCleanly(); + } + + private static void AssertDisposedCleanly() + { + _ = dotMemory.Check(memory => + { + Assert.Equal(0, memory.GetObjects(where => where.Type.Is<RawDatagramConnection>()).ObjectsCount); + }); + } + + private static RawDatagramConnection ConnectionFactory() + { + return new RawDatagramConnection(ProtocolType.Udp, new IPEndPoint(IPAddress.Loopback, 0)); + } + } +} diff --git a/NetSharp/NetSharp.Tests/RawStreamConnectionTests.cs b/NetSharp/NetSharp.Tests/RawStreamConnectionTests.cs @@ -0,0 +1,84 @@ +using System; +using System.Net; +using System.Net.Sockets; + +using JetBrains.dotMemoryUnit; + +using NetSharp.Raw; +using NetSharp.Raw.Stream; + +using Xunit; + +namespace NetSharp.Tests +{ + public class RawStreamConnectionTests + { + private static readonly IPEndPoint ClientLocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0); + private static readonly IPEndPoint ServerLocalEndPoint = new IPEndPoint(IPAddress.Loopback, 12345); + + [Fact] + public void DisposesCleanly() + { + static void Instantiate() + { + using RawStreamConnection conn = ConnectionFactory(); + conn.Bind(ServerLocalEndPoint); + + conn.Start(); + + conn.Close(); + } + + Instantiate(); + + AssertDisposedCleanly(); + } + + [Fact] + public void FullTest() + { + using RawStreamConnection server = ConnectionFactory(); + server.Bind(ServerLocalEndPoint); + + static void PacketHandler( + EndPoint point, + in RawPacketHeader header, + in ReadOnlyMemory<byte> data, + IRawStreamWriter writer) + { + } + + server.RegisterHandler(0, PacketHandler); + server.Start(); + + using RawStreamConnection client = ConnectionFactory(); + client.Bind(ClientLocalEndPoint); + + client.ConnectAsync(ServerLocalEndPoint).GetAwaiter().GetResult(); + + byte[] packet = { 42 }; + _ = client.SendAsync(0, packet).GetAwaiter().GetResult(); + + client.DisconnectAsync().GetAwaiter().GetResult(); + client.Close(); + + server.DeregisterHandler(0, PacketHandler); + server.Close(); + + AssertDisposedCleanly(); + } + + private static void AssertDisposedCleanly() + { + _ = dotMemory.Check(memory => + { + Assert.Equal(0, memory.GetObjects(where => where.Type.Is<RawStreamConnection>()).ObjectsCount); + }); + } + + private static RawStreamConnection ConnectionFactory() + { + return new RawStreamConnection(ProtocolType.Tcp, new IPEndPoint(IPAddress.Loopback, 0)); + } + } +} diff --git a/NetSharp/NetSharp.sln b/NetSharp/NetSharp.sln @@ -11,11 +11,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetSharp", "NetSharp\NetSharp.csproj", "{424A38DB-7CA0-4C94-90B4-EF246CB4BDC4}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetSharp.Examples", "NetSharp.Examples\NetSharp.Examples.csproj", "{5861E518-E712-4031-9AA1-0313736FF957}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetSharp.Tests", "NetSharp.Tests\NetSharp.Tests.csproj", "{20FCC003-4342-49E3-9F66-640458C74B02}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetSharp.Tests", "NetSharp.Tests\NetSharp.Tests.csproj", "{20FCC003-4342-49E3-9F66-640458C74B02}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetSharp.Benchmarks", "NetSharp.Benchmarks\NetSharp.Benchmarks.csproj", "{1C01A0C1-49C9-4B27-A4C8-7C66081982DC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetSharp.Benchmarks", "NetSharp.Benchmarks\NetSharp.Benchmarks.csproj", "{1C01A0C1-49C9-4B27-A4C8-7C66081982DC}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetSharp.Examples", "NetSharp.Examples\NetSharp.Examples.csproj", "{12CD0A92-409B-4207-AED0-77FEFDBDE948}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -27,10 +27,6 @@ Global {424A38DB-7CA0-4C94-90B4-EF246CB4BDC4}.Debug|Any CPU.Build.0 = Debug|Any CPU {424A38DB-7CA0-4C94-90B4-EF246CB4BDC4}.Release|Any CPU.ActiveCfg = Release|Any CPU {424A38DB-7CA0-4C94-90B4-EF246CB4BDC4}.Release|Any CPU.Build.0 = Release|Any CPU - {5861E518-E712-4031-9AA1-0313736FF957}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5861E518-E712-4031-9AA1-0313736FF957}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5861E518-E712-4031-9AA1-0313736FF957}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5861E518-E712-4031-9AA1-0313736FF957}.Release|Any CPU.Build.0 = Release|Any CPU {20FCC003-4342-49E3-9F66-640458C74B02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {20FCC003-4342-49E3-9F66-640458C74B02}.Debug|Any CPU.Build.0 = Debug|Any CPU {20FCC003-4342-49E3-9F66-640458C74B02}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -39,6 +35,10 @@ Global {1C01A0C1-49C9-4B27-A4C8-7C66081982DC}.Debug|Any CPU.Build.0 = Debug|Any CPU {1C01A0C1-49C9-4B27-A4C8-7C66081982DC}.Release|Any CPU.ActiveCfg = Release|Any CPU {1C01A0C1-49C9-4B27-A4C8-7C66081982DC}.Release|Any CPU.Build.0 = Release|Any CPU + {12CD0A92-409B-4207-AED0-77FEFDBDE948}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {12CD0A92-409B-4207-AED0-77FEFDBDE948}.Debug|Any CPU.Build.0 = Debug|Any CPU + {12CD0A92-409B-4207-AED0-77FEFDBDE948}.Release|Any CPU.ActiveCfg = Release|Any CPU + {12CD0A92-409B-4207-AED0-77FEFDBDE948}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/NetSharp/NetSharp/AssemblyAttributes.cs b/NetSharp/NetSharp/AssemblyAttributes.cs @@ -1,6 +1,8 @@ -using System.Resources; +using System; +using System.Resources; using System.Runtime.CompilerServices; +[assembly: CLSCompliant(false)] [assembly: InternalsVisibleTo("NetSharp.Benchmarks", AllInternalsVisible = true)] [assembly: InternalsVisibleTo("NetSharp.Tests", AllInternalsVisible = true)] [assembly: NeutralResourcesLanguage("en")] diff --git a/NetSharp/NetSharp/GlobalSuppressions.cs b/NetSharp/NetSharp/GlobalSuppressions.cs @@ -1,7 +1,6 @@ -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. +// This file is used by Code Analysis to maintain SuppressMessage attributes that are applied to this project. +// Project-level suppressions either have no target or are given a specific target and scoped to a namespace, type, +// member, etc. using System.Diagnostics.CodeAnalysis; diff --git a/NetSharp/NetSharp/Interfaces/IRawNetworkReader.cs b/NetSharp/NetSharp/Interfaces/IRawNetworkReader.cs @@ -1,22 +0,0 @@ -namespace NetSharp.Interfaces -{ - /// <summary> - /// Describes the methods and properties that a raw network reader (i.e. binary server) must implement. - /// </summary> - public interface IRawNetworkReader - { - /// <summary> - /// Stops queuing up new asynchronous read operations. Does not terminate existing asynchronous operations; the underlying socket must be - /// terminated to cancel all current 'in-flight' asynchronous operations. - /// </summary> - public void Shutdown(); - - /// <summary> - /// Queues up <paramref name="concurrentTasks" /> new asynchronous read operations. - /// </summary> - /// <param name="concurrentTasks"> - /// The number of asynchronous read operation to queue up. - /// </param> - public void Start(ushort concurrentTasks); - } -} diff --git a/NetSharp/NetSharp/Interfaces/IRawNetworkWriter.cs b/NetSharp/NetSharp/Interfaces/IRawNetworkWriter.cs @@ -1,83 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Threading.Tasks; - -namespace NetSharp.Interfaces -{ - /// <summary> - /// Describes the methods and properties that a raw network writer (i.e. binary client) must implement. - /// </summary> - public interface IRawNetworkWriter - { - /// <summary> - /// Reads bytes from the network until either the given <paramref name="readBuffer" /> is filled, or a single datagram has been received. - /// </summary> - /// <param name="remoteEndPoint"> - /// The remote endpoint from which to receive bytes. Ignored for stream connections, where the connected socket's remote endpoint is used instead. - /// </param> - /// <param name="readBuffer"> - /// The buffer into which to place any received bytes. - /// </param> - /// <param name="flags"> - /// The <see cref="SocketFlags" /> associated with the network operation. - /// </param> - /// <returns> - /// The number of bytes of data read from the remote connection. - /// </returns> - public int Read(ref EndPoint remoteEndPoint, Memory<byte> readBuffer, SocketFlags flags = SocketFlags.None); - - /// <summary> - /// Asynchronously reads bytes from the network until either the given <paramref name="readBuffer" /> is filled, or a single datagram has been received. - /// </summary> - /// <param name="remoteEndPoint"> - /// The remote endpoint from which to receive bytes. Ignored for stream connections, where the connected socket's remote endpoint is used instead. - /// </param> - /// <param name="readBuffer"> - /// The buffer into which to place any received bytes. - /// </param> - /// <param name="flags"> - /// The <see cref="SocketFlags" /> associated with the network operation. - /// </param> - /// <returns> - /// The number of bytes of data read from the remote connection. - /// </returns> - public ValueTask<int> ReadAsync(EndPoint remoteEndPoint, Memory<byte> readBuffer, SocketFlags flags = SocketFlags.None); - - /// <summary> - /// Writes bytes in the given <paramref name="writeBuffer" /> to the network. If using a datagram connection, make sure that the - /// <paramref name="writeBuffer" /> size doesn't exceed the max datagram size for that transport. - /// </summary> - /// <param name="remoteEndPoint"> - /// The remote endpoint to which to send bytes. Ignored for stream connections, where the connected socket's remote endpoint is used instead. - /// </param> - /// <param name="writeBuffer"> - /// The buffer whose contents to send. - /// </param> - /// <param name="flags"> - /// The <see cref="SocketFlags" /> associated with the network operation. - /// </param> - /// <returns> - /// The number of bytes of data sent to the remote endpoint. - /// </returns> - public int Write(EndPoint remoteEndPoint, ReadOnlyMemory<byte> writeBuffer, SocketFlags flags = SocketFlags.None); - - /// <summary> - /// Asynchronously writes bytes in the given <paramref name="writeBuffer" /> to the network. If using a datagram connection, make sure that - /// the <paramref name="writeBuffer" /> size doesn't exceed the max datagram size for that transport. - /// </summary> - /// <param name="remoteEndPoint"> - /// The remote endpoint to which to send bytes. Ignored for stream connections, where the connected socket's remote endpoint is used instead. - /// </param> - /// <param name="writeBuffer"> - /// The buffer whose contents to send. - /// </param> - /// <param name="flags"> - /// The <see cref="SocketFlags" /> associated with the network operation. - /// </param> - /// <returns> - /// The number of bytes of data sent to the remote endpoint. - /// </returns> - public ValueTask<int> WriteAsync(EndPoint remoteEndPoint, ReadOnlyMemory<byte> writeBuffer, SocketFlags flags = SocketFlags.None); - } -} diff --git a/NetSharp/NetSharp/NetSharp.csproj b/NetSharp/NetSharp/NetSharp.csproj @@ -3,6 +3,7 @@ <PropertyGroup> <Nullable>enable</Nullable> <TargetFramework>netstandard2.1</TargetFramework> + <AnalysisMode>AllEnabledByDefault</AnalysisMode> <AnalysisLevel>latest</AnalysisLevel> <Configurations>Debug;Release</Configurations> </PropertyGroup> @@ -40,10 +41,6 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.0"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> - </PackageReference> <PackageReference Include="Microsoft.CSharp" Version="4.7.0" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118"> <PrivateAssets>all</PrivateAssets> @@ -75,8 +72,4 @@ <LastGenOutput>Resources.Designer.cs</LastGenOutput> </EmbeddedResource> </ItemGroup> - - <ItemGroup> - <Folder Include="Deprecated\" /> - </ItemGroup> </Project> diff --git a/NetSharp/NetSharp/NetSharp.xml b/NetSharp/NetSharp/NetSharp.xml @@ -4,488 +4,511 @@ <name>NetSharp</name> </assembly> <members> - <member name="T:NetSharp.Interfaces.IRawNetworkReader"> - <summary> - Describes the methods and properties that a raw network reader (i.e. binary server) must implement. - </summary> - </member> - <member name="M:NetSharp.Interfaces.IRawNetworkReader.Shutdown"> - <summary> - Stops queuing up new asynchronous read operations. Does not terminate existing asynchronous operations; the underlying socket must be - terminated to cancel all current 'in-flight' asynchronous operations. - </summary> - </member> - <member name="M:NetSharp.Interfaces.IRawNetworkReader.Start(System.UInt16)"> - <summary> - Queues up <paramref name="concurrentTasks" /> new asynchronous read operations. - </summary> - <param name="concurrentTasks"> - The number of asynchronous read operation to queue up. - </param> - </member> - <member name="T:NetSharp.Interfaces.IRawNetworkWriter"> + <member name="T:NetSharp.Properties.Resources"> <summary> - Describes the methods and properties that a raw network writer (i.e. binary client) must implement. + A strongly-typed resource class, for looking up localized strings, etc. </summary> </member> - <member name="M:NetSharp.Interfaces.IRawNetworkWriter.Read(System.Net.EndPoint@,System.Memory{System.Byte},System.Net.Sockets.SocketFlags)"> + <member name="P:NetSharp.Properties.Resources.Culture"> <summary> - Reads bytes from the network until either the given <paramref name="readBuffer" /> is filled, or a single datagram has been received. + Overrides the current thread's CurrentUICulture property for all + resource lookups using this strongly typed resource class. </summary> - <param name="remoteEndPoint"> - The remote endpoint from which to receive bytes. Ignored for stream connections, where the connected socket's remote endpoint is used instead. - </param> - <param name="readBuffer"> - The buffer into which to place any received bytes. - </param> - <param name="flags"> - The <see cref="T:System.Net.Sockets.SocketFlags" /> associated with the network operation. - </param> - <returns> - The number of bytes of data read from the remote connection. - </returns> </member> - <member name="M:NetSharp.Interfaces.IRawNetworkWriter.ReadAsync(System.Net.EndPoint,System.Memory{System.Byte},System.Net.Sockets.SocketFlags)"> + <member name="P:NetSharp.Properties.Resources.RawDatagramNetworkReaderRentedBufferSizeError"> <summary> - Asynchronously reads bytes from the network until either the given <paramref name="readBuffer" /> is filled, or a single datagram has been received. + Looks up a localized string similar to Cannot rent a temporary buffer of size: {0} bytes. The maximum temporary buffer size is {1} bytes. </summary> - <param name="remoteEndPoint"> - The remote endpoint from which to receive bytes. Ignored for stream connections, where the connected socket's remote endpoint is used instead. - </param> - <param name="readBuffer"> - The buffer into which to place any received bytes. - </param> - <param name="flags"> - The <see cref="T:System.Net.Sockets.SocketFlags" /> associated with the network operation. - </param> - <returns> - The number of bytes of data read from the remote connection. - </returns> </member> - <member name="M:NetSharp.Interfaces.IRawNetworkWriter.Write(System.Net.EndPoint,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags)"> + <member name="P:NetSharp.Properties.Resources.RawDatagramSizeError"> <summary> - Writes bytes in the given <paramref name="writeBuffer" /> to the network. If using a datagram connection, make sure that the - <paramref name="writeBuffer" /> size doesn't exceed the max datagram size for that transport. + Looks up a localized string similar to The datagram size must be between 0 and 65507 bytes. </summary> - <param name="remoteEndPoint"> - The remote endpoint to which to send bytes. Ignored for stream connections, where the connected socket's remote endpoint is used instead. - </param> - <param name="writeBuffer"> - The buffer whose contents to send. - </param> - <param name="flags"> - The <see cref="T:System.Net.Sockets.SocketFlags" /> associated with the network operation. - </param> - <returns> - The number of bytes of data sent to the remote endpoint. - </returns> </member> - <member name="M:NetSharp.Interfaces.IRawNetworkWriter.WriteAsync(System.Net.EndPoint,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags)"> + <member name="P:NetSharp.Properties.Resources.RawStreamMessageSizeUnderflow"> <summary> - Asynchronously writes bytes in the given <paramref name="writeBuffer" /> to the network. If using a datagram connection, make sure that - the <paramref name="writeBuffer" /> size doesn't exceed the max datagram size for that transport. + Looks up a localized string similar to The maximum pooled message size must be greater than 0 bytes. </summary> - <param name="remoteEndPoint"> - The remote endpoint to which to send bytes. Ignored for stream connections, where the connected socket's remote endpoint is used instead. - </param> - <param name="writeBuffer"> - The buffer whose contents to send. - </param> - <param name="flags"> - The <see cref="T:System.Net.Sockets.SocketFlags" /> associated with the network operation. - </param> - <returns> - The number of bytes of data sent to the remote endpoint. - </returns> </member> - <member name="T:NetSharp.Raw.Datagram.RawDatagramRequestHandler"> + <member name="P:NetSharp.Properties.Resources.ResourceManager"> <summary> - Represents a method that handles a request received by a <see cref="T:NetSharp.Raw.Datagram.RawDatagramNetworkReader" />. + Returns the cached ResourceManager instance used by this class. </summary> - <param name="remoteEndPoint"> - The remote endpoint from which the request was received. - </param> - <param name="requestBuffer"> - The buffer containing the received request. - </param> - <param name="receivedRequestBytes"> - The number of bytes of user data received in the request. - </param> - <param name="responseBuffer"> - The buffer into which the response should be written. - </param> - <returns> - Whether there exists a response to be sent back to the remote endpoint. - </returns> </member> - <member name="T:NetSharp.Raw.Datagram.RawDatagramNetworkReader"> + <member name="F:NetSharp.Raw.Datagram.RawDatagramConnection.MaxDatagramDataLength"> <summary> - Implements a raw network reader using a datagram-based protocol. + The maximum size that a user supplied data buffer can be to fit into a UDP diagram with a preceding packet header. </summary> </member> - <member name="M:NetSharp.Raw.Datagram.RawDatagramNetworkReader.#ctor(System.Net.Sockets.Socket@,NetSharp.Raw.Datagram.RawDatagramRequestHandler,System.Net.EndPoint,System.Int32,System.Int32,System.UInt32)"> - <inheritdoc cref="M:NetSharp.Raw.RawNetworkReaderBase.#ctor(System.Net.Sockets.Socket@,System.Net.EndPoint,System.Int32,System.Int32,System.UInt32)" /> - </member> - <member name="M:NetSharp.Raw.Datagram.RawDatagramNetworkReader.Start(System.UInt16)"> + <member name="M:NetSharp.Raw.Datagram.RawDatagramConnection.SendAsync(System.UInt16,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags)"> <inheritdoc /> </member> - <member name="M:NetSharp.Raw.Datagram.RawDatagramNetworkReader.CanReuseStateObject(System.Net.Sockets.SocketAsyncEventArgs@)"> + <member name="M:NetSharp.Raw.Datagram.RawDatagramConnection.CreateSocketArgsHook(System.Net.Sockets.SocketAsyncEventArgs@)"> <inheritdoc /> </member> - <member name="M:NetSharp.Raw.Datagram.RawDatagramNetworkReader.CreateStateObject"> + <member name="M:NetSharp.Raw.Datagram.RawDatagramConnection.DestroySocketArgsHook(System.Net.Sockets.SocketAsyncEventArgs@)"> <inheritdoc /> </member> - <member name="M:NetSharp.Raw.Datagram.RawDatagramNetworkReader.DestroyStateObject(System.Net.Sockets.SocketAsyncEventArgs)"> + <member name="M:NetSharp.Raw.Datagram.RawDatagramConnection.ResetSocketArgsHook(System.Net.Sockets.SocketAsyncEventArgs@)"> <inheritdoc /> </member> - <member name="M:NetSharp.Raw.Datagram.RawDatagramNetworkReader.ResetStateObject(System.Net.Sockets.SocketAsyncEventArgs@)"> + <member name="M:NetSharp.Raw.Datagram.RawDatagramConnection.HandlerThreadWork"> <inheritdoc /> </member> - <member name="T:NetSharp.Raw.Datagram.RawDatagramNetworkWriter"> + <member name="T:NetSharp.Raw.RawConnectionBase"> <summary> - Implements a raw network writer using a datagram-based protocol. + Provides base functionality to all raw network connections. </summary> </member> - <member name="M:NetSharp.Raw.Datagram.RawDatagramNetworkWriter.#ctor(System.Net.Sockets.Socket@,System.Net.EndPoint,System.Int32,System.Int32,System.UInt32)"> - <inheritdoc cref="M:NetSharp.Raw.RawNetworkWriterBase.#ctor(System.Net.Sockets.Socket@,System.Net.EndPoint,System.Int32,System.Int32,System.UInt32)"/> + <member name="M:NetSharp.Raw.RawConnectionBase.#ctor(System.Net.Sockets.SocketType,System.Net.Sockets.ProtocolType,System.Net.EndPoint)"> + <summary> + Initialises a new instance of the <see cref="T:NetSharp.Raw.RawConnectionBase" /> class. + </summary> + <param name="connectionSocketType"> + The socket type for the underlying network connection. + </param> + <param name="connectionProtocolType"> + The protocol type for the underlying network connection. + </param> + <param name="defaultRemoteEndPoint"> + The default remote endpoint to which network writes will be made. + </param> </member> - <member name="M:NetSharp.Raw.Datagram.RawDatagramNetworkWriter.Read(System.Net.EndPoint@,System.Memory{System.Byte},System.Net.Sockets.SocketFlags)"> - <inheritdoc /> + <member name="P:NetSharp.Raw.RawConnectionBase.DefaultRemoteEndPoint"> + <summary> + The default remote endpoint to which network writes are made. + </summary> </member> - <member name="M:NetSharp.Raw.Datagram.RawDatagramNetworkWriter.ReadAsync(System.Net.EndPoint,System.Memory{System.Byte},System.Net.Sockets.SocketFlags)"> - <inheritdoc /> + <member name="P:NetSharp.Raw.RawConnectionBase.LocalEndPoint"> + <summary> + The currently bound local endpoint. + </summary> </member> - <member name="M:NetSharp.Raw.Datagram.RawDatagramNetworkWriter.Write(System.Net.EndPoint,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags)"> - <inheritdoc /> + <member name="P:NetSharp.Raw.RawConnectionBase.RemoteEndPoint"> + <summary> + The currently connected remote endpoint. + </summary> </member> - <member name="M:NetSharp.Raw.Datagram.RawDatagramNetworkWriter.WriteAsync(System.Net.EndPoint,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags)"> - <inheritdoc /> + <member name="P:NetSharp.Raw.RawConnectionBase.Connection"> + <summary> + The underlying network connection. + </summary> </member> - <member name="M:NetSharp.Raw.Datagram.RawDatagramNetworkWriter.CanReuseStateObject(System.Net.Sockets.SocketAsyncEventArgs@)"> - <inheritdoc /> + <member name="P:NetSharp.Raw.RawConnectionBase.IsDisposed"> + <summary> + Whether the underlying network connection has been disposed or not. + </summary> </member> - <member name="M:NetSharp.Raw.Datagram.RawDatagramNetworkWriter.CreateStateObject"> - <inheritdoc /> + <member name="M:NetSharp.Raw.RawConnectionBase.Bind(System.Net.EndPoint)"> + <summary> + Binds the underlying network connection to the given local endpoint. + </summary> + <param name="localEndPoint"> + The local network endpoint to which we should bind. + </param> </member> - <member name="M:NetSharp.Raw.Datagram.RawDatagramNetworkWriter.DestroyStateObject(System.Net.Sockets.SocketAsyncEventArgs)"> - <inheritdoc /> + <member name="M:NetSharp.Raw.RawConnectionBase.Close"> + <summary> + Stops listening for incoming connections and data, and releases all managed resources. + </summary> </member> - <member name="M:NetSharp.Raw.Datagram.RawDatagramNetworkWriter.ResetStateObject(System.Net.Sockets.SocketAsyncEventArgs@)"> + <member name="M:NetSharp.Raw.RawConnectionBase.Dispose"> <inheritdoc /> </member> - <member name="T:NetSharp.Raw.RawNetworkConnectionBase"> + <member name="M:NetSharp.Raw.RawConnectionBase.Start(System.Int32)"> <summary> - Defines fields and methods common to all network connections. + Starts listening to the network for incoming connections and data. </summary> + <param name="concurrentTasks"> + The number of concurrent read tasks that should be maintained. + </param> </member> - <member name="F:NetSharp.Raw.RawNetworkConnectionBase.DefaultMaxPooledBufferSize"> + <member name="M:NetSharp.Raw.RawConnectionBase.CreateSocketArgsHook(System.Net.Sockets.SocketAsyncEventArgs@)"> <summary> - The maximum size of a pooled buffer that can be used with the <see cref="P:System.Buffers.ArrayPool`1.Shared" /> property, before a new custom pool must - be created. Taken from: https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Buffers/ConfigurableArrayPool.cs. + Allows for custom initialisation of a pooled <see cref="T:System.Net.Sockets.SocketAsyncEventArgs" /> instance. </summary> + <param name="instance"> + The instance to initialise. + </param> </member> - <member name="F:NetSharp.Raw.RawNetworkConnectionBase.DefaultMaxPooledBuffersPerBucket"> + <member name="M:NetSharp.Raw.RawConnectionBase.DestroySocketArgsHook(System.Net.Sockets.SocketAsyncEventArgs@)"> <summary> - The maximum number of pooled buffers per bucket that can be used with the <see cref="P:System.Buffers.ArrayPool`1.Shared" /> property, before a new custom - pool must be created. Taken from: https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Buffers/ConfigurableArrayPool.cs. + Allows for custom destruction of a pooled <see cref="T:System.Net.Sockets.SocketAsyncEventArgs" /> instance. </summary> + <param name="instance"> + The instance to destroy. + </param> </member> - <member name="F:NetSharp.Raw.RawNetworkConnectionBase.MaxDatagramSize"> + <member name="M:NetSharp.Raw.RawConnectionBase.Dispose(System.Boolean)"> <summary> - The maximum size that a user supplied data buffer can be to fit into a UDP datagram. + Disposes of this <see cref="T:NetSharp.Raw.RawConnectionBase" /> instance. </summary> + <param name="disposing"> + Whether the <see cref="M:NetSharp.Raw.RawConnectionBase.Dispose" /> method was called. + </param> </member> - <member name="M:NetSharp.Raw.RawNetworkConnectionBase.#ctor(System.Net.Sockets.Socket@,System.Net.EndPoint,System.Int32,System.Int32,System.UInt32)"> + <member name="M:NetSharp.Raw.RawConnectionBase.HandlerThreadWork"> <summary> - Initialises a new instance of the <see cref="T:NetSharp.Raw.RawNetworkConnectionBase" /> class. + Handler work delegate, started when a call to <see cref="M:NetSharp.Raw.RawConnectionBase.Start(System.Int32)" /> is made. </summary> - <param name="rawConnection"> - The underlying <see cref="T:System.Net.Sockets.Socket" /> to use for the connection. - </param> - <param name="defaultEndPoint"> - The default endpoint to use to represent remote clients. - </param> - <param name="maxPooledBufferSize"> - The maximum size of a pooled buffer. - </param> - <param name="pooledBuffersPerBucket"> - The number of pooled buffers to hold in a single pool bucket. - </param> - <param name="preallocatedStateObjects"> - The number of state objects to preallocate. - </param> </member> - <member name="P:NetSharp.Raw.RawNetworkConnectionBase.ArgsPool"> + <member name="M:NetSharp.Raw.RawConnectionBase.RentBuffer(System.Int32)"> <summary> - The object pool to use to pool <see cref="T:System.Net.Sockets.SocketAsyncEventArgs" /> instances. + Rents a pooled buffer of at least the specified length. The buffer MUST be returned via a call to <see + cref="M:NetSharp.Raw.RawConnectionBase.ReturnBuffer(System.Byte[],System.Boolean)" /> once it has been used. </summary> + <param name="minimumBufferLength"> + The minimum length of the rented buffer. + </param> + <returns> + The rented buffer. + </returns> </member> - <member name="P:NetSharp.Raw.RawNetworkConnectionBase.BufferPool"> + <member name="M:NetSharp.Raw.RawConnectionBase.RentSocketArgs"> <summary> - The object pool to use to pool byte buffer instance. + Rents a pooled <see cref="T:System.Net.Sockets.SocketAsyncEventArgs" /> instance. The socket args MUST be returned via a call to + <see cref="M:NetSharp.Raw.RawConnectionBase.ReturnSocketArgs(System.Net.Sockets.SocketAsyncEventArgs)" /> once they have been used. </summary> + <returns> + The rented socket args. + </returns> </member> - <member name="P:NetSharp.Raw.RawNetworkConnectionBase.Connection"> + <member name="M:NetSharp.Raw.RawConnectionBase.ResetSocketArgsHook(System.Net.Sockets.SocketAsyncEventArgs@)"> <summary> - The underlying connection socket. + Allows for custom resetting of a pooled <see cref="T:System.Net.Sockets.SocketAsyncEventArgs" /> instance. </summary> + <param name="instance"> + The instance to reset. + </param> </member> - <member name="P:NetSharp.Raw.RawNetworkConnectionBase.DefaultEndPoint"> + <member name="M:NetSharp.Raw.RawConnectionBase.ReturnBuffer(System.Byte[],System.Boolean)"> <summary> - The default endpoint to use to represent remote clients. + Returns a previously rented pooled buffer, optionally without clearing it. </summary> + <param name="buffer"> + The rented buffer. + </param> + <param name="clearBuffer"> + Whether to clear the data held in the buffer. + </param> </member> - <member name="M:NetSharp.Raw.RawNetworkConnectionBase.Dispose"> - <inheritdoc /> - </member> - <member name="M:NetSharp.Raw.RawNetworkConnectionBase.CanReuseStateObject(System.Net.Sockets.SocketAsyncEventArgs@)"> - <inheritdoc cref="T:NetSharp.Utils.SlimObjectPool`1.CanReuseObjectPredicate" /> - </member> - <member name="M:NetSharp.Raw.RawNetworkConnectionBase.CleanupTransmissionBufferAndState(System.Net.Sockets.SocketAsyncEventArgs)"> + <member name="M:NetSharp.Raw.RawConnectionBase.ReturnSocketArgs(System.Net.Sockets.SocketAsyncEventArgs)"> <summary> - Performs cleanup on the given <paramref name="args" /> instance. Neither the given <paramref name="args"/> instance, nor the - <see cref="P:System.Net.Sockets.SocketAsyncEventArgs.Buffer" /> or <see cref="P:System.Net.Sockets.SocketAsyncEventArgs.MemoryBuffer" /> attached to it can be used after this call. + Returns a previously rented <see cref="T:System.Net.Sockets.SocketAsyncEventArgs" /> instance. </summary> - <param name="args"> - The used <see cref="T:System.Net.Sockets.SocketAsyncEventArgs" /> that can be cleaned up to be reused. + <param name="socketArgs"> + The rented socket args. </param> </member> - <member name="M:NetSharp.Raw.RawNetworkConnectionBase.CreateStateObject"> - <inheritdoc cref="T:NetSharp.Utils.SlimObjectPool`1.CreateObjectDelegate" /> + <member name="M:NetSharp.Raw.RawConnectionBase.StartHook(System.Int32)"> + <summary> + Allows for custom setup before we start listening to the network. + </summary> + <param name="concurrentTasks"> + The number of concurrent read tasks that should be maintained. + </param> </member> - <member name="M:NetSharp.Raw.RawNetworkConnectionBase.DestroyStateObject(System.Net.Sockets.SocketAsyncEventArgs)"> - <inheritdoc cref="T:NetSharp.Utils.SlimObjectPool`1.DestroyObjectDelegate" /> + <member name="T:NetSharp.Raw.RawPacketHeader"> + <summary> + Contains metadata about a network packet. + </summary> </member> - <member name="M:NetSharp.Raw.RawNetworkConnectionBase.Dispose(System.Boolean)"> + <member name="F:NetSharp.Raw.RawPacketHeader.Length"> <summary> - Allows for inheritors to dispose of their own resources. + The total length of the packet when serialised. </summary> - <param name="disposing"> - Whether the <see cref="M:NetSharp.Raw.RawNetworkConnectionBase.Dispose" /> method is being called. - </param> </member> - <member name="M:NetSharp.Raw.RawNetworkConnectionBase.ResetStateObject(System.Net.Sockets.SocketAsyncEventArgs@)"> - <inheritdoc cref="T:NetSharp.Utils.SlimObjectPool`1.ResetObjectDelegate" /> + <member name="F:NetSharp.Raw.RawPacketHeader.DataLength"> + <summary> + The length of the data following this packet. + </summary> </member> - <member name="T:NetSharp.Raw.RawNetworkReaderBase"> + <member name="F:NetSharp.Raw.RawPacketHeader.Type"> <summary> - Provides fields and methods common to all network reader connections. + The packet type. </summary> </member> - <member name="M:NetSharp.Raw.RawNetworkReaderBase.#ctor(System.Net.Sockets.Socket@,System.Net.EndPoint,System.Int32,System.Int32,System.UInt32)"> - <inheritdoc cref="M:NetSharp.Raw.RawNetworkConnectionBase.#ctor(System.Net.Sockets.Socket@,System.Net.EndPoint,System.Int32,System.Int32,System.UInt32)"/> + <member name="M:NetSharp.Raw.RawPacketHeader.op_Inequality(NetSharp.Raw.RawPacketHeader,NetSharp.Raw.RawPacketHeader)"> + <summary> + Checks whether two <see cref="T:NetSharp.Raw.RawPacketHeader" /> instances are not equal. + </summary> + <param name="left"> + The first instance. + </param> + <param name="right"> + The second instance. + </param> + <returns> + Whether the two instances are not equal. + </returns> </member> - <member name="P:NetSharp.Raw.RawNetworkReaderBase.ShutdownToken"> + <member name="M:NetSharp.Raw.RawPacketHeader.op_Equality(NetSharp.Raw.RawPacketHeader,NetSharp.Raw.RawPacketHeader)"> <summary> - The <see cref="T:System.Threading.CancellationToken" /> for the network reader. + Checks whether two <see cref="T:NetSharp.Raw.RawPacketHeader" /> instances are equal. </summary> + <param name="left"> + The first instance. + </param> + <param name="right"> + The second instance. + </param> + <returns> + Whether the two instances are equal. + </returns> </member> - <member name="M:NetSharp.Raw.RawNetworkReaderBase.Shutdown"> + <member name="M:NetSharp.Raw.RawPacketHeader.Equals(System.Object)"> <inheritdoc /> </member> - <member name="M:NetSharp.Raw.RawNetworkReaderBase.Start(System.UInt16)"> + <member name="M:NetSharp.Raw.RawPacketHeader.Equals(NetSharp.Raw.RawPacketHeader)"> <inheritdoc /> </member> - <member name="M:NetSharp.Raw.RawNetworkReaderBase.Dispose(System.Boolean)"> + <member name="M:NetSharp.Raw.RawPacketHeader.GetHashCode"> <inheritdoc /> </member> - <member name="T:NetSharp.Raw.RawNetworkWriterBase"> + <member name="T:NetSharp.Raw.Stream.IRawStreamPacketHandler"> <summary> - Provides fields and methods common to all network writer connections. + Describes the interface for a stream network connection that can handle received packets. </summary> </member> - <member name="M:NetSharp.Raw.RawNetworkWriterBase.#ctor(System.Net.Sockets.Socket@,System.Net.EndPoint,System.Int32,System.Int32,System.UInt32)"> - <inheritdoc cref="M:NetSharp.Raw.RawNetworkConnectionBase.#ctor(System.Net.Sockets.Socket@,System.Net.EndPoint,System.Int32,System.Int32,System.UInt32)"/> - </member> - <member name="M:NetSharp.Raw.RawNetworkWriterBase.Read(System.Net.EndPoint@,System.Memory{System.Byte},System.Net.Sockets.SocketFlags)"> - <inheritdoc /> - </member> - <member name="M:NetSharp.Raw.RawNetworkWriterBase.ReadAsync(System.Net.EndPoint,System.Memory{System.Byte},System.Net.Sockets.SocketFlags)"> - <inheritdoc /> + <member name="M:NetSharp.Raw.Stream.IRawStreamPacketHandler.DeregisterHandler(System.Int32,NetSharp.Raw.Stream.RawStreamPacketHandler)"> + <summary> + Deregisters a previously registered handler for the given packet type. No further invocations of the given + handler will be made. + </summary> + <param name="id"> + The packet type for which to deregister the handler. + </param> + <param name="handler"> + The handler to deregister. + </param> </member> - <member name="M:NetSharp.Raw.RawNetworkWriterBase.Write(System.Net.EndPoint,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags)"> - <inheritdoc /> + <member name="M:NetSharp.Raw.Stream.IRawStreamPacketHandler.RegisterHandler(System.Int32,NetSharp.Raw.Stream.RawStreamPacketHandler)"> + <summary> + Registers the given handler for the given packet type. + </summary> + <param name="id"> + The packet type for which to register the handler. + </param> + <param name="handler"> + The handler to register. + </param> </member> - <member name="M:NetSharp.Raw.RawNetworkWriterBase.WriteAsync(System.Net.EndPoint,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags)"> - <inheritdoc /> + <member name="T:NetSharp.Raw.Stream.IRawStreamWriter"> + <summary> + Describes the interface for a stream network connection that can write to the network. + </summary> </member> - <member name="T:NetSharp.Raw.Stream.RawStreamRequestHandler"> + <member name="M:NetSharp.Raw.Stream.IRawStreamWriter.SendAsync(System.UInt16,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags)"> <summary> - Represents a method that handles a request received by a <see cref="T:NetSharp.Raw.Stream.RawStreamNetworkReader" />. + Writes the given packet header and data to the network asynchronously, using the given socket flags for the transmission. </summary> - <param name="remoteEndPoint"> - The remote endpoint from which the request was received. + <param name="type"> + The type of packet being written to the network. </param> - <param name="requestBuffer"> - The buffer containing the received request. - </param> - <param name="receivedRequestBytes"> - The number of bytes of user data received in the request. + <param name="buffer"> + The data held by the packet being written to the network. </param> - <param name="responseBuffer"> - The buffer into which the response should be written. + <param name="flags"> + The <see cref="T:System.Net.Sockets.SocketFlags" /> to use for the transmission. </param> <returns> - Whether there exists a response to be sent back to the remote endpoint. + The number of bytes written to the network. </returns> </member> - <member name="T:NetSharp.Raw.Stream.RawStreamNetworkReader"> + <member name="T:NetSharp.Raw.Stream.RawStreamPacketHandler"> <summary> - Implements a raw network reader using a stream-based protocol. + Handles a message received on a raw stream connection. </summary> + <param name="remoteEndPoint"> + The remote endpoint from which the received message originated. + </param> + <param name="header"> + The header of the received message. + </param> + <param name="data"> + The data held by the received message. + </param> + <param name="writer"> + A reference to the network connection, to interact with the network further. + </param> </member> - <member name="M:NetSharp.Raw.Stream.RawStreamNetworkReader.#ctor(System.Net.Sockets.Socket@,NetSharp.Raw.Stream.RawStreamRequestHandler,System.Net.EndPoint,System.Int32,System.Int32,System.UInt32)"> - <inheritdoc cref="M:NetSharp.Raw.RawNetworkReaderBase.#ctor(System.Net.Sockets.Socket@,System.Net.EndPoint,System.Int32,System.Int32,System.UInt32)"/> - </member> - <member name="M:NetSharp.Raw.Stream.RawStreamNetworkReader.Start(System.UInt16)"> - <inheritdoc /> - </member> - <member name="M:NetSharp.Raw.Stream.RawStreamNetworkReader.CanReuseStateObject(System.Net.Sockets.SocketAsyncEventArgs@)"> - <inheritdoc /> + <member name="T:NetSharp.Raw.Stream.RawStreamConnection"> + <summary> + Represents a network connection using a stream-based protocol to interact over the network, that is capable of + sending raw bytes. + </summary> </member> - <member name="M:NetSharp.Raw.Stream.RawStreamNetworkReader.CreateStateObject"> - <inheritdoc /> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.#ctor(System.Net.Sockets.ProtocolType,System.Net.EndPoint)"> + <summary> + Initialises a new instance of the <see cref="T:NetSharp.Raw.Stream.RawStreamConnection" /> class. + </summary> + <param name="connectionProtocolType"> + The protocol that the underlying network connection should use. + </param> + <param name="defaultRemoteEndPoint"> + The default remote endpoint that should be used for pending connections. + </param> </member> - <member name="M:NetSharp.Raw.Stream.RawStreamNetworkReader.DestroyStateObject(System.Net.Sockets.SocketAsyncEventArgs)"> - <inheritdoc /> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.ConnectAsync(System.Net.EndPoint)"> + <summary> + Connects asynchronously to the given remote network endpoint. + </summary> + <param name="remoteEndPoint"> + The endpoint of the remote network connection to which we want to connect. + </param> + <returns> + A <see cref="T:System.Threading.Tasks.Task" /> object representing the asynchronous operation. + </returns> </member> - <member name="M:NetSharp.Raw.Stream.RawStreamNetworkReader.ResetStateObject(System.Net.Sockets.SocketAsyncEventArgs@)"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.DeregisterHandler(System.Int32,NetSharp.Raw.Stream.RawStreamPacketHandler)"> <inheritdoc /> </member> - <member name="T:NetSharp.Raw.Stream.RawStreamNetworkWriter"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.DisconnectAsync(System.Boolean)"> <summary> - Implements a raw network writer using a stream-based protocol. + Disconnects asynchronously from the currently connected remote network connection. </summary> + <param name="leaveConnectionReusable"> + Whether the underlying network connection should be left in a reusable state after this call completes. + </param> + <returns> + A <see cref="T:System.Threading.Tasks.Task" /> object representing the asynchronous operation. + </returns> </member> - <member name="M:NetSharp.Raw.Stream.RawStreamNetworkWriter.#ctor(System.Net.Sockets.Socket@,System.Net.EndPoint,System.Int32,System.Int32,System.UInt32)"> - <inheritdoc cref="M:NetSharp.Raw.RawNetworkWriterBase.#ctor(System.Net.Sockets.Socket@,System.Net.EndPoint,System.Int32,System.Int32,System.UInt32)" /> - </member> - <member name="M:NetSharp.Raw.Stream.RawStreamNetworkWriter.Read(System.Net.EndPoint@,System.Memory{System.Byte},System.Net.Sockets.SocketFlags)"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.RegisterHandler(System.Int32,NetSharp.Raw.Stream.RawStreamPacketHandler)"> <inheritdoc /> </member> - <member name="M:NetSharp.Raw.Stream.RawStreamNetworkWriter.ReadAsync(System.Net.EndPoint,System.Memory{System.Byte},System.Net.Sockets.SocketFlags)"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.SendAsync(System.UInt16,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags)"> <inheritdoc /> </member> - <member name="M:NetSharp.Raw.Stream.RawStreamNetworkWriter.Write(System.Net.EndPoint,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags)"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.CreateSocketArgsHook(System.Net.Sockets.SocketAsyncEventArgs@)"> <inheritdoc /> </member> - <member name="M:NetSharp.Raw.Stream.RawStreamNetworkWriter.WriteAsync(System.Net.EndPoint,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags)"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.DestroySocketArgsHook(System.Net.Sockets.SocketAsyncEventArgs@)"> <inheritdoc /> </member> - <member name="M:NetSharp.Raw.Stream.RawStreamNetworkWriter.CanReuseStateObject(System.Net.Sockets.SocketAsyncEventArgs@)"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.Dispose(System.Boolean)"> <inheritdoc /> </member> - <member name="M:NetSharp.Raw.Stream.RawStreamNetworkWriter.CreateStateObject"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.HandlerThreadWork"> <inheritdoc /> </member> - <member name="M:NetSharp.Raw.Stream.RawStreamNetworkWriter.DestroyStateObject(System.Net.Sockets.SocketAsyncEventArgs)"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.ResetSocketArgsHook(System.Net.Sockets.SocketAsyncEventArgs@)"> <inheritdoc /> </member> - <member name="M:NetSharp.Raw.Stream.RawStreamNetworkWriter.ResetStateObject(System.Net.Sockets.SocketAsyncEventArgs@)"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.StartHook(System.Int32)"> <inheritdoc /> </member> - <member name="T:NetSharp.Raw.Stream.RawStreamPacket"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.ConfigureSendRequestAsync(System.Net.Sockets.SocketAsyncEventArgs,System.Byte[]@,NetSharp.Raw.RawPacketHeader@,System.ReadOnlyMemory{System.Byte}@,NetSharp.Raw.Stream.RawStreamConnection.StateToken,System.Threading.Tasks.TaskCompletionSource{System.Int32})"> <summary> - Provides helper methods to manipulate the binary packet format used by stream network handlers. + Prepares the given socket args for sending a request to the network. </summary> </member> - <member name="M:NetSharp.Raw.Stream.RawStreamPacket.Serialise(System.Memory{System.Byte}@,NetSharp.Raw.Stream.RawStreamPacketHeader@,System.ReadOnlyMemory{System.Byte}@)"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.CleanupArgs(System.Net.Sockets.SocketAsyncEventArgs,System.Boolean)"> <summary> - Serialises the given <paramref name="packetHeader" /> and <paramref name="packetData" /> into the given <paramref name="buffer" />. + Cleans up and returns the given socket args. </summary> - <param name="buffer"> - The buffer into which the packet should be serialised. Must be at least of size <see cref="F:NetSharp.Raw.Stream.RawStreamPacketHeader.TotalSize" /> + the size - of the user data given by <paramref name="packetHeader" />. - </param> - <param name="packetHeader"> - The header containing metatdata abut the raw stream packet. - </param> - <param name="packetData"> - The user data held in the raw stream packet. - </param> </member> - <member name="M:NetSharp.Raw.Stream.RawStreamPacket.TotalPacketSize(NetSharp.Raw.Stream.RawStreamPacketHeader@)"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.CloseClientConnection(System.Net.Sockets.SocketAsyncEventArgs)"> <summary> - Calculates the total size of a raw stream packet, using the packet data size in the given <paramref name="packetHeader" />. + Closes the remote network connection associated with the given socket args. </summary> - <param name="packetHeader"> - The header for which to calculate the total packet size. - </param> - <returns> - The total size of a raw stream packet with the given <paramref name="packetHeader" />. - </returns> </member> - <member name="T:NetSharp.Raw.Stream.RawStreamPacketHeader"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.ConfigureReceiveDataAsync(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamConnection.StateToken,NetSharp.Raw.RawPacketHeader@)"> <summary> - Holds metadata about a raw stream packet. + Prepares the given socket args for receiving a packet's data from the network. </summary> </member> - <member name="F:NetSharp.Raw.Stream.RawStreamPacketHeader.TotalSize"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.ConfigureReceiveHeaderAsync(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamConnection.StateToken)"> <summary> - The total size of the header in bytes. + Prepares the given socket args for receiving a packet's header from the network. </summary> </member> - <member name="F:NetSharp.Raw.Stream.RawStreamPacketHeader.DataSize"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.HandleAccepted(System.Net.Sockets.SocketAsyncEventArgs)"> <summary> - The size of the user supplied data segment in bytes. + Handles a completed <see cref="M:System.Net.Sockets.Socket.AcceptAsync(System.Net.Sockets.SocketAsyncEventArgs)" /> call. </summary> </member> - <member name="M:NetSharp.Raw.Stream.RawStreamPacketHeader.#ctor(System.Int32)"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.HandleConnected(System.Net.Sockets.SocketAsyncEventArgs)"> <summary> - Initialises a new instance of the <see cref="T:NetSharp.Raw.Stream.RawStreamPacketHeader"/> struct. + Handles a completed <see cref="M:System.Net.Sockets.Socket.ConnectAsync(System.Net.Sockets.SocketAsyncEventArgs)" /> call. </summary> - <param name="dataSize"> - The size of the user supplied data segment. - </param> </member> - <member name="M:NetSharp.Raw.Stream.RawStreamPacketHeader.Deserialise(System.Memory{System.Byte}@)"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.HandleDisconnected(System.Net.Sockets.SocketAsyncEventArgs)"> <summary> - Deserialises a <see cref="T:NetSharp.Raw.Stream.RawStreamPacketHeader" /> instance from the given <paramref name="buffer" />. + Handles a completed <see cref="M:System.Net.Sockets.Socket.DisconnectAsync(System.Net.Sockets.SocketAsyncEventArgs)" /> call. </summary> - <param name="buffer"> - A buffer containing a serialised <see cref="T:NetSharp.Raw.Stream.RawStreamPacketHeader" /> instance. Must be at least of size <see cref="F:NetSharp.Raw.Stream.RawStreamPacketHeader.TotalSize" />. - </param> - <returns> - The deserialised instance. - </returns> </member> - <member name="M:NetSharp.Raw.Stream.RawStreamPacketHeader.Serialise(System.Memory{System.Byte}@)"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.HandleIoCompleted(System.Object,System.Net.Sockets.SocketAsyncEventArgs)"> <summary> - Serialises the current <see cref="T:NetSharp.Raw.Stream.RawStreamPacketHeader" /> instance into the given <paramref name="buffer" />. + Handles the completion of an asynchronous socket operation. </summary> - <param name="buffer"> - The buffer into which to serialise the current instance. Must be at least of size <see cref="F:NetSharp.Raw.Stream.RawStreamPacketHeader.TotalSize" />. - </param> </member> - <member name="T:NetSharp.Properties.Resources"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.HandleReceived(System.Net.Sockets.SocketAsyncEventArgs)"> <summary> - A strongly-typed resource class, for looking up localized strings, etc. + Handles the completion of a <see cref="M:System.Net.Sockets.Socket.ReceiveAsync(System.Net.Sockets.SocketAsyncEventArgs)" /> call. </summary> </member> - <member name="P:NetSharp.Properties.Resources.Culture"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.HandleReceivedData(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamConnection.StateToken)"> <summary> - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. + Handles the completion of a <see cref="M:System.Net.Sockets.Socket.ReceiveAsync(System.Net.Sockets.SocketAsyncEventArgs)" /> call, when receiving a packet's data from the network. </summary> </member> - <member name="P:NetSharp.Properties.Resources.RawDatagramNetworkReaderRentedBufferSizeError"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.HandleReceivedHeader(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamConnection.StateToken)"> <summary> - Looks up a localized string similar to Cannot rent a temporary buffer of size: {0} bytes. The maximum temporary buffer size is {1} bytes. + Handles the completion of a <see cref="M:System.Net.Sockets.Socket.ReceiveAsync(System.Net.Sockets.SocketAsyncEventArgs)" /> call, when receiving a packet's header from + the network. </summary> </member> - <member name="P:NetSharp.Properties.Resources.RawDatagramSizeError"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.HandleSent(System.Net.Sockets.SocketAsyncEventArgs)"> <summary> - Looks up a localized string similar to The datagram size must be between 0 and 65507 bytes. + Handles the completion of a <see cref="M:System.Net.Sockets.Socket.SendAsync(System.Net.Sockets.SocketAsyncEventArgs)" /> call. </summary> </member> - <member name="P:NetSharp.Properties.Resources.RawStreamMessageSizeUnderflow"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.HandleSentRequest(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamConnection.StateToken)"> <summary> - Looks up a localized string similar to The maximum pooled message size must be greater than 0 bytes. + Handles the completion of a <see cref="M:System.Net.Sockets.Socket.SendAsync(System.Net.Sockets.SocketAsyncEventArgs)" /> call, when sending a request packet to the + network. In this case, the <see cref="P:System.Net.Sockets.SocketAsyncEventArgs.ConnectSocket" /> will be used to perform the transmission. </summary> </member> - <member name="P:NetSharp.Properties.Resources.ResourceManager"> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.HandleSentResponse(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamConnection.StateToken)"> <summary> - Returns the cached ResourceManager instance used by this class. + Handles the completion of a <see cref="M:System.Net.Sockets.Socket.SendAsync(System.Net.Sockets.SocketAsyncEventArgs)" /> call, when sending a response packet to the + network. In this case, the <see cref="P:System.Net.Sockets.SocketAsyncEventArgs.AcceptSocket" /> will be used to perform the transmission. + </summary> + </member> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.StartOrContinueReceiving(System.Net.Sockets.SocketAsyncEventArgs)"> + <summary> + Starts or continues an asynchronous network read operation. + </summary> + </member> + <member name="M:NetSharp.Raw.Stream.RawStreamConnection.StartOrContinueSending(System.Net.Sockets.Socket,System.Net.Sockets.SocketAsyncEventArgs)"> + <summary> + Starts or continues an asynchronous network write operation using the given socket. + </summary> + </member> + <member name="T:NetSharp.Raw.Stream.RawStreamConnection.StateToken"> + <summary> + State token for the stream network connection. + </summary> + </member> + <member name="P:NetSharp.Raw.Stream.RawStreamConnection.StateToken.BytesToTransfer"> + <summary> + The number of bytes that we need to transfer over the network. + </summary> + </member> + <member name="P:NetSharp.Raw.Stream.RawStreamConnection.StateToken.OperationCompletionSource"> + <summary> + The <see cref="T:System.Threading.Tasks.TaskCompletionSource`1" /> for asynchronous network operations. + </summary> + </member> + <member name="P:NetSharp.Raw.Stream.RawStreamConnection.StateToken.RequestCompletionSource"> + <summary> + The <see cref="T:System.Threading.Tasks.TaskCompletionSource`1" /> for asynchronous packet writes. + </summary> + </member> + <member name="P:NetSharp.Raw.Stream.RawStreamConnection.StateToken.RequestHeader"> + <summary> + The deserialised request packet header. </summary> </member> <member name="T:NetSharp.Utils.SlimObjectPool`1"> @@ -496,35 +519,21 @@ The type of item stored in the pool. </typeparam> </member> - <member name="M:NetSharp.Utils.SlimObjectPool`1.#ctor(NetSharp.Utils.SlimObjectPool{`0}.CreateObjectDelegate,NetSharp.Utils.SlimObjectPool{`0}.ResetObjectDelegate,NetSharp.Utils.SlimObjectPool{`0}.DestroyObjectDelegate,NetSharp.Utils.SlimObjectPool{`0}.CanReuseObjectPredicate)"> + <member name="M:NetSharp.Utils.SlimObjectPool`1.#ctor(NetSharp.Utils.SlimObjectPool{`0}.CreateObject,NetSharp.Utils.SlimObjectPool{`0}.ResetObject,NetSharp.Utils.SlimObjectPool{`0}.DestroyObject)"> <summary> - Initialises a new instance of the <see cref="T:NetSharp.Utils.SlimObjectPool`1"/> class. + Initialises a new instance of the <see cref="T:NetSharp.Utils.SlimObjectPool`1" /> class. </summary> - <param name="createDelegate"> + <param name="create"> The delegate method to use to create new pooled object instances. </param> - <param name="resetDelegate"> + <param name="reset"> The delegate method to use to reset used pooled object instances. </param> - <param name="destroyDelegate"> + <param name="destroy"> The delegate method to use to destroy pooled object instances that cannot be reused. </param> - <param name="rebufferPredicate"> - The delegate method to use to decide whether an instance can be reused. - </param> - </member> - <member name="T:NetSharp.Utils.SlimObjectPool`1.CanReuseObjectPredicate"> - <summary> - Delegate method to check whether the given <paramref name="instance" /> can and should be placed back into the pool. If <c>true</c> is returned, the <paramref name="instance" /> is reset and placed back into the pool. Otherwise, the instance is destroyed. - </summary> - <param name="instance"> - The instance to check. - </param> - <returns> - Whether the given instance should be placed back into the pool. - </returns> </member> - <member name="T:NetSharp.Utils.SlimObjectPool`1.CreateObjectDelegate"> + <member name="T:NetSharp.Utils.SlimObjectPool`1.CreateObject"> <summary> Delegate method for creating fresh <typeparamref name="T" /> instances to be stored in the pool. </summary> @@ -532,7 +541,7 @@ A configured <typeparamref name="T" /> instance. </returns> </member> - <member name="T:NetSharp.Utils.SlimObjectPool`1.DestroyObjectDelegate"> + <member name="T:NetSharp.Utils.SlimObjectPool`1.DestroyObject"> <summary> Delegate method to destroy a used <paramref name="instance" /> which cannot be reused. </summary> @@ -540,7 +549,7 @@ The instance to destroy. </param> </member> - <member name="T:NetSharp.Utils.SlimObjectPool`1.ResetObjectDelegate"> + <member name="T:NetSharp.Utils.SlimObjectPool`1.ResetObject"> <summary> Delegate method to reset a used <paramref name="instance" /> before placing it back into the pool. </summary> diff --git a/NetSharp/NetSharp/Raw/Datagram/RawDatagramConnection.cs b/NetSharp/NetSharp/Raw/Datagram/RawDatagramConnection.cs @@ -0,0 +1,107 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace NetSharp.Raw.Datagram +{ + public delegate void RawDatagramPacketHandler( + EndPoint remoteEndPoint, + in RawPacketHeader header, + in ReadOnlyMemory<byte> data, + IRawDatagramWriter writer); + + public interface IRawDatagramPacketHandler + { + } + + public interface IRawDatagramWriter : IRawDatagramPacketHandler + { + ValueTask<int> SendAsync(ushort type, ReadOnlyMemory<byte> data, SocketFlags flags = SocketFlags.None); + } + + public sealed class RawDatagramConnection : RawConnectionBase, IRawDatagramWriter + { + /// <summary> + /// The maximum size that a user supplied data buffer can be to fit into a UDP diagram with a preceding packet header. + /// </summary> + public const int MaxDatagramDataLength = ushort.MaxValue - 28 - RawPacketHeader.Length; + + public RawDatagramConnection(ProtocolType connectionProtocolType, EndPoint defaultRemoteEndPoint) + : base(SocketType.Dgram, connectionProtocolType, defaultRemoteEndPoint) + { + } + + /// <inheritdoc /> + public ValueTask<int> SendAsync(ushort type, ReadOnlyMemory<byte> data, SocketFlags flags = SocketFlags.None) + { + return new ValueTask<int>(0); + } + + /// <inheritdoc /> + protected override void CreateSocketArgsHook(ref SocketAsyncEventArgs instance) + { + if (instance == default) + { + return; + } + + instance.Completed += HandleIoCompleted; + + base.CreateSocketArgsHook(ref instance); + } + + /// <inheritdoc /> + protected override void DestroySocketArgsHook(ref SocketAsyncEventArgs instance) + { + if (instance == default) + { + return; + } + + instance.Completed -= HandleIoCompleted; + + base.DestroySocketArgsHook(ref instance); + } + + /// <inheritdoc /> + protected override void ResetSocketArgsHook(ref SocketAsyncEventArgs instance) + { + if (instance == default) + { + return; + } + + instance.AcceptSocket = null; + + base.ResetSocketArgsHook(ref instance); + } + + /// <inheritdoc /> + protected override void HandlerTaskWork() + { + // TODO: implement start read task for datagram connections + } + + private void HandleIoCompleted(object sender, SocketAsyncEventArgs args) + { + switch (args.LastOperation) + { + case SocketAsyncOperation.Connect: + break; + + case SocketAsyncOperation.Disconnect: + break; + + case SocketAsyncOperation.ReceiveFrom: + break; + + case SocketAsyncOperation.SendTo: + break; + + default: + break; + } + } + } +} diff --git a/NetSharp/NetSharp/Raw/Datagram/RawDatagramNetworkReader.cs b/NetSharp/NetSharp/Raw/Datagram/RawDatagramNetworkReader.cs @@ -1,207 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Runtime.CompilerServices; - -namespace NetSharp.Raw.Datagram -{ - /// <summary> - /// Represents a method that handles a request received by a <see cref="RawDatagramNetworkReader" />. - /// </summary> - /// <param name="remoteEndPoint"> - /// The remote endpoint from which the request was received. - /// </param> - /// <param name="requestBuffer"> - /// The buffer containing the received request. - /// </param> - /// <param name="receivedRequestBytes"> - /// The number of bytes of user data received in the request. - /// </param> - /// <param name="responseBuffer"> - /// The buffer into which the response should be written. - /// </param> - /// <returns> - /// Whether there exists a response to be sent back to the remote endpoint. - /// </returns> - // TODO implement this in a better, more robust and extensible way - public delegate bool RawDatagramRequestHandler( - EndPoint remoteEndPoint, - in ReadOnlyMemory<byte> requestBuffer, - int receivedRequestBytes, - in Memory<byte> responseBuffer); - - /// <summary> - /// Implements a raw network reader using a datagram-based protocol. - /// </summary> - public sealed class RawDatagramNetworkReader : RawNetworkReaderBase - { - private readonly int datagramSize; - - private readonly RawDatagramRequestHandler requestHandler; - - /// <inheritdoc cref="RawNetworkReaderBase(ref Socket, EndPoint, int, int, uint)" /> - public RawDatagramNetworkReader( - ref Socket rawConnection, - RawDatagramRequestHandler? requestHandler, - EndPoint defaultEndPoint, - int datagramSize, - int pooledBuffersPerBucket = 50, - uint preallocatedStateObjects = 0) - : base(ref rawConnection, defaultEndPoint, datagramSize, pooledBuffersPerBucket, preallocatedStateObjects) - { - if (datagramSize <= 0 || datagramSize > MaxDatagramSize) - { - throw new ArgumentOutOfRangeException(nameof(datagramSize), datagramSize, Properties.Resources.RawDatagramSizeError); - } - - this.datagramSize = datagramSize; - - this.requestHandler = requestHandler ?? DefaultRequestHandler; - } - - /// <inheritdoc /> - public override void Start(ushort concurrentReadTasks) - { - for (ushort i = 0; i < concurrentReadTasks; i++) - { - StartDefaultReceiveFrom(); - } - } - - /// <inheritdoc /> - protected override bool CanReuseStateObject(ref SocketAsyncEventArgs instance) - { - return true; - } - - /// <inheritdoc /> - protected override SocketAsyncEventArgs CreateStateObject() - { - SocketAsyncEventArgs instance = new SocketAsyncEventArgs { RemoteEndPoint = DefaultEndPoint }; - instance.Completed += HandleIoCompleted; - - instance.RemoteEndPoint = DefaultEndPoint; - - return instance; - } - - /// <inheritdoc /> - protected override void DestroyStateObject(SocketAsyncEventArgs instance) - { - instance.Completed -= HandleIoCompleted; - instance.Dispose(); - } - - /// <inheritdoc /> - protected override void ResetStateObject(ref SocketAsyncEventArgs instance) - { - instance.RemoteEndPoint = DefaultEndPoint; - } - - private static bool DefaultRequestHandler( - EndPoint remoteEndPoint, - in ReadOnlyMemory<byte> requestBuffer, - int receivedRequestBytes, - in Memory<byte> responseBuffer) - { - return requestBuffer.TryCopyTo(responseBuffer); - } - - private void CompleteReceiveFrom(SocketAsyncEventArgs args) - { - byte[] receiveBuffer = args.Buffer; - - switch (args.SocketError) - { - case SocketError.Success: - byte[] testBuffer = Array.Empty<byte>(); - - byte[] responseBuffer = BufferPool.Rent(datagramSize); - - bool responseExists = requestHandler(args.RemoteEndPoint, receiveBuffer, args.BytesTransferred, responseBuffer); - BufferPool.Return(receiveBuffer, true); - - if (responseExists) - { - args.SetBuffer(responseBuffer, 0, datagramSize); - - StartSendTo(args); - return; - } - - BufferPool.Return(responseBuffer, true); - break; - - default: - CleanupTransmissionBufferAndState(args); - break; - } - } - - private void CompleteSendTo(SocketAsyncEventArgs args) - { - switch (args.SocketError) - { - default: - CleanupTransmissionBufferAndState(args); - break; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ConfigureAsyncReceiveFrom(SocketAsyncEventArgs args) - { - byte[] receiveBuffer = BufferPool.Rent(datagramSize); - - args.SetBuffer(receiveBuffer, 0, datagramSize); - } - - private void HandleIoCompleted(object sender, SocketAsyncEventArgs args) - { - switch (args.LastOperation) - { - case SocketAsyncOperation.ReceiveFrom: - StartDefaultReceiveFrom(); - CompleteReceiveFrom(args); - break; - - case SocketAsyncOperation.SendTo: - CompleteSendTo(args); - break; - } - } - - private void StartDefaultReceiveFrom() - { - if (!ConnectionDisposed) - { - SocketAsyncEventArgs args = ArgsPool.Rent(); - - ConfigureAsyncReceiveFrom(args); - - StartReceiveFrom(args); - } - } - - private void StartReceiveFrom(SocketAsyncEventArgs args) - { - if (!ConnectionDisposed && Connection.ReceiveFromAsync(args)) - { - return; - } - - StartDefaultReceiveFrom(); - CompleteReceiveFrom(args); - } - - private void StartSendTo(SocketAsyncEventArgs args) - { - if (!ConnectionDisposed && Connection.SendToAsync(args)) - { - return; - } - - CompleteSendTo(args); - } - } -} diff --git a/NetSharp/NetSharp/Raw/Datagram/RawDatagramNetworkWriter.cs b/NetSharp/NetSharp/Raw/Datagram/RawDatagramNetworkWriter.cs @@ -1,269 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Threading.Tasks; - -using NetSharp.Properties; - -namespace NetSharp.Raw.Datagram -{ - /// <summary> - /// Implements a raw network writer using a datagram-based protocol. - /// </summary> - public sealed class RawDatagramNetworkWriter : RawNetworkWriterBase - { - private readonly int datagramSize; - - /// <inheritdoc cref="RawNetworkWriterBase(ref Socket, EndPoint, int, int, uint)"/> - public RawDatagramNetworkWriter( - ref Socket rawConnection, - EndPoint defaultEndPoint, - int datagramSize, - int pooledBuffersPerBucket = 50, - uint preallocatedStateObjects = 0) - : base(ref rawConnection, defaultEndPoint, datagramSize, pooledBuffersPerBucket, preallocatedStateObjects) - { - if (datagramSize <= 0 || datagramSize > MaxDatagramSize) - { - throw new ArgumentOutOfRangeException(nameof(datagramSize), datagramSize, Resources.RawDatagramSizeError); - } - - this.datagramSize = datagramSize; - } - - /// <inheritdoc /> - public override int Read(ref EndPoint remoteEndPoint, Memory<byte> readBuffer, SocketFlags flags = SocketFlags.None) - { - int totalBytes = readBuffer.Length; - if (totalBytes > datagramSize) - { - throw new ArgumentException( - string.Format(Resources.Culture, Resources.RawDatagramNetworkReaderRentedBufferSizeError, totalBytes, datagramSize), - nameof(readBuffer)); - } - - byte[] transmissionBuffer = BufferPool.Rent(datagramSize); - - int readBytes = Connection.ReceiveFrom(transmissionBuffer, flags, ref remoteEndPoint); - - transmissionBuffer.CopyTo(readBuffer); - BufferPool.Return(transmissionBuffer, true); - - return readBytes; - } - - /// <inheritdoc /> - public override ValueTask<int> ReadAsync(EndPoint remoteEndPoint, Memory<byte> readBuffer, SocketFlags flags = SocketFlags.None) - { - int totalBytes = readBuffer.Length; - if (totalBytes > datagramSize) - { - throw new ArgumentException( - string.Format(Resources.Culture, Resources.RawDatagramNetworkReaderRentedBufferSizeError, totalBytes, datagramSize), - nameof(readBuffer)); - } - - TaskCompletionSource<int> tcs = new TaskCompletionSource<int>(); - SocketAsyncEventArgs args = ArgsPool.Rent(); - - byte[] transmissionBuffer = BufferPool.Rent(datagramSize); - - args.SetBuffer(transmissionBuffer, 0, datagramSize); - - args.RemoteEndPoint = remoteEndPoint; - args.SocketFlags = flags; - - PacketReadToken token = new PacketReadToken(tcs, in readBuffer); - args.UserToken = token; - - if (Connection.ReceiveFromAsync(args)) - { - return new ValueTask<int>(tcs.Task); - } - - // inlining CompleteReceiveFrom(SocketAsyncEventArgs) for performance - int result = args.BytesTransferred; - - transmissionBuffer.CopyTo(readBuffer); - - CleanupTransmissionBufferAndState(args); // transmissionBuffer was assigned to args.Buffer earlier, so this call is safe - - return new ValueTask<int>(result); - } - - /// <inheritdoc /> - public override int Write(EndPoint remoteEndPoint, ReadOnlyMemory<byte> writeBuffer, SocketFlags flags = SocketFlags.None) - { - int totalBytes = writeBuffer.Length; - if (totalBytes > datagramSize) - { - throw new ArgumentException( - string.Format(Resources.Culture, Resources.RawDatagramNetworkReaderRentedBufferSizeError, totalBytes, datagramSize), - nameof(writeBuffer)); - } - - byte[] transmissionBuffer = BufferPool.Rent(datagramSize); - writeBuffer.CopyTo(transmissionBuffer); - - int writtenBytes = Connection.SendTo(transmissionBuffer, flags, remoteEndPoint); - - BufferPool.Return(transmissionBuffer); - - return writtenBytes; - } - - /// <inheritdoc /> - public override ValueTask<int> WriteAsync(EndPoint remoteEndPoint, ReadOnlyMemory<byte> writeBuffer, SocketFlags flags = SocketFlags.None) - { - int totalBytes = writeBuffer.Length; - if (totalBytes > datagramSize) - { - throw new ArgumentException( - string.Format(Resources.Culture, Resources.RawDatagramNetworkReaderRentedBufferSizeError, totalBytes, datagramSize), - nameof(writeBuffer)); - } - - TaskCompletionSource<int> tcs = new TaskCompletionSource<int>(); - SocketAsyncEventArgs args = ArgsPool.Rent(); - - byte[] transmissionBuffer = BufferPool.Rent(datagramSize); - writeBuffer.CopyTo(transmissionBuffer); - - args.SetBuffer(transmissionBuffer, 0, datagramSize); - - args.RemoteEndPoint = remoteEndPoint; - args.SocketFlags = flags; - - PacketWriteToken token = new PacketWriteToken(tcs); - args.UserToken = token; - - if (Connection.SendToAsync(args)) - { - return new ValueTask<int>(tcs.Task); - } - - // inlining CompleteSendTo(SocketAsyncEventArgs) for performance - int result = args.BytesTransferred; - - CleanupTransmissionBufferAndState(args); // transmissionBuffer was assigned to args.Buffer earlier, so this call is safe - - return new ValueTask<int>(result); - } - - /// <inheritdoc /> - protected override bool CanReuseStateObject(ref SocketAsyncEventArgs instance) - { - return true; - } - - /// <inheritdoc /> - protected override SocketAsyncEventArgs CreateStateObject() - { - SocketAsyncEventArgs instance = new SocketAsyncEventArgs(); - instance.Completed += HandleIoCompleted; - - return instance; - } - - /// <inheritdoc /> - protected override void DestroyStateObject(SocketAsyncEventArgs instance) - { - instance.Completed -= HandleIoCompleted; - instance.Dispose(); - } - - /// <inheritdoc /> - protected override void ResetStateObject(ref SocketAsyncEventArgs instance) - { - } - - private void CompleteReceiveFrom(SocketAsyncEventArgs args) - { - PacketReadToken token = (PacketReadToken)args.UserToken; - - byte[] receiveBuffer = args.Buffer; - - switch (args.SocketError) - { - case SocketError.Success: - receiveBuffer.CopyTo(token.UserBuffer); - token.CompletionSource.SetResult(args.BytesTransferred); - break; - - case SocketError.OperationAborted: - token.CompletionSource.SetCanceled(); - break; - - default: - token.CompletionSource.SetException(new SocketException((int)args.SocketError)); - break; - } - - BufferPool.Return(receiveBuffer, true); - ArgsPool.Return(args); - } - - private void CompleteSendTo(SocketAsyncEventArgs args) - { - PacketWriteToken token = (PacketWriteToken)args.UserToken; - - byte[] sendBuffer = args.Buffer; - - switch (args.SocketError) - { - case SocketError.Success: - token.CompletionSource.SetResult(args.BytesTransferred); - break; - - case SocketError.OperationAborted: - token.CompletionSource.SetCanceled(); - break; - - default: - token.CompletionSource.SetException(new SocketException((int)args.SocketError)); - break; - } - - BufferPool.Return(sendBuffer, true); - ArgsPool.Return(args); - } - - private void HandleIoCompleted(object sender, SocketAsyncEventArgs args) - { - switch (args.LastOperation) - { - case SocketAsyncOperation.SendTo: - CompleteSendTo(args); - break; - - case SocketAsyncOperation.ReceiveFrom: - CompleteReceiveFrom(args); - break; - } - } - - private readonly struct PacketReadToken - { - public readonly TaskCompletionSource<int> CompletionSource; - - public readonly Memory<byte> UserBuffer; - - public PacketReadToken(TaskCompletionSource<int> completionSource, in Memory<byte> userBuffer) - { - CompletionSource = completionSource; - - UserBuffer = userBuffer; - } - } - - private readonly struct PacketWriteToken - { - public readonly TaskCompletionSource<int> CompletionSource; - - public PacketWriteToken(TaskCompletionSource<int> completionSource) - { - CompletionSource = completionSource; - } - } - } -} diff --git a/NetSharp/NetSharp/Raw/RawConnectionBase.cs b/NetSharp/NetSharp/Raw/RawConnectionBase.cs @@ -0,0 +1,278 @@ +using System; +using System.Buffers; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +using NetSharp.Utils; + +namespace NetSharp.Raw +{ + /// <summary> + /// Provides base functionality to all raw network connections. + /// </summary> + public abstract class RawConnectionBase : IDisposable + { + private readonly ArrayPool<byte> bufferPool; + private readonly Socket connection; + private readonly SlimObjectPool<SocketAsyncEventArgs> socketArgsPool; + + private int activeOperations; // TODO: consider removing since we have handler threads + private bool isBound; + private bool isDisposed; + + /// <summary> + /// Initialises a new instance of the <see cref="RawConnectionBase" /> class. + /// </summary> + /// <param name="connectionSocketType"> + /// The socket type for the underlying network connection. + /// </param> + /// <param name="connectionProtocolType"> + /// The protocol type for the underlying network connection. + /// </param> + /// <param name="defaultRemoteEndPoint"> + /// The default remote endpoint to which network writes will be made. + /// </param> + protected RawConnectionBase( + SocketType connectionSocketType, + ProtocolType connectionProtocolType, + EndPoint defaultRemoteEndPoint) + { + bufferPool = ArrayPool<byte>.Shared; + + connection = new Socket(connectionSocketType, connectionProtocolType); + + socketArgsPool = + new SlimObjectPool<SocketAsyncEventArgs>(CreateSocketArgs, ResetSocketArgs, DestroySocketArgs); + + DefaultRemoteEndPoint = defaultRemoteEndPoint; + } + + /// <summary> + /// The default remote endpoint to which network writes are made. + /// </summary> + public EndPoint DefaultRemoteEndPoint { get; } + + /// <summary> + /// The currently bound local endpoint. + /// </summary> + public EndPoint LocalEndPoint => connection.LocalEndPoint; + + /// <summary> + /// The currently connected remote endpoint. + /// </summary> + public EndPoint? RemoteEndPoint => connection.RemoteEndPoint; + + /// <summary> + /// The underlying network connection. + /// </summary> + protected ref readonly Socket Connection => ref connection; + + /// <summary> + /// Whether the underlying network connection has been disposed or not. + /// </summary> + protected bool IsDisposed => isDisposed; + + /// <summary> + /// Binds the underlying network connection to the given local endpoint. + /// </summary> + /// <param name="localEndPoint"> + /// The local network endpoint to which we should bind. + /// </param> + public void Bind(EndPoint localEndPoint) + { + connection.Bind(localEndPoint); + isBound = true; + } + + /// <summary> + /// Stops listening for incoming connections and data, and releases all managed resources. + /// </summary> + public void Close() + { + // TODO: consider doing other stuff to close the connection? + Dispose(); + } + + /// <inheritdoc /> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Starts listening to the network for incoming connections and data. + /// </summary> + /// <param name="concurrentTasks"> + /// The number of concurrent read tasks that should be maintained. + /// </param> + public void Start(int concurrentTasks = 1) + { + Debug.Assert(isBound, "Connection must be bound before we can start listening to the network!"); + + StartHook(concurrentTasks); + + int taskCount = concurrentTasks < 1 ? 1 : concurrentTasks; + + for (int i = 0; i < taskCount; i++) + { + HandlerTaskWork(); + } + } + + /// <summary> + /// Allows for custom initialisation of a pooled <see cref="SocketAsyncEventArgs" /> instance. + /// </summary> + /// <param name="instance"> + /// The instance to initialise. + /// </param> + protected virtual void CreateSocketArgsHook(ref SocketAsyncEventArgs instance) + { + } + + /// <summary> + /// Allows for custom destruction of a pooled <see cref="SocketAsyncEventArgs" /> instance. + /// </summary> + /// <param name="instance"> + /// The instance to destroy. + /// </param> + protected virtual void DestroySocketArgsHook(ref SocketAsyncEventArgs instance) + { + } + + /// <summary> + /// Disposes of this <see cref="RawConnectionBase" /> instance. + /// </summary> + /// <param name="disposing"> + /// Whether the <see cref="Dispose()" /> method was called. + /// </param> + protected virtual void Dispose(bool disposing) + { + if (isDisposed) + { + return; + } + + if (disposing) + { + connection.Dispose(); + + // TODO: find better way of waiting for completion of all tasks + while (activeOperations > 0) + { + } + + socketArgsPool.Dispose(); + } + + // TODO: Set large fields to null + isDisposed = true; + } + + /// <summary> + /// Handler work delegate, started when a call to <see cref="Start" /> is made. + /// </summary> + protected abstract void HandlerTaskWork(); + + /// <summary> + /// Rents a pooled buffer of at least the specified length. The buffer MUST be returned via a call to <see + /// cref="ReturnBuffer(byte[], bool)" /> once it has been used. + /// </summary> + /// <param name="minimumBufferLength"> + /// The minimum length of the rented buffer. + /// </param> + /// <returns> + /// The rented buffer. + /// </returns> + protected byte[] RentBuffer(int minimumBufferLength) + { + return bufferPool.Rent(minimumBufferLength); + } + + /// <summary> + /// Rents a pooled <see cref="SocketAsyncEventArgs" /> instance. The socket args MUST be returned via a call to + /// <see cref="ReturnSocketArgs(SocketAsyncEventArgs)" /> once they have been used. + /// </summary> + /// <returns> + /// The rented socket args. + /// </returns> + protected SocketAsyncEventArgs RentSocketArgs() + { + _ = Interlocked.Increment(ref activeOperations); + + return socketArgsPool.Rent(); + } + + /// <summary> + /// Allows for custom resetting of a pooled <see cref="SocketAsyncEventArgs" /> instance. + /// </summary> + /// <param name="instance"> + /// The instance to reset. + /// </param> + protected virtual void ResetSocketArgsHook(ref SocketAsyncEventArgs instance) + { + } + + /// <summary> + /// Returns a previously rented pooled buffer, optionally without clearing it. + /// </summary> + /// <param name="buffer"> + /// The rented buffer. + /// </param> + /// <param name="clearBuffer"> + /// Whether to clear the data held in the buffer. + /// </param> + protected void ReturnBuffer(byte[] buffer, bool clearBuffer = true) + { + bufferPool.Return(buffer, clearBuffer); + } + + /// <summary> + /// Returns a previously rented <see cref="SocketAsyncEventArgs" /> instance. + /// </summary> + /// <param name="socketArgs"> + /// The rented socket args. + /// </param> + protected void ReturnSocketArgs(SocketAsyncEventArgs socketArgs) + { + socketArgsPool.Return(socketArgs); + + _ = Interlocked.Decrement(ref activeOperations); + } + + /// <summary> + /// Allows for custom setup before we start listening to the network. + /// </summary> + /// <param name="concurrentTasks"> + /// The number of concurrent read tasks that should be maintained. + /// </param> + protected virtual void StartHook(int concurrentTasks) + { + } + + private SocketAsyncEventArgs CreateSocketArgs() + { + SocketAsyncEventArgs instance = new SocketAsyncEventArgs { RemoteEndPoint = DefaultRemoteEndPoint }; + + CreateSocketArgsHook(ref instance); + + return instance; + } + + private void DestroySocketArgs(SocketAsyncEventArgs instance) + { + DestroySocketArgsHook(ref instance); + + instance.Dispose(); + } + + private void ResetSocketArgs(ref SocketAsyncEventArgs instance) + { + ResetSocketArgsHook(ref instance); + + instance.RemoteEndPoint = DefaultRemoteEndPoint; + } + } +} diff --git a/NetSharp/NetSharp/Raw/RawNetworkConnectionBase.cs b/NetSharp/NetSharp/Raw/RawNetworkConnectionBase.cs @@ -1,168 +0,0 @@ -using System; -using System.Buffers; -using System.Diagnostics; -using System.Net; -using System.Net.Sockets; -using System.Runtime.CompilerServices; - -using NetSharp.Utils; - -namespace NetSharp.Raw -{ - /// <summary> - /// Defines fields and methods common to all network connections. - /// </summary> - public abstract class RawNetworkConnectionBase : IDisposable - { - /// <summary> - /// The maximum size of a pooled buffer that can be used with the <see cref="ArrayPool{T}.Shared" /> property, before a new custom pool must - /// be created. Taken from: https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Buffers/ConfigurableArrayPool.cs. - /// </summary> - protected const int DefaultMaxPooledBufferSize = 1024 * 1024; - - /// <summary> - /// The maximum number of pooled buffers per bucket that can be used with the <see cref="ArrayPool{T}.Shared" /> property, before a new custom - /// pool must be created. Taken from: https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Buffers/ConfigurableArrayPool.cs. - /// </summary> - protected const int DefaultMaxPooledBuffersPerBucket = 50; - - /// <summary> - /// The maximum size that a user supplied data buffer can be to fit into a UDP datagram. - /// </summary> - protected const int MaxDatagramSize = ushort.MaxValue - 28; // 65535 - 28 = 65507 - - protected volatile bool ConnectionDisposed; - - private readonly SlimObjectPool<SocketAsyncEventArgs> argsPool; - - private readonly ArrayPool<byte> bufferPool; - - private readonly Socket connection; - - private readonly EndPoint defaultEndPoint; - - /// <summary> - /// Initialises a new instance of the <see cref="RawNetworkConnectionBase" /> class. - /// </summary> - /// <param name="rawConnection"> - /// The underlying <see cref="Socket" /> to use for the connection. - /// </param> - /// <param name="defaultEndPoint"> - /// The default endpoint to use to represent remote clients. - /// </param> - /// <param name="maxPooledBufferSize"> - /// The maximum size of a pooled buffer. - /// </param> - /// <param name="pooledBuffersPerBucket"> - /// The number of pooled buffers to hold in a single pool bucket. - /// </param> - /// <param name="preallocatedStateObjects"> - /// The number of state objects to preallocate. - /// </param> - protected RawNetworkConnectionBase( - ref Socket rawConnection, - EndPoint defaultEndPoint, - int maxPooledBufferSize, - int pooledBuffersPerBucket = 50, - uint preallocatedStateObjects = 0) - { - connection = rawConnection; - - bufferPool = maxPooledBufferSize <= DefaultMaxPooledBufferSize && pooledBuffersPerBucket <= DefaultMaxPooledBuffersPerBucket - ? ArrayPool<byte>.Shared - : ArrayPool<byte>.Create(maxPooledBufferSize, pooledBuffersPerBucket); - - this.defaultEndPoint = defaultEndPoint; - - argsPool = new SlimObjectPool<SocketAsyncEventArgs>(CreateStateObject, ResetStateObject, DestroyStateObject, CanReuseStateObject); - - // TODO implement pooling in better way - /* - for (uint i = 0; i < preallocatedStateObjects; i++) - { - argsPool.Return(CreateStateObject()); - } - */ - } - - /// <summary> - /// The object pool to use to pool <see cref="SocketAsyncEventArgs" /> instances. - /// </summary> - protected SlimObjectPool<SocketAsyncEventArgs> ArgsPool => argsPool; - - /// <summary> - /// The object pool to use to pool byte buffer instance. - /// </summary> - protected ArrayPool<byte> BufferPool => bufferPool; - - /// <summary> - /// The underlying connection socket. - /// </summary> - protected Socket Connection => connection; - - /// <summary> - /// The default endpoint to use to represent remote clients. - /// </summary> - protected EndPoint DefaultEndPoint => defaultEndPoint; - - /// <inheritdoc /> - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// <inheritdoc cref="SlimObjectPool{T}.CanReuseObjectPredicate" /> - protected abstract bool CanReuseStateObject(ref SocketAsyncEventArgs instance); - - /// <summary> - /// Performs cleanup on the given <paramref name="args" /> instance. Neither the given <paramref name="args"/> instance, nor the - /// <see cref="SocketAsyncEventArgs.Buffer" /> or <see cref="SocketAsyncEventArgs.MemoryBuffer" /> attached to it can be used after this call. - /// </summary> - /// <param name="args"> - /// The used <see cref="SocketAsyncEventArgs" /> that can be cleaned up to be reused. - /// </param> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void CleanupTransmissionBufferAndState(SocketAsyncEventArgs args) - { - Debug.Assert(args != default, "Attempted to cleanup \'null\' SocketAsyncEventArgs instance!"); - - if (args.Buffer != default) - { - bufferPool.Return(args.Buffer, true); - args.SetBuffer(Array.Empty<byte>(), 0, 0); - } - - argsPool.Return(args); - } - - /// <inheritdoc cref="SlimObjectPool{T}.CreateObjectDelegate" /> - protected abstract SocketAsyncEventArgs CreateStateObject(); - - /// <inheritdoc cref="SlimObjectPool{T}.DestroyObjectDelegate" /> - protected abstract void DestroyStateObject(SocketAsyncEventArgs instance); - - /// <summary> - /// Allows for inheritors to dispose of their own resources. - /// </summary> - /// <param name="disposing"> - /// Whether the <see cref="Dispose()" /> method is being called. - /// </param> - protected virtual void Dispose(bool disposing) - { - ConnectionDisposed = true; - Debug.WriteLine("Disposing of NetworkConnectionBase!"); - - if (disposing) - { - // dispose of managed resources - argsPool?.Dispose(); - } - - // dispose of unmanaged resources - } - - /// <inheritdoc cref="SlimObjectPool{T}.ResetObjectDelegate" /> - protected abstract void ResetStateObject(ref SocketAsyncEventArgs instance); - } -} diff --git a/NetSharp/NetSharp/Raw/RawNetworkReaderBase.cs b/NetSharp/NetSharp/Raw/RawNetworkReaderBase.cs @@ -1,59 +0,0 @@ -using System.Net; -using System.Net.Sockets; -using System.Threading; - -using NetSharp.Interfaces; - -namespace NetSharp.Raw -{ - /// <summary> - /// Provides fields and methods common to all network reader connections. - /// </summary> - public abstract class RawNetworkReaderBase : RawNetworkConnectionBase, IRawNetworkReader - { - private readonly CancellationToken shutdownToken; - - private readonly CancellationTokenSource shutdownTokenSource; - - /// <inheritdoc cref="RawNetworkConnectionBase(ref Socket, EndPoint, int, int, uint)"/> - private protected RawNetworkReaderBase( - ref Socket rawConnection, - EndPoint defaultEndPoint, - int maxPooledBufferSize, - int pooledBuffersPerBucket = 50, - uint preallocatedStateObjects = 0) - : base(ref rawConnection, defaultEndPoint, maxPooledBufferSize, pooledBuffersPerBucket, preallocatedStateObjects) - { - shutdownTokenSource = new CancellationTokenSource(); - shutdownToken = shutdownTokenSource.Token; - } - - /// <summary> - /// The <see cref="CancellationToken" /> for the network reader. - /// </summary> - protected ref readonly CancellationToken ShutdownToken => ref shutdownToken; - - /// <inheritdoc /> - public void Shutdown() - { - shutdownTokenSource.Cancel(); - } - - /// <inheritdoc /> - public abstract void Start(ushort concurrentReadTasks); - - /// <inheritdoc /> - protected override void Dispose(bool disposing) - { - if (!disposing) - { - return; - } - - shutdownTokenSource.Cancel(); - shutdownTokenSource.Dispose(); - - base.Dispose(disposing); - } - } -} diff --git a/NetSharp/NetSharp/Raw/RawNetworkWriterBase.cs b/NetSharp/NetSharp/Raw/RawNetworkWriterBase.cs @@ -1,38 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Threading.Tasks; - -using NetSharp.Interfaces; - -namespace NetSharp.Raw -{ - /// <summary> - /// Provides fields and methods common to all network writer connections. - /// </summary> - public abstract class RawNetworkWriterBase : RawNetworkConnectionBase, IRawNetworkWriter - { - /// <inheritdoc cref="RawNetworkConnectionBase(ref Socket, EndPoint, int, int, uint)"/> - protected RawNetworkWriterBase( - ref Socket rawConnection, - EndPoint defaultEndPoint, - int maxPooledBufferSize = DefaultMaxPooledBufferSize, - int pooledBuffersPerBucket = 50, - uint preallocatedStateObjects = 0) - : base(ref rawConnection, defaultEndPoint, maxPooledBufferSize, pooledBuffersPerBucket, preallocatedStateObjects) - { - } - - /// <inheritdoc /> - public abstract int Read(ref EndPoint remoteEndPoint, Memory<byte> readBuffer, SocketFlags flags = SocketFlags.None); - - /// <inheritdoc /> - public abstract ValueTask<int> ReadAsync(EndPoint remoteEndPoint, Memory<byte> readBuffer, SocketFlags flags = SocketFlags.None); - - /// <inheritdoc /> - public abstract int Write(EndPoint remoteEndPoint, ReadOnlyMemory<byte> writeBuffer, SocketFlags flags = SocketFlags.None); - - /// <inheritdoc /> - public abstract ValueTask<int> WriteAsync(EndPoint remoteEndPoint, ReadOnlyMemory<byte> writeBuffer, SocketFlags flags = SocketFlags.None); - } -} diff --git a/NetSharp/NetSharp/Raw/RawPacket.cs b/NetSharp/NetSharp/Raw/RawPacket.cs @@ -0,0 +1,30 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace NetSharp.Raw +{ + internal static class RawPacket + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Serialise( + in Memory<byte> backingBuffer, + in RawPacketHeader packetHeader, + in ReadOnlyMemory<byte> packetData) + { + Debug.Assert( + backingBuffer.Length >= RawPacketHeader.Length + packetData.Length, + "Attempted to serialise packet to an undersized buffer!"); + + packetHeader.Serialise(backingBuffer.Span.Slice(0, RawPacketHeader.Length)); + + packetData.CopyTo(backingBuffer.Slice(RawPacketHeader.Length, packetData.Length)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int TotalSize(in RawPacketHeader packetHeader) + { + return RawPacketHeader.Length + packetHeader.DataLength; + } + } +} diff --git a/NetSharp/NetSharp/Raw/RawPacketHeader.cs b/NetSharp/NetSharp/Raw/RawPacketHeader.cs @@ -0,0 +1,115 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace NetSharp.Raw +{ + /// <summary> + /// Contains metadata about a network packet. + /// </summary> + public readonly struct RawPacketHeader : IEquatable<RawPacketHeader> + { + /// <summary> + /// The total length of the packet when serialised. + /// </summary> + public const int Length = sizeof(ushort) + sizeof(int); + +#pragma warning disable CA1051 + + /// <summary> + /// The length of the data following this packet. + /// </summary> + public readonly int DataLength; + + /// <summary> + /// The packet type. + /// </summary> + public readonly ushort Type; + +#pragma warning restore CA1051 + + internal RawPacketHeader(ushort type, int dataLength) + { + Type = type; + DataLength = dataLength; + } + + /// <summary> + /// Checks whether two <see cref="RawPacketHeader" /> instances are not equal. + /// </summary> + /// <param name="left"> + /// The first instance. + /// </param> + /// <param name="right"> + /// The second instance. + /// </param> + /// <returns> + /// Whether the two instances are not equal. + /// </returns> + public static bool operator !=(RawPacketHeader left, RawPacketHeader right) + { + return !(left == right); + } + + /// <summary> + /// Checks whether two <see cref="RawPacketHeader" /> instances are equal. + /// </summary> + /// <param name="left"> + /// The first instance. + /// </param> + /// <param name="right"> + /// The second instance. + /// </param> + /// <returns> + /// Whether the two instances are equal. + /// </returns> + public static bool operator ==(RawPacketHeader left, RawPacketHeader right) + { + return left.Equals(right); + } + + /// <inheritdoc /> + public override bool Equals(object obj) + { + return obj is RawPacketHeader other && Equals(other); + } + + /// <inheritdoc /> + public bool Equals(RawPacketHeader other) + { + return DataLength == other.DataLength && Type == other.Type; + } + + /// <inheritdoc /> + public override int GetHashCode() + { + return HashCode.Combine(DataLength, Type); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static RawPacketHeader Deserialise(in ReadOnlySpan<byte> buffer) + { + int offset = 0; + + ushort type = MemoryMarshal.Read<ushort>(buffer.Slice(offset, sizeof(ushort))); + offset += sizeof(ushort); + + int dataSize = MemoryMarshal.Read<int>(buffer.Slice(offset, sizeof(int))); + + return new RawPacketHeader(type, dataSize); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void Serialise(in Span<byte> buffer) + { + int offset = 0; + + ushort type = Type; + MemoryMarshal.Write(buffer.Slice(offset, sizeof(ushort)), ref type); + offset += sizeof(ushort); + + int dataSize = DataLength; + MemoryMarshal.Write(buffer.Slice(offset, sizeof(int)), ref dataSize); + } + } +} diff --git a/NetSharp/NetSharp/Raw/Stream/IRawStreamPacketHandler.cs b/NetSharp/NetSharp/Raw/Stream/IRawStreamPacketHandler.cs @@ -0,0 +1,31 @@ +namespace NetSharp.Raw.Stream +{ + /// <summary> + /// Describes the interface for a stream network connection that can handle received packets. + /// </summary> + public interface IRawStreamPacketHandler + { + /// <summary> + /// Deregisters a previously registered handler for the given packet type. No further invocations of the given + /// handler will be made. + /// </summary> + /// <param name="id"> + /// The packet type for which to deregister the handler. + /// </param> + /// <param name="handler"> + /// The handler to deregister. + /// </param> + void DeregisterHandler(int id, RawStreamPacketHandler handler); + + /// <summary> + /// Registers the given handler for the given packet type. + /// </summary> + /// <param name="id"> + /// The packet type for which to register the handler. + /// </param> + /// <param name="handler"> + /// The handler to register. + /// </param> + void RegisterHandler(int id, RawStreamPacketHandler handler); + } +} diff --git a/NetSharp/NetSharp/Raw/Stream/IRawStreamWriter.cs b/NetSharp/NetSharp/Raw/Stream/IRawStreamWriter.cs @@ -0,0 +1,29 @@ +using System; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace NetSharp.Raw.Stream +{ + /// <summary> + /// Describes the interface for a stream network connection that can write to the network. + /// </summary> + public interface IRawStreamWriter : IRawStreamPacketHandler + { + /// <summary> + /// Writes the given packet header and data to the network asynchronously, using the given socket flags for the transmission. + /// </summary> + /// <param name="type"> + /// The type of packet being written to the network. + /// </param> + /// <param name="buffer"> + /// The data held by the packet being written to the network. + /// </param> + /// <param name="flags"> + /// The <see cref="SocketFlags" /> to use for the transmission. + /// </param> + /// <returns> + /// The number of bytes written to the network. + /// </returns> + ValueTask<int> SendAsync(ushort type, ReadOnlyMemory<byte> buffer, SocketFlags flags = SocketFlags.None); + } +} diff --git a/NetSharp/NetSharp/Raw/Stream/RawStreamConnection.cs b/NetSharp/NetSharp/Raw/Stream/RawStreamConnection.cs @@ -0,0 +1,722 @@ +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +using NetSharp.Utils; + +namespace NetSharp.Raw.Stream +{ + /// <summary> + /// Handles a message received on a raw stream connection. + /// </summary> + /// <param name="remoteEndPoint"> + /// The remote endpoint from which the received message originated. + /// </param> + /// <param name="header"> + /// The header of the received message. + /// </param> + /// <param name="data"> + /// The data held by the received message. + /// </param> + /// <param name="writer"> + /// A reference to the network connection, to interact with the network further. + /// </param> + public delegate void RawStreamPacketHandler( + EndPoint remoteEndPoint, + in RawPacketHeader header, + in ReadOnlyMemory<byte> data, + IRawStreamWriter writer); + + /// <summary> + /// Represents a network connection using a stream-based protocol to interact over the network, that is capable of + /// sending raw bytes. + /// </summary> + public sealed class RawStreamConnection : RawConnectionBase, IRawStreamWriter + { + private readonly ConcurrentDictionary<int, RawStreamPacketHandler> registeredHandlers; + private readonly SlimObjectPool<StateToken> stateTokenPool; + + /// <summary> + /// Initialises a new instance of the <see cref="RawStreamConnection" /> class. + /// </summary> + /// <param name="connectionProtocolType"> + /// The protocol that the underlying network connection should use. + /// </param> + /// <param name="defaultRemoteEndPoint"> + /// The default remote endpoint that should be used for pending connections. + /// </param> + public RawStreamConnection(ProtocolType connectionProtocolType, EndPoint defaultRemoteEndPoint) + : base(SocketType.Stream, connectionProtocolType, defaultRemoteEndPoint) + { + registeredHandlers = new ConcurrentDictionary<int, RawStreamPacketHandler>(); + + static StateToken CreateStateToken() => new StateToken(); + + static void ResetStateToken(ref StateToken instance) => instance.Reset(); + + static void DestroyStateToken(StateToken instance) => instance.Dispose(); + + stateTokenPool = new SlimObjectPool<StateToken>(CreateStateToken, ResetStateToken, DestroyStateToken); + } + + /// <summary> + /// Connects asynchronously to the given remote network endpoint. + /// </summary> + /// <param name="remoteEndPoint"> + /// The endpoint of the remote network connection to which we want to connect. + /// </param> + /// <returns> + /// A <see cref="Task" /> object representing the asynchronous operation. + /// </returns> + public Task ConnectAsync(EndPoint remoteEndPoint) + { + TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(); + SocketAsyncEventArgs socketArgs = RentSocketArgs(); + + StateToken state = stateTokenPool.Rent(); + state.OperationCompletionSource = tcs; + + socketArgs.UserToken = state; + + socketArgs.RemoteEndPoint = remoteEndPoint; + + if (Connection.ConnectAsync(socketArgs)) + { + return tcs.Task; + } + + CleanupArgs(socketArgs); + + return Task.CompletedTask; + } + + /// <inheritdoc /> + public void DeregisterHandler(int id, RawStreamPacketHandler handler) + { + if (registeredHandlers.TryGetValue(id, out RawStreamPacketHandler previousMulticast)) + { + // ReSharper disable once DelegateSubtraction + RawStreamPacketHandler? newMulticast = previousMulticast - handler; + + if (newMulticast != default) + { + _ = registeredHandlers.TryUpdate(id, newMulticast, previousMulticast); + } + } + } + + /// <summary> + /// Disconnects asynchronously from the currently connected remote network connection. + /// </summary> + /// <param name="leaveConnectionReusable"> + /// Whether the underlying network connection should be left in a reusable state after this call completes. + /// </param> + /// <returns> + /// A <see cref="Task" /> object representing the asynchronous operation. + /// </returns> + public Task DisconnectAsync(bool leaveConnectionReusable = false) + { + TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(); + SocketAsyncEventArgs socketArgs = RentSocketArgs(); + + StateToken state = stateTokenPool.Rent(); + state.OperationCompletionSource = tcs; + + socketArgs.UserToken = state; + + socketArgs.DisconnectReuseSocket = leaveConnectionReusable; + + if (Connection.DisconnectAsync(socketArgs)) + { + return tcs.Task; + } + + CleanupArgs(socketArgs); + + return Task.CompletedTask; + } + + /// <inheritdoc /> + public void RegisterHandler(int id, RawStreamPacketHandler handler) + { + _ = registeredHandlers.AddOrUpdate(id, _ => handler, (_, multicast) => multicast + handler); + } + + /// <inheritdoc /> + public ValueTask<int> SendAsync(ushort type, ReadOnlyMemory<byte> buffer, SocketFlags flags = SocketFlags.None) + { + TaskCompletionSource<int> tcs = new TaskCompletionSource<int>(); + SocketAsyncEventArgs socketArgs = RentSocketArgs(); + + RawPacketHeader header = new RawPacketHeader(type, buffer.Length); + byte[] ownedBuffer = RentBuffer(RawPacket.TotalSize(in header)); + + StateToken state = stateTokenPool.Rent(); + + ConfigureSendRequestAsync(socketArgs, ref ownedBuffer, in header, in buffer, state, tcs); + + socketArgs.SocketFlags = flags; + + StartOrContinueSending(Connection, socketArgs); + + return new ValueTask<int>(tcs.Task); + } + + /// <inheritdoc /> + protected override void CreateSocketArgsHook(ref SocketAsyncEventArgs instance) + { + if (instance == default) + { + return; + } + + instance.Completed += HandleIoCompleted; + + base.CreateSocketArgsHook(ref instance); + } + + /// <inheritdoc /> + protected override void DestroySocketArgsHook(ref SocketAsyncEventArgs instance) + { + if (instance == default) + { + return; + } + + instance.Completed -= HandleIoCompleted; + + base.DestroySocketArgsHook(ref instance); + } + + /// <inheritdoc /> + protected override void Dispose(bool disposing) + { + if (IsDisposed) + { + return; + } + + if (disposing) + { + stateTokenPool.Dispose(); + } + + base.Dispose(disposing); + } + + /// <inheritdoc /> + protected override void HandlerTaskWork() + { + while (true) + { + SocketAsyncEventArgs socketArgs = RentSocketArgs(); + + if (Connection.AcceptAsync(socketArgs)) + { + // we want to stop starting accept operations when there are no more connections in the queue. when + // this happens Connection.AcceptAsync() will return "true", so we can break. + break; + } + + HandleAccepted(socketArgs); + } + } + + /// <inheritdoc /> + protected override void ResetSocketArgsHook(ref SocketAsyncEventArgs instance) + { + if (instance == default) + { + return; + } + + instance.AcceptSocket = null; + + base.ResetSocketArgsHook(ref instance); + } + + /// <inheritdoc /> + protected override void StartHook(int concurrentTasks) + { + Connection.Listen(concurrentTasks); + + base.StartHook(concurrentTasks); + } + + /// <summary> + /// Prepares the given socket args for sending a request to the network. + /// </summary> + private static void ConfigureSendRequestAsync( + SocketAsyncEventArgs args, + ref byte[] ownedBuffer, + in RawPacketHeader pendingHeader, + in ReadOnlyMemory<byte> pendingData, + StateToken state, + TaskCompletionSource<int> tcs) + { + Memory<byte> ownedBufferMemory = new Memory<byte>(ownedBuffer); + RawPacket.Serialise(in ownedBufferMemory, in pendingHeader, in pendingData); + + int totalTransferredBytes = RawPacket.TotalSize(in pendingHeader); + args.SetBuffer(ownedBuffer, 0, totalTransferredBytes); + + state.BytesToTransfer = totalTransferredBytes; + state.RequestCompletionSource = tcs; + + args.UserToken = state; + } + + /// <summary> + /// Cleans up and returns the given socket args. + /// </summary> + private void CleanupArgs(SocketAsyncEventArgs args, bool cleanupUserToken = true) + { + if (cleanupUserToken) + { + stateTokenPool.Return((StateToken)args.UserToken); + } + + ReturnSocketArgs(args); + } + + /// <summary> + /// Closes the remote network connection associated with the given socket args. + /// </summary> + private void CloseClientConnection(SocketAsyncEventArgs args) + { + Socket connection = args.AcceptSocket; + + connection.Disconnect(false); + connection.Shutdown(SocketShutdown.Both); + connection.Close(); + connection.Dispose(); + + CleanupArgs(args); + } + + /// <summary> + /// Prepares the given socket args for receiving a packet's data from the network. + /// </summary> + private void ConfigureReceiveDataAsync(SocketAsyncEventArgs args, StateToken state, in RawPacketHeader header) + { + ReturnBuffer(args.Buffer); // return and clear the previously parsed request header buffer + + byte[] pendingDataBuffer = RentBuffer(header.DataLength); + + args.SetBuffer(pendingDataBuffer, 0, header.DataLength); + + state.BytesToTransfer = header.DataLength; + state.RequestHeader = header; + + args.UserToken = state; + } + + /// <summary> + /// Prepares the given socket args for receiving a packet's header from the network. + /// </summary> + private void ConfigureReceiveHeaderAsync(SocketAsyncEventArgs args, StateToken state) + { + ReturnBuffer(args.Buffer); // return and clear the previously sent response packet buffer + + byte[] pendingHeaderBuffer = RentBuffer(RawPacketHeader.Length); + + args.SetBuffer(pendingHeaderBuffer, 0, RawPacketHeader.Length); + + state.BytesToTransfer = RawPacketHeader.Length; + + args.UserToken = state; + } + + /// <summary> + /// Handles a completed <see cref="Socket.AcceptAsync" /> call. + /// </summary> + private void HandleAccepted(SocketAsyncEventArgs args) + { + switch (args.SocketError) + { + case SocketError.Success: + // the buffer is set to allow a simpler ConfigureReceiveHeader() implementation. Since returning an + // empty buffer is ignored in the array pool, this allows us to just return the last assigned buffer + // in the ConfigureXXX() method to the pool (this means that usually we will usually be returning + // the ResponseDataBuffer). + args.SetBuffer(Array.Empty<byte>(), 0, 0); + + StateToken state = stateTokenPool.Rent(); + ConfigureReceiveHeaderAsync(args, state); + + StartOrContinueReceiving(args); + break; + + default: + CleanupArgs(args, false); // there is no StateToken to cleanup + break; + } + } + + /// <summary> + /// Handles a completed <see cref="Socket.ConnectAsync(SocketAsyncEventArgs)" /> call. + /// </summary> + private void HandleConnected(SocketAsyncEventArgs args) + { + StateToken state = (StateToken)args.UserToken; + TaskCompletionSource<bool>? tcs = state.OperationCompletionSource; + + Debug.Assert( + tcs != default, + "HandleConnected was passed a state token without the correct TaskCompletionSource!"); + + switch (args.SocketError) + { + case SocketError.Success: + tcs.SetResult(true); + break; + + case SocketError.OperationAborted: + tcs.SetCanceled(); + break; + + default: + tcs.SetException(new SocketException((int)args.SocketError)); + break; + } + + CleanupArgs(args); + } + + /// <summary> + /// Handles a completed <see cref="Socket.DisconnectAsync" /> call. + /// </summary> + private void HandleDisconnected(SocketAsyncEventArgs args) + { + StateToken state = (StateToken)args.UserToken; + TaskCompletionSource<bool>? tcs = state.OperationCompletionSource; + + Debug.Assert( + tcs != default, + "HandleDisconnected was passed a state token without the correct TaskCompletionSource!"); + + switch (args.SocketError) + { + case SocketError.Success: + tcs.SetResult(true); + break; + + case SocketError.OperationAborted: + tcs.SetCanceled(); + break; + + default: + tcs.SetException(new SocketException((int)args.SocketError)); + break; + } + + CleanupArgs(args); + } + + /// <summary> + /// Handles the completion of an asynchronous socket operation. + /// </summary> + private void HandleIoCompleted(object sender, SocketAsyncEventArgs args) + { + switch (args.LastOperation) + { + case SocketAsyncOperation.Accept: + if (args.SocketError != SocketError.OperationAborted) + { + HandlerTaskWork(); + } + + HandleAccepted(args); + break; + + case SocketAsyncOperation.Connect: + HandleConnected(args); + break; + + case SocketAsyncOperation.Disconnect: + HandleDisconnected(args); + break; + + case SocketAsyncOperation.Receive: + HandleReceived(args); + break; + + case SocketAsyncOperation.Send: + HandleSent(args); + break; + } + } + + /// <summary> + /// Handles the completion of a <see cref="Socket.ReceiveAsync" /> call. + /// </summary> + private void HandleReceived(SocketAsyncEventArgs args) + { + StateToken state = (StateToken)args.UserToken; + + switch (args.SocketError) + { + case SocketError.Success: + switch (state.BytesToTransfer) + { + case RawPacketHeader.Length: + HandleReceivedHeader(args, state); + break; + + default: + HandleReceivedData(args, state); + break; + } + + break; + + default: + CloseClientConnection(args); + break; + } + } + + /// <summary> + /// Handles the completion of a <see cref="Socket.ReceiveAsync" /> call, when receiving a packet's data from the network. + /// </summary> + private void HandleReceivedData(SocketAsyncEventArgs args, StateToken state) + { + int received = args.BytesTransferred; + int previouslyReceived = args.Offset; + int totalReceived = previouslyReceived + received; + int expected = state.BytesToTransfer; + + RawPacketHeader header = state.RequestHeader!.Value; + + byte[] dataBuffer = args.Buffer; + ReadOnlyMemory<byte> dataBufferMemory = new ReadOnlyMemory<byte>(dataBuffer, 0, header.DataLength); + + if (totalReceived == expected) + { + if (registeredHandlers.TryGetValue(header.Type, out RawStreamPacketHandler handler)) + { + handler.Invoke(args.AcceptSocket.RemoteEndPoint, in header, in dataBufferMemory, this); + } + + ConfigureReceiveHeaderAsync(args, state); + StartOrContinueReceiving(args); + } + else if (totalReceived > 0 && totalReceived < expected) + { + args.SetBuffer(totalReceived, expected - totalReceived); + StartOrContinueReceiving(args); + } + else if (received == 0) + { + CloseClientConnection(args); + } + } + + /// <summary> + /// Handles the completion of a <see cref="Socket.ReceiveAsync" /> call, when receiving a packet's header from + /// the network. + /// </summary> + private void HandleReceivedHeader(SocketAsyncEventArgs args, StateToken state) + { + int received = args.BytesTransferred; + int previouslyReceived = args.Offset; + int totalReceived = previouslyReceived + received; + int expected = state.BytesToTransfer; + + byte[] headerBuffer = args.Buffer; + ReadOnlySpan<byte> headerBufferMemory = new ReadOnlySpan<byte>(headerBuffer); + + if (totalReceived == expected) + { + RawPacketHeader header = RawPacketHeader.Deserialise(in headerBufferMemory); + + ConfigureReceiveDataAsync(args, state, in header); + StartOrContinueReceiving(args); + } + else if (totalReceived > 0 && totalReceived < expected) + { + args.SetBuffer(totalReceived, expected - totalReceived); + StartOrContinueReceiving(args); + } + else if (received == 0) + { + CloseClientConnection(args); + } + } + + /// <summary> + /// Handles the completion of a <see cref="Socket.SendAsync" /> call. + /// </summary> + private void HandleSent(SocketAsyncEventArgs args) + { + StateToken state = (StateToken)args.UserToken; + TaskCompletionSource<int>? tcs = state.RequestCompletionSource; + + switch (tcs) + { + case null: + switch (args.SocketError) + { + case SocketError.Success: + HandleSentResponse(args, state); + break; + + default: + CloseClientConnection(args); + break; + } + + break; + + default: + switch (args.SocketError) + { + case SocketError.Success: + HandleSentRequest(args, state); + break; + + case SocketError.OperationAborted: + tcs.SetCanceled(); + CleanupArgs(args); + break; + + default: + tcs.SetException(new SocketException((int)args.SocketError)); + CleanupArgs(args); + break; + } + + break; + } + } + + /// <summary> + /// Handles the completion of a <see cref="Socket.SendAsync" /> call, when sending a request packet to the + /// network. In this case, the <see cref="SocketAsyncEventArgs.ConnectSocket" /> will be used to perform the transmission. + /// </summary> + private void HandleSentRequest(SocketAsyncEventArgs args, StateToken state) + { + TaskCompletionSource<int> tcs = state.RequestCompletionSource!; + + int sent = args.BytesTransferred; + int previouslySent = args.Offset; + int totalSent = previouslySent + sent; + int expected = state.BytesToTransfer; + + if (totalSent == expected) + { + tcs.SetResult(totalSent - RawPacketHeader.Length); + CleanupArgs(args); + } + else if (totalSent > 0 && totalSent < expected) + { + args.SetBuffer(totalSent, expected - totalSent); + StartOrContinueSending(args.ConnectSocket, args); + } + else if (sent == 0) + { + // connection is dead + tcs.SetException(new SocketException((int)SocketError.HostDown)); + CleanupArgs(args); + } + } + + /// <summary> + /// Handles the completion of a <see cref="Socket.SendAsync" /> call, when sending a response packet to the + /// network. In this case, the <see cref="SocketAsyncEventArgs.AcceptSocket" /> will be used to perform the transmission. + /// </summary> + private void HandleSentResponse(SocketAsyncEventArgs args, StateToken state) + { + int sent = args.BytesTransferred; + int previouslySent = args.Offset; + int totalSent = previouslySent + sent; + int expected = state.BytesToTransfer; + + if (totalSent == expected) + { + ConfigureReceiveHeaderAsync(args, state); + StartOrContinueReceiving(args); + } + else if (totalSent > 0 && totalSent < expected) + { + args.SetBuffer(totalSent, expected - totalSent); + StartOrContinueSending(args.AcceptSocket, args); + } + else if (sent == 0) + { + CloseClientConnection(args); + } + } + + /// <summary> + /// Starts or continues an asynchronous network read operation. + /// </summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void StartOrContinueReceiving(SocketAsyncEventArgs args) + { + if (args.AcceptSocket.ReceiveAsync(args)) + { + return; + } + + HandleReceived(args); + } + + /// <summary> + /// Starts or continues an asynchronous network write operation using the given socket. + /// </summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void StartOrContinueSending(Socket connection, SocketAsyncEventArgs args) + { + if (connection.SendAsync(args)) + { + return; + } + + HandleSent(args); + } + + /// <summary> + /// State token for the stream network connection. + /// </summary> + private sealed class StateToken : IDisposable + { + /// <summary> + /// The number of bytes that we need to transfer over the network. + /// </summary> + internal int BytesToTransfer { get; set; } + + /// <summary> + /// The <see cref="TaskCompletionSource{TResult}" /> for asynchronous network operations. + /// </summary> + internal TaskCompletionSource<bool>? OperationCompletionSource { get; set; } + + /// <summary> + /// The <see cref="TaskCompletionSource{TResult}" /> for asynchronous packet writes. + /// </summary> + internal TaskCompletionSource<int>? RequestCompletionSource { get; set; } + + /// <summary> + /// The deserialised request packet header. + /// </summary> + internal RawPacketHeader? RequestHeader { get; set; } + + public void Dispose() + { + Reset(); + } + + internal void Reset() + { + BytesToTransfer = 0; + OperationCompletionSource = null; + RequestCompletionSource = null; + RequestHeader = null; + } + } + } +} diff --git a/NetSharp/NetSharp/Raw/Stream/RawStreamNetworkReader.cs b/NetSharp/NetSharp/Raw/Stream/RawStreamNetworkReader.cs @@ -1,408 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; - -namespace NetSharp.Raw.Stream -{ - /// <summary> - /// Represents a method that handles a request received by a <see cref="RawStreamNetworkReader" />. - /// </summary> - /// <param name="remoteEndPoint"> - /// The remote endpoint from which the request was received. - /// </param> - /// <param name="requestBuffer"> - /// The buffer containing the received request. - /// </param> - /// <param name="receivedRequestBytes"> - /// The number of bytes of user data received in the request. - /// </param> - /// <param name="responseBuffer"> - /// The buffer into which the response should be written. - /// </param> - /// <returns> - /// Whether there exists a response to be sent back to the remote endpoint. - /// </returns> - // TODO implement this in a better, more robust and extensible way - public delegate bool RawStreamRequestHandler( - EndPoint remoteEndPoint, - in ReadOnlyMemory<byte> requestBuffer, - int receivedRequestBytes, - in Memory<byte> responseBuffer); - - /// <summary> - /// Implements a raw network reader using a stream-based protocol. - /// </summary> - public sealed class RawStreamNetworkReader : RawNetworkReaderBase - { - private readonly RawStreamRequestHandler requestHandler; - - /// <inheritdoc cref="RawNetworkReaderBase(ref Socket, EndPoint, int, int, uint)"/> - public RawStreamNetworkReader( - ref Socket rawConnection, - RawStreamRequestHandler? requestHandler, - EndPoint defaultEndPoint, - int maxPooledMessageSize, - int pooledBuffersPerBucket = 50, - uint preallocatedStateObjects = 0) - : base(ref rawConnection, defaultEndPoint, maxPooledMessageSize, pooledBuffersPerBucket, preallocatedStateObjects) - { - if (maxPooledMessageSize <= 0) - { - throw new ArgumentOutOfRangeException(nameof(maxPooledMessageSize), maxPooledMessageSize, Properties.Resources.RawStreamMessageSizeUnderflow); - } - - this.requestHandler = requestHandler ?? DefaultRequestHandler; - } - - /// <inheritdoc /> - public override void Start(ushort concurrentReadTasks) - { - for (ushort i = 0; i < concurrentReadTasks; i++) - { - StartDefaultAccept(); - } - } - - /// <inheritdoc /> - protected override bool CanReuseStateObject(ref SocketAsyncEventArgs instance) - { - return true; - } - - /// <inheritdoc /> - protected override SocketAsyncEventArgs CreateStateObject() - { - SocketAsyncEventArgs args = new SocketAsyncEventArgs(); - args.Completed += HandleIoCompleted; - - return args; - } - - /// <inheritdoc /> - protected override void DestroyStateObject(SocketAsyncEventArgs instance) - { - instance.Completed -= HandleIoCompleted; - instance.Dispose(); - } - - /// <inheritdoc /> - protected override void ResetStateObject(ref SocketAsyncEventArgs instance) - { - instance.AcceptSocket = null; - } - - private static bool DefaultRequestHandler( - EndPoint remoteEndPoint, - in ReadOnlyMemory<byte> requestBuffer, - int receivedRequestBytes, - in Memory<byte> responseBuffer) - { - return requestBuffer.TryCopyTo(responseBuffer); - } - - private void CloseClientConnection(SocketAsyncEventArgs args) - { - Socket serversideClient = args.AcceptSocket; - - serversideClient.Disconnect(false); - serversideClient.Shutdown(SocketShutdown.Both); - serversideClient.Close(); - serversideClient.Dispose(); - - CleanupTransmissionBufferAndState(args); - } - - private void CompleteAccept(SocketAsyncEventArgs args) - { - switch (args.SocketError) - { - case SocketError.Success: - // the buffer is set to allow a simpler ConfigureReceiveHeader() implemetation. Since returning an empty buffer is ignored in the - // array pool, this allows us to just return the last assigned buffer in the ConfigureXXX() method to the pool (this means that - // usually we will usually be returning the ResponseDataBuffer). - args.SetBuffer(Array.Empty<byte>(), 0, 0); - - ConfigureAsyncReceiveHeader(args); - StartOrContinueReceive(args); - break; - - default: - CleanupTransmissionBufferAndState(args); - break; - } - } - - private void CompleteReceive(SocketAsyncEventArgs args) - { - PacketReadToken readToken = (PacketReadToken)args.UserToken; - - bool receivingHeader = readToken.BytesToTransfer == RawStreamPacketHeader.TotalSize; - - switch (args.SocketError) - { - case SocketError.Success: - switch (receivingHeader) - { - case true: - CompleteReceiveHeader(args, in readToken); - break; - - case false: - CompleteReceiveData(args, in readToken); - break; - } - - break; - - default: - CloseClientConnection(args); - break; - } - } - - private void CompleteReceiveData(SocketAsyncEventArgs args, in PacketReadToken readToken) - { - Memory<byte> dataBuffer = args.Buffer; - - int receivedBytes = args.BytesTransferred, - previousReceivedBytes = args.Offset, - totalReceivedBytes = previousReceivedBytes + receivedBytes, - expectedBytes = readToken.BytesToTransfer; - - if (totalReceivedBytes == expectedBytes) - { - // transmission complete - EndPoint clientEndPoint = args.AcceptSocket.RemoteEndPoint; - - // TODO use user-supplied delegate to generate response packet header - RawStreamPacketHeader responseHeader = new RawStreamPacketHeader(expectedBytes); - int responseBufferSize = RawStreamPacket.TotalPacketSize(in responseHeader); - - byte[] responseBuffer = BufferPool.Rent(responseBufferSize); - - Memory<byte> responseBufferMemory = responseBuffer.AsMemory(RawStreamPacketHeader.TotalSize, responseHeader.DataSize); - - // TODO rework request handler - bool responseExists = requestHandler(clientEndPoint, dataBuffer, totalReceivedBytes, responseBufferMemory); - - switch (responseExists) - { - case true: - ConfigureAsyncSendPacket(args, ref responseBuffer, in responseHeader, responseBufferMemory); - StartOrContinueSend(args); - break; - - case false: - // we manually returns the response buffer, as it wasnt set to be the args.Buffer, and since we dont have a response packet we - // can reuse it as a packet header buffer in the below ConfigureAsyncReceiveHeader() call - BufferPool.Return(responseBuffer, true); - - ConfigureAsyncReceiveHeader(args); - StartOrContinueReceive(args); - break; - } - } - else if (totalReceivedBytes > 0 && totalReceivedBytes < expectedBytes) - { - // transmission not complete - args.SetBuffer(totalReceivedBytes, expectedBytes - totalReceivedBytes); - - StartOrContinueReceive(args); - } - else if (receivedBytes == 0) - { - // connection is dead - CloseClientConnection(args); - } - } - - private void CompleteReceiveHeader(SocketAsyncEventArgs args, in PacketReadToken readToken) - { - Memory<byte> headerBuffer = args.Buffer; - - int receivedBytes = args.BytesTransferred, - previousReceivedBytes = args.Offset, - totalReceivedBytes = previousReceivedBytes + receivedBytes, - expectedBytes = readToken.BytesToTransfer; - - if (totalReceivedBytes == expectedBytes) - { - // transmission complete - RawStreamPacketHeader header = RawStreamPacketHeader.Deserialise(in headerBuffer); - - ConfigureAsyncReceiveData(args, in header); - StartOrContinueReceive(args); - } - else if (totalReceivedBytes > 0 && totalReceivedBytes < expectedBytes) - { - // transmission not complete - args.SetBuffer(totalReceivedBytes, expectedBytes - totalReceivedBytes); - - StartOrContinueReceive(args); - } - else if (receivedBytes == 0) - { - // connection is dead - CloseClientConnection(args); - } - } - - private void CompleteSend(SocketAsyncEventArgs args) - { - PacketWriteToken writeToken = (PacketWriteToken)args.UserToken; - - int sentBytes = args.BytesTransferred, - previousSentBytes = args.Offset, - totalSentBytes = previousSentBytes + sentBytes, - expectedBytes = writeToken.BytesToTransfer; - - switch (args.SocketError) - { - case SocketError.Success: - if (totalSentBytes == expectedBytes) - { - // transmission complete - ConfigureAsyncReceiveHeader(args); - StartOrContinueReceive(args); - } - else if (totalSentBytes > 0 && totalSentBytes < expectedBytes) - { - // transmission not complete - args.SetBuffer(totalSentBytes, expectedBytes - totalSentBytes); - - StartOrContinueSend(args); - } - else if (sentBytes == 0) - { - // connection is dead - CloseClientConnection(args); - } - - break; - - default: - CloseClientConnection(args); - break; - } - } - - private void ConfigureAsyncReceiveData(SocketAsyncEventArgs args, in RawStreamPacketHeader receivedPacketHeader) - { - BufferPool.Return(args.Buffer, true); // return and clear the requestHeaderBuffer (as it was already parsed) - - byte[] pendingPacketDataBuffer = BufferPool.Rent(receivedPacketHeader.DataSize); - - args.SetBuffer(pendingPacketDataBuffer, 0, receivedPacketHeader.DataSize); - args.UserToken = new PacketReadToken(receivedPacketHeader.DataSize); - } - - private void ConfigureAsyncReceiveHeader(SocketAsyncEventArgs args) - { - BufferPool.Return(args.Buffer, true); // return and clear the responseDataBuffer (or requestDataBuffer if no response was generated) - - byte[] pendingPacketHeaderBuffer = BufferPool.Rent(RawStreamPacketHeader.TotalSize); - - args.SetBuffer(pendingPacketHeaderBuffer, 0, RawStreamPacketHeader.TotalSize); - args.UserToken = new PacketReadToken(RawStreamPacketHeader.TotalSize); - } - - private void ConfigureAsyncSendPacket( - SocketAsyncEventArgs args, - ref byte[] pendingPacketBuffer, - in RawStreamPacketHeader pendingPacketHeader, - in ReadOnlyMemory<byte> pendingPacketData) - { - BufferPool.Return(args.Buffer, true); // return and clear the requestDataBuffer (as it was already parsed) - - RawStreamPacket.Serialise(pendingPacketBuffer, in pendingPacketHeader, in pendingPacketData); - - int totalPacketSize = RawStreamPacket.TotalPacketSize(in pendingPacketHeader); - args.SetBuffer(pendingPacketBuffer, 0, totalPacketSize); - args.UserToken = new PacketWriteToken(totalPacketSize); - } - - private void HandleIoCompleted(object sender, SocketAsyncEventArgs args) - { - switch (args.LastOperation) - { - case SocketAsyncOperation.Accept: - StartDefaultAccept(); - CompleteAccept(args); - break; - - case SocketAsyncOperation.Send: - CompleteSend(args); - break; - - case SocketAsyncOperation.Receive: - CompleteReceive(args); - break; - } - } - - private void StartAccept(SocketAsyncEventArgs args) - { - if (!ConnectionDisposed && Connection.AcceptAsync(args)) - { - return; - } - - StartDefaultAccept(); - CompleteAccept(args); - } - - private void StartDefaultAccept() - { - if (!ConnectionDisposed) - { - SocketAsyncEventArgs args = ArgsPool.Rent(); - - StartAccept(args); - } - } - - private void StartOrContinueReceive(SocketAsyncEventArgs args) - { - Socket serversideClient = args.AcceptSocket; - - if (serversideClient.ReceiveAsync(args)) - { - return; - } - - CompleteReceive(args); - } - - private void StartOrContinueSend(SocketAsyncEventArgs args) - { - Socket serversideClient = args.AcceptSocket; - - if (serversideClient.SendAsync(args)) - { - return; - } - - CompleteSend(args); - } - - private readonly struct PacketReadToken - { - public readonly int BytesToTransfer; - - public PacketReadToken(int bytesToTransfer) - { - BytesToTransfer = bytesToTransfer; - } - } - - private readonly struct PacketWriteToken - { - public readonly int BytesToTransfer; - - public PacketWriteToken(int bytesToTransfer) - { - BytesToTransfer = bytesToTransfer; - } - } - } -} diff --git a/NetSharp/NetSharp/Raw/Stream/RawStreamNetworkWriter.cs b/NetSharp/NetSharp/Raw/Stream/RawStreamNetworkWriter.cs @@ -1,431 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Threading.Tasks; - -namespace NetSharp.Raw.Stream -{ - /// <summary> - /// Implements a raw network writer using a stream-based protocol. - /// </summary> - public sealed class RawStreamNetworkWriter : RawNetworkWriterBase - { - /// <inheritdoc cref="RawNetworkWriterBase(ref Socket, EndPoint, int, int, uint)" /> - public RawStreamNetworkWriter( - ref Socket rawConnection, - EndPoint defaultEndPoint, - int maxPooledMessageSize = DefaultMaxPooledBufferSize, - int pooledBuffersPerBucket = 50, - uint preallocatedStateObjects = 0) - : base(ref rawConnection, defaultEndPoint, maxPooledMessageSize, pooledBuffersPerBucket, preallocatedStateObjects) - { - if (maxPooledMessageSize <= 0) - { - throw new ArgumentOutOfRangeException(nameof(maxPooledMessageSize), maxPooledMessageSize, Properties.Resources.RawStreamMessageSizeUnderflow); - } - } - - /// <inheritdoc /> - public override int Read(ref EndPoint remoteEndPoint, Memory<byte> readBuffer, SocketFlags flags = SocketFlags.None) - { - static int ReadBytesIntoBuffer(Socket connection, ref byte[] buffer, int count, SocketFlags flags) - { - int readBytes = 0; - - do - { - readBytes += connection.Receive(buffer, readBytes, count - readBytes, flags); - } - while (readBytes < count && readBytes > 0); - - return readBytes; - } - - byte[] pendingHeaderBuffer = BufferPool.Rent(RawStreamPacketHeader.TotalSize); - - _ = ReadBytesIntoBuffer(Connection, ref pendingHeaderBuffer, RawStreamPacketHeader.TotalSize, flags); - - RawStreamPacketHeader packetHeader = RawStreamPacketHeader.Deserialise(pendingHeaderBuffer); - BufferPool.Return(pendingHeaderBuffer, true); // return and clear the pendingHeaderBuffer (as it was already parsed) - - byte[] pendingPacketDataBuffer = BufferPool.Rent(packetHeader.DataSize); - - int bodyBytes = ReadBytesIntoBuffer(Connection, ref pendingPacketDataBuffer, packetHeader.DataSize, flags); - - pendingPacketDataBuffer.AsMemory(0, readBuffer.Length).CopyTo(readBuffer); - BufferPool.Return(pendingPacketDataBuffer, true); // return and clear the pendingDataBuffer (as it was already copied) - - return bodyBytes; // we only return the number of bytes of user data that were read - } - - /// <inheritdoc /> - public override ValueTask<int> ReadAsync(EndPoint remoteEndPoint, Memory<byte> readBuffer, SocketFlags flags = SocketFlags.None) - { - TaskCompletionSource<int> tcs = new TaskCompletionSource<int>(); - SocketAsyncEventArgs args = ArgsPool.Rent(); - - ConfigureAsyncReceiveHeader(args, in readBuffer, tcs); - - args.RemoteEndPoint = remoteEndPoint; - args.SocketFlags = flags; - - StartReceive(args); - - return new ValueTask<int>(tcs.Task); - } - - /// <inheritdoc /> - public override int Write(EndPoint remoteEndPoint, ReadOnlyMemory<byte> writeBuffer, SocketFlags flags = SocketFlags.None) - { - static int WriteBytesFromBuffer(Socket connection, ref byte[] buffer, int count, SocketFlags flags) - { - int writtenBytes = 0; - - do - { - writtenBytes += connection.Send(buffer, writtenBytes, count - writtenBytes, flags); - } - while (writtenBytes < count && writtenBytes > 0); - - return writtenBytes; - } - - RawStreamPacketHeader pendingPacketHeader = new RawStreamPacketHeader(writeBuffer.Length); - int totalPacketSize = RawStreamPacket.TotalPacketSize(in pendingPacketHeader); - byte[] pendingPacketBuffer = BufferPool.Rent(totalPacketSize); - - RawStreamPacket.Serialise(pendingPacketBuffer, in pendingPacketHeader, in writeBuffer); - - _ = WriteBytesFromBuffer(Connection, ref pendingPacketBuffer, totalPacketSize, flags); - BufferPool.Return(pendingPacketBuffer, true); // return and clear the pendingPacketBuffer (as it was already cleared) - - return pendingPacketHeader.DataSize; // we only return the number of bytes of user data that were written - } - - /// <inheritdoc /> - public override ValueTask<int> WriteAsync(EndPoint remoteEndPoint, ReadOnlyMemory<byte> writeBuffer, SocketFlags flags = SocketFlags.None) - { - TaskCompletionSource<int> tcs = new TaskCompletionSource<int>(); - SocketAsyncEventArgs args = ArgsPool.Rent(); - - RawStreamPacketHeader pendingPacketHeader = new RawStreamPacketHeader(writeBuffer.Length); - int totalPacketSize = RawStreamPacket.TotalPacketSize(in pendingPacketHeader); - byte[] pendingPacketBuffer = BufferPool.Rent(totalPacketSize); - - ConfigureAsyncSendPacket(args, ref pendingPacketBuffer, in pendingPacketHeader, in writeBuffer, tcs); - - args.RemoteEndPoint = remoteEndPoint; - args.SocketFlags = flags; - - StartSend(args); - - return new ValueTask<int>(tcs.Task); - } - - /// <inheritdoc /> - protected override bool CanReuseStateObject(ref SocketAsyncEventArgs instance) - { - return true; - } - - /// <inheritdoc /> - protected override SocketAsyncEventArgs CreateStateObject() - { - SocketAsyncEventArgs args = new SocketAsyncEventArgs(); - args.Completed += HandleIoCompleted; - - return args; - } - - /// <inheritdoc /> - protected override void DestroyStateObject(SocketAsyncEventArgs instance) - { - instance.Completed -= HandleIoCompleted; - instance.Dispose(); - } - - /// <inheritdoc /> - protected override void ResetStateObject(ref SocketAsyncEventArgs instance) - { - } - - private static void ConfigureAsyncSendPacket( - SocketAsyncEventArgs args, - ref byte[] pendingPacketBuffer, - in RawStreamPacketHeader pendingPacketHeader, - in ReadOnlyMemory<byte> userDataBuffer, - TaskCompletionSource<int> tcs) - { - RawStreamPacket.Serialise(pendingPacketBuffer, in pendingPacketHeader, in userDataBuffer); - - int totalPacketSize = RawStreamPacket.TotalPacketSize(in pendingPacketHeader); - args.SetBuffer(pendingPacketBuffer, 0, totalPacketSize); - args.UserToken = new PacketWriteToken(totalPacketSize, tcs); - } - - private void CompleteReceive(SocketAsyncEventArgs args) - { - PacketReadToken readToken = (PacketReadToken)args.UserToken; - - bool receivingHeader = readToken.BytesToTransfer == RawStreamPacketHeader.TotalSize; - - switch (args.SocketError) - { - case SocketError.OperationAborted: - readToken.CompletionSource.SetCanceled(); - - CleanupTransmissionBufferAndState(args); - break; - - case SocketError.Success: - switch (receivingHeader) - { - case true: - CompleteReceiveHeader(args, in readToken); - break; - - case false: - CompleteReceiveData(args, in readToken); - break; - } - - break; - - default: - readToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); - - CleanupTransmissionBufferAndState(args); - break; - } - } - - private void CompleteReceiveData(SocketAsyncEventArgs args, in PacketReadToken readToken) - { - int receivedBytes = args.BytesTransferred, - previousReceivedBytes = args.Offset, - totalReceivedBytes = previousReceivedBytes + receivedBytes, - expectedBytes = readToken.BytesToTransfer; - - if (totalReceivedBytes == expectedBytes) - { - // transmission complete - args.Buffer.AsMemory(0, readToken.UserDataBuffer.Length).CopyTo(readToken.UserDataBuffer); - - // we only return the number of bytes of user data that were read - readToken.CompletionSource.SetResult(totalReceivedBytes); - - CleanupTransmissionBufferAndState(args); - } - else if (totalReceivedBytes > 0 && totalReceivedBytes < expectedBytes) - { - // transmission not complete - args.SetBuffer(totalReceivedBytes, expectedBytes - totalReceivedBytes); - - ContinueReceive(args); - } - else if (receivedBytes == 0) - { - // connection is dead - readToken.CompletionSource.SetException(new SocketException((int)SocketError.HostDown)); - - CleanupTransmissionBufferAndState(args); - } - } - - private void CompleteReceiveHeader(SocketAsyncEventArgs args, in PacketReadToken readToken) - { - int receivedBytes = args.BytesTransferred, - previousReceivedBytes = args.Offset, - totalReceivedBytes = previousReceivedBytes + receivedBytes, - expectedBytes = readToken.BytesToTransfer; - - if (totalReceivedBytes == expectedBytes) - { - // transmission complete - Memory<byte> headerBuffer = args.Buffer.AsMemory(0, RawStreamPacketHeader.TotalSize); - RawStreamPacketHeader header = RawStreamPacketHeader.Deserialise(in headerBuffer); - - ConfigureAsyncReceiveData(args, in header, in readToken.UserDataBuffer, readToken.CompletionSource); - - StartReceive(args); - } - else if (totalReceivedBytes > 0 && totalReceivedBytes < expectedBytes) - { - // transmission not complete - args.SetBuffer(totalReceivedBytes, expectedBytes - totalReceivedBytes); - - ContinueReceive(args); - } - else if (receivedBytes == 0) - { - // connection is dead - readToken.CompletionSource.SetException(new SocketException((int)SocketError.HostDown)); - - CleanupTransmissionBufferAndState(args); - } - } - - private void CompleteSend(SocketAsyncEventArgs args) - { - PacketWriteToken writeToken = (PacketWriteToken)args.UserToken; - - int sentBytes = args.BytesTransferred, - previousSentBytes = args.Offset, - totalSentBytes = previousSentBytes + sentBytes, - expectedBytes = writeToken.BytesToTransfer; - - switch (args.SocketError) - { - case SocketError.OperationAborted: - writeToken.CompletionSource.SetCanceled(); - - CleanupTransmissionBufferAndState(args); - break; - - case SocketError.Success: - if (totalSentBytes == expectedBytes) - { - // transmission complete we only return the number of bytes of user data that were written - writeToken.CompletionSource.SetResult(totalSentBytes - RawStreamPacketHeader.TotalSize); - - CleanupTransmissionBufferAndState(args); - } - else if (totalSentBytes > 0 && totalSentBytes < expectedBytes) - { - // transmission not complete - args.SetBuffer(totalSentBytes, expectedBytes - totalSentBytes); - - ContinueSend(args); - } - else if (sentBytes == 0) - { - // connection is dead - writeToken.CompletionSource.SetException(new SocketException((int)SocketError.HostDown)); - - CleanupTransmissionBufferAndState(args); - } - - break; - - default: - writeToken.CompletionSource.SetException(new SocketException((int)args.SocketError)); - - CleanupTransmissionBufferAndState(args); - break; - } - } - - private void ConfigureAsyncReceiveData( - SocketAsyncEventArgs args, - in RawStreamPacketHeader receivedPacketHeader, - in Memory<byte> userDataBuffer, - TaskCompletionSource<int> tcs) - { - BufferPool.Return(args.Buffer, true); // return and clear the requestHeaderBuffer (as it was already parsed) - - byte[] pendingPacketDataBuffer = BufferPool.Rent(receivedPacketHeader.DataSize); - - args.SetBuffer(pendingPacketDataBuffer, 0, receivedPacketHeader.DataSize); - - args.UserToken = new PacketReadToken(receivedPacketHeader.DataSize, receivedPacketHeader, in userDataBuffer, tcs); - } - - private void ConfigureAsyncReceiveHeader(SocketAsyncEventArgs args, in Memory<byte> userDataBuffer, TaskCompletionSource<int> tcs) - { - byte[] pendingPacketHeaderBuffer = BufferPool.Rent(RawStreamPacketHeader.TotalSize); - - args.SetBuffer(pendingPacketHeaderBuffer, 0, RawStreamPacketHeader.TotalSize); - args.UserToken = new PacketReadToken(RawStreamPacketHeader.TotalSize, null, in userDataBuffer, tcs); - } - - private void ContinueReceive(SocketAsyncEventArgs args) - { - if (Connection.ReceiveAsync(args)) - { - return; - } - - CompleteReceive(args); - } - - private void ContinueSend(SocketAsyncEventArgs args) - { - if (Connection.SendAsync(args)) - { - return; - } - - CompleteSend(args); - } - - private void HandleIoCompleted(object sender, SocketAsyncEventArgs args) - { - switch (args.LastOperation) - { - case SocketAsyncOperation.Receive: - CompleteReceive(args); - break; - - case SocketAsyncOperation.Send: - CompleteSend(args); - break; - } - } - - private void StartReceive(SocketAsyncEventArgs args) - { - if (Connection.ReceiveAsync(args)) - { - return; - } - - CompleteReceive(args); - } - - private void StartSend(SocketAsyncEventArgs args) - { - if (Connection.SendAsync(args)) - { - return; - } - - CompleteSend(args); - } - - private readonly struct PacketReadToken - { - internal readonly int BytesToTransfer; - - internal readonly TaskCompletionSource<int> CompletionSource; - - internal readonly RawStreamPacketHeader? Header; - - internal readonly Memory<byte> UserDataBuffer; - - internal PacketReadToken(int bytesToTransfer, in RawStreamPacketHeader? header, in Memory<byte> userDataBuffer, TaskCompletionSource<int> tcs) - { - BytesToTransfer = bytesToTransfer; - - Header = header; - - UserDataBuffer = userDataBuffer; - - CompletionSource = tcs; - } - } - - private readonly struct PacketWriteToken - { - internal readonly int BytesToTransfer; - - internal readonly TaskCompletionSource<int> CompletionSource; - - internal PacketWriteToken(int bytesToTransfer, TaskCompletionSource<int> tcs) - { - BytesToTransfer = bytesToTransfer; - - CompletionSource = tcs; - } - } - } -} diff --git a/NetSharp/NetSharp/Raw/Stream/RawStreamPacket.cs b/NetSharp/NetSharp/Raw/Stream/RawStreamPacket.cs @@ -1,47 +0,0 @@ -using System; -using System.Runtime.CompilerServices; - -namespace NetSharp.Raw.Stream -{ - /// <summary> - /// Provides helper methods to manipulate the binary packet format used by stream network handlers. - /// </summary> - internal static class RawStreamPacket - { - /// <summary> - /// Serialises the given <paramref name="packetHeader" /> and <paramref name="packetData" /> into the given <paramref name="buffer" />. - /// </summary> - /// <param name="buffer"> - /// The buffer into which the packet should be serialised. Must be at least of size <see cref="RawStreamPacketHeader.TotalSize" /> + the size - /// of the user data given by <paramref name="packetHeader" />. - /// </param> - /// <param name="packetHeader"> - /// The header containing metatdata abut the raw stream packet. - /// </param> - /// <param name="packetData"> - /// The user data held in the raw stream packet. - /// </param> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void Serialise(in Memory<byte> buffer, in RawStreamPacketHeader packetHeader, in ReadOnlyMemory<byte> packetData) - { - packetHeader.Serialise(buffer.Slice(0, RawStreamPacketHeader.TotalSize)); - - packetData.CopyTo(buffer.Slice(RawStreamPacketHeader.TotalSize, packetData.Length)); - } - - /// <summary> - /// Calculates the total size of a raw stream packet, using the packet data size in the given <paramref name="packetHeader" />. - /// </summary> - /// <param name="packetHeader"> - /// The header for which to calculate the total packet size. - /// </param> - /// <returns> - /// The total size of a raw stream packet with the given <paramref name="packetHeader" />. - /// </returns> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static int TotalPacketSize(in RawStreamPacketHeader packetHeader) - { - return RawStreamPacketHeader.TotalSize + packetHeader.DataSize; - } - } -} diff --git a/NetSharp/NetSharp/Raw/Stream/RawStreamPacketHeader.cs b/NetSharp/NetSharp/Raw/Stream/RawStreamPacketHeader.cs @@ -1,64 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace NetSharp.Raw.Stream -{ - /// <summary> - /// Holds metadata about a raw stream packet. - /// </summary> - internal readonly struct RawStreamPacketHeader - { - /// <summary> - /// The total size of the header in bytes. - /// </summary> - internal const int TotalSize = sizeof(int); - - /// <summary> - /// The size of the user supplied data segment in bytes. - /// </summary> - internal readonly int DataSize; - - /// <summary> - /// Initialises a new instance of the <see cref="RawStreamPacketHeader"/> struct. - /// </summary> - /// <param name="dataSize"> - /// The size of the user supplied data segment. - /// </param> - internal RawStreamPacketHeader(int dataSize) - { - DataSize = dataSize; - } - - /// <summary> - /// Deserialises a <see cref="RawStreamPacketHeader" /> instance from the given <paramref name="buffer" />. - /// </summary> - /// <param name="buffer"> - /// A buffer containing a serialised <see cref="RawStreamPacketHeader" /> instance. Must be at least of size <see cref="TotalSize" />. - /// </param> - /// <returns> - /// The deserialised instance. - /// </returns> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static RawStreamPacketHeader Deserialise(in Memory<byte> buffer) - { - int dataSize = MemoryMarshal.Read<int>(buffer.Span.Slice(0, sizeof(int))); - - return new RawStreamPacketHeader(dataSize); - } - - /// <summary> - /// Serialises the current <see cref="RawStreamPacketHeader" /> instance into the given <paramref name="buffer" />. - /// </summary> - /// <param name="buffer"> - /// The buffer into which to serialise the current instance. Must be at least of size <see cref="TotalSize" />. - /// </param> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void Serialise(in Memory<byte> buffer) - { - int dataSize = DataSize; - - MemoryMarshal.Write(buffer.Span.Slice(0, sizeof(int)), ref dataSize); - } - } -} diff --git a/NetSharp/NetSharp/Utils/SlimObjectPool.cs b/NetSharp/NetSharp/Utils/SlimObjectPool.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Concurrent; -using System.Runtime.CompilerServices; namespace NetSharp.Utils { @@ -10,68 +9,44 @@ namespace NetSharp.Utils /// <typeparam name="T"> /// The type of item stored in the pool. /// </typeparam> - public sealed class SlimObjectPool<T> : IDisposable + internal sealed class SlimObjectPool<T> : IDisposable { - private readonly CanReuseObjectPredicate canObjectBeRebufferedPredicate; + private readonly CreateObject createObject; - private readonly CreateObjectDelegate createObjectDelegate; - - private readonly DestroyObjectDelegate destroyObjectDelegate; + private readonly DestroyObject destroyObject; private readonly IProducerConsumerCollection<T> objectBuffer; - private readonly ResetObjectDelegate resetObjectDelegate; + private readonly ResetObject resetObject; /// <summary> - /// Initialises a new instance of the <see cref="SlimObjectPool{T}"/> class. + /// Initialises a new instance of the <see cref="SlimObjectPool{T}" /> class. /// </summary> - /// <param name="createDelegate"> + /// <param name="create"> /// The delegate method to use to create new pooled object instances. /// </param> - /// <param name="resetDelegate"> + /// <param name="reset"> /// The delegate method to use to reset used pooled object instances. /// </param> - /// <param name="destroyDelegate"> + /// <param name="destroy"> /// The delegate method to use to destroy pooled object instances that cannot be reused. /// </param> - /// <param name="rebufferPredicate"> - /// The delegate method to use to decide whether an instance can be reused. - /// </param> - public SlimObjectPool( - CreateObjectDelegate createDelegate, - ResetObjectDelegate resetDelegate, - DestroyObjectDelegate destroyDelegate, - CanReuseObjectPredicate rebufferPredicate) + internal SlimObjectPool(CreateObject create, ResetObject reset, DestroyObject destroy) { - createObjectDelegate = createDelegate; - - resetObjectDelegate = resetDelegate; - - destroyObjectDelegate = destroyDelegate; - - canObjectBeRebufferedPredicate = rebufferPredicate; + createObject = create; + resetObject = reset; + destroyObject = destroy; objectBuffer = new ConcurrentBag<T>(); } /// <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 CanReuseObjectPredicate(ref 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(); + internal delegate T CreateObject(); /// <summary> /// Delegate method to destroy a used <paramref name="instance" /> which cannot be reused. @@ -79,7 +54,7 @@ namespace NetSharp.Utils /// <param name="instance"> /// The instance to destroy. /// </param> - public delegate void DestroyObjectDelegate(T instance); + internal delegate void DestroyObject(T instance); /// <summary> /// Delegate method to reset a used <paramref name="instance" /> before placing it back into the pool. @@ -87,14 +62,14 @@ namespace NetSharp.Utils /// <param name="instance"> /// The instance which should be reset. /// </param> - public delegate void ResetObjectDelegate(ref T instance); + internal delegate void ResetObject(ref T instance); /// <inheritdoc /> public void Dispose() { foreach (T pooledObject in objectBuffer) { - destroyObjectDelegate(pooledObject); + destroyObject(pooledObject); } } @@ -104,12 +79,11 @@ namespace NetSharp.Utils /// <returns> /// The <typeparamref name="T" /> instance which was fetched from the pool. /// </returns> - [MethodImpl(MethodImplOptions.Synchronized)] - public T Rent() + internal T Rent() { bool successfullyRentedInstance = objectBuffer.TryTake(out T instance); - return successfullyRentedInstance ? instance : createObjectDelegate(); + return successfullyRentedInstance ? instance : createObject(); } /// <summary> @@ -118,23 +92,15 @@ namespace NetSharp.Utils /// <param name="instance"> /// The previously leased instance which should be returned. /// </param> - [MethodImpl(MethodImplOptions.Synchronized)] - public void Return(T instance) + internal void Return(T instance) { - if (canObjectBeRebufferedPredicate(ref instance)) - { - resetObjectDelegate(ref instance); + resetObject(ref instance); - bool successfullyRebufferedInstance = objectBuffer.TryAdd(instance); + bool successfullyRebufferedInstance = objectBuffer.TryAdd(instance); - if (!successfullyRebufferedInstance) - { - destroyObjectDelegate(instance); - } - } - else + if (!successfullyRebufferedInstance) { - destroyObjectDelegate(instance); + destroyObject(instance); } } } diff --git a/NetSharp/NetSharp/stylecop.json b/NetSharp/NetSharp/stylecop.json @@ -54,7 +54,6 @@ "allowBuiltInTypeAliases": false }, "spacingRules": { - } } }