2022-02-04 09:45:38 +00:00
|
|
|
|
using System.Buffers;
|
|
|
|
|
using System.Net;
|
|
|
|
|
using System.Net.Sockets;
|
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
using Shared;
|
|
|
|
|
using Shared.Packet;
|
|
|
|
|
using Shared.Packet.Packets;
|
|
|
|
|
|
|
|
|
|
namespace Server;
|
|
|
|
|
|
|
|
|
|
public class Server {
|
|
|
|
|
public readonly List<Client> Clients = new List<Client>();
|
|
|
|
|
public readonly Logger Logger = new Logger("Server");
|
2022-02-10 01:44:50 +00:00
|
|
|
|
private readonly MemoryPool<byte> memoryPool = MemoryPool<byte>.Shared;
|
2022-02-14 19:45:58 +00:00
|
|
|
|
public event Action<Client, IPacket> PacketHandler = null!;
|
|
|
|
|
public event Action<Client, ConnectPacket> ClientJoined = null!;
|
2022-02-10 01:44:50 +00:00
|
|
|
|
|
2022-02-04 09:45:38 +00:00
|
|
|
|
public async Task Listen(ushort port) {
|
|
|
|
|
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
2022-02-14 19:45:58 +00:00
|
|
|
|
serverSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
2022-02-08 22:46:12 +00:00
|
|
|
|
serverSocket.Bind(new IPEndPoint(IPAddress.Any, port));
|
2022-02-04 09:45:38 +00:00
|
|
|
|
serverSocket.Listen();
|
2022-02-10 01:44:50 +00:00
|
|
|
|
|
2022-02-04 09:45:38 +00:00
|
|
|
|
Logger.Info($"Listening on port {port}");
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
Socket socket = await serverSocket.AcceptAsync();
|
2022-02-10 01:44:50 +00:00
|
|
|
|
|
2022-02-04 09:45:38 +00:00
|
|
|
|
Logger.Warn("ok");
|
|
|
|
|
|
2022-02-11 16:16:19 +00:00
|
|
|
|
try {
|
|
|
|
|
if (Clients.Count > Constants.MaxClients) {
|
|
|
|
|
Logger.Warn("Turned away client due to max clients");
|
|
|
|
|
await socket.DisconnectAsync(false);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2022-02-04 09:45:38 +00:00
|
|
|
|
|
2022-02-11 16:16:19 +00:00
|
|
|
|
HandleSocket(socket);
|
|
|
|
|
} catch {
|
|
|
|
|
// super ignore this
|
|
|
|
|
}
|
2022-02-04 09:45:38 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void FillPacket<T>(PacketHeader header, T packet, Memory<byte> memory) where T : unmanaged, IPacket {
|
|
|
|
|
Span<byte> data = memory.Span;
|
|
|
|
|
|
|
|
|
|
MemoryMarshal.Write(data, ref header);
|
|
|
|
|
MemoryMarshal.Write(data[Constants.HeaderSize..], ref packet);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// broadcast packets to all clients
|
2022-02-11 04:25:47 +00:00
|
|
|
|
public async Task Broadcast<T>(T packet, Client sender) where T : unmanaged, IPacket {
|
2022-02-10 01:52:33 +00:00
|
|
|
|
IMemoryOwner<byte> memory = memoryPool.Rent(Constants.MaxPacketSize);
|
2022-02-04 09:45:38 +00:00
|
|
|
|
|
|
|
|
|
PacketHeader header = new PacketHeader {
|
|
|
|
|
Id = sender?.Id ?? Guid.Empty,
|
2022-02-10 04:29:10 +00:00
|
|
|
|
Type = Constants.PacketMap[typeof(T)].Type
|
2022-02-04 09:45:38 +00:00
|
|
|
|
};
|
|
|
|
|
FillPacket(header, packet, memory.Memory);
|
|
|
|
|
await Broadcast(memory, sender);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2022-02-10 01:44:50 +00:00
|
|
|
|
/// Takes ownership of data and disposes once done.
|
2022-02-04 09:45:38 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="data">Memory owner to dispose once done</param>
|
|
|
|
|
/// <param name="sender">Optional sender to not broadcast data to</param>
|
|
|
|
|
public async Task Broadcast(IMemoryOwner<byte> data, Client? sender = null) {
|
2022-02-10 08:42:35 +00:00
|
|
|
|
await Task.WhenAll(Clients.Where(c => c.Connected && c != sender).Select(client => client.Send(data.Memory, sender)));
|
2022-02-04 09:45:38 +00:00
|
|
|
|
data.Dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2022-02-10 01:44:50 +00:00
|
|
|
|
/// Broadcasts memory whose memory shouldn't be disposed, should only be fired by server code.
|
2022-02-04 09:45:38 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="data">Memory to send to the clients</param>
|
|
|
|
|
/// <param name="sender">Optional sender to not broadcast data to</param>
|
|
|
|
|
public async void Broadcast(Memory<byte> data, Client? sender = null) {
|
2022-02-10 08:42:35 +00:00
|
|
|
|
await Task.WhenAll(Clients.Where(c => c.Connected && c != sender).Select(client => client.Send(data, sender)));
|
2022-02-04 09:45:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Client? FindExistingClient(Guid id) {
|
|
|
|
|
return Clients.Find(client => client.Id == id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private async void HandleSocket(Socket socket) {
|
2022-02-10 08:42:35 +00:00
|
|
|
|
Client client = new Client { Socket = socket, Server = this };
|
2022-02-04 09:45:38 +00:00
|
|
|
|
IMemoryOwner<byte> memory = null!;
|
|
|
|
|
bool first = true;
|
|
|
|
|
try {
|
|
|
|
|
while (true) {
|
|
|
|
|
memory = memoryPool.Rent(Constants.MaxPacketSize);
|
|
|
|
|
int size = await socket.ReceiveAsync(memory.Memory, SocketFlags.None);
|
2022-02-10 01:44:50 +00:00
|
|
|
|
if (size == 0) {
|
|
|
|
|
// treat it as a disconnect and exit
|
2022-02-04 09:45:38 +00:00
|
|
|
|
Logger.Info($"Socket {socket.RemoteEndPoint} disconnected.");
|
|
|
|
|
await socket.DisconnectAsync(false);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PacketHeader header = GetHeader(memory.Memory.Span[..size]);
|
|
|
|
|
//Logger.Info($"first = {first}, type = {header.Type}, data = " + memory.Memory.Span[..size].Hex());
|
|
|
|
|
// connection initialization
|
|
|
|
|
if (first) {
|
|
|
|
|
first = false;
|
2022-02-10 01:44:50 +00:00
|
|
|
|
if (header.Type != PacketType.Connect) throw new Exception($"First packet was not init, instead it was {header.Type}");
|
2022-02-04 09:45:38 +00:00
|
|
|
|
|
|
|
|
|
ConnectPacket connect = MemoryMarshal.Read<ConnectPacket>(memory.Memory.Span[Constants.HeaderSize..size]);
|
|
|
|
|
lock (Clients) {
|
|
|
|
|
bool firstConn = false;
|
|
|
|
|
switch (connect.ConnectionType) {
|
|
|
|
|
case ConnectionTypes.FirstConnection: {
|
|
|
|
|
firstConn = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case ConnectionTypes.Reconnecting: {
|
2022-02-11 16:16:19 +00:00
|
|
|
|
client.Id = header.Id;
|
2022-02-14 19:45:58 +00:00
|
|
|
|
client.Name = connect.ClientName;
|
2022-02-04 09:45:38 +00:00
|
|
|
|
if (FindExistingClient(header.Id) is { } newClient) {
|
|
|
|
|
if (newClient.Connected) throw new Exception($"Tried to join as already connected user {header.Id}");
|
|
|
|
|
newClient.Socket = client.Socket;
|
|
|
|
|
client = newClient;
|
|
|
|
|
} else {
|
|
|
|
|
firstConn = true;
|
2022-02-14 19:45:58 +00:00
|
|
|
|
connect.ConnectionType = ConnectionTypes.FirstConnection;
|
2022-02-04 09:45:38 +00:00
|
|
|
|
}
|
2022-02-10 01:44:50 +00:00
|
|
|
|
|
2022-02-04 09:45:38 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
throw new Exception($"Invalid connection type {connect.ConnectionType}");
|
|
|
|
|
}
|
2022-02-09 21:56:57 +00:00
|
|
|
|
|
|
|
|
|
client.Connected = true;
|
2022-02-04 09:45:38 +00:00
|
|
|
|
if (firstConn) {
|
|
|
|
|
// do any cleanup required when it comes to new clients
|
|
|
|
|
List<Client> toDisconnect = Clients.FindAll(c => c.Id == header.Id && c.Connected && c.Socket != null);
|
|
|
|
|
Clients.RemoveAll(c => c.Id == header.Id);
|
2022-02-10 01:44:50 +00:00
|
|
|
|
|
2022-02-04 09:45:38 +00:00
|
|
|
|
client.Id = header.Id;
|
|
|
|
|
Clients.Add(client);
|
|
|
|
|
|
|
|
|
|
Parallel.ForEachAsync(toDisconnect, (c, token) => c.Socket!.DisconnectAsync(false, token));
|
|
|
|
|
// done disconnecting and removing stale clients with the same id
|
2022-02-14 19:45:58 +00:00
|
|
|
|
|
|
|
|
|
ClientJoined?.Invoke(client, connect);
|
2022-02-04 09:45:38 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-02-10 01:44:50 +00:00
|
|
|
|
|
2022-02-10 01:42:46 +00:00
|
|
|
|
List<Client> otherConnectedPlayers = Clients.FindAll(c => c.Id != header.Id && c.Connected && c.Socket != null);
|
|
|
|
|
await Parallel.ForEachAsync(otherConnectedPlayers, async (other, _) => {
|
2022-02-11 04:25:47 +00:00
|
|
|
|
IMemoryOwner<byte> tempBuffer = MemoryPool<byte>.Shared.Rent(Constants.MaxPacketSize);
|
2022-02-10 01:44:50 +00:00
|
|
|
|
PacketHeader connectHeader = new PacketHeader {
|
2022-02-10 01:42:46 +00:00
|
|
|
|
Id = other.Id,
|
|
|
|
|
Type = PacketType.Connect
|
|
|
|
|
};
|
2022-02-11 04:25:47 +00:00
|
|
|
|
MemoryMarshal.Write(tempBuffer.Memory.Span, ref connectHeader);
|
2022-02-10 01:44:50 +00:00
|
|
|
|
ConnectPacket connectPacket = new ConnectPacket {
|
2022-02-10 01:42:46 +00:00
|
|
|
|
ConnectionType = ConnectionTypes.FirstConnection // doesn't matter what it is :)
|
|
|
|
|
};
|
2022-02-11 04:25:47 +00:00
|
|
|
|
MemoryMarshal.Write(tempBuffer.Memory.Span[Constants.HeaderSize..], ref connectPacket);
|
|
|
|
|
await client.Send(tempBuffer.Memory, null);
|
2022-02-12 23:37:03 +00:00
|
|
|
|
if (other.CurrentCostume.HasValue) {
|
2022-02-11 04:25:47 +00:00
|
|
|
|
connectHeader.Type = PacketType.Costume;
|
|
|
|
|
MemoryMarshal.Write(tempBuffer.Memory.Span, ref connectHeader);
|
2022-02-12 23:37:03 +00:00
|
|
|
|
other.CurrentCostume.Value.Serialize(tempBuffer.Memory.Span[Constants.HeaderSize..]);
|
2022-02-11 04:25:47 +00:00
|
|
|
|
await client.Send(tempBuffer.Memory, null);
|
|
|
|
|
}
|
2022-02-14 19:45:58 +00:00
|
|
|
|
|
2022-02-11 04:25:47 +00:00
|
|
|
|
tempBuffer.Dispose();
|
2022-02-10 01:42:46 +00:00
|
|
|
|
});
|
2022-02-10 01:44:50 +00:00
|
|
|
|
|
2022-02-09 21:48:05 +00:00
|
|
|
|
Logger.Info($"Client {socket.RemoteEndPoint} ({client.Id}) connected.");
|
2022-02-04 09:45:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-02-11 04:25:47 +00:00
|
|
|
|
if (header.Type == PacketType.Costume) {
|
2022-02-12 23:37:03 +00:00
|
|
|
|
CostumePacket costumePacket = new CostumePacket {
|
|
|
|
|
BodyName = ""
|
|
|
|
|
};
|
|
|
|
|
costumePacket.Deserialize(memory.Memory.Span[Constants.HeaderSize..]);
|
|
|
|
|
client.CurrentCostume = costumePacket;
|
2022-02-11 04:25:47 +00:00
|
|
|
|
}
|
2022-02-14 19:45:58 +00:00
|
|
|
|
|
2022-02-04 09:45:38 +00:00
|
|
|
|
await Broadcast(memory, client);
|
|
|
|
|
}
|
2022-02-10 01:44:50 +00:00
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
if (e is SocketException { SocketErrorCode: SocketError.ConnectionReset }) {
|
2022-02-09 21:48:05 +00:00
|
|
|
|
Logger.Info($"Client {socket.RemoteEndPoint} ({client.Id}) disconnected from the server");
|
2022-02-04 09:45:38 +00:00
|
|
|
|
} else {
|
2022-02-09 21:48:05 +00:00
|
|
|
|
Logger.Error($"Exception on socket {socket.RemoteEndPoint} ({client.Id}) and disconnecting for: {e}");
|
2022-02-08 16:15:31 +00:00
|
|
|
|
Task.Run(() => socket.DisconnectAsync(false));
|
2022-02-04 09:45:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
memory?.Dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Clients.Remove(client);
|
2022-02-08 16:15:31 +00:00
|
|
|
|
client.Dispose();
|
|
|
|
|
Task.Run(() => Broadcast(new DisconnectPacket(), client));
|
2022-02-04 09:45:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static PacketHeader GetHeader(Span<byte> data) {
|
|
|
|
|
//no need to error check, the client will disconnect when the packet is invalid :)
|
|
|
|
|
return MemoryMarshal.Read<PacketHeader>(data);
|
|
|
|
|
}
|
2021-11-29 04:04:34 +00:00
|
|
|
|
}
|