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-16 00:35:38 +00:00
public Func < Client , IPacket , bool > ? PacketHandler = null ! ;
2022-02-14 19:45:58 +00:00
public event Action < Client , ConnectPacket > ClientJoined = null ! ;
2022-02-10 01:44:50 +00:00
2022-02-18 01:56:12 +00:00
public async Task Listen ( ) {
2022-02-04 09:45:38 +00:00
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-18 01:56:12 +00:00
serverSocket . Bind ( new IPEndPoint ( IPAddress . Parse ( Settings . Instance . Server . Address ) , Settings . Instance . Server . Port ) ) ;
2022-02-04 09:45:38 +00:00
serverSocket . Listen ( ) ;
2022-02-10 01:44:50 +00:00
2022-02-18 01:56:12 +00:00
Logger . Info ( $"Listening on {serverSocket.LocalEndPoint}" ) ;
2022-02-04 09:45:38 +00:00
while ( true ) {
Socket socket = await serverSocket . AcceptAsync ( ) ;
2022-02-16 20:33:21 +00:00
socket . SetSocketOption ( SocketOptionLevel . Socket , SocketOptionName . NoDelay , true ) ;
2022-02-10 01:44:50 +00:00
2022-02-15 20:26:50 +00:00
Logger . Warn ( $"Accepted connection for client {socket.RemoteEndPoint}" ) ;
2022-02-04 09:45:38 +00:00
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-15 20:26:50 +00:00
Task . Run ( ( ) = > HandleSocket ( socket ) ) ;
2022-02-11 16:16:19 +00:00
} catch {
// super ignore this
}
2022-02-04 09:45:38 +00:00
}
}
2022-02-16 00:35:38 +00:00
public static void FillPacket < T > ( PacketHeader header , T packet , Memory < byte > memory ) where T : struct , IPacket {
2022-02-04 09:45:38 +00:00
Span < byte > data = memory . Span ;
MemoryMarshal . Write ( data , ref header ) ;
2022-02-14 22:07:42 +00:00
packet . Serialize ( data [ Constants . HeaderSize . . ] ) ;
2022-02-04 09:45:38 +00:00
}
// broadcast packets to all clients
2022-02-16 20:33:21 +00:00
public delegate void PacketReplacer < in T > ( Client from , Client to , T value ) ; // replacer must send
public async Task BroadcastReplace < T > ( T packet , Client sender , PacketReplacer < T > packetReplacer ) where T : struct , IPacket {
foreach ( Client client in Clients . Where ( client = > sender . Id ! = client . Id ) ) {
packetReplacer ( sender , client , packet ) ;
}
}
2022-02-16 00:35:38 +00:00
public async Task Broadcast < T > ( T packet , Client sender ) where T : struct , IPacket {
2022-02-16 21:20:03 +00:00
IMemoryOwner < byte > memory = memoryPool . RentZero ( 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-15 20:26:50 +00:00
Client client = new Client ( 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 ) ;
2022-02-15 21:17:18 +00:00
{
int readOffset = 0 ;
while ( readOffset < Constants . MaxPacketSize ) {
int size = await socket . ReceiveAsync ( memory . Memory [ readOffset . . Constants . MaxPacketSize ] , SocketFlags . None ) ;
if ( size = = 0 ) {
// treat it as a disconnect and exit
Logger . Info ( $"Socket {socket.RemoteEndPoint} disconnected." ) ;
if ( socket . Connected ) await socket . DisconnectAsync ( false ) ;
break ;
}
readOffset + = size ;
}
if ( readOffset < Constants . MaxPacketSize ) break ;
2022-02-04 09:45:38 +00:00
}
2022-02-15 21:17:18 +00:00
PacketHeader header = GetHeader ( memory . Memory . Span [ . . Constants . MaxPacketSize ] ) ;
2022-02-16 20:33:21 +00:00
2022-02-04 09:45:38 +00:00
// 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
2022-02-14 20:40:42 +00:00
ConnectPacket connect = new ConnectPacket ( ) ;
2022-02-15 21:17:18 +00:00
connect . Deserialize ( memory . Memory . Span [ Constants . HeaderSize . . Constants . MaxPacketSize ] ) ;
2022-02-04 09:45:38 +00:00
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-16 21:20:03 +00:00
IMemoryOwner < byte > tempBuffer = MemoryPool < byte > . Shared . RentZero ( 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-15 20:26:50 +00:00
ConnectionType = ConnectionTypes . FirstConnection , // doesn't matter what it is :)
2022-02-15 20:39:34 +00:00
ClientName = other . Name
2022-02-10 01:42:46 +00:00
} ;
2022-02-14 22:06:49 +00:00
connectPacket . Serialize ( tempBuffer . Memory . Span [ Constants . HeaderSize . . ] ) ;
2022-02-11 04:25:47 +00:00
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-15 20:26:50 +00:00
Logger . Info ( $"Client {client.Name} ({client.Id}/{socket.RemoteEndPoint}) connected." ) ;
2022-02-16 20:33:21 +00:00
} else if ( header . Id ! = client . Id & & client . Id ! = Guid . Empty ) throw new Exception ( $"Client {client.Name} sent packet with invalid client id {header.Id} instead of {client.Id}" ) ;
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 = ""
} ;
2022-02-16 21:20:03 +00:00
costumePacket . Deserialize ( memory . Memory . Span [ Constants . HeaderSize . . Constants . MaxPacketSize ] ) ;
2022-02-12 23:37:03 +00:00
client . CurrentCostume = costumePacket ;
2022-02-11 04:25:47 +00:00
}
2022-02-14 19:45:58 +00:00
2022-02-14 21:17:36 +00:00
try {
2022-02-15 20:26:50 +00:00
// if (header.Type is not PacketType.Cap and not PacketType.Player) client.Logger.Warn($"lol {header.Type}");
2022-02-14 20:40:42 +00:00
IPacket packet = ( IPacket ) Activator . CreateInstance ( Constants . PacketIdMap [ header . Type ] ) ! ;
2022-02-16 21:20:03 +00:00
packet . Deserialize ( memory . Memory . Span [ Constants . HeaderSize . . Constants . MaxPacketSize ] ) ;
2022-02-16 20:33:21 +00:00
if ( PacketHandler ? . Invoke ( client , packet ) is false ) {
memory . Dispose ( ) ;
continue ;
}
2022-02-16 00:35:38 +00:00
} catch ( Exception e ) {
client . Logger . Error ( $"Packet handler warning: {e}" ) ;
2022-02-14 20:40:42 +00:00
}
2022-02-15 20:26:50 +00:00
Broadcast ( memory , client ) ;
2022-02-04 09:45:38 +00:00
}
2022-02-10 01:44:50 +00:00
} catch ( Exception e ) {
if ( e is SocketException { SocketErrorCode : SocketError . ConnectionReset } ) {
2022-02-15 20:26:50 +00:00
client . Logger . Info ( $"Client {socket.RemoteEndPoint} ({client.Id}) disconnected from the server" ) ;
2022-02-04 09:45:38 +00:00
} else {
2022-02-15 20:26:50 +00:00
client . Logger . Error ( $"Exception on socket {socket.RemoteEndPoint} ({client.Id}) and disconnecting for: {e}" ) ;
if ( socket . Connected ) 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
}