NetSharp

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

commit cbc87b28ece603083065b5cb2b481f5d0d0f4eba
parent 90ae2ac2a6ff6ce6e8d30aee854ad32b39f0dad0
Author: Mikolaj Lenczewski <33129490+EnderRifter@users.noreply.github.com>
Date:   Sun, 16 Feb 2020 15:48:29 +0000

Fixed huge memory issue! Yay! Udp is now somewhat more useable.

Diffstat:
MNetSharp/NetSharp/NetSharp.csproj | 3+--
MNetSharp/NetSharp/NetSharp.xml | 8+++++++-
MNetSharp/NetSharp/Servers/UdpServer.cs | 34+++++++++++++++++++++++++---------
MNetSharp/NetSharp/Utils/NetworkOperations.cs | 48++++++++++++++++++++++++++++++++++++------------
MNetSharp/NetSharpExamples/Program.cs | 2+-
5 files changed, 70 insertions(+), 25 deletions(-)

diff --git a/NetSharp/NetSharp/NetSharp.csproj b/NetSharp/NetSharp/NetSharp.csproj @@ -18,7 +18,6 @@ <ItemGroup> <PackageReference Include="Microsoft.CSharp" Version="4.7.0" /> - <PackageReference Include="System.Dynamic.Runtime" Version="4.3.0" /> - <PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.11.0" /> + <PackageReference Include="System.Threading.Channels" Version="4.7.0" /> </ItemGroup> </Project> \ No newline at end of file diff --git a/NetSharp/NetSharp/NetSharp.xml b/NetSharp/NetSharp/NetSharp.xml @@ -1557,6 +1557,11 @@ Holds currently connected and active clients, as well as their current received packet queues. </summary> </member> + <member name="F:NetSharp.Servers.UdpServer.clientChannelOptions"> + <summary> + The options that should be applied to every channel created to handle a client. + </summary> + </member> <member name="M:NetSharp.Servers.UdpServer.#ctor"> <inheritdoc /> </member> @@ -1787,13 +1792,14 @@ <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> <returns>The result of the receive operation.</returns> </member> - <member name="M:NetSharp.Utils.NetworkOperations.ReadFromAsync(System.Net.Sockets.Socket,System.Net.EndPoint,System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)"> + <member name="M:NetSharp.Utils.NetworkOperations.ReadFromAsync(System.Net.Sockets.Socket,System.Int32,System.Net.EndPoint,System.Net.Sockets.SocketFlags,System.Threading.CancellationToken)"> <summary> Reads a datagram asynchronously from the given remote endpoint, via the given socket. The given <see cref="T:System.Net.Sockets.SocketFlags"/> are associated with the read, and the given <see cref="T:System.Threading.CancellationToken"/> is used to allow for asynchronous task cancellation. </summary> <param name="socket">The socket which should read data from the network.</param> + <param name="count">The number of bytes to read from the network.</param> <param name="remoteEndPoint">The remote endpoint from which data should be read.</param> <param name="socketFlags">The socket flags associated with the receive operation.</param> <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> diff --git a/NetSharp/NetSharp/Servers/UdpServer.cs b/NetSharp/NetSharp/Servers/UdpServer.cs @@ -2,8 +2,8 @@ using System.Collections.Concurrent; using System.Net; using System.Net.Sockets; +using System.Threading.Channels; using System.Threading.Tasks; -using System.Threading.Tasks.Dataflow; using NetSharp.Interfaces; using NetSharp.Packets; using NetSharp.Packets.Builtin; @@ -20,19 +20,28 @@ namespace NetSharp.Servers /// <summary> /// Holds currently connected and active clients, as well as their current received packet queues. /// </summary> - private readonly ConcurrentDictionary<EndPoint, BufferBlock<Packet>> activeClients; + private readonly ConcurrentDictionary<EndPoint, Channel<Packet>> activeClients; + + /// <summary> + /// The options that should be applied to every channel created to handle a client. + /// </summary> + private static readonly UnboundedChannelOptions clientChannelOptions = new UnboundedChannelOptions + { + SingleReader = true, + SingleWriter = true + }; /// <inheritdoc /> public UdpServer() : base(SocketType.Dgram, ProtocolType.Udp, SocketOptionManager.Udp) { - activeClients = new ConcurrentDictionary<EndPoint, BufferBlock<Packet>>(); + activeClients = new ConcurrentDictionary<EndPoint, Channel<Packet>>(); } /// <inheritdoc /> protected override async Task HandleClientAsync(ClientHandlerArgs args) { EndPoint clientEndPoint = args.ClientEndPoint; - BufferBlock<Packet> clientPacketBuffer = activeClients[clientEndPoint]; + Channel<Packet> clientPacketBuffer = activeClients[clientEndPoint]; logger.LogMessage($"Initialised client handler for client socket: [Remote EP: {clientEndPoint}]"); @@ -41,7 +50,7 @@ namespace NetSharp.Servers do { // receive a single raw packet from the network - Packet rawRequest = await clientPacketBuffer.ReceiveAsync(serverShutdownCancellationTokenSource.Token); + Packet rawRequest = await clientPacketBuffer.Reader.ReadAsync(serverShutdownCancellationTokenSource.Token); if (rawRequest.Equals(NullPacket) || rawRequest.Type == PacketRegistry.GetPacketId<DisconnectPacket>()) { @@ -89,8 +98,15 @@ namespace NetSharp.Servers logger.LogMessage($"Stopping client handler for client socket: [Remote EP: {clientEndPoint}]"); - activeClients.TryRemove(clientEndPoint, out BufferBlock<Packet> remainingPackets); - logger.LogMessage($"Client handler has {remainingPackets.Count} packets left, which will be dropped"); + if (activeClients.TryRemove(clientEndPoint, out Channel<Packet> packetChannel)) + { + packetChannel.Writer.Complete(); + logger.LogMessage($"Shutting down packet channel for client socket: [Remote EP: {clientEndPoint}]"); + } + else + { + logger.LogMessage($"Couldn't shut down packet channel for client socket: [Remote EP: {clientEndPoint}]"); + } } catch (TaskCanceledException) { logger.LogMessage("Client handling was cancelled via a task cancellation."); } catch (OperationCanceledException) { logger.LogMessage("Client handling was cancelled via an operation cancellation."); } @@ -130,7 +146,7 @@ namespace NetSharp.Servers { ClientHandlerArgs args = ClientHandlerArgs.ForUdpClientHandler(in clientEndPoint); - activeClients.TryAdd(clientEndPoint, new BufferBlock<Packet>()); + activeClients.TryAdd(clientEndPoint, Channel.CreateUnbounded<Packet>(clientChannelOptions)); await Task.Factory.StartNew(DoHandleClientAsync, args, serverShutdownCancellationTokenSource.Token, @@ -138,7 +154,7 @@ namespace NetSharp.Servers TaskScheduler.Current); } - activeClients[clientEndPoint].Post(request); + await activeClients[clientEndPoint].Writer.WriteAsync(request); } OnServerStopped(); diff --git a/NetSharp/NetSharp/Utils/NetworkOperations.cs b/NetSharp/NetSharp/Utils/NetworkOperations.cs @@ -25,7 +25,7 @@ namespace NetSharp.Utils /// <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> /// <returns>The result of the receive operation.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Task<TransmissionResult> ReadAsync(Socket socket, int count, SocketFlags socketFlags, + internal static Task<TransmissionResult> ReadAsync(Socket socket, int count, SocketFlags socketFlags, CancellationToken cancellationToken) { return Task.Factory.StartNew(() => @@ -50,24 +50,30 @@ namespace NetSharp.Utils /// is used to allow for asynchronous task cancellation. /// </summary> /// <param name="socket">The socket which should read data from the network.</param> + /// <param name="count">The number of bytes to read from the network.</param> /// <param name="remoteEndPoint">The remote endpoint from which data should be read.</param> /// <param name="socketFlags">The socket flags associated with the receive operation.</param> /// <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> /// <returns>The result of the receive operation.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Task<TransmissionResult> ReadFromAsync(Socket socket, EndPoint remoteEndPoint, + internal static Task<TransmissionResult> ReadFromAsync(Socket socket, int count, EndPoint remoteEndPoint, SocketFlags socketFlags, CancellationToken cancellationToken) { return Task.Factory.StartNew(() => { - byte[] byteBuffer = new byte[Constants.UdpMaxBufferSize]; - + byte[] byteBuffer = new byte[count]; EndPoint actualRemoteEndPoint = remoteEndPoint; + int receivedBytesCount = 0; + + while (count > receivedBytesCount) + { + IPPacketInformation _; - int receivedByteCount = socket.ReceiveFrom(byteBuffer, socketFlags, ref actualRemoteEndPoint); + receivedBytesCount += socket.ReceiveMessageFrom(byteBuffer, receivedBytesCount, + count - receivedBytesCount, ref socketFlags, ref actualRemoteEndPoint, out _); + } - return new TransmissionResult(new Memory<byte>(byteBuffer, 0, receivedByteCount), receivedByteCount, - actualRemoteEndPoint); + return new TransmissionResult(byteBuffer, receivedBytesCount, actualRemoteEndPoint); }, cancellationToken); } @@ -119,10 +125,28 @@ namespace NetSharp.Utils public static async Task<(Packet packet, TransmissionResult packetResult)> ReadPacketFromAsync( Socket socket, EndPoint remoteEndPoint, SocketFlags socketFlags, CancellationToken cancellationToken) { - TransmissionResult packetResult = - await ReadFromAsync(socket, remoteEndPoint, socketFlags, cancellationToken); + TransmissionResult packetHeaderResult = + await ReadFromAsync(socket, Packet.HeaderSize, remoteEndPoint, socketFlags, cancellationToken); + + int packetSize = EndianAwareBitConverter.ToInt32(packetHeaderResult.Buffer.Span.Slice(0, sizeof(int))); + + if (packetSize == 0) + { + return (Packet.Deserialise(packetHeaderResult.Buffer), packetHeaderResult); + } + + TransmissionResult packetDataResult = + await ReadFromAsync(socket, packetSize, packetHeaderResult.RemoteEndPoint, socketFlags, cancellationToken); + + byte[] serialisedPacket = new byte[Packet.HeaderSize + packetSize]; + + Memory<byte> serialisedPacketHeader = new Memory<byte>(serialisedPacket, 0, Packet.HeaderSize); + packetHeaderResult.Buffer.CopyTo(serialisedPacketHeader); + + Memory<byte> serialisedPacketData = new Memory<byte>(serialisedPacket, Packet.HeaderSize, packetSize); + packetDataResult.Buffer.CopyTo(serialisedPacketData); - return (Packet.Deserialise(packetResult.Buffer), packetResult); + return (Packet.Deserialise(serialisedPacket), packetDataResult); } /// <summary> @@ -135,7 +159,7 @@ namespace NetSharp.Utils /// <param name="socketFlags">The socket flags associated with the send operation.</param> /// <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Task WriteAsync(Socket socket, ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, + internal static Task WriteAsync(Socket socket, ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken) { return Task.Factory.StartNew(() => @@ -192,7 +216,7 @@ namespace NetSharp.Utils /// <param name="socketFlags">The socket flags associated with the send operation.</param> /// <param name="cancellationToken">The cancellation token to use for asynchronous cancellation.</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Task WriteToAsync(Socket socket, EndPoint remoteEndPoint, ReadOnlyMemory<byte> buffer, + internal static Task WriteToAsync(Socket socket, EndPoint remoteEndPoint, ReadOnlyMemory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken) { return Task.Factory.StartNew(() => diff --git a/NetSharp/NetSharpExamples/Program.cs b/NetSharp/NetSharpExamples/Program.cs @@ -147,7 +147,7 @@ namespace NetSharpExamples await using Stream serverOutputStream = File.OpenWrite(serverLogFile); using Server server = new UdpServer(); - server.ChangeLoggingStream(Console.OpenStandardOutput()); + server.ChangeLoggingStream(Console.OpenStandardOutput(), LogLevel.Warn); //server.ChangeLoggingStream(serverOutputStream, LogLevel.Error); Console.WriteLine("Starting server...");