Projektdateien hinzufügen.
This commit is contained in:
1 parent
cbbf47885d
commit
2674eb2a10
32 files changed
+2091
No files matched your search
@@ -0,0 +1,275 @@
|
||||
using LobbyServerDto;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Lobbies
|
||||
{
|
||||
public class LobbyClient : IDisposable
|
||||
{
|
||||
TcpLobbbyClient tcpClient = new TcpLobbbyClient();
|
||||
private readonly ConcurrentQueue<LobbyClientEvent> events = new ConcurrentQueue<LobbyClientEvent>();
|
||||
BufferRental bufferRental = new BufferRental(4096);
|
||||
public void Connect(string host, int port, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
using var cts = cancellationToken.Register(() =>
|
||||
{
|
||||
tcpClient.Stop();
|
||||
});
|
||||
|
||||
tcpClient.DataReceived -= TcpClient_DataReceived;
|
||||
tcpClient.DataReceived += TcpClient_DataReceived;
|
||||
|
||||
tcpClient.Disconnected -= TcpClient_Disconnected;
|
||||
tcpClient.Disconnected += TcpClient_Disconnected;
|
||||
|
||||
tcpClient.Connected -= TcpClient_Connected;
|
||||
tcpClient.Connected += TcpClient_Connected;
|
||||
|
||||
_ = Task.Run(() => tcpClient.Connect(host, port));
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<LobbyClientEvent> ReadEvents(int maxEvents)
|
||||
{
|
||||
if (events.Count > 0)
|
||||
{
|
||||
maxEvents = Math.Min(maxEvents, events.Count);
|
||||
|
||||
while (maxEvents > 0)
|
||||
{
|
||||
if(events.TryDequeue(out var _event))
|
||||
{
|
||||
yield return _event;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void HostLobby(Guid gameId, string name, int gameMode, int maxPlayerCount,string? password, string? ip, int port)
|
||||
{
|
||||
var lobbyCreate = new LobbyCreate()
|
||||
{
|
||||
GameId = gameId,
|
||||
Name = name,
|
||||
GameMode = gameMode,
|
||||
MaxPlayerCount = maxPlayerCount,
|
||||
PlayerCount = 0,
|
||||
PasswordHash = string.IsNullOrEmpty(password) ? null : SHA256.HashData(Encoding.UTF8.GetBytes(password)),
|
||||
HostIp = ip,
|
||||
HostPort = port
|
||||
};
|
||||
|
||||
byte[] messageData = bufferRental.Rent();
|
||||
var len = lobbyCreate.Serialize(messageData);
|
||||
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
||||
}
|
||||
|
||||
public void RequestLobbyHostInfo(Guid lobbyId, string? password)
|
||||
{
|
||||
var lobbyCreate = new LobbyRequestHostInfo()
|
||||
{
|
||||
LobbyId = lobbyId,
|
||||
PasswordHash = string.IsNullOrEmpty(password) ? null : SHA256.HashData(Encoding.UTF8.GetBytes(password)),
|
||||
};
|
||||
|
||||
byte[] messageData = bufferRental.Rent();
|
||||
var len = lobbyCreate.Serialize(messageData);
|
||||
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
||||
}
|
||||
|
||||
public void RequestLobbyNatPunch(Guid lobbyId, string? password, string? clientIp, int clientPort)
|
||||
{
|
||||
var lobbyRequestNatPunch = new LobbyRequestNatPunch()
|
||||
{
|
||||
LobbyId = lobbyId,
|
||||
PasswordHash = string.IsNullOrEmpty(password) ? null : SHA256.HashData(Encoding.UTF8.GetBytes(password)),
|
||||
ClientIp = clientIp,
|
||||
ClientPort = clientPort
|
||||
};
|
||||
|
||||
byte[] messageData = bufferRental.Rent();
|
||||
var len = lobbyRequestNatPunch.Serialize(messageData);
|
||||
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
||||
}
|
||||
|
||||
public void UpdateLobby(string name, int gameMode, int maxPlayerCount, int playerCount, string? password, string? ip, int port)
|
||||
{
|
||||
var lobbyUpdate = new LobbyUpdate()
|
||||
{
|
||||
Name = name,
|
||||
GameMode = gameMode,
|
||||
MaxPlayerCount = maxPlayerCount,
|
||||
PlayerCount = playerCount,
|
||||
PasswordHash = string.IsNullOrEmpty(password) ? null : SHA256.HashData(Encoding.UTF8.GetBytes(password)),
|
||||
HostIp = ip,
|
||||
HostPort = port
|
||||
};
|
||||
|
||||
byte[] messageData = bufferRental.Rent();
|
||||
var len = lobbyUpdate.Serialize(messageData);
|
||||
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
||||
}
|
||||
|
||||
public void CloseLobby()
|
||||
{
|
||||
var lobbyDelete = new LobbyDelete()
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
byte[] messageData = bufferRental.Rent();
|
||||
var len = lobbyDelete.Serialize(messageData);
|
||||
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
||||
}
|
||||
|
||||
public void ObserveLobbies(Guid gameId)
|
||||
{
|
||||
var lobbiesObserve = new LobbiesObserve() { GameId = gameId };
|
||||
|
||||
byte[] messageData = bufferRental.Rent();
|
||||
var len = lobbiesObserve.Serialize(messageData);
|
||||
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
||||
}
|
||||
|
||||
public void StopObservingLobbies()
|
||||
{
|
||||
var lobbiesStopObserve = new LobbiesStopObserve() { };
|
||||
|
||||
byte[] messageData = bufferRental.Rent();
|
||||
var len = lobbiesStopObserve.Serialize(messageData);
|
||||
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
||||
}
|
||||
|
||||
public void NotifyLobbyNatPunchDone(int natPunchId)
|
||||
{
|
||||
var lobbyNatPunchDone = new LobbyNatPunchDone() { NatPunchId = natPunchId };
|
||||
|
||||
byte[] messageData = bufferRental.Rent();
|
||||
var len = lobbyNatPunchDone.Serialize(messageData);
|
||||
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
tcpClient.Stop();
|
||||
}
|
||||
|
||||
private void TcpClient_Connected()
|
||||
{
|
||||
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.Connected, EventData = null });
|
||||
}
|
||||
|
||||
private void TcpClient_Disconnected(bool clean, string error)
|
||||
{
|
||||
if(!clean)
|
||||
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.Failed, EventData = new LobbyClientDisconnectReason { WasError = true, ErrorMessage = error } });
|
||||
else
|
||||
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.Disconnected, EventData = new LobbyClientDisconnectReason { WasError = false, ErrorMessage = string.Empty } });
|
||||
}
|
||||
|
||||
private int PeekTypeId(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
int typeId = 0;
|
||||
int shift = 0;
|
||||
int offset = 0;
|
||||
byte b;
|
||||
do
|
||||
{
|
||||
{
|
||||
// Check for a corrupted stream. Read a max of 5 bytes.
|
||||
// In a future version, add a DataFormatException.
|
||||
if (shift == 5 * 7) // 5 bytes max per Int32, shift += 7
|
||||
throw new FormatException("Format_Bad7BitInt32");
|
||||
|
||||
// ReadByte handles end of stream cases for us.
|
||||
b = buffer[offset++];
|
||||
typeId |= (b & 0x7F) << shift;
|
||||
shift += 7;
|
||||
}
|
||||
} while ((b & 0x80) != 0);
|
||||
|
||||
return typeId;
|
||||
}
|
||||
|
||||
private void TcpClient_DataReceived(int dataLength, Memory<byte> data)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (dataLength > 0)
|
||||
{
|
||||
switch (PeekTypeId(data.Span))
|
||||
{
|
||||
case LobbyInfo.TypeId:
|
||||
{
|
||||
var lobbyInfo = LobbyInfo.Deserialize(data.Span);
|
||||
if (lobbyInfo != null)
|
||||
{
|
||||
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.LobbyUpdate, EventData = lobbyInfo });
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LobbyDelete.TypeId:
|
||||
{
|
||||
var lobbyDelete = LobbyDelete.Deserialize(data.Span);
|
||||
if (lobbyDelete != null)
|
||||
{
|
||||
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.LobbyDelete, EventData = lobbyDelete });
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LobbyHostInfo.TypeId:
|
||||
{
|
||||
var lobbyHostInfo = LobbyHostInfo.Deserialize(data.Span);
|
||||
if (lobbyHostInfo != null)
|
||||
{
|
||||
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.LobbyHostInfo, EventData = lobbyHostInfo });
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LobbyRequestNatPunch.TypeId:
|
||||
{
|
||||
var lobbyRequestNatPunch = LobbyRequestNatPunch.Deserialize(data.Span);
|
||||
if (lobbyRequestNatPunch != null)
|
||||
{
|
||||
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.LobbyRequestNatPunch, EventData = lobbyRequestNatPunch });
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LobbyNatPunchDone.TypeId:
|
||||
{
|
||||
var lobbyNatPunchDone = LobbyNatPunchDone.Deserialize(data.Span);
|
||||
if (lobbyNatPunchDone != null)
|
||||
{
|
||||
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.LobbyNatPunchDone, EventData = lobbyNatPunchDone });
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
tcpClient.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LobbyServerDto\LobbyServerDto.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Lobbies
|
||||
{
|
||||
public class LobbyClientDisconnectReason
|
||||
{
|
||||
public bool WasError { get; internal set; }
|
||||
public string? ErrorMessage { get; internal set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Lobbies
|
||||
{
|
||||
public class LobbyClientEvent
|
||||
{
|
||||
public LobbyClientEventTypes EventType { get; internal set; }
|
||||
public object? EventData { get; internal set; }
|
||||
}
|
||||
|
||||
public enum LobbyClientEventTypes
|
||||
{
|
||||
Connected,
|
||||
Disconnected,
|
||||
Failed,
|
||||
LobbyAdd,
|
||||
LobbyUpdate,
|
||||
LobbyDelete,
|
||||
LobbyHostInfo,
|
||||
LobbyRequestNatPunch,
|
||||
LobbyNatPunchDone,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Lobbies
|
||||
{
|
||||
internal class TcpLobbbyClient : IDisposable
|
||||
{
|
||||
internal delegate void DataReceivedEventArgs(int bytes, Memory<byte> data);
|
||||
internal event DataReceivedEventArgs? DataReceived;
|
||||
|
||||
internal delegate void DisconnectedEventArgs(bool clean, string error);
|
||||
internal event DisconnectedEventArgs? Disconnected;
|
||||
|
||||
|
||||
internal delegate void ConnectedEventArgs();
|
||||
internal event ConnectedEventArgs? Connected;
|
||||
|
||||
TcpClient? tcpClient;
|
||||
NetworkStream? networkStream;
|
||||
CancellationTokenSource? cancellationTokenSource = new CancellationTokenSource();
|
||||
bool running = false;
|
||||
|
||||
internal async Task Connect(string host, int port)
|
||||
{
|
||||
bool wasError = false;
|
||||
string error = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
running = true;
|
||||
tcpClient = new TcpClient();
|
||||
await tcpClient.ConnectAsync(host, port, cancellationTokenSource!.Token);
|
||||
networkStream = tcpClient.GetStream();
|
||||
|
||||
Memory<byte> buffer = new byte[4096];
|
||||
Memory<byte> target = new byte[4096];
|
||||
|
||||
int currentOffset = 0;
|
||||
int currentMessageRemainingLength = 0;
|
||||
int currentMessageLength = 0;
|
||||
bool validMessage = true;
|
||||
int currentReadOffset = 0;
|
||||
bool offsetSizeInt = false;
|
||||
Connected?.Invoke();
|
||||
|
||||
while (running)
|
||||
{
|
||||
int copyOffset = 0;
|
||||
int receivedBytes = currentReadOffset;
|
||||
|
||||
if (currentReadOffset < 4)
|
||||
{
|
||||
receivedBytes += await networkStream.ReadAsync(buffer.Slice(currentReadOffset), cancellationTokenSource.Token) + currentReadOffset;
|
||||
}
|
||||
|
||||
if (receivedBytes == 0 && running && !cancellationTokenSource.Token.IsCancellationRequested)
|
||||
{
|
||||
throw new Exception("Connection lost!");
|
||||
}
|
||||
|
||||
if (receivedBytes > 3 || (currentMessageRemainingLength > 0 && receivedBytes > currentMessageRemainingLength))
|
||||
{
|
||||
currentReadOffset = 0;
|
||||
if (currentMessageLength == 0)
|
||||
{
|
||||
currentMessageRemainingLength = BitConverter.ToInt32(buffer.Span);
|
||||
currentMessageLength = currentMessageRemainingLength;
|
||||
receivedBytes -= 4;
|
||||
copyOffset += 4;
|
||||
offsetSizeInt = true;
|
||||
}
|
||||
else
|
||||
offsetSizeInt = false;
|
||||
|
||||
var receivedCount = Math.Min(receivedBytes, currentMessageRemainingLength);
|
||||
receivedBytes -= receivedCount;
|
||||
copyOffset += receivedCount;
|
||||
|
||||
if (validMessage && currentOffset + receivedCount > 0)
|
||||
{
|
||||
if (currentOffset + receivedCount < target.Length)
|
||||
buffer.Slice(offsetSizeInt ? 4 : 0, receivedCount).CopyTo(target.Slice(currentOffset));
|
||||
else
|
||||
validMessage = false;
|
||||
}
|
||||
|
||||
currentOffset += receivedCount;
|
||||
currentMessageRemainingLength -= receivedCount;
|
||||
|
||||
if (currentMessageRemainingLength <= 0)
|
||||
{
|
||||
if (validMessage)
|
||||
DataReceived?.Invoke(currentMessageLength, target);
|
||||
|
||||
if (receivedBytes > 0)
|
||||
{
|
||||
buffer.Slice(copyOffset, receivedBytes).CopyTo(buffer);
|
||||
currentReadOffset += receivedBytes;
|
||||
}
|
||||
|
||||
currentOffset = 0;
|
||||
currentMessageLength = 0;
|
||||
currentMessageRemainingLength = 0;
|
||||
validMessage = true;
|
||||
}
|
||||
}
|
||||
else if (receivedBytes > 0)
|
||||
{
|
||||
currentReadOffset += receivedBytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
error = e.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
wasError = running;
|
||||
|
||||
running = false;
|
||||
|
||||
networkStream?.Dispose();
|
||||
tcpClient?.Dispose();
|
||||
|
||||
tcpClient = null;
|
||||
networkStream = null;
|
||||
|
||||
Disconnected?.Invoke(!wasError, error);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task Send(byte[] buffer, int offset, int count)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (running && networkStream != null)
|
||||
{
|
||||
await networkStream.WriteAsync(BitConverter.GetBytes(count - offset), 0, 4, cancellationTokenSource!.Token);
|
||||
await networkStream.WriteAsync(buffer, offset, count, cancellationTokenSource!.Token);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
internal void Stop()
|
||||
{
|
||||
running = false;
|
||||
cancellationTokenSource?.Cancel();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
cancellationTokenSource?.Cancel();
|
||||
tcpClient?.Dispose();
|
||||
cancellationTokenSource?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user