commit 2de9d320ce5082fbf9d2d8cd05b4548dceff3554
parent ab046ff034d626e0a975ab22bdd335e249a8548a
Author: Mikołaj Lenczewski <mikolaj.lenczewski308@gmail.com>
Date: Thu, 4 Mar 2021 23:35:49 +0000
Started another refactor of project
+ moved common dotMemory operations and constants to new TestUtils class
~ split RawStreamConnection into discrete server and client classes, to
better reflect their separate operating modes (server: async
non-blocking, client: (a)sync (non-)blocking)
~ reorganised namespaces, putting some common RawStream types under
Raw.Stream namespace
~ extended IRawStreamClient interface to include ReceiveAsync method,
and added custom return type for SendAsync and said method
Diffstat:
23 files changed, 1705 insertions(+), 1506 deletions(-)
diff --git a/NetSharp/NetSharp.Benchmarks/Benchmarks/RawStreamConnectionBenchmark.cs b/NetSharp/NetSharp.Benchmarks/Benchmarks/RawStreamConnectionBenchmark.cs
@@ -1,16 +0,0 @@
-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/NetSharp.Benchmarks.csproj b/NetSharp/NetSharp.Benchmarks/NetSharp.Benchmarks.csproj
@@ -9,4 +9,8 @@
<ProjectReference Include="..\NetSharp\NetSharp.csproj" />
</ItemGroup>
+ <ItemGroup>
+ <Folder Include="Benchmarks\" />
+ </ItemGroup>
+
</Project>
diff --git a/NetSharp/NetSharp.Examples/Examples/Raw Stream Examples/RawStreamClient.cs b/NetSharp/NetSharp.Examples/Examples/Raw Stream Examples/RawStreamClient.cs
@@ -1,47 +0,0 @@
-using System;
-using System.Net.Sockets;
-using System.Threading;
-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}");
-
- Thread.Sleep(1000);
- } 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
@@ -1,60 +0,0 @@
-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
@@ -1,47 +0,0 @@
-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();
-
- Console.WriteLine("[Server] Attempting to stop the server...");
- server.Close();
- Console.WriteLine("[Server] Successfully shut down the server!");
-
- return Task.CompletedTask;
- }
- }
-}
diff --git a/NetSharp/NetSharp.Examples/NetSharp.Examples.csproj b/NetSharp/NetSharp.Examples/NetSharp.Examples.csproj
@@ -9,4 +9,8 @@
<ProjectReference Include="..\NetSharp\NetSharp.csproj" />
</ItemGroup>
+ <ItemGroup>
+ <Folder Include="Examples\" />
+ </ItemGroup>
+
</Project>
diff --git a/NetSharp/NetSharp.Tests/RawDatagramConnectionTests.cs b/NetSharp/NetSharp.Tests/RawDatagramConnectionTests.cs
@@ -11,9 +11,6 @@ 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()
{
@@ -21,23 +18,14 @@ namespace NetSharp.Tests
{
using RawDatagramConnection conn = ConnectionFactory();
- conn.Bind(ClientLocalEndPoint);
- conn.Start();
+ conn.Bind(TestUtils.ClientLocalEndPoint);
conn.Close();
}
Instantiate();
- AssertDisposedCleanly();
- }
-
- private static void AssertDisposedCleanly()
- {
- _ = dotMemory.Check(memory =>
- {
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is<RawDatagramConnection>()).ObjectsCount);
- });
+ TestUtils.AssertDisposedCleanly<RawDatagramConnection>();
}
private static RawDatagramConnection ConnectionFactory()
diff --git a/NetSharp/NetSharp.Tests/RawStreamConnectionTests.cs b/NetSharp/NetSharp.Tests/RawStreamConnectionTests.cs
@@ -13,72 +13,46 @@ 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()
+ public void ServerDisposesCleanly()
{
static void Instantiate()
{
- using RawStreamConnection conn = ConnectionFactory();
- conn.Bind(ClientLocalEndPoint);
-
- conn.Start();
+ using RawStreamServer conn = ServerFactory();
+ conn.Bind(TestUtils.ClientLocalEndPoint);
conn.Close();
}
Instantiate();
- AssertDisposedCleanly();
+ TestUtils.AssertDisposedCleanly<RawStreamServer>();
}
[Fact]
- public void FullTest()
+ public void ClientDisposesCleanly()
{
- using RawStreamConnection server = ConnectionFactory();
- server.Bind(ServerLocalEndPoint);
-
- static void PacketHandler(
- EndPoint point,
- in RawPacketHeader header,
- in ReadOnlyMemory<byte> data,
- IRawStreamWriter writer)
+ static void Instantiate()
{
- }
-
- server.RegisterHandler(0, PacketHandler);
- server.Start();
-
- using RawStreamConnection client = ConnectionFactory();
- client.Bind(ClientLocalEndPoint);
+ using RawStreamClient conn = ClientFactory();
+ conn.Bind(TestUtils.ClientLocalEndPoint);
- client.ConnectAsync(ServerLocalEndPoint).GetAwaiter().GetResult();
-
- byte[] packet = { 42 };
- _ = client.SendAsync(0, packet).GetAwaiter().GetResult();
-
- client.DisconnectAsync().GetAwaiter().GetResult();
- client.Close();
+ conn.Close();
+ }
- server.DeregisterHandler(0, PacketHandler);
- server.Close();
+ Instantiate();
- AssertDisposedCleanly();
+ TestUtils.AssertDisposedCleanly<RawStreamClient>();
}
- private static void AssertDisposedCleanly()
+ private static RawStreamServer ServerFactory()
{
- _ = dotMemory.Check(memory =>
- {
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is<RawStreamConnection>()).ObjectsCount);
- });
+ return new RawStreamServer(ProtocolType.Tcp, new IPEndPoint(IPAddress.Loopback, 0));
}
- private static RawStreamConnection ConnectionFactory()
+ private static RawStreamClient ClientFactory()
{
- return new RawStreamConnection(ProtocolType.Tcp, new IPEndPoint(IPAddress.Loopback, 0));
+ return new RawStreamClient(ProtocolType.Tcp, new IPEndPoint(IPAddress.Loopback, 0));
}
}
}
diff --git a/NetSharp/NetSharp.Tests/TestUtils.cs b/NetSharp/NetSharp.Tests/TestUtils.cs
@@ -0,0 +1,20 @@
+using JetBrains.dotMemoryUnit;
+using System.Net;
+using Xunit;
+
+namespace NetSharp.Tests
+{
+ internal static class TestUtils
+ {
+ internal static readonly IPEndPoint ClientLocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0);
+ internal static readonly IPEndPoint ServerLocalEndPoint = new IPEndPoint(IPAddress.Loopback, 12345);
+
+ internal static void AssertDisposedCleanly<T>()
+ {
+ _ = dotMemory.Check(memory =>
+ {
+ Assert.Equal(0, memory.GetObjects(where => where.Type.Is<T>()).ObjectsCount);
+ });
+ }
+ }
+}
diff --git a/NetSharp/NetSharp/NetSharp.xml b/NetSharp/NetSharp/NetSharp.xml
@@ -52,9 +52,17 @@
<member name="M:NetSharp.Raw.Datagram.RawDatagramConnection.ResetSocketArgsHook(System.Net.Sockets.SocketAsyncEventArgs@)">
<inheritdoc />
</member>
- <member name="M:NetSharp.Raw.Datagram.RawDatagramConnection.HandlerTaskWork">
+ <member name="M:NetSharp.Raw.Datagram.RawDatagramConnection.Start(System.Int32)">
<inheritdoc />
</member>
+ <member name="M:NetSharp.Raw.IRawServer.Start(System.Int32)">
+ <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>
+ </member>
<member name="T:NetSharp.Raw.RawConnectionBase">
<summary>
Provides base functionality to all raw network connections.
@@ -99,6 +107,11 @@
Whether the underlying network connection has been disposed or not.
</summary>
</member>
+ <member name="P:NetSharp.Raw.RawConnectionBase.IsBound">
+ <summary>
+ Whether the underlying network connection has been bound to a local endpoint or not.
+ </summary>
+ </member>
<member name="M:NetSharp.Raw.RawConnectionBase.Bind(System.Net.EndPoint)">
<summary>
Binds the underlying network connection to the given local endpoint.
@@ -115,14 +128,6 @@
<member name="M:NetSharp.Raw.RawConnectionBase.Dispose">
<inheritdoc />
</member>
- <member name="M:NetSharp.Raw.RawConnectionBase.Start(System.Int32)">
- <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>
- </member>
<member name="M:NetSharp.Raw.RawConnectionBase.CreateSocketArgsHook(System.Net.Sockets.SocketAsyncEventArgs@)">
<summary>
Allows for custom initialisation of a pooled <see cref="T:System.Net.Sockets.SocketAsyncEventArgs" /> instance.
@@ -147,11 +152,6 @@
Whether the <see cref="M:NetSharp.Raw.RawConnectionBase.Dispose" /> method was called.
</param>
</member>
- <member name="M:NetSharp.Raw.RawConnectionBase.HandlerTaskWork">
- <summary>
- Handler work delegate, started when a call to <see cref="M:NetSharp.Raw.RawConnectionBase.Start(System.Int32)" /> is made.
- </summary>
- </member>
<member name="M:NetSharp.Raw.RawConnectionBase.RentBuffer(System.Int32)">
<summary>
Rents a pooled buffer of at least the specified length. The buffer MUST be returned via a call to <see
@@ -208,69 +208,65 @@
The number of concurrent read tasks that should be maintained.
</param>
</member>
- <member name="T:NetSharp.Raw.RawPacketHeader">
- <summary>
- Contains metadata about a network packet.
- </summary>
- </member>
- <member name="F:NetSharp.Raw.RawPacketHeader.Length">
- <summary>
- The total length of the packet when serialised.
- </summary>
- </member>
- <member name="F:NetSharp.Raw.RawPacketHeader.DataLength">
+ <member name="T:NetSharp.Raw.Stream.IRawStreamClient">
<summary>
- The length of the data following this packet.
- </summary>
- </member>
- <member name="F:NetSharp.Raw.RawPacketHeader.Type">
- <summary>
- The packet type.
+ Describes the interface for a stream network connection that can write to the network.
</summary>
</member>
- <member name="M:NetSharp.Raw.RawPacketHeader.op_Inequality(NetSharp.Raw.RawPacketHeader,NetSharp.Raw.RawPacketHeader)">
+ <member name="M:NetSharp.Raw.Stream.IRawStreamClient.SendAsync(System.UInt32,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags)">
<summary>
- Checks whether two <see cref="T:NetSharp.Raw.RawPacketHeader" /> instances are not equal.
+ Writes the given packet header and data to the network asynchronously, using the given socket flags for the transmission.
</summary>
- <param name="left">
- The first instance.
+ <param name="type">
+ The type of packet being written to the network.
</param>
- <param name="right">
- The second instance.
+ <param name="buffer">
+ The data held by the packet being written to the network.
+ </param>
+ <param name="flags">
+ The <see cref="T:System.Net.Sockets.SocketFlags" /> to use for the transmission.
</param>
<returns>
- Whether the two instances are not equal.
+ The number of bytes written to the network. -1 if the object has been disposed.
</returns>
</member>
- <member name="M:NetSharp.Raw.RawPacketHeader.op_Equality(NetSharp.Raw.RawPacketHeader,NetSharp.Raw.RawPacketHeader)">
+ <member name="M:NetSharp.Raw.Stream.IRawStreamClient.ReceiveAsync(System.Memory{System.Byte},System.Net.Sockets.SocketFlags)">
<summary>
- Checks whether two <see cref="T:NetSharp.Raw.RawPacketHeader" /> instances are equal.
+ Reads a packet from the network asynchronously, using the given socket flags for the transmission.
</summary>
- <param name="left">
- The first instance.
+ <param name="buffer">
+ The data held by the packet being written to the network.
</param>
- <param name="right">
- The second instance.
+ <param name="flags">
+ The <see cref="T:System.Net.Sockets.SocketFlags" /> to use for the transmission.
</param>
<returns>
- Whether the two instances are equal.
+ The number of bytes written to the network. -1 if the object has been disposed.
</returns>
</member>
- <member name="M:NetSharp.Raw.RawPacketHeader.Equals(System.Object)">
- <inheritdoc />
- </member>
- <member name="M:NetSharp.Raw.RawPacketHeader.Equals(NetSharp.Raw.RawPacketHeader)">
- <inheritdoc />
- </member>
- <member name="M:NetSharp.Raw.RawPacketHeader.GetHashCode">
- <inheritdoc />
+ <member name="T:NetSharp.Raw.Stream.RawStreamPacketHandler">
+ <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>
</member>
<member name="T:NetSharp.Raw.Stream.IRawStreamPacketHandler">
<summary>
Describes the interface for a stream network connection that can handle received packets.
</summary>
</member>
- <member name="M:NetSharp.Raw.Stream.IRawStreamPacketHandler.DeregisterHandler(System.Int32,NetSharp.Raw.Stream.RawStreamPacketHandler)">
+ <member name="M:NetSharp.Raw.Stream.IRawStreamPacketHandler.DeregisterHandler(System.UInt32,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.
@@ -282,7 +278,7 @@
The handler to deregister.
</param>
</member>
- <member name="M:NetSharp.Raw.Stream.IRawStreamPacketHandler.RegisterHandler(System.Int32,NetSharp.Raw.Stream.RawStreamPacketHandler)">
+ <member name="M:NetSharp.Raw.Stream.IRawStreamPacketHandler.RegisterHandler(System.UInt32,NetSharp.Raw.Stream.RawStreamPacketHandler)">
<summary>
Registers the given handler for the given packet type.
</summary>
@@ -293,54 +289,71 @@
The handler to register.
</param>
</member>
- <member name="T:NetSharp.Raw.Stream.IRawStreamWriter">
+ <member name="T:NetSharp.Raw.Stream.RawPacketHeader">
<summary>
- Describes the interface for a stream network connection that can write to the network.
+ Contains metadata about a network packet.
</summary>
</member>
- <member name="M:NetSharp.Raw.Stream.IRawStreamWriter.SendAsync(System.UInt16,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags)">
+ <member name="F:NetSharp.Raw.Stream.RawPacketHeader.Length">
<summary>
- Writes the given packet header and data to the network asynchronously, using the given socket flags for the transmission.
+ The total length of the packet when serialised.
</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.
+ </member>
+ <member name="F:NetSharp.Raw.Stream.RawPacketHeader.DataLength">
+ <summary>
+ The length of the data following this packet.
+ </summary>
+ </member>
+ <member name="F:NetSharp.Raw.Stream.RawPacketHeader.Type">
+ <summary>
+ The packet type.
+ </summary>
+ </member>
+ <member name="M:NetSharp.Raw.Stream.RawPacketHeader.op_Inequality(NetSharp.Raw.Stream.RawPacketHeader,NetSharp.Raw.Stream.RawPacketHeader)">
+ <summary>
+ Checks whether two <see cref="T:NetSharp.Raw.Stream.RawPacketHeader" /> instances are not equal.
+ </summary>
+ <param name="left">
+ The first instance.
</param>
- <param name="flags">
- The <see cref="T:System.Net.Sockets.SocketFlags" /> to use for the transmission.
+ <param name="right">
+ The second instance.
</param>
<returns>
- The number of bytes written to the network.
+ Whether the two instances are not equal.
</returns>
</member>
- <member name="T:NetSharp.Raw.Stream.RawStreamPacketHandler">
+ <member name="M:NetSharp.Raw.Stream.RawPacketHeader.op_Equality(NetSharp.Raw.Stream.RawPacketHeader,NetSharp.Raw.Stream.RawPacketHeader)">
<summary>
- Handles a message received on a raw stream connection.
+ Checks whether two <see cref="T:NetSharp.Raw.Stream.RawPacketHeader" /> instances are equal.
</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 name="left">
+ The first instance.
</param>
- <param name="writer">
- A reference to the network connection, to interact with the network further.
+ <param name="right">
+ The second instance.
</param>
+ <returns>
+ Whether the two instances are equal.
+ </returns>
</member>
- <member name="T:NetSharp.Raw.Stream.RawStreamConnection">
+ <member name="M:NetSharp.Raw.Stream.RawPacketHeader.Equals(System.Object)">
+ <inheritdoc />
+ </member>
+ <member name="M:NetSharp.Raw.Stream.RawPacketHeader.Equals(NetSharp.Raw.Stream.RawPacketHeader)">
+ <inheritdoc />
+ </member>
+ <member name="M:NetSharp.Raw.Stream.RawPacketHeader.GetHashCode">
+ <inheritdoc />
+ </member>
+ <member name="T:NetSharp.Raw.Stream.RawStreamClient">
<summary>
- Represents a network connection using a stream-based protocol to interact over the network, that is capable of
- sending raw bytes.
+ Represents a client using a stream-based protocol to interact with a server. Handles raw binary packets.
</summary>
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.#ctor(System.Net.Sockets.ProtocolType,System.Net.EndPoint)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.#ctor(System.Net.Sockets.ProtocolType,System.Net.EndPoint)">
<summary>
- Initialises a new instance of the <see cref="T:NetSharp.Raw.Stream.RawStreamConnection" /> class.
+ Initialises a new instance of the <see cref="T:NetSharp.Raw.Stream.RawStreamClient" /> class.
</summary>
<param name="connectionProtocolType">
The protocol that the underlying network connection should use.
@@ -349,7 +362,7 @@
The default remote endpoint that should be used for pending connections.
</param>
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.ConnectAsync(System.Net.EndPoint)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.ConnectAsync(System.Net.EndPoint)">
<summary>
Connects asynchronously to the given remote network endpoint.
</summary>
@@ -360,10 +373,7 @@
A <see cref="T:System.Threading.Tasks.Task" /> object representing the asynchronous operation.
</returns>
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.DeregisterHandler(System.Int32,NetSharp.Raw.Stream.RawStreamPacketHandler)">
- <inheritdoc />
- </member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.DisconnectAsync(System.Boolean)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.DisconnectAsync(System.Boolean)">
<summary>
Disconnects asynchronously from the currently connected remote network connection.
</summary>
@@ -374,128 +384,208 @@
A <see cref="T:System.Threading.Tasks.Task" /> object representing the asynchronous operation.
</returns>
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.RegisterHandler(System.Int32,NetSharp.Raw.Stream.RawStreamPacketHandler)">
- <inheritdoc />
- </member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.SendAsync(System.UInt16,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.SendAsync(System.UInt32,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags)">
<inheritdoc />
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.CreateSocketArgsHook(System.Net.Sockets.SocketAsyncEventArgs@)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.ReceiveAsync(System.Memory{System.Byte},System.Net.Sockets.SocketFlags)">
<inheritdoc />
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.DestroySocketArgsHook(System.Net.Sockets.SocketAsyncEventArgs@)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.CreateSocketArgsHook(System.Net.Sockets.SocketAsyncEventArgs@)">
<inheritdoc />
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.Dispose(System.Boolean)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.DestroySocketArgsHook(System.Net.Sockets.SocketAsyncEventArgs@)">
<inheritdoc />
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.HandlerTaskWork">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.Dispose(System.Boolean)">
<inheritdoc />
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.ResetSocketArgsHook(System.Net.Sockets.SocketAsyncEventArgs@)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.ResetSocketArgsHook(System.Net.Sockets.SocketAsyncEventArgs@)">
<inheritdoc />
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.StartHook(System.Int32)">
- <inheritdoc />
- </member>
- <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.WriterStateToken,System.Threading.Tasks.TaskCompletionSource{System.Int32})">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.ConfigureSendRequestAsync(System.Net.Sockets.SocketAsyncEventArgs,System.Byte[]@,NetSharp.Raw.Stream.RawPacketHeader@,System.ReadOnlyMemory{System.Byte}@,NetSharp.Raw.Stream.RawStreamClient.WriterStateToken,System.Threading.Tasks.TaskCompletionSource{System.Int32})">
<summary>
Prepares the given socket args for sending a request to the network.
</summary>
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.CleanupArgs(System.Net.Sockets.SocketAsyncEventArgs,System.Boolean)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.CleanupArgs(System.Net.Sockets.SocketAsyncEventArgs,System.Boolean)">
<summary>
Cleans up and returns the given socket args.
</summary>
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.ConfigureReceiveDataAsync(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamConnection.ReaderStateToken,NetSharp.Raw.RawPacketHeader@)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.ConfigureReceiveDataAsync(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamClient.WriterStateToken,NetSharp.Raw.Stream.RawPacketHeader@)">
<summary>
Prepares the given socket args for receiving a packet's data from the network.
</summary>
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.ConfigureReceiveHeaderAsync(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamConnection.ReaderStateToken)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.ConfigureReceiveHeaderAsync(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamClient.WriterStateToken)">
<summary>
Prepares the given socket args for receiving a packet's header from the network.
</summary>
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.HandleAccepted(System.Net.Sockets.SocketAsyncEventArgs)">
- <summary>
- Handles a completed <see cref="M:System.Net.Sockets.Socket.AcceptAsync(System.Net.Sockets.SocketAsyncEventArgs)" /> call.
- </summary>
- </member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.HandleConnected(System.Net.Sockets.SocketAsyncEventArgs)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.HandleConnected(System.Net.Sockets.SocketAsyncEventArgs)">
<summary>
Handles a completed <see cref="M:System.Net.Sockets.Socket.ConnectAsync(System.Net.Sockets.SocketAsyncEventArgs)" /> call.
</summary>
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.HandleDisconnected(System.Net.Sockets.SocketAsyncEventArgs)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.HandleDisconnected(System.Net.Sockets.SocketAsyncEventArgs)">
<summary>
Handles a completed <see cref="M:System.Net.Sockets.Socket.DisconnectAsync(System.Net.Sockets.SocketAsyncEventArgs)" /> call.
</summary>
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.HandleIoCompleted(System.Object,System.Net.Sockets.SocketAsyncEventArgs)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.HandleIoCompleted(System.Object,System.Net.Sockets.SocketAsyncEventArgs)">
<summary>
Handles the completion of an asynchronous socket operation.
</summary>
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.HandleReceived(System.Net.Sockets.SocketAsyncEventArgs)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.HandleReceived(System.Net.Sockets.SocketAsyncEventArgs)">
<summary>
Handles the completion of a <see cref="M:System.Net.Sockets.Socket.ReceiveAsync(System.Net.Sockets.SocketAsyncEventArgs)" /> call.
</summary>
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.HandleReceivedData(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamConnection.ReaderStateToken)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.HandleReceivedData(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamClient.WriterStateToken)">
<summary>
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="M:NetSharp.Raw.Stream.RawStreamConnection.HandleReceivedHeader(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamConnection.ReaderStateToken)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.HandleReceivedHeader(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamClient.WriterStateToken)">
<summary>
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="M:NetSharp.Raw.Stream.RawStreamConnection.HandleSent(System.Net.Sockets.SocketAsyncEventArgs)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.HandleSent(System.Net.Sockets.SocketAsyncEventArgs)">
<summary>
Handles the completion of a <see cref="M:System.Net.Sockets.Socket.SendAsync(System.Net.Sockets.SocketAsyncEventArgs)" /> call.
</summary>
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.HandleSentRequest(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamConnection.WriterStateToken)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.HandleSentRequest(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamClient.WriterStateToken)">
<summary>
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="M:NetSharp.Raw.Stream.RawStreamConnection.HandleSentResponse(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamConnection.ReaderStateToken)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.StartOrContinueReceiving(System.Net.Sockets.SocketAsyncEventArgs)">
<summary>
- 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.
+ Starts or continues an asynchronous network read operation.
</summary>
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.StartOrContinueReceiving(System.Net.Sockets.SocketAsyncEventArgs)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.StartOrContinueSending(System.Net.Sockets.Socket,System.Net.Sockets.SocketAsyncEventArgs)">
<summary>
- Starts or continues an asynchronous network read operation.
+ Starts or continues an asynchronous network write operation using the given socket.
</summary>
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.StartOrContinueSending(System.Net.Sockets.Socket,System.Net.Sockets.SocketAsyncEventArgs)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamClient.WriterStateToken.Dispose">
+ <inheritdoc />
+ </member>
+ <member name="T:NetSharp.Raw.Stream.RawStreamServer">
<summary>
- Starts or continues an asynchronous network write operation using the given socket.
+ Represents a server using a stream-based protocol to interact with clients. Handles raw binary packets.
</summary>
</member>
- <member name="P:NetSharp.Raw.Stream.RawStreamConnection.OperationStateToken.OperationCompletionSource">
+ <member name="M:NetSharp.Raw.Stream.RawStreamServer.#ctor(System.Net.Sockets.ProtocolType,System.Net.EndPoint)">
<summary>
- The <see cref="T:System.Threading.Tasks.TaskCompletionSource`1" /> for asynchronous network operations.
+ Initialises a new instance of the <see cref="T:NetSharp.Raw.Stream.RawStreamServer" /> 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.RawStreamServer.DeregisterHandler(System.UInt32,NetSharp.Raw.Stream.RawStreamPacketHandler)">
+ <inheritdoc />
+ </member>
+ <member name="M:NetSharp.Raw.Stream.RawStreamServer.RegisterHandler(System.UInt32,NetSharp.Raw.Stream.RawStreamPacketHandler)">
+ <inheritdoc />
+ </member>
+ <member name="M:NetSharp.Raw.Stream.RawStreamServer.Start(System.Int32)">
+ <inheritdoc />
+ </member>
+ <member name="M:NetSharp.Raw.Stream.RawStreamServer.CreateSocketArgsHook(System.Net.Sockets.SocketAsyncEventArgs@)">
+ <inheritdoc />
+ </member>
+ <member name="M:NetSharp.Raw.Stream.RawStreamServer.DestroySocketArgsHook(System.Net.Sockets.SocketAsyncEventArgs@)">
+ <inheritdoc />
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.OperationStateToken.Dispose">
+ <member name="M:NetSharp.Raw.Stream.RawStreamServer.Dispose(System.Boolean)">
<inheritdoc />
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.ReaderStateToken.Dispose">
+ <member name="M:NetSharp.Raw.Stream.RawStreamServer.ResetSocketArgsHook(System.Net.Sockets.SocketAsyncEventArgs@)">
+ <inheritdoc />
+ </member>
+ <member name="M:NetSharp.Raw.Stream.RawStreamServer.StartHook(System.Int32)">
+ <inheritdoc />
+ </member>
+ <member name="M:NetSharp.Raw.Stream.RawStreamServer.CleanupArgs(System.Net.Sockets.SocketAsyncEventArgs,System.Boolean)">
+ <summary>
+ Cleans up and returns the given socket args.
+ </summary>
+ </member>
+ <member name="M:NetSharp.Raw.Stream.RawStreamServer.ConfigureReceiveDataAsync(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamServer.ReaderStateToken,NetSharp.Raw.Stream.RawPacketHeader@)">
+ <summary>
+ Prepares the given socket args for receiving a packet's data from the network.
+ </summary>
+ </member>
+ <member name="M:NetSharp.Raw.Stream.RawStreamServer.ConfigureReceiveHeaderAsync(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamServer.ReaderStateToken)">
+ <summary>
+ Prepares the given socket args for receiving a packet's header from the network.
+ </summary>
+ </member>
+ <member name="M:NetSharp.Raw.Stream.RawStreamServer.HandleAccepted(System.Net.Sockets.SocketAsyncEventArgs)">
+ <summary>
+ Handles a completed <see cref="M:System.Net.Sockets.Socket.AcceptAsync(System.Net.Sockets.SocketAsyncEventArgs)" /> call.
+ </summary>
+ </member>
+ <member name="M:NetSharp.Raw.Stream.RawStreamServer.HandleIoCompleted(System.Object,System.Net.Sockets.SocketAsyncEventArgs)">
+ <summary>
+ Handles the completion of an asynchronous socket operation.
+ </summary>
+ </member>
+ <member name="M:NetSharp.Raw.Stream.RawStreamServer.HandleReceived(System.Net.Sockets.SocketAsyncEventArgs)">
+ <summary>
+ Handles the completion of a <see cref="M:System.Net.Sockets.Socket.ReceiveAsync(System.Net.Sockets.SocketAsyncEventArgs)" /> call.
+ </summary>
+ </member>
+ <member name="M:NetSharp.Raw.Stream.RawStreamServer.HandleReceivedData(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamServer.ReaderStateToken)">
+ <summary>
+ 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="M:NetSharp.Raw.Stream.RawStreamServer.HandleReceivedHeader(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamServer.ReaderStateToken)">
+ <summary>
+ 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="M:NetSharp.Raw.Stream.RawStreamServer.HandleSent(System.Net.Sockets.SocketAsyncEventArgs)">
+ <summary>
+ Handles the completion of a <see cref="M:System.Net.Sockets.Socket.SendAsync(System.Net.Sockets.SocketAsyncEventArgs)" /> call.
+ </summary>
+ </member>
+ <member name="M:NetSharp.Raw.Stream.RawStreamServer.HandleSentResponse(System.Net.Sockets.SocketAsyncEventArgs,NetSharp.Raw.Stream.RawStreamServer.ReaderStateToken)">
+ <summary>
+ 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.RawStreamServer.StartOrContinueReceiving(System.Net.Sockets.SocketAsyncEventArgs)">
+ <summary>
+ Starts or continues an asynchronous network read operation.
+ </summary>
+ </member>
+ <member name="M:NetSharp.Raw.Stream.RawStreamServer.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="M:NetSharp.Raw.Stream.RawStreamServer.ReaderStateToken.Dispose">
<inheritdoc />
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.RemoteConnectionWrapper.SendAsync(System.UInt16,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags)">
+ <member name="M:NetSharp.Raw.Stream.RawStreamServer.RemoteConnectionWrapper.ReceiveAsync(System.Memory{System.Byte},System.Net.Sockets.SocketFlags)">
<inheritdoc />
</member>
- <member name="M:NetSharp.Raw.Stream.RawStreamConnection.WriterStateToken.Dispose">
+ <member name="M:NetSharp.Raw.Stream.RawStreamServer.RemoteConnectionWrapper.SendAsync(System.UInt32,System.ReadOnlyMemory{System.Byte},System.Net.Sockets.SocketFlags)">
<inheritdoc />
</member>
<member name="T:NetSharp.Utils.SlimObjectPool`1">
diff --git a/NetSharp/NetSharp/Raw/Datagram/RawDatagramConnection.cs b/NetSharp/NetSharp/Raw/Datagram/RawDatagramConnection.cs
@@ -1,7 +1,9 @@
using System;
+using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
+using NetSharp.Raw.Stream;
namespace NetSharp.Raw.Datagram
{
@@ -78,9 +80,18 @@ namespace NetSharp.Raw.Datagram
}
/// <inheritdoc />
- protected override void HandlerTaskWork()
+ public void Start(int concurrentTasks = 1)
{
- // TODO: implement start read task for datagram connections
+ 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++)
+ {
+ // TODO: implement task
+ }
}
private void HandleIoCompleted(object sender, SocketAsyncEventArgs args)
diff --git a/NetSharp/NetSharp/Raw/IRawServer.cs b/NetSharp/NetSharp/Raw/IRawServer.cs
@@ -0,0 +1,13 @@
+namespace NetSharp.Raw
+{
+ public interface IRawServer
+ {
+ /// <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);
+ }
+}
+\ No newline at end of file
diff --git a/NetSharp/NetSharp/Raw/RawConnectionBase.cs b/NetSharp/NetSharp/Raw/RawConnectionBase.cs
@@ -17,9 +17,8 @@ namespace NetSharp.Raw
private readonly ArrayPool<byte> bufferPool;
private readonly Socket connection;
private readonly SlimObjectPool<SocketAsyncEventArgs> socketArgsPool;
-
- private int activeOperations;
- private readonly object activeOperationsLock = new object();
+ private readonly object rentedSocketArgsLock = new object();
+ private int rentedSocketArgs;
private bool isBound;
private bool isDisposed;
@@ -76,6 +75,11 @@ namespace NetSharp.Raw
protected bool IsDisposed => isDisposed;
/// <summary>
+ /// Whether the underlying network connection has been bound to a local endpoint or not.
+ /// </summary>
+ protected bool IsBound => isBound;
+
+ /// <summary>
/// Binds the underlying network connection to the given local endpoint.
/// </summary>
/// <param name="localEndPoint">
@@ -104,26 +108,6 @@ namespace NetSharp.Raw
}
/// <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">
@@ -160,28 +144,22 @@ namespace NetSharp.Raw
{
connection.Dispose();
- lock (activeOperationsLock)
+ lock (rentedSocketArgsLock)
{
- while (activeOperations > 0)
+ while (rentedSocketArgs > 0)
{
- // will keep reacquiring the lock and blocking until we reach the activeOperations == 0 case
- _ = Monitor.Wait(activeOperationsLock);
+ // will keep reacquiring the lock and blocking until we reach the rentedSocketArgs == 0 case
+ _ = Monitor.Wait(rentedSocketArgsLock);
}
}
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>
@@ -205,7 +183,7 @@ namespace NetSharp.Raw
/// </returns>
protected SocketAsyncEventArgs RentSocketArgs()
{
- _ = Interlocked.Increment(ref activeOperations);
+ _ = Interlocked.Increment(ref rentedSocketArgs);
return socketArgsPool.Rent();
}
@@ -244,10 +222,10 @@ namespace NetSharp.Raw
{
socketArgsPool.Return(socketArgs);
- lock (activeOperationsLock)
+ lock (rentedSocketArgsLock)
{
- _ = Interlocked.Decrement(ref activeOperations);
- Monitor.Pulse(activeOperationsLock); // signals that we might have reached the activeOperations == 0 state
+ rentedSocketArgs--;
+ Monitor.Pulse(rentedSocketArgsLock); // signals that we might have reached the rentedSocketArgs == 0 state
}
}
@@ -279,9 +257,9 @@ namespace NetSharp.Raw
private void ResetSocketArgs(ref SocketAsyncEventArgs instance)
{
- ResetSocketArgsHook(ref instance);
-
instance.RemoteEndPoint = DefaultRemoteEndPoint;
+
+ ResetSocketArgsHook(ref instance);
}
}
}
diff --git a/NetSharp/NetSharp/Raw/RawPacket.cs b/NetSharp/NetSharp/Raw/RawPacket.cs
@@ -1,30 +0,0 @@
-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
@@ -1,115 +0,0 @@
-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/IRawStreamClient.cs b/NetSharp/NetSharp/Raw/Stream/IRawStreamClient.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+
+namespace NetSharp.Raw.Stream
+{
+ // TODO(mikolaj): see about adding an enum for possible connection closure reasons
+ public readonly struct RawStreamWriteAsyncResult
+ {
+ public readonly int WrittenBytes;
+
+ public RawStreamWriteAsyncResult(int writtenBytes)
+ {
+ WrittenBytes = writtenBytes;
+ }
+ }
+
+ // TODO(mikolaj): see about adding an enum for possible connection closure reasons
+ public readonly struct RawStreamReadAsyncResult
+ {
+ public readonly int ReadBytes;
+ public readonly uint PacketType;
+
+ public RawStreamReadAsyncResult(int readBytes, uint packetType)
+ {
+ ReadBytes = readBytes;
+ PacketType = packetType;
+ }
+ }
+
+ /// <summary>
+ /// Describes the interface for a stream network connection that can write to the network.
+ /// </summary>
+ public interface IRawStreamClient
+ {
+ /// <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. -1 if the object has been disposed.
+ /// </returns>
+ ValueTask<RawStreamWriteAsyncResult> SendAsync(uint type, ReadOnlyMemory<byte> buffer, SocketFlags flags = SocketFlags.None);
+
+ /// <summary>
+ /// Reads a packet from the network asynchronously, using the given socket flags for the transmission.
+ /// </summary>
+ /// <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. -1 if the object has been disposed.
+ /// </returns>
+ ValueTask<RawStreamReadAsyncResult> ReceiveAsync(Memory<byte> buffer, SocketFlags flags = SocketFlags.None);
+ }
+}
diff --git a/NetSharp/NetSharp/Raw/Stream/IRawStreamPacketHandler.cs b/NetSharp/NetSharp/Raw/Stream/IRawStreamPacketHandler.cs
@@ -1,6 +1,30 @@
-namespace NetSharp.Raw.Stream
+using System;
+using System.Net;
+
+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,
+ IRawStreamClient writer);
+
+ /// <summary>
/// Describes the interface for a stream network connection that can handle received packets.
/// </summary>
public interface IRawStreamPacketHandler
@@ -15,7 +39,7 @@
/// <param name="handler">
/// The handler to deregister.
/// </param>
- void DeregisterHandler(int id, RawStreamPacketHandler handler);
+ void DeregisterHandler(uint id, RawStreamPacketHandler handler);
/// <summary>
/// Registers the given handler for the given packet type.
@@ -26,6 +50,6 @@
/// <param name="handler">
/// The handler to register.
/// </param>
- void RegisterHandler(int id, RawStreamPacketHandler handler);
+ void RegisterHandler(uint id, RawStreamPacketHandler handler);
}
}
diff --git a/NetSharp/NetSharp/Raw/Stream/IRawStreamWriter.cs b/NetSharp/NetSharp/Raw/Stream/IRawStreamWriter.cs
@@ -1,29 +0,0 @@
-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
- {
- /// <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/RawPacket.cs b/NetSharp/NetSharp/Raw/Stream/RawPacket.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace NetSharp.Raw.Stream
+{
+ 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/Stream/RawPacketHeader.cs b/NetSharp/NetSharp/Raw/Stream/RawPacketHeader.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace NetSharp.Raw.Stream
+{
+ /// <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(int) + sizeof(uint);
+
+#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 uint Type;
+
+#pragma warning restore CA1051
+
+ internal RawPacketHeader(uint 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;
+
+ uint type = MemoryMarshal.Read<uint>(buffer.Slice(offset, sizeof(uint)));
+ 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;
+
+ uint type = Type;
+ MemoryMarshal.Write(buffer.Slice(offset, sizeof(uint)), ref type);
+ offset += sizeof(uint);
+
+ int dataSize = DataLength;
+ MemoryMarshal.Write(buffer.Slice(offset, sizeof(int)), ref dataSize);
+ }
+ }
+}
diff --git a/NetSharp/NetSharp/Raw/Stream/RawStreamClient.cs b/NetSharp/NetSharp/Raw/Stream/RawStreamClient.cs
@@ -0,0 +1,551 @@
+using System;
+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>
+ /// Represents a client using a stream-based protocol to interact with a server. Handles raw binary packets.
+ /// </summary>
+ public sealed class RawStreamClient : RawConnectionBase, IRawStreamClient
+ {
+ private readonly SlimObjectPool<WriterStateToken> writerStatePool;
+
+ /// <summary>
+ /// Initialises a new instance of the <see cref="RawStreamClient" /> 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 RawStreamClient(ProtocolType connectionProtocolType, EndPoint defaultRemoteEndPoint)
+ : base(SocketType.Stream, connectionProtocolType, defaultRemoteEndPoint)
+ {
+ static WriterStateToken CreateWriterToken()
+ {
+ return new WriterStateToken();
+ }
+
+ static void ResetWriterToken(ref WriterStateToken instance)
+ {
+ instance.Reset();
+ }
+
+ static void DestroyWriterToken(WriterStateToken instance)
+ {
+ instance.Dispose();
+ }
+
+ writerStatePool = new SlimObjectPool<WriterStateToken>(
+ CreateWriterToken,
+ ResetWriterToken,
+ DestroyWriterToken);
+ }
+
+ /// <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();
+
+ socketArgs.UserToken = tcs;
+
+ socketArgs.RemoteEndPoint = remoteEndPoint;
+
+ if (Connection.ConnectAsync(socketArgs))
+ {
+ return tcs.Task;
+ }
+
+ CleanupArgs(socketArgs);
+
+ return Task.CompletedTask;
+ }
+
+ /// <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();
+
+ socketArgs.UserToken = tcs;
+
+ socketArgs.DisconnectReuseSocket = leaveConnectionReusable;
+
+ if (Connection.DisconnectAsync(socketArgs))
+ {
+ return tcs.Task;
+ }
+
+ CleanupArgs(socketArgs);
+
+ return Task.CompletedTask;
+ }
+
+ /// <inheritdoc />
+ public ValueTask<RawStreamWriteAsyncResult> SendAsync(uint type, ReadOnlyMemory<byte> buffer, SocketFlags flags)
+ {
+ TaskCompletionSource<RawStreamWriteAsyncResult> tcs = new TaskCompletionSource<RawStreamWriteAsyncResult>();
+ SocketAsyncEventArgs socketArgs = RentSocketArgs();
+
+ RawPacketHeader header = new RawPacketHeader(type, buffer.Length);
+ byte[] ownedBuffer = RentBuffer(RawPacket.TotalSize(in header));
+
+ WriterStateToken writerState = writerStatePool.Rent();
+
+ //ConfigureSendRequestAsync(socketArgs, ref ownedBuffer, in header, in buffer, writerState, tcs);
+
+ socketArgs.SocketFlags = flags;
+
+ StartOrContinueSending(Connection, socketArgs);
+
+ return new ValueTask<RawStreamWriteAsyncResult>(tcs.Task);
+ }
+
+ /// <inheritdoc />
+ public ValueTask<RawStreamReadAsyncResult> ReceiveAsync(Memory<byte> buffer, SocketFlags flags = SocketFlags.None)
+ {
+ TaskCompletionSource<RawStreamReadAsyncResult> tcs = new TaskCompletionSource<RawStreamReadAsyncResult>();
+ SocketAsyncEventArgs socketArgs = RentSocketArgs();
+
+ byte[] ownedBuffer = RentBuffer(RawPacketHeader.Length);
+
+ WriterStateToken writerState = writerStatePool.Rent();
+
+ throw new NotImplementedException();
+ }
+
+ /// <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)
+ {
+ // TODO(mikolaj): close remote connection
+ writerStatePool.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+
+ /// <inheritdoc />
+ protected override void ResetSocketArgsHook(ref SocketAsyncEventArgs instance)
+ {
+ if (instance == default)
+ {
+ return;
+ }
+
+ instance.AcceptSocket = null;
+
+ base.ResetSocketArgsHook(ref instance);
+ }
+
+ /// <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,
+ WriterStateToken writerState,
+ 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);
+
+ writerState.BytesToTransfer = totalTransferredBytes;
+ writerState.RequestCompletionSource = tcs;
+
+ args.UserToken = writerState;
+ }
+
+ /// <summary>
+ /// Cleans up and returns the given socket args.
+ /// </summary>
+ private void CleanupArgs(SocketAsyncEventArgs args, bool cleanupUserToken = true)
+ {
+ if (cleanupUserToken)
+ {
+ switch (args.UserToken)
+ {
+ case WriterStateToken writerState:
+ writerStatePool.Return(writerState);
+ break;
+ }
+ }
+
+ ReturnSocketArgs(args);
+ }
+
+ /// <summary>
+ /// Prepares the given socket args for receiving a packet's data from the network.
+ /// </summary>
+ private void ConfigureReceiveDataAsync(SocketAsyncEventArgs args, WriterStateToken writerState, 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);
+
+ writerState.BytesToTransfer = header.DataLength;
+ writerState.Header = header;
+
+ args.UserToken = writerState;
+ }
+
+ /// <summary>
+ /// Prepares the given socket args for receiving a packet's header from the network.
+ /// </summary>
+ private void ConfigureReceiveHeaderAsync(SocketAsyncEventArgs args, WriterStateToken writerState)
+ {
+ ReturnBuffer(args.Buffer); // return and clear the previously sent response packet buffer
+
+ byte[] pendingHeaderBuffer = RentBuffer(RawPacketHeader.Length);
+
+ args.SetBuffer(pendingHeaderBuffer, 0, RawPacketHeader.Length);
+
+ writerState.BytesToTransfer = RawPacketHeader.Length;
+
+ args.UserToken = writerState;
+ }
+
+ /// <summary>
+ /// Handles a completed <see cref="Socket.ConnectAsync(SocketAsyncEventArgs)" /> call.
+ /// </summary>
+ private void HandleConnected(SocketAsyncEventArgs args)
+ {
+ TaskCompletionSource<bool> tcs = (TaskCompletionSource<bool>)args.UserToken;
+
+ 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)
+ {
+ TaskCompletionSource<bool> tcs = (TaskCompletionSource<bool>)args.UserToken;
+
+ 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.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)
+ {
+ WriterStateToken writerState = (WriterStateToken)args.UserToken;
+ TaskCompletionSource<int> tcs = writerState.RequestCompletionSource!;
+
+ switch (args.SocketError)
+ {
+ case SocketError.Success:
+ switch (writerState.BytesToTransfer)
+ {
+ case RawPacketHeader.Length:
+ HandleReceivedHeader(args, writerState);
+ break;
+
+ default:
+ HandleReceivedData(args, writerState);
+ break;
+ }
+
+ break;
+
+ default:
+ tcs.SetException(new SocketException((int)args.SocketError));
+ 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, WriterStateToken writerState)
+ {
+ TaskCompletionSource<int> tcs = writerState.RequestCompletionSource!;
+
+ int received = args.BytesTransferred;
+ int previouslyReceived = args.Offset;
+ int totalReceived = previouslyReceived + received;
+ int expected = writerState.BytesToTransfer;
+
+ if (totalReceived == expected)
+ {
+ // TODO(mikolaj): copy over received bytes to user memory buffer
+ tcs.SetResult(totalReceived - RawPacketHeader.Length);
+ CleanupArgs(args);
+ }
+ else if (totalReceived > 0 && totalReceived < expected)
+ {
+ args.SetBuffer(totalReceived, expected - totalReceived);
+ StartOrContinueSending(args.ConnectSocket, args);
+ }
+ else if (received == 0)
+ {
+ // connection is dead
+ tcs.SetException(new SocketException((int)SocketError.HostDown));
+ CleanupArgs(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, WriterStateToken writerState)
+ {
+ TaskCompletionSource<int> tcs = writerState.RequestCompletionSource!;
+
+ int received = args.BytesTransferred;
+ int previouslyReceived = args.Offset;
+ int totalReceived = previouslyReceived + received;
+ int expected = writerState.BytesToTransfer;
+
+ byte[] headerBuffer = args.Buffer;
+ ReadOnlySpan<byte> headerBufferMemory = new ReadOnlySpan<byte>(headerBuffer);
+
+ if (totalReceived == expected)
+ {
+ RawPacketHeader header = RawPacketHeader.Deserialise(in headerBufferMemory);
+
+ ConfigureReceiveDataAsync(args, writerState, in header);
+ StartOrContinueReceiving(args);
+ }
+ else if (totalReceived > 0 && totalReceived < expected)
+ {
+ args.SetBuffer(totalReceived, expected - totalReceived);
+ StartOrContinueReceiving(args);
+ }
+ else if (received == 0)
+ {
+ tcs.SetException(new SocketException((int)args.SocketError));
+ }
+ }
+
+ /// <summary>
+ /// Handles the completion of a <see cref="Socket.SendAsync" /> call.
+ /// </summary>
+ private void HandleSent(SocketAsyncEventArgs args)
+ {
+ WriterStateToken writerState = (WriterStateToken)args.UserToken;
+
+ switch (args.SocketError)
+ {
+ case SocketError.Success:
+ HandleSentRequest(args, writerState);
+ break;
+
+ case SocketError.OperationAborted:
+ writerState.RequestCompletionSource!.SetCanceled();
+ CleanupArgs(args);
+ break;
+
+ default:
+ writerState.RequestCompletionSource!.SetException(
+ new SocketException((int)args.SocketError));
+ CleanupArgs(args);
+ 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, WriterStateToken writerState)
+ {
+ TaskCompletionSource<int> tcs = writerState.RequestCompletionSource!;
+
+ int sent = args.BytesTransferred;
+ int previouslySent = args.Offset;
+ int totalSent = previouslySent + sent;
+ int expected = writerState.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>
+ /// 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);
+ }
+
+ private sealed class WriterStateToken : IDisposable
+ {
+ internal int BytesToTransfer { get; set; }
+
+ internal Memory<byte> UserBuffer { get; set; }
+
+ internal RawPacketHeader? Header { get; set; }
+
+ internal TaskCompletionSource<int>? RequestCompletionSource { get; set; }
+
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ Reset();
+ }
+
+ internal void Reset()
+ {
+ BytesToTransfer = 0;
+ UserBuffer = Memory<byte>.Empty;
+ Header = null;
+ RequestCompletionSource = null;
+ }
+ }
+ }
+}
diff --git a/NetSharp/NetSharp/Raw/Stream/RawStreamConnection.cs b/NetSharp/NetSharp/Raw/Stream/RawStreamConnection.cs
@@ -1,931 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Net;
-using System.Net.Sockets;
-using System.Runtime.CompilerServices;
-using System.Threading;
-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, IRawStreamPacketHandler
- {
- private readonly object activeRemoteConnectionsLock = new object();
- private readonly SlimObjectPool<OperationStateToken> operationStatePool;
- private readonly SlimObjectPool<ReaderStateToken> readerStatePool;
- private readonly ConcurrentDictionary<int, RawStreamPacketHandler> registeredHandlers;
- private readonly List<RemoteConnectionWrapper> remoteConnections;
- private readonly object remoteConnectionsLock = new object();
- private readonly SlimObjectPool<WriterStateToken> writerStatePool;
-
- private int activeRemoteConnections;
-
- /// <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)
- {
- activeRemoteConnections = 0;
- remoteConnections = new List<RemoteConnectionWrapper>();
-
- registeredHandlers = new ConcurrentDictionary<int, RawStreamPacketHandler>();
-
- static OperationStateToken CreateOperationToken()
- {
- return new OperationStateToken();
- }
-
- static void ResetOperationToken(ref OperationStateToken instance)
- {
- instance.Reset();
- }
-
- static void DestroyOperationToken(OperationStateToken instance)
- {
- instance.Dispose();
- }
-
- operationStatePool = new SlimObjectPool<OperationStateToken>(
- CreateOperationToken,
- ResetOperationToken,
- DestroyOperationToken);
-
- static ReaderStateToken CreateReaderToken()
- {
- return new ReaderStateToken();
- }
-
- static void ResetReaderToken(ref ReaderStateToken instance)
- {
- instance.Reset();
- }
-
- static void DestroyReaderToken(ReaderStateToken instance)
- {
- instance.Dispose();
- }
-
- readerStatePool = new SlimObjectPool<ReaderStateToken>(
- CreateReaderToken,
- ResetReaderToken,
- DestroyReaderToken);
-
- static WriterStateToken CreateWriterToken()
- {
- return new WriterStateToken();
- }
-
- static void ResetWriterToken(ref WriterStateToken instance)
- {
- instance.Reset();
- }
-
- static void DestroyWriterToken(WriterStateToken instance)
- {
- instance.Dispose();
- }
-
- writerStatePool = new SlimObjectPool<WriterStateToken>(
- CreateWriterToken,
- ResetWriterToken,
- DestroyWriterToken);
- }
-
- /// <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();
-
- OperationStateToken operationState = operationStatePool.Rent();
- operationState.OperationCompletionSource = tcs;
-
- socketArgs.UserToken = operationState;
-
- 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();
-
- OperationStateToken operationState = operationStatePool.Rent();
- operationState.OperationCompletionSource = tcs;
-
- socketArgs.UserToken = operationState;
-
- 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)
- {
- return DoSendAsync(Connection, type, buffer, flags);
- }
-
- /// <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)
- {
- foreach (RemoteConnectionWrapper remoteConnection in remoteConnections)
- {
- remoteConnection.LocalShutdown();
- }
-
- lock (activeRemoteConnectionsLock)
- {
- while (activeRemoteConnections > 0)
- {
- _ = Monitor.Wait(activeRemoteConnectionsLock);
- }
- }
-
- operationStatePool.Dispose();
- readerStatePool.Dispose();
- writerStatePool.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,
- WriterStateToken writerState,
- 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);
-
- writerState.BytesToTransfer = totalTransferredBytes;
- writerState.RequestCompletionSource = tcs;
-
- args.UserToken = writerState;
- }
-
- /// <summary>
- /// Cleans up and returns the given socket args.
- /// </summary>
- private void CleanupArgs(SocketAsyncEventArgs args, bool cleanupUserToken = true)
- {
- if (cleanupUserToken)
- {
- switch (args.UserToken)
- {
- case OperationStateToken operationState:
- operationStatePool.Return(operationState);
- break;
-
- case ReaderStateToken readerState:
- readerStatePool.Return(readerState);
- break;
-
- case WriterStateToken writerState:
- writerStatePool.Return(writerState);
- break;
- }
- }
-
- ReturnSocketArgs(args);
- }
-
- /// <summary>
- /// Prepares the given socket args for receiving a packet's data from the network.
- /// </summary>
- private void ConfigureReceiveDataAsync(SocketAsyncEventArgs args, ReaderStateToken readerState, 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);
-
- readerState.BytesToTransfer = header.DataLength;
- readerState.RequestHeader = header;
-
- args.UserToken = readerState;
- }
-
- /// <summary>
- /// Prepares the given socket args for receiving a packet's header from the network.
- /// </summary>
- private void ConfigureReceiveHeaderAsync(SocketAsyncEventArgs args, ReaderStateToken readerState)
- {
- ReturnBuffer(args.Buffer); // return and clear the previously sent response packet buffer
-
- byte[] pendingHeaderBuffer = RentBuffer(RawPacketHeader.Length);
-
- args.SetBuffer(pendingHeaderBuffer, 0, RawPacketHeader.Length);
-
- readerState.BytesToTransfer = RawPacketHeader.Length;
-
- args.UserToken = readerState;
- }
-
- private ValueTask<int> DoSendAsync(Socket connection, ushort type, ReadOnlyMemory<byte> buffer, SocketFlags flags)
- {
- TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
- SocketAsyncEventArgs socketArgs = RentSocketArgs();
-
- RawPacketHeader header = new RawPacketHeader(type, buffer.Length);
- byte[] ownedBuffer = RentBuffer(RawPacket.TotalSize(in header));
-
- WriterStateToken writerState = writerStatePool.Rent();
-
- ConfigureSendRequestAsync(socketArgs, ref ownedBuffer, in header, in buffer, writerState, tcs);
-
- socketArgs.SocketFlags = flags;
-
- StartOrContinueSending(connection, socketArgs);
-
- return new ValueTask<int>(tcs.Task);
- }
-
- /// <summary>
- /// Handles a completed <see cref="Socket.AcceptAsync" /> call.
- /// </summary>
- private void HandleAccepted(SocketAsyncEventArgs args)
- {
- switch (args.SocketError)
- {
- case SocketError.Success:
- _ = Interlocked.Increment(ref activeRemoteConnections);
-
- // 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);
-
- ReaderStateToken readerState = readerStatePool.Rent();
-
- Socket remoteConnection = args.AcceptSocket;
- RemoteConnectionWrapper wrapper = new RemoteConnectionWrapper(this, remoteConnection, readerState);
-
- lock (remoteConnectionsLock)
- {
- remoteConnections.Add(wrapper);
- }
-
- WaitHandle[] eventHandles =
- {
- readerState.ConnectionClosed.WaitHandle,
- readerState.RequestReceived.WaitHandle,
- };
-
- while (true)
- {
- ConfigureReceiveHeaderAsync(args, readerState);
- StartOrContinueReceiving(args);
-
- int completedEvent = WaitHandle.WaitAny(eventHandles);
-
- if (completedEvent == 0)
- {
- // the connection has been closed
- break;
- }
-
- // since the request received event has been set, we need to reset it
- readerState.RequestReceived.Reset();
-
- RawPacketHeader header = readerState.RequestHeader!.Value;
-
- byte[] dataBuffer = args.Buffer;
- ReadOnlyMemory<byte> dataBufferMemory = new ReadOnlyMemory<byte>(dataBuffer, 0, header.DataLength);
-
- if (registeredHandlers.TryGetValue(header.Type, out RawStreamPacketHandler handler))
- {
- handler.Invoke(remoteConnection.RemoteEndPoint, in header, in dataBufferMemory, wrapper);
- }
- }
-
- lock (activeRemoteConnectionsLock)
- {
- _ = Interlocked.Decrement(ref activeRemoteConnections);
- Monitor.Pulse(activeRemoteConnectionsLock); // signals that we may have reached the activeRemoteConnections == 0 state
- }
-
- lock (remoteConnectionsLock)
- {
- // TODO: ensure that this is atomic via locking or something else. are properties inherently atomic???
- if (!readerState.LocalShutdownSignaled)
- {
- // since we were shutdown remotely, we need to remove the remote connection from the list of tracked connections
- // TODO: come up with a better way of tracking active remote connections (have the wrapper deregister itself?)
- _ = remoteConnections.Remove(wrapper);
-
- remoteConnection.Disconnect(false);
- remoteConnection.Shutdown(SocketShutdown.Both);
- remoteConnection.Close();
- remoteConnection.Dispose();
- }
- }
-
- CleanupArgs(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)
- {
- OperationStateToken readerState = (OperationStateToken)args.UserToken;
- TaskCompletionSource<bool>? tcs = readerState.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)
- {
- OperationStateToken readerState = (OperationStateToken)args.UserToken;
- TaskCompletionSource<bool>? tcs = readerState.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)
- {
- ReaderStateToken readerState = (ReaderStateToken)args.UserToken;
-
- switch (args.SocketError)
- {
- case SocketError.Success:
- switch (readerState.BytesToTransfer)
- {
- case RawPacketHeader.Length:
- HandleReceivedHeader(args, readerState);
- break;
-
- default:
- HandleReceivedData(args, readerState);
- break;
- }
-
- break;
-
- default:
- readerState.ConnectionClosed.Set();
- 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, ReaderStateToken readerState)
- {
- int received = args.BytesTransferred;
- int previouslyReceived = args.Offset;
- int totalReceived = previouslyReceived + received;
- int expected = readerState.BytesToTransfer;
-
- if (totalReceived == expected)
- {
- readerState.RequestReceived.Set();
- }
- else if (totalReceived > 0 && totalReceived < expected)
- {
- args.SetBuffer(totalReceived, expected - totalReceived);
- StartOrContinueReceiving(args);
- }
- else if (received == 0)
- {
- readerState.ConnectionClosed.Set();
- }
- }
-
- /// <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, ReaderStateToken readerState)
- {
- int received = args.BytesTransferred;
- int previouslyReceived = args.Offset;
- int totalReceived = previouslyReceived + received;
- int expected = readerState.BytesToTransfer;
-
- byte[] headerBuffer = args.Buffer;
- ReadOnlySpan<byte> headerBufferMemory = new ReadOnlySpan<byte>(headerBuffer);
-
- if (totalReceived == expected)
- {
- RawPacketHeader header = RawPacketHeader.Deserialise(in headerBufferMemory);
-
- ConfigureReceiveDataAsync(args, readerState, in header);
- StartOrContinueReceiving(args);
- }
- else if (totalReceived > 0 && totalReceived < expected)
- {
- args.SetBuffer(totalReceived, expected - totalReceived);
- StartOrContinueReceiving(args);
- }
- else if (received == 0)
- {
- readerState.ConnectionClosed.Set();
- }
- }
-
- /// <summary>
- /// Handles the completion of a <see cref="Socket.SendAsync" /> call.
- /// </summary>
- private void HandleSent(SocketAsyncEventArgs args)
- {
- switch (args.UserToken)
- {
- case ReaderStateToken readerState:
- switch (args.SocketError)
- {
- case SocketError.Success:
- HandleSentResponse(args, readerState);
- break;
-
- default:
- readerState.ConnectionClosed.Set();
- break;
- }
-
- break;
-
- case WriterStateToken writerState:
- switch (args.SocketError)
- {
- case SocketError.Success:
- HandleSentRequest(args, writerState);
- break;
-
- case SocketError.OperationAborted:
- writerState.RequestCompletionSource!.SetCanceled();
- CleanupArgs(args);
- break;
-
- default:
- writerState.RequestCompletionSource!.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, WriterStateToken writerState)
- {
- TaskCompletionSource<int> tcs = writerState.RequestCompletionSource!;
-
- int sent = args.BytesTransferred;
- int previouslySent = args.Offset;
- int totalSent = previouslySent + sent;
- int expected = writerState.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, ReaderStateToken readerState)
- {
- int sent = args.BytesTransferred;
- int previouslySent = args.Offset;
- int totalSent = previouslySent + sent;
- int expected = readerState.BytesToTransfer;
-
- if (totalSent == expected)
- {
- // TODO do we need to do any notification that a send has completed?
- }
- else if (totalSent > 0 && totalSent < expected)
- {
- args.SetBuffer(totalSent, expected - totalSent);
- StartOrContinueSending(args.AcceptSocket, args);
- }
- else if (sent == 0)
- {
- readerState.ConnectionClosed.Set();
- }
- }
-
- /// <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);
- }
-
- private sealed class OperationStateToken : IDisposable
- {
- /// <summary>
- /// The <see cref="TaskCompletionSource{TResult}" /> for asynchronous network operations.
- /// </summary>
- internal TaskCompletionSource<bool>? OperationCompletionSource { get; set; }
-
- /// <inheritdoc />
- public void Dispose()
- {
- Reset();
- }
-
- internal void Reset()
- {
- OperationCompletionSource = null;
- }
- }
-
- private sealed class ReaderStateToken : IDisposable
- {
- internal int BytesToTransfer { get; set; }
-
- internal ManualResetEventSlim ConnectionClosed { get; } = new ManualResetEventSlim(false);
-
- internal bool LocalShutdownSignaled { get; set; }
-
- internal RawPacketHeader? RequestHeader { get; set; }
-
- internal ManualResetEventSlim RequestReceived { get; } = new ManualResetEventSlim(false);
-
- /// <inheritdoc />
- public void Dispose()
- {
- Reset();
-
- ConnectionClosed.Dispose();
- RequestReceived.Dispose();
- }
-
- internal void Reset()
- {
- BytesToTransfer = 0;
- RequestHeader = null;
-
- ConnectionClosed.Reset();
- RequestReceived.Reset();
- }
- }
-
- private sealed class RemoteConnectionWrapper : IRawStreamWriter
- {
- private readonly WeakReference<Socket> connectionRef;
- private readonly WeakReference<RawStreamConnection> parentRef;
- private readonly WeakReference<ReaderStateToken> tokenRef;
-
- internal RemoteConnectionWrapper(RawStreamConnection parent, Socket connection, ReaderStateToken token)
- {
- parentRef = new WeakReference<RawStreamConnection>(parent);
- connectionRef = new WeakReference<Socket>(connection);
- tokenRef = new WeakReference<ReaderStateToken>(token);
- }
-
- public void LocalShutdown()
- {
- bool connectionDisposed = !connectionRef.TryGetTarget(out Socket connection);
- bool tokenDisposed = !tokenRef.TryGetTarget(out ReaderStateToken token);
-
- if (connectionDisposed || tokenDisposed)
- {
- return;
- }
-
- token.LocalShutdownSignaled = true;
-
- connection.Disconnect(false);
- connection.Shutdown(SocketShutdown.Both);
- connection.Close();
- connection.Dispose();
- }
-
- /// <inheritdoc />
- public ValueTask<int> SendAsync(ushort type, ReadOnlyMemory<byte> buffer, SocketFlags flags = SocketFlags.None)
- {
- bool parentDisposed = !parentRef.TryGetTarget(out RawStreamConnection parent);
- bool connectionDisposed = !connectionRef.TryGetTarget(out Socket connection);
-
- if (parentDisposed || connectionDisposed)
- {
- // TODO: just use a result of -1? some other error code?
- throw new ObjectDisposedException(parentDisposed ? nameof(parent) : nameof(connection));
- }
-
- return parent.DoSendAsync(connection, type, buffer, flags);
- }
- }
-
- private sealed class WriterStateToken : IDisposable
- {
- internal int BytesToTransfer { get; set; }
-
- internal TaskCompletionSource<int>? RequestCompletionSource { get; set; }
-
- /// <inheritdoc />
- public void Dispose()
- {
- Reset();
- }
-
- internal void Reset()
- {
- BytesToTransfer = 0;
- RequestCompletionSource = null;
- }
- }
- }
-}
diff --git a/NetSharp/NetSharp/Raw/Stream/RawStreamServer.cs b/NetSharp/NetSharp/Raw/Stream/RawStreamServer.cs
@@ -0,0 +1,604 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Net;
+using System.Net.Sockets;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+using NetSharp.Utils;
+
+namespace NetSharp.Raw.Stream
+{
+ /// <summary>
+ /// Represents a server using a stream-based protocol to interact with clients. Handles raw binary packets.
+ /// </summary>
+ public sealed class RawStreamServer : RawConnectionBase, IRawServer, IRawStreamPacketHandler
+ {
+ private readonly object activeRemoteConnectionsLock = new object();
+ private readonly SlimObjectPool<ReaderStateToken> readerStatePool;
+ private readonly ConcurrentDictionary<uint, RawStreamPacketHandler> registeredHandlers;
+ private readonly List<RemoteConnectionWrapper> remoteConnections;
+ private readonly object remoteConnectionsLock = new object();
+
+ private int activeRemoteConnections;
+
+ /// <summary>
+ /// Initialises a new instance of the <see cref="RawStreamServer" /> 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 RawStreamServer(ProtocolType connectionProtocolType, EndPoint defaultRemoteEndPoint)
+ : base(SocketType.Stream, connectionProtocolType, defaultRemoteEndPoint)
+ {
+ activeRemoteConnections = 0;
+ remoteConnections = new List<RemoteConnectionWrapper>();
+
+ registeredHandlers = new ConcurrentDictionary<uint, RawStreamPacketHandler>();
+
+ static ReaderStateToken CreateReaderToken()
+ {
+ return new ReaderStateToken();
+ }
+
+ static void ResetReaderToken(ref ReaderStateToken instance)
+ {
+ instance.Reset();
+ }
+
+ static void DestroyReaderToken(ReaderStateToken instance)
+ {
+ instance.Dispose();
+ }
+
+ readerStatePool = new SlimObjectPool<ReaderStateToken>(
+ CreateReaderToken,
+ ResetReaderToken,
+ DestroyReaderToken);
+ }
+
+ /// <inheritdoc />
+ public void DeregisterHandler(uint 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);
+ }
+ }
+ }
+
+ /// <inheritdoc />
+ public void RegisterHandler(uint id, RawStreamPacketHandler handler)
+ {
+ _ = registeredHandlers.AddOrUpdate(id, _ => handler, (_, multicast) => multicast + handler);
+ }
+
+ /// <inheritdoc />
+ 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++)
+ {
+ AcceptAllPending();
+ }
+ }
+
+ /// <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)
+ {
+ foreach (RemoteConnectionWrapper remoteConnection in remoteConnections)
+ {
+ remoteConnection.LocalShutdown();
+ }
+
+ lock (activeRemoteConnectionsLock)
+ {
+ while (activeRemoteConnections > 0)
+ {
+ _ = Monitor.Wait(activeRemoteConnectionsLock);
+ }
+ }
+
+ readerStatePool.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+
+ /// <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);
+ }
+
+ private void AcceptAllPending()
+ {
+ 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);
+ }
+ }
+
+ /// <summary>
+ /// Cleans up and returns the given socket args.
+ /// </summary>
+ private void CleanupArgs(SocketAsyncEventArgs args, bool cleanupUserToken = true)
+ {
+ ReaderStateToken readerState = (ReaderStateToken)args.UserToken;
+
+ if (cleanupUserToken)
+ {
+ readerStatePool.Return(readerState);
+ }
+
+ ReturnSocketArgs(args);
+ }
+
+ /// <summary>
+ /// Prepares the given socket args for receiving a packet's data from the network.
+ /// </summary>
+ private void ConfigureReceiveDataAsync(SocketAsyncEventArgs args, ReaderStateToken readerState, 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);
+
+ readerState.BytesToTransfer = header.DataLength;
+ readerState.RequestHeader = header;
+
+ args.UserToken = readerState;
+ }
+
+ /// <summary>
+ /// Prepares the given socket args for receiving a packet's header from the network.
+ /// </summary>
+ private void ConfigureReceiveHeaderAsync(SocketAsyncEventArgs args, ReaderStateToken readerState)
+ {
+ ReturnBuffer(args.Buffer); // return and clear the previously sent response packet buffer
+
+ byte[] pendingHeaderBuffer = RentBuffer(RawPacketHeader.Length);
+
+ args.SetBuffer(pendingHeaderBuffer, 0, RawPacketHeader.Length);
+
+ readerState.BytesToTransfer = RawPacketHeader.Length;
+
+ args.UserToken = readerState;
+ }
+
+ /// <summary>
+ /// Handles a completed <see cref="Socket.AcceptAsync" /> call.
+ /// </summary>
+ private void HandleAccepted(SocketAsyncEventArgs args)
+ {
+ switch (args.SocketError)
+ {
+ case SocketError.Success:
+ _ = Interlocked.Increment(ref activeRemoteConnections);
+
+ // 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);
+
+ ReaderStateToken readerState = readerStatePool.Rent();
+
+ Socket remoteConnection = args.AcceptSocket;
+ RemoteConnectionWrapper wrapper = new RemoteConnectionWrapper(this, remoteConnection, readerState);
+
+ lock (remoteConnectionsLock)
+ {
+ remoteConnections.Add(wrapper);
+ }
+
+ WaitHandle[] eventHandles =
+ {
+ readerState.ConnectionClosed.WaitHandle,
+ readerState.RequestReceived.WaitHandle,
+ };
+
+ while (true)
+ {
+ ConfigureReceiveHeaderAsync(args, readerState);
+ StartOrContinueReceiving(args);
+
+ int completedEvent = WaitHandle.WaitAny(eventHandles);
+
+ if (completedEvent == 0)
+ {
+ // the connection has been closed
+ break;
+ }
+
+ // since the request received event has been set, we need to reset it
+ readerState.RequestReceived.Reset();
+
+ RawPacketHeader header = readerState.RequestHeader!.Value;
+
+ byte[] dataBuffer = args.Buffer;
+ ReadOnlyMemory<byte> dataBufferMemory = new ReadOnlyMemory<byte>(dataBuffer, 0, header.DataLength);
+
+ if (registeredHandlers.TryGetValue(header.Type, out RawStreamPacketHandler handler))
+ {
+ handler.Invoke(remoteConnection.RemoteEndPoint, in header, in dataBufferMemory, wrapper);
+ }
+ }
+
+ lock (activeRemoteConnectionsLock)
+ {
+ _ = Interlocked.Decrement(ref activeRemoteConnections);
+ Monitor.Pulse(activeRemoteConnectionsLock); // signals that we may have reached the activeRemoteConnections == 0 state
+ }
+
+ lock (remoteConnectionsLock)
+ {
+ // TODO: ensure that this is atomic via locking or something else. are properties of integral types inherently atomic???
+ if (!readerState.LocalShutdownSignaled)
+ {
+ // since we were shutdown remotely, we need to remove the remote connection from the list of tracked connections
+ // TODO: come up with a better way of tracking active remote connections (have the wrapper deregister itself?)
+ _ = remoteConnections.Remove(wrapper);
+
+ remoteConnection.Disconnect(false);
+ remoteConnection.Shutdown(SocketShutdown.Both);
+ remoteConnection.Close();
+ remoteConnection.Dispose();
+ }
+ }
+
+ CleanupArgs(args);
+ break;
+
+ default:
+ CleanupArgs(args, false); // there is no StateToken to cleanup
+ break;
+ }
+ }
+
+ /// <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)
+ {
+ AcceptAllPending();
+ }
+
+ HandleAccepted(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)
+ {
+ ReaderStateToken readerState = (ReaderStateToken)args.UserToken;
+
+ switch (args.SocketError)
+ {
+ case SocketError.Success:
+ switch (readerState.BytesToTransfer)
+ {
+ case RawPacketHeader.Length:
+ HandleReceivedHeader(args, readerState);
+ break;
+
+ default:
+ HandleReceivedData(args, readerState);
+ break;
+ }
+
+ break;
+
+ default:
+ readerState.ConnectionClosed.Set();
+ 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, ReaderStateToken readerState)
+ {
+ int received = args.BytesTransferred;
+ int previouslyReceived = args.Offset;
+ int totalReceived = previouslyReceived + received;
+ int expected = readerState.BytesToTransfer;
+
+ if (totalReceived == expected)
+ {
+ readerState.RequestReceived.Set();
+ }
+ else if (totalReceived > 0 && totalReceived < expected)
+ {
+ args.SetBuffer(totalReceived, expected - totalReceived);
+ StartOrContinueReceiving(args);
+ }
+ else if (received == 0)
+ {
+ readerState.ConnectionClosed.Set();
+ }
+ }
+
+ /// <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, ReaderStateToken readerState)
+ {
+ int received = args.BytesTransferred;
+ int previouslyReceived = args.Offset;
+ int totalReceived = previouslyReceived + received;
+ int expected = readerState.BytesToTransfer;
+
+ byte[] headerBuffer = args.Buffer;
+ ReadOnlySpan<byte> headerBufferMemory = new ReadOnlySpan<byte>(headerBuffer);
+
+ if (totalReceived == expected)
+ {
+ RawPacketHeader header = RawPacketHeader.Deserialise(in headerBufferMemory);
+
+ ConfigureReceiveDataAsync(args, readerState, in header);
+ StartOrContinueReceiving(args);
+ }
+ else if (totalReceived > 0 && totalReceived < expected)
+ {
+ args.SetBuffer(totalReceived, expected - totalReceived);
+ StartOrContinueReceiving(args);
+ }
+ else if (received == 0)
+ {
+ readerState.ConnectionClosed.Set();
+ }
+ }
+
+ /// <summary>
+ /// Handles the completion of a <see cref="Socket.SendAsync" /> call.
+ /// </summary>
+ private void HandleSent(SocketAsyncEventArgs args)
+ {
+ ReaderStateToken readerState = (ReaderStateToken)args.UserToken;
+ switch (args.UserToken)
+ {
+ case SocketError.Success:
+ HandleSentResponse(args, readerState);
+ break;
+
+ default:
+ readerState.ConnectionClosed.Set();
+ break;
+ }
+ }
+
+ /// <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, ReaderStateToken readerState)
+ {
+ int sent = args.BytesTransferred;
+ int previouslySent = args.Offset;
+ int totalSent = previouslySent + sent;
+ int expected = readerState.BytesToTransfer;
+
+ if (totalSent > 0 && totalSent < expected)
+ {
+ args.SetBuffer(totalSent, expected - totalSent);
+ StartOrContinueSending(args.AcceptSocket, args);
+ }
+ else if (sent == 0)
+ {
+ readerState.ConnectionClosed.Set();
+ }
+ }
+
+ /// <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);
+ }
+
+ private sealed class ReaderStateToken : IDisposable
+ {
+ internal int BytesToTransfer { get; set; }
+
+ internal ManualResetEventSlim ConnectionClosed { get; } = new ManualResetEventSlim(false);
+
+ internal bool LocalShutdownSignaled { get; set; }
+
+ internal RawPacketHeader? RequestHeader { get; set; }
+
+ internal ManualResetEventSlim RequestReceived { get; } = new ManualResetEventSlim(false);
+
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ Reset();
+
+ ConnectionClosed.Dispose();
+ RequestReceived.Dispose();
+ }
+
+ internal void Reset()
+ {
+ BytesToTransfer = 0;
+ RequestHeader = null;
+
+ ConnectionClosed.Reset();
+ RequestReceived.Reset();
+ }
+ }
+
+ private sealed class RemoteConnectionWrapper : IRawStreamClient
+ {
+ private readonly WeakReference<Socket> connectionRef;
+ private readonly WeakReference<RawStreamServer> parentRef;
+ private readonly WeakReference<ReaderStateToken> tokenRef;
+
+ internal RemoteConnectionWrapper(RawStreamServer parent, Socket connection, ReaderStateToken token)
+ {
+ parentRef = new WeakReference<RawStreamServer>(parent);
+ connectionRef = new WeakReference<Socket>(connection);
+ tokenRef = new WeakReference<ReaderStateToken>(token);
+ }
+
+ public void LocalShutdown()
+ {
+ bool connectionDisposed = !connectionRef.TryGetTarget(out Socket connection);
+ bool tokenDisposed = !tokenRef.TryGetTarget(out ReaderStateToken token);
+
+ if (connectionDisposed || tokenDisposed)
+ {
+ return;
+ }
+
+ token.LocalShutdownSignaled = true;
+
+ connection.Disconnect(false);
+ connection.Shutdown(SocketShutdown.Both);
+ connection.Close();
+ connection.Dispose();
+ }
+
+ /// <inheritdoc />
+ public ValueTask<RawStreamReadAsyncResult> ReceiveAsync(Memory<byte> buffer, SocketFlags flags = SocketFlags.None)
+ {
+ bool parentDisposed = !parentRef.TryGetTarget(out RawStreamServer parent);
+ bool connectionDisposed = !connectionRef.TryGetTarget(out Socket connection);
+
+ if (parentDisposed || connectionDisposed)
+ {
+ return new ValueTask<RawStreamReadAsyncResult>(new RawStreamReadAsyncResult(-1, 0));
+ }
+
+ throw new NotImplementedException();
+ }
+
+ /// <inheritdoc />
+ public ValueTask<RawStreamWriteAsyncResult> SendAsync(uint type, ReadOnlyMemory<byte> buffer, SocketFlags flags)
+ {
+ bool parentDisposed = !parentRef.TryGetTarget(out RawStreamServer parent);
+ bool connectionDisposed = !connectionRef.TryGetTarget(out Socket connection);
+
+ if (parentDisposed || connectionDisposed)
+ {
+ return new ValueTask<RawStreamWriteAsyncResult>(new RawStreamWriteAsyncResult(-1));
+ }
+
+ throw new NotImplementedException();
+ }
+ }
+ }
+}