From d1542df493b279e50c6aaa7afb3b6a6e8553bdff Mon Sep 17 00:00:00 2001 From: Thomas Woischnig Date: Sun, 26 Apr 2026 00:54:27 +0200 Subject: [PATCH] Fix threading issues? --- .../Runtime/LobbyClient.dll | Bin 40448 -> 39936 bytes .../Runtime/LobbyServerDto.dll | Bin 25088 -> 25088 bytes Assets/NetworkLobbyClient/package.json | 2 +- LobbyClient/TcpClient.cs | 130 +++++++----------- LobbyClientTest/Program.cs | 1 - LobbyServer/TcpLobbyServer.cs | 117 ++++++---------- LobbyServer/UdpCandidateServer.cs | 3 +- LobbyServerDto/BufferRental.cs | 1 - 8 files changed, 94 insertions(+), 160 deletions(-) diff --git a/Assets/NetworkLobbyClient/Runtime/LobbyClient.dll b/Assets/NetworkLobbyClient/Runtime/LobbyClient.dll index 1752f7df4820ac5422b4a59ee7e2ac87bb0e4ba4..fe2de95136850fdcedec267d3aec18dadf7b86c0 100644 GIT binary patch literal 39936 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

    wK)xAY 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 VJ`>gW+l2oE>hk|r;s0|B{6Eg0R0jY6 literal 40448 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 A1ONa4 diff --git a/Assets/NetworkLobbyClient/Runtime/LobbyServerDto.dll b/Assets/NetworkLobbyClient/Runtime/LobbyServerDto.dll index 735b14d5ed269bd1e07c2fd8b1fb03c4c34061a7..b069a447ebf25a3a6f57dad1dd2dc34efe20cea0 100644 GIT binary patch delta 237 zcmZoT!q{+xaY6@+)%EAi8+#N&1nL~Vcy)T2xNT2RdHUo?q}k>@A-XIYDTzjjmL|rA zDQU?`DQ1=y7AY2%si~<+mL^7t$(D&msRowjsi~GGlLaFmv#|XaxHkDmWPkv}%CBru z+PO~KE*&tMp*Q8^agee{%tzUU(Y@Ne8p|^t0*HD{}hHq1|uN0WH4bc zW-w$(VMt?0W=I0kWecM=;D}n2SMcUNA-XIYrsfu=rl}SN zsm6xMiH0eO7AeU_rsk<8X~|}WriO_IMyaO8hGwRwlLaFmvrJKsnKt=HWPm{4!CKyC zgHP_$L^j7+*8L{}AGMF=1Fqi^i zDv&k+lEw^%49N_MKr#hLS^&vp1|y)nIZ)1oAq~hg1In2KaUxK?5m3|=tj7#Un{LjI HpUMOPVn0Uk diff --git a/Assets/NetworkLobbyClient/package.json b/Assets/NetworkLobbyClient/package.json index 0dfb399..a2d0fa7 100644 --- a/Assets/NetworkLobbyClient/package.json +++ b/Assets/NetworkLobbyClient/package.json @@ -1,6 +1,6 @@ { "name": "com.incobyte.lobbyclient", - "version": "1.0.7", + "version": "1.0.8", "displayName": "Game Lobby Client", "description": "Provides a client for the game lobby server to list and join lobbies", "unity": "2022.3", diff --git a/LobbyClient/TcpClient.cs b/LobbyClient/TcpClient.cs index edad351..25ce41d 100644 --- a/LobbyClient/TcpClient.cs +++ b/LobbyClient/TcpClient.cs @@ -1,4 +1,5 @@ -using System; +using LobbyServerDto; +using System; using System.IO; using System.Net.Sockets; using System.Threading; @@ -25,7 +26,20 @@ namespace Lobbies private NetworkStream? networkStream; private CancellationTokenSource? cancellationTokenSource = new CancellationTokenSource(); private bool running = false; - private readonly SemaphoreSlim sendLock = new SemaphoreSlim(1, 1); + private readonly BufferRental bufferRental = new BufferRental(MaxMessageSize); + private async Task ReadExact(NetworkStream stream, Memory buffer, int length, CancellationToken token) + { + int offset = 0; + + while (offset < length) + { + int read = await stream.ReadAsync(buffer.Slice(offset), token); + if (read == 0) + throw new EndOfStreamException(); + + offset += read; + } + } internal async Task Connect(string host, int port) { @@ -60,81 +74,26 @@ namespace Lobbies pingTask = Task.Run(() => PingLoop(token), token); Memory buffer = new byte[MaxMessageSize]; - Memory target = new byte[MaxMessageSize]; - - int bufferedBytes = 0; - int currentMessageLength = -1; - int currentMessageOffset = 0; while (running && !token.IsCancellationRequested) { - if (bufferedBytes == buffer.Length) - throw new InvalidDataException("Receive buffer overflow."); + await ReadExact(networkStream, buffer, HeaderSize, token); - int bytesRead = await networkStream.ReadAsync(buffer.Slice(bufferedBytes), token); - if (bytesRead == 0) - break; + int length = BitConverter.ToInt32(buffer.Span.Slice(0, HeaderSize)); - bufferedBytes += bytesRead; + if (length < 0) + throw new InvalidDataException("Negative message length received."); - while (true) - { - if (currentMessageLength < 0) - { - if (bufferedBytes < HeaderSize) - break; + if (length > MaxMessageSize) + throw new InvalidDataException($"Message too large: {length} > {MaxMessageSize}."); - currentMessageLength = BitConverter.ToInt32(buffer.Span.Slice(0, HeaderSize)); + if (length == 0) + continue; - if (currentMessageLength < 0) - throw new InvalidDataException("Negative message length received."); + await ReadExact(networkStream, buffer, length, token); - if (currentMessageLength > MaxMessageSize) - throw new InvalidDataException($"Message too large: {currentMessageLength} > {MaxMessageSize}."); - - if (bufferedBytes > HeaderSize) - buffer.Slice(HeaderSize, bufferedBytes - HeaderSize).CopyTo(buffer); - - bufferedBytes -= HeaderSize; - currentMessageOffset = 0; - - if (currentMessageLength == 0) - { - currentMessageLength = -1; - continue; - } - } - - int remainingMessageBytes = currentMessageLength - currentMessageOffset; - if (remainingMessageBytes <= 0) - { - DataReceived?.Invoke(currentMessageLength, target.Slice(0, currentMessageLength)); - currentMessageLength = -1; - currentMessageOffset = 0; - continue; - } - - if (bufferedBytes == 0) - break; - - int chunkSize = Math.Min(bufferedBytes, remainingMessageBytes); - - buffer.Slice(0, chunkSize).CopyTo(target.Slice(currentMessageOffset)); - currentMessageOffset += chunkSize; - - if (bufferedBytes > chunkSize) - buffer.Slice(chunkSize, bufferedBytes - chunkSize).CopyTo(buffer); - - bufferedBytes -= chunkSize; - - if (currentMessageOffset == currentMessageLength) - { - DataReceived?.Invoke(currentMessageLength, target.Slice(0, currentMessageLength)); - currentMessageLength = -1; - currentMessageOffset = 0; - } - } - } + DataReceived?.Invoke(length, buffer.Slice(0, length)); + } } catch (OperationCanceledException) { @@ -185,37 +144,42 @@ namespace Lobbies if (!running || networkStream == null) return; - await sendLock.WaitAsync(token); try { await networkStream.WriteAsync(BitConverter.GetBytes(0), 0, 4, token); } - finally + catch { - sendLock.Release(); } } internal async Task Send(byte[] buffer, int offset, int count) { + byte[] frame = bufferRental.Rent(); try { if (!running || networkStream == null || cancellationTokenSource == null) return; - await sendLock.WaitAsync(cancellationTokenSource.Token); - try - { - await networkStream.WriteAsync(BitConverter.GetBytes(count), 0, 4, cancellationTokenSource.Token); - await networkStream.WriteAsync(buffer, offset, count, cancellationTokenSource.Token); - } - finally - { - sendLock.Release(); - } + if (count < 0 || count > MaxMessageSize) + throw new ArgumentOutOfRangeException(nameof(count)); + + if (offset < 0 || offset + count > buffer.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + + BitConverter.GetBytes(count).CopyTo(frame, 0); + Buffer.BlockCopy(buffer, offset, frame, HeaderSize, count); + + await networkStream.WriteAsync(frame, 0, HeaderSize + count, cancellationTokenSource.Token); } - catch { } - } + catch + { + } + finally + { + bufferRental.Return(frame); + } + } internal void Stop() { diff --git a/LobbyClientTest/Program.cs b/LobbyClientTest/Program.cs index 83c51d5..f737bc6 100644 --- a/LobbyClientTest/Program.cs +++ b/LobbyClientTest/Program.cs @@ -3,7 +3,6 @@ using Lobbies; using LobbyClientTest; using LobbyServerDto; using System.Net; -using System.Net.WebSockets; Console.WriteLine("Starting lobby client v0.7!"); var lobbyClient = new LobbyClient(); diff --git a/LobbyServer/TcpLobbyServer.cs b/LobbyServer/TcpLobbyServer.cs index 4a407af..94b7127 100644 --- a/LobbyServer/TcpLobbyServer.cs +++ b/LobbyServer/TcpLobbyServer.cs @@ -13,13 +13,17 @@ namespace LobbyServer public delegate void ClientDisconnectedEventArgs(int clientId); public event ClientDisconnectedEventArgs? ClientDisconnected; + private const int HeaderSize = 4; + private const int MaxMessageSize = 4096; + private readonly BufferRental bufferRental = new BufferRental(MaxMessageSize); + internal class Client : IDisposable { internal CancellationTokenSource? cancellationToken = null; internal NetworkStream? stream; internal TcpClient? client; internal DateTime lastSeenUtc = DateTime.UtcNow; - + public void Dispose() { try { stream?.Dispose(); } catch { } @@ -100,15 +104,36 @@ namespace LobbyServer public async Task Send(int clientId, byte[] buffer, int offset, int count) { + byte[] frame = bufferRental.Rent(); try { if (activeClients.TryGetValue(clientId, out var lobbyClient) && lobbyClient.stream != null && lobbyClient.cancellationToken != null) - { - await lobbyClient.stream.WriteAsync(BitConverter.GetBytes(count), 0, 4, lobbyClient.cancellationToken.Token); - await lobbyClient.stream.WriteAsync(buffer, offset, count, lobbyClient.cancellationToken.Token); + { + BitConverter.GetBytes(count).CopyTo(frame, 0); + Buffer.BlockCopy(buffer, offset, frame, HeaderSize, count); + + await lobbyClient.stream.WriteAsync(frame, 0, HeaderSize + count, lobbyClient.cancellationToken.Token); } } catch { } + finally + { + bufferRental.Return(frame); + } + } + + private async Task ReadExact(NetworkStream stream, Memory buffer, int length, CancellationToken token) + { + int offset = 0; + + while (offset < length) + { + int read = await stream.ReadAsync(buffer.Slice(offset), token); + if (read == 0) + throw new EndOfStreamException(); + + offset += read; + } } private async Task ClientThread(TcpClient client) @@ -120,8 +145,8 @@ namespace LobbyServer { var stream = client.GetStream(); - Memory buffer = new byte[4096]; - Memory target = new byte[4096]; + Memory buffer = new byte[MaxMessageSize]; + lobbyClient = new Client { @@ -133,83 +158,31 @@ namespace LobbyServer activeClients.TryAdd(myId, lobbyClient); - int bufferedBytes = 0; - int currentMessageLength = -1; - int currentMessageOffset = 0; - bool validMessage = true; - var lobbyClientConnectionInfo = new LobbyClientConnectionInfo { Id = myId }; byte[] sendBuffer = new byte[128]; int sendLen = lobbyClientConnectionInfo.Serialize(sendBuffer); await Send(myId, sendBuffer, 0, sendLen); - + while (running && !lobbyClient.cancellationToken.Token.IsCancellationRequested) { - int bytesRead = await stream.ReadAsync(buffer.Slice(bufferedBytes), lobbyClient.cancellationToken.Token); - if (bytesRead == 0) - break; - - bufferedBytes += bytesRead; + await ReadExact(stream, buffer, HeaderSize, lobbyClient.cancellationToken.Token); lobbyClient.lastSeenUtc = DateTime.UtcNow; - while (true) - { - if (currentMessageLength < 0) - { - if (bufferedBytes < 4) - break; + int length = BitConverter.ToInt32(buffer.Span.Slice(0, HeaderSize)); - currentMessageLength = BitConverter.ToInt32(buffer.Span.Slice(0, 4)); + if (length < 0) + throw new InvalidDataException("Negative message length received."); - if (currentMessageLength < 0) - throw new InvalidDataException("Negative message length received."); + if (length > MaxMessageSize) + throw new InvalidDataException($"Message too large: {length} > {MaxMessageSize}."); - buffer.Slice(4, bufferedBytes - 4).CopyTo(buffer); - bufferedBytes -= 4; - currentMessageOffset = 0; - validMessage = currentMessageLength <= target.Length; - - if (currentMessageLength == 0) - { - currentMessageLength = -1; - continue; - } - } - - if (bufferedBytes == 0) - break; - - int chunkSize = Math.Min(bufferedBytes, currentMessageLength - currentMessageOffset); - - if (chunkSize > 0) - { - if (validMessage) - { - buffer.Slice(0, chunkSize).CopyTo(target.Slice(currentMessageOffset)); - } - - currentMessageOffset += chunkSize; - - buffer.Slice(chunkSize, bufferedBytes - chunkSize).CopyTo(buffer); - bufferedBytes -= chunkSize; - } - - if (currentMessageOffset < currentMessageLength) - break; - - if (validMessage) - { - DataReceived?.Invoke(myId, currentMessageLength, target.Slice(0, currentMessageLength)); - } - - currentMessageLength = -1; - currentMessageOffset = 0; - validMessage = true; - } - - if (bufferedBytes == buffer.Length && currentMessageLength < 0) - throw new InvalidDataException("Receive buffer overflow while waiting for message header."); - } + if (length == 0) + continue; + + await ReadExact(stream, buffer, length, lobbyClient.cancellationToken.Token); + + DataReceived?.Invoke(myId, length, buffer.Slice(0, length)); + } } finally { diff --git a/LobbyServer/UdpCandidateServer.cs b/LobbyServer/UdpCandidateServer.cs index d77535d..9ef9495 100644 --- a/LobbyServer/UdpCandidateServer.cs +++ b/LobbyServer/UdpCandidateServer.cs @@ -1,11 +1,10 @@ using System.Net.Sockets; -using System.Net; using LobbyServerDto; namespace LobbyServer { /// - /// Small udp server to receive udp packets and report their remote ip and port to a event delegate + /// Small udp server to receive udp packets and report their remote ip and port to an event delegate /// internal class UdpCandidateServer : IDisposable { diff --git a/LobbyServerDto/BufferRental.cs b/LobbyServerDto/BufferRental.cs index 2792ca9..231f300 100644 --- a/LobbyServerDto/BufferRental.cs +++ b/LobbyServerDto/BufferRental.cs @@ -1,5 +1,4 @@ using System.Collections.Concurrent; -using System.Drawing; namespace LobbyServerDto {