Initial
This commit is contained in:
1 parent
2674eb2a10
commit
840218f119
25 files changed
+650
-169
No files matched your search
+119
-48
@@ -2,6 +2,9 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
@@ -14,8 +17,21 @@ namespace Lobbies
|
||||
TcpLobbbyClient tcpClient = new TcpLobbbyClient();
|
||||
private readonly ConcurrentQueue<LobbyClientEvent> events = new ConcurrentQueue<LobbyClientEvent>();
|
||||
BufferRental bufferRental = new BufferRental(4096);
|
||||
|
||||
AutoResetEvent waitForExternalIp = new AutoResetEvent(false);
|
||||
|
||||
private string? host;
|
||||
private int port;
|
||||
private int connectionId;
|
||||
|
||||
public string? externalIp;
|
||||
public int externalPort;
|
||||
|
||||
public void Connect(string host, int port, CancellationToken cancellationToken)
|
||||
{
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -30,9 +46,6 @@ namespace Lobbies
|
||||
tcpClient.Disconnected -= TcpClient_Disconnected;
|
||||
tcpClient.Disconnected += TcpClient_Disconnected;
|
||||
|
||||
tcpClient.Connected -= TcpClient_Connected;
|
||||
tcpClient.Connected += TcpClient_Connected;
|
||||
|
||||
_ = Task.Run(() => tcpClient.Connect(host, port));
|
||||
}
|
||||
catch
|
||||
@@ -93,19 +106,25 @@ namespace Lobbies
|
||||
_ = 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 delegate void SendUdpMessageCallback(IPEndPoint remoteEndpoint, byte[] messageBuffer, int messageLength);
|
||||
public void RequestLobbyNatPunch(Guid lobbyId, string? password, SendUdpMessageCallback sendUdpCallback)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
QueryExternalIpAndPort(sendUdpCallback);
|
||||
var lobbyRequestNatPunch = new LobbyRequestNatPunch()
|
||||
{
|
||||
LobbyId = lobbyId,
|
||||
PasswordHash = string.IsNullOrEmpty(password) ? null : SHA256.HashData(Encoding.UTF8.GetBytes(password)),
|
||||
ClientIp = externalIp,
|
||||
ClientPort = externalPort
|
||||
};
|
||||
|
||||
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)
|
||||
@@ -156,23 +175,75 @@ namespace Lobbies
|
||||
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
||||
}
|
||||
|
||||
public void NotifyLobbyNatPunchDone(int natPunchId)
|
||||
public void NotifyLobbyNatPunchDone(int natPunchId, string externalIp, int externalPort)
|
||||
{
|
||||
var lobbyNatPunchDone = new LobbyNatPunchDone() { NatPunchId = natPunchId };
|
||||
var lobbyNatPunchDone = new LobbyNatPunchDone() { NatPunchId = natPunchId, ExternalIp = externalIp, ExternalPort = externalPort };
|
||||
|
||||
byte[] messageData = bufferRental.Rent();
|
||||
var len = lobbyNatPunchDone.Serialize(messageData);
|
||||
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
public static IPAddress[] GetIPsByName(string hostName, bool ip4Wanted, bool ip6Wanted)
|
||||
{
|
||||
tcpClient.Stop();
|
||||
// Check if the hostname is already an IPAddress
|
||||
IPAddress? outIpAddress;
|
||||
if (IPAddress.TryParse(hostName, out outIpAddress) == true)
|
||||
return new IPAddress[] { outIpAddress };
|
||||
//<----------
|
||||
|
||||
IPAddress[] addresslist = Dns.GetHostAddresses(hostName);
|
||||
|
||||
if (addresslist == null || addresslist.Length == 0)
|
||||
return new IPAddress[0];
|
||||
//<----------
|
||||
|
||||
if (ip4Wanted && ip6Wanted)
|
||||
return addresslist;
|
||||
//<----------
|
||||
|
||||
if (ip4Wanted)
|
||||
return addresslist.Where(o => o.AddressFamily == AddressFamily.InterNetwork).ToArray();
|
||||
//<----------
|
||||
|
||||
if (ip6Wanted)
|
||||
return addresslist.Where(o => o.AddressFamily == AddressFamily.InterNetworkV6).ToArray();
|
||||
//<----------
|
||||
|
||||
return new IPAddress[0];
|
||||
}
|
||||
|
||||
private void TcpClient_Connected()
|
||||
public void QueryExternalIpAndPort(SendUdpMessageCallback sendUdpCallback)
|
||||
{
|
||||
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.Connected, EventData = null });
|
||||
byte[] messageData = bufferRental.Rent();
|
||||
|
||||
try
|
||||
{
|
||||
waitForExternalIp.Reset();
|
||||
var queryExternalPortAndIp = new QueryExternalPortAndIp() { LobbyClientId = connectionId };
|
||||
var len = queryExternalPortAndIp.Serialize(messageData);
|
||||
var ip = GetIPsByName(host!, true, false).First();
|
||||
|
||||
do
|
||||
{
|
||||
sendUdpCallback(new IPEndPoint(ip, port), messageData, len);
|
||||
}
|
||||
while (!waitForExternalIp.WaitOne(100));
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
bufferRental.Return(messageData);
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
waitForExternalIp.Set();
|
||||
tcpClient.Stop();
|
||||
}
|
||||
|
||||
private void TcpClient_Disconnected(bool clean, string error)
|
||||
@@ -183,38 +254,24 @@ namespace Lobbies
|
||||
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))
|
||||
switch (LobbyMessageIdentifier.ReadLobbyMessageIdentifier(data.Span))
|
||||
{
|
||||
case LobbyClientConnectionInfo.TypeId:
|
||||
{
|
||||
var lobbyClientConnectionInfo = LobbyClientConnectionInfo.Deserialize(data.Span);
|
||||
if (lobbyClientConnectionInfo != null)
|
||||
{
|
||||
connectionId = lobbyClientConnectionInfo.Id;
|
||||
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.Connected, EventData = null });
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LobbyInfo.TypeId:
|
||||
{
|
||||
var lobbyInfo = LobbyInfo.Deserialize(data.Span);
|
||||
@@ -260,6 +317,19 @@ namespace Lobbies
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SeenExternalIpAndPort.TypeId:
|
||||
{
|
||||
var seenExternalIpAndPort = SeenExternalIpAndPort.Deserialize(data.Span);
|
||||
if (seenExternalIpAndPort != null)
|
||||
{
|
||||
externalIp = seenExternalIpAndPort.Ip;
|
||||
externalPort = seenExternalIpAndPort.Port;
|
||||
waitForExternalIp.Set();
|
||||
|
||||
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.ExternalIpAndPort, EventData = seenExternalIpAndPort });
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,8 +338,9 @@ namespace Lobbies
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
tcpClient.Dispose();
|
||||
waitForExternalIp.Dispose();
|
||||
tcpClient.Dispose();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Lobbies
|
||||
namespace Lobbies
|
||||
{
|
||||
public class LobbyClientDisconnectReason
|
||||
{
|
||||
|
||||
@@ -1,27 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Lobbies
|
||||
namespace Lobbies
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains a lobby client event
|
||||
/// </summary>
|
||||
public class LobbyClientEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifier type of the event
|
||||
/// </summary>
|
||||
public LobbyClientEventTypes EventType { get; internal set; }
|
||||
/// <summary>
|
||||
/// Optional data object, contains additional information for the event. See <see cref="LobbyClientEventTypes"/> for possible values.
|
||||
/// </summary>
|
||||
public object? EventData { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The list of events that can occur.
|
||||
/// </summary>
|
||||
public enum LobbyClientEventTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Client connected to lobby server. EventData is null.
|
||||
/// </summary>
|
||||
Connected,
|
||||
/// <summary>
|
||||
/// Client disconnected from lobby server graceful. EventData is <see cref="LobbyClientDisconnectReason"/>.
|
||||
/// </summary>
|
||||
Disconnected,
|
||||
/// <summary>
|
||||
/// Connection lost to lobby server. EventData is LobbyClientDisconnectReason.
|
||||
/// </summary>
|
||||
Failed,
|
||||
/// <summary>
|
||||
/// A lobby was added to the lobby list for the observed game id. EventData is <see cref="LobbyAdd"/> with the lobby data.
|
||||
/// </summary>
|
||||
LobbyAdd,
|
||||
/// <summary>
|
||||
/// A lobby was changed for the observed game id. EventData is <see cref="LobbyUpdate"/> with the lobby data.
|
||||
/// </summary>
|
||||
LobbyUpdate,
|
||||
/// <summary>
|
||||
/// A lobby was removed from the lobby list for the observed game id. EventData is <see cref="LobbyDelete"/> with the lobby id.
|
||||
/// </summary>
|
||||
LobbyDelete,
|
||||
/// <summary>
|
||||
/// The game host shared his self known address. EventData is <see cref="LobbyHostInfo"/> with the games hosts self known address, most likely internal.
|
||||
/// </summary>
|
||||
LobbyHostInfo,
|
||||
/// <summary>
|
||||
/// A nat punch was requested by another LobbyClient to be performed by the host LobbyClient. EventData is <see cref="LobbyRequestNatPunch"/> with the clients seen external address to nat punch to.
|
||||
/// </summary>
|
||||
LobbyRequestNatPunch,
|
||||
/// <summary>
|
||||
/// Game host has finished the nat punch. EventData is <see cref="LobbyRequestNatPunch"/> with the games hosts external address.
|
||||
/// </summary>
|
||||
LobbyNatPunchDone,
|
||||
/// <summary>
|
||||
/// Response to a query of our external ip and port seen by the lobby server. EventData is <see cref="SeenExternalIpAndPort"/> with the clients seen external address.
|
||||
/// </summary>
|
||||
ExternalIpAndPort
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user