461 lines
19 KiB
C#
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"); |