Fix threading issues?
parent
da2a9431df
commit
d1542df493
Binary file not shown.
Binary file not shown.
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<byte> 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<byte> buffer = new byte[MaxMessageSize];
|
||||
Memory<byte> 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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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<byte> 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<byte> buffer = new byte[4096];
|
||||
Memory<byte> target = new byte[4096];
|
||||
Memory<byte> 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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
using System.Net.Sockets;
|
||||
using System.Net;
|
||||
using LobbyServerDto;
|
||||
|
||||
namespace LobbyServer
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
internal class UdpCandidateServer : IDisposable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Drawing;
|
||||
|
||||
namespace LobbyServerDto
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue