LobbyServer/LobbyServer/Program.cs

461 lines
19 KiB
C#

using LobbyServer;
using LobbyServerDto;
using System.Collections.Concurrent;
using System.Net;
using var closing = new AutoResetEvent(false);
using var tcpServer = new TcpServer();
using var udpServer = new UdpCandidateServer();
ConcurrentDictionary<Guid, Lobby> lobbiesById = new ConcurrentDictionary<Guid, Lobby>();
ConcurrentDictionary<Guid, List<Lobby>> lobbiesByGameId = new ConcurrentDictionary<Guid, List<Lobby>>();
ConcurrentDictionary<int, Lobby> lobbiesByClientId = new ConcurrentDictionary<int, Lobby>();
ConcurrentDictionary<int, Guid> clientWatchingGameIdLobbies = new ConcurrentDictionary<int, Guid>();
ConcurrentDictionary<Guid, List<int>> clientsWatchingGameId = new ConcurrentDictionary<Guid, List<int>>();
BufferRental bufferRental = new BufferRental(4096);
udpServer.QueryIpAndPort += (clientId, ip, port) =>
{
var messageData = bufferRental.Rent();
var seenExternalIpAndPort = new SeenExternalIpAndPort() { Ip = ip, Port = port };
var messageDataLength = seenExternalIpAndPort.Serialize(messageData);
_ = Task.Run(async () =>
{
await tcpServer.Send(clientId, messageData, 0, messageDataLength);
bufferRental.Return(messageData);
});
};
tcpServer.DataReceived += (clientId, dataLength, data) =>
{
if (dataLength > 0)
{
switch (LobbyMessageIdentifier.ReadLobbyMessageIdentifier(data.Span))
{
case LobbyCreate.TypeId:
{
var lobbyCreate = LobbyCreate.Deserialize(data.Span);
if (lobbyCreate != null)
{
if (!GameGuids.ValidGuids.Contains(lobbyCreate.GameId))
throw new Exception("Invalid game guid!");
List<IPAddress> hostIpAddresses = new List<IPAddress>();
hostIpAddresses.Add(IPAddress.Parse(tcpServer.GetClientIp(clientId)!));
if(lobbyCreate.HostIps != null)
hostIpAddresses.AddRange(lobbyCreate.HostIps);
var lobby = new Lobby
{
Name = lobbyCreate.Name,
GameId = lobbyCreate.GameId,
GameMode = lobbyCreate.GameMode,
PlayerCount = lobbyCreate.PlayerCount,
MaxPlayerCount = lobbyCreate.MaxPlayerCount,
PasswordHash = lobbyCreate.PasswordHash,
PasswordSalt = lobbyCreate.PasswordSalt,
HostClientId = clientId,
HostIps = hostIpAddresses.ToArray(),
HostPort = lobbyCreate.HostPort,
HostTryPort = lobbyCreate.HostTryPort
};
if(lobbiesByClientId.TryGetValue(clientId, out var existingLobby))
{
var lobbyDelete = LobbyDelete.Deserialize(data.Span);
if (lobbyDelete != null)
{
if (lobbiesByClientId.TryRemove(clientId, out var _))
{
if (lobbiesById.TryRemove(existingLobby.Id, out var _))
{
if (lobbiesByGameId.TryGetValue(existingLobby.GameId, out var lobbyList))
{
lock (lobbyList)
{
lobbyList.Remove(existingLobby);
}
}
_ = Task.Run(() => SendLobbyUpdate(Lobby.LobbyUpdateType.Delete, existingLobby));
}
}
}
}
if (lobbiesByClientId.TryAdd(clientId, lobby))
{
lobbiesById.TryAdd(lobby.Id, lobby);
lobbiesByGameId.AddOrUpdate(lobby.GameId, new List<Lobby>() { lobby }, (key, lobbyList) =>
{
lock (lobbyList)
{
lobbyList.Add(lobby);
return lobbyList;
}
});
}
_ = Task.Run(() => SendLobbyUpdate(Lobby.LobbyUpdateType.Add, lobby));
}
}
break;
case LobbyUpdate.TypeId:
{
var lobbyUpdate = LobbyUpdate.Deserialize(data.Span);
if (lobbyUpdate != null)
{
if (lobbiesByClientId.TryGetValue(clientId, out var existingLobby))
{
existingLobby.Name = lobbyUpdate.Name;
existingLobby.GameMode = lobbyUpdate.GameMode;
existingLobby.PlayerCount = lobbyUpdate.PlayerCount;
existingLobby.MaxPlayerCount = lobbyUpdate.MaxPlayerCount;
existingLobby.PasswordHash = lobbyUpdate.PasswordHash;
existingLobby.PasswordSalt = lobbyUpdate.PasswordSalt;
List<IPAddress> hostIpAddresses = new List<IPAddress>();
hostIpAddresses.Add(IPAddress.Parse(tcpServer.GetClientIp(clientId)!));
if (lobbyUpdate.HostIps != null)
hostIpAddresses.AddRange(lobbyUpdate.HostIps);
if (!Enumerable.SequenceEqual(existingLobby.HostIps, hostIpAddresses))
existingLobby.HostIps = hostIpAddresses.ToArray();
existingLobby.HostPort = lobbyUpdate.HostPort;
existingLobby.HostTryPort = lobbyUpdate.HostTryPort;
_ = Task.Run(() => SendLobbyUpdate(Lobby.LobbyUpdateType.Update, existingLobby));
}
}
}
break;
case LobbyDelete.TypeId:
{
var lobbyDelete = LobbyDelete.Deserialize(data.Span);
if (lobbyDelete != null)
{
if (lobbiesByClientId.TryRemove(clientId, out var existingLobby))
{
if (lobbiesById.TryRemove(existingLobby.Id, out var _))
{
if (lobbiesByGameId.TryGetValue(existingLobby.GameId, out var lobbyList))
{
lock (lobbyList)
{
lobbyList.Remove(existingLobby);
}
}
_ = Task.Run(() => SendLobbyUpdate(Lobby.LobbyUpdateType.Delete, existingLobby));
}
}
}
}
break;
case LobbiesObserve.TypeId:
{
var lobbyObserve = LobbiesObserve.Deserialize(data.Span);
if (lobbyObserve != null)
{
if (!GameGuids.ValidGuids.Contains(lobbyObserve.GameId))
throw new Exception("Invalid game guid!");
if (clientWatchingGameIdLobbies.TryAdd(clientId, lobbyObserve.GameId))
{
clientsWatchingGameId.AddOrUpdate(lobbyObserve.GameId, new List<int>() { clientId }, (key, clientWatchingLobbyList) =>
{
lock (clientWatchingLobbyList)
{
clientWatchingLobbyList.Add(clientId);
return clientWatchingLobbyList;
}
});
if (lobbiesByGameId.TryGetValue(lobbyObserve.GameId, out var lobbyList))
{
lock (lobbyList)
{
_ = Task.Run(() => SendLobbiesToClient(clientId, lobbyList.ToList()));
}
}
}
}
}
break;
case LobbiesStopObserve.TypeId:
{
var lobbyStopObserve = LobbiesStopObserve.Deserialize(data.Span);
if (lobbyStopObserve != null)
{
if (clientWatchingGameIdLobbies.TryRemove(clientId, out var gameGuid))
{
if (clientsWatchingGameId.TryGetValue(gameGuid, out var clientWatchingLobbyList))
{
lock (clientWatchingLobbyList)
{
clientWatchingLobbyList.Remove(clientId);
}
}
}
}
}
break;
case LobbyRequestHostInfo.TypeId:
{
var lobbyRequestHostInfo = LobbyRequestHostInfo.Deserialize(data.Span);
if(lobbyRequestHostInfo != null)
{
if(lobbiesById.TryGetValue(lobbyRequestHostInfo.LobbyId, out var lobby))
{
if(lobby.PasswordHash != null)
{
if(lobbyRequestHostInfo.PasswordHash == null || !lobby.PasswordHash.SequenceEqual(lobbyRequestHostInfo.PasswordHash))
{
var messageData = bufferRental.Rent();
var lobbyWrongPassword = new LobbyWrongPassword() { LobbyId = lobby.Id };
var messageDataLength = lobbyWrongPassword.Serialize(messageData);
_ = Task.Run(async () => {
await tcpServer.Send(clientId, messageData, 0, messageDataLength);
bufferRental.Return(messageData);
});
return;
}
}
{
var messageData = bufferRental.Rent();
var lobbyHostInfo = new LobbyHostInfo() { LobbyId = lobby.Id, HostIps = lobby.HostIps, HostPort = lobby.HostPort, HostTryPort = lobby.HostTryPort };
var messageDataLength = lobbyHostInfo.Serialize(messageData);
_ = Task.Run(async () =>
{
await tcpServer.Send(clientId, messageData, 0, messageDataLength);
bufferRental.Return(messageData);
});
}
}
else
{
var messageData = bufferRental.Rent();
var lobbyNotFound = new LobbyNotFound() { LobbyId = lobbyRequestHostInfo.LobbyId };
var messageDataLength = lobbyNotFound.Serialize(messageData);
_ = Task.Run(async () => {
await tcpServer.Send(clientId, messageData, 0, messageDataLength);
bufferRental.Return(messageData);
});
return;
}
}
}
break;
case LobbyRequestNatPunch.TypeId:
{
var lobbyRequestNatPunch = LobbyRequestNatPunch.Deserialize(data.Span);
if (lobbyRequestNatPunch != null)
{
if (lobbiesById.TryGetValue(lobbyRequestNatPunch.LobbyId, out var lobby))
{
if (lobby.PasswordHash != null)
{
if (lobbyRequestNatPunch.PasswordHash == null || !lobby.PasswordHash.SequenceEqual(lobbyRequestNatPunch.PasswordHash))
{
var messageData = bufferRental.Rent();
var lobbyWrongPassword = new LobbyWrongPassword() { LobbyId = lobby.Id };
var messageDataLength = lobbyWrongPassword.Serialize(messageData);
_ = Task.Run(async () => {
await tcpServer.Send(clientId, messageData, 0, messageDataLength);
bufferRental.Return(messageData);
});
return;
}
}
{
if (string.IsNullOrEmpty(lobbyRequestNatPunch.ClientIp))
{
lobbyRequestNatPunch.ClientIp = tcpServer.GetClientIp(clientId)!;
}
lobbyRequestNatPunch.NatPunchId = clientId;
var messageData = bufferRental.Rent();
var messageDataLength = lobbyRequestNatPunch.Serialize(messageData);
_ = Task.Run(async () =>
{
await tcpServer.Send(lobby.HostClientId, messageData, 0, messageDataLength);
bufferRental.Return(messageData);
});
}
}
}
}
break;
case LobbyNatPunchDone.TypeId:
{
var lobbyNatPunchDone = LobbyNatPunchDone.Deserialize(data.Span);
if (lobbyNatPunchDone != null)
{
if (lobbiesByClientId.TryGetValue(clientId, out var lobby))
{
lobbyNatPunchDone.LobbyId = lobby.Id;
var messageData = bufferRental.Rent();
var messageDataLength = lobbyNatPunchDone.Serialize(messageData);
_ = Task.Run(async () =>
{
await tcpServer.Send(lobbyNatPunchDone.NatPunchId, messageData, 0, messageDataLength);
bufferRental.Return(messageData);
});
}
}
}
break;
}
}
};
tcpServer.ClientDisconnected += (clientId) =>
{
if(clientWatchingGameIdLobbies.TryRemove(clientId, out var gameId))
{
if(clientsWatchingGameId.TryGetValue(gameId, out var clientWatchingLobbyList))
{
lock(clientWatchingLobbyList)
{
clientWatchingLobbyList.Remove(clientId);
}
}
}
if (lobbiesByClientId.TryRemove(clientId, out var lobby))
{
lobbiesById.TryRemove(lobby.Id, out var _);
if(lobbiesByGameId.TryGetValue(lobby.GameId, out var lobbyList))
{
lock(lobbyList)
{
lobbyList.Remove(lobby);
}
}
_ = Task.Run(() => SendLobbyUpdate(Lobby.LobbyUpdateType.Delete, lobby));
if (clientsWatchingGameId.TryGetValue(gameId, out var clientWatchingLobbyList))
{
lock (clientWatchingLobbyList)
{
clientWatchingLobbyList.Remove(clientId);
}
}
}
};
async Task SendLobbyUpdate(Lobby.LobbyUpdateType lobbyUpdateType, Lobby lobby)
{
byte[] messageData = bufferRental.Rent();
try
{
if (clientsWatchingGameId.TryGetValue(lobby.GameId, out var clientWatchingLobbyList))
{
int messageDataLength = 0;
switch (lobbyUpdateType)
{
case Lobby.LobbyUpdateType.Add:
case Lobby.LobbyUpdateType.Update:
var lobbyInfo = new LobbyInfo
{
Id = lobby.Id,
Name = lobby.Name,
GameMode = lobby.GameMode,
MaxPlayerCount = lobby.MaxPlayerCount,
PlayerCount = lobby.PlayerCount,
PasswordProtected = lobby.PasswordHash != null && lobby.PasswordHash.Length > 0,
PasswordSalt = lobby.PasswordSalt
};
messageDataLength = lobbyInfo.Serialize(messageData);
break;
case Lobby.LobbyUpdateType.Delete:
var lobbyDelete = new LobbyDelete { Id = lobby.Id, GameId = lobby.GameId };
messageDataLength = lobbyDelete.Serialize(messageData);
break;
}
List<Task> tasks = new List<Task>();
if (messageDataLength != 0)
{
lock (clientWatchingLobbyList)
{
foreach (var watchingClientId in clientWatchingLobbyList)
{
tasks.Add(tcpServer.Send(watchingClientId, messageData, 0, messageDataLength));
}
}
}
await Task.WhenAll(tasks);
}
}
finally
{
bufferRental.Return(messageData);
}
}
async Task SendLobbiesToClient(int clientId, List<Lobby> lobbies)
{
byte[] messageData = bufferRental.Rent();
try
{
foreach (var lobby in lobbies)
{
var lobbyInfo = new LobbyInfo
{
Id = lobby.Id,
Name = lobby.Name,
GameMode = lobby.GameMode,
MaxPlayerCount = lobby.MaxPlayerCount,
PlayerCount = lobby.PlayerCount,
PasswordProtected = lobby.PasswordHash != null && lobby.PasswordHash.Length > 0,
PasswordSalt = lobby.PasswordSalt,
};
var messageDataLength = lobbyInfo.Serialize(messageData);
await tcpServer.Send(clientId, messageData, 0, messageDataLength);
}
}
finally
{
bufferRental.Return(messageData);
}
}
Console.CancelKeyPress += (sender, args) =>
{
Console.WriteLine($"{DateTime.Now}: Closing application");
tcpServer.Stop();
closing.Set();
args.Cancel = true;
};
Console.WriteLine($"{DateTime.Now}: Application started v0.8");
udpServer.Start(8088);
tcpServer.Start(8088);
closing.WaitOne();
Console.WriteLine($"{DateTime.Now}: Application closed");