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-22 04:05:13 +00:00
public async Task Listen ( CancellationToken ? token = null ) {
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
2022-02-22 04:05:13 +00:00
try {
while ( true ) {
Socket socket = token . HasValue ? await serverSocket . AcceptAsync ( token . Value ) : await serverSocket . AcceptAsync ( ) ;
socket . SetSocketOption ( SocketOptionLevel . Socket , SocketOptionName . NoDelay , true ) ;
2022-02-10 01:44:50 +00:00
2022-02-22 04:05:13 +00:00
Logger . Warn ( $"Accepted connection for client {socket.RemoteEndPoint}" ) ;
2022-02-04 09:45:38 +00:00
2022-02-22 04:05:13 +00:00
try {
Task . Run ( ( ) = > HandleSocket ( socket ) ) ;
2022-03-01 21:08:53 +00:00
}
2022-03-12 02:29:17 +00:00
catch ( Exception e ) {
Logger . Error ( $"Error occured while setting up socket handler? {e}" ) ;
2022-02-22 04:05:13 +00:00
}
2022-02-11 16:16:19 +00:00
}
2022-03-01 21:08:53 +00:00
}
catch ( OperationCanceledException ) {
2022-02-22 04:05:13 +00:00
// ignore the exception, it's just for closing the server
2022-03-01 21:08:53 +00:00
2022-03-12 05:42:51 +00:00
Logger . Info ( "Server closing" ) ;
2022-02-22 04:05:13 +00:00
2022-03-12 05:42:51 +00:00
try {
serverSocket . Shutdown ( SocketShutdown . Both ) ;
2022-03-22 09:20:49 +00:00
}
catch {
2022-03-12 05:42:51 +00:00
// ignored
2022-03-22 09:20:49 +00:00
}
finally {
2022-03-12 05:42:51 +00:00
serverSocket . Close ( ) ;
}
2022-03-01 21:08:53 +00:00
2022-03-12 05:42:51 +00:00
Logger . Info ( "Server closed" ) ;
}
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
2022-03-01 21:08:53 +00:00
2022-02-16 20:33:21 +00:00
public async Task BroadcastReplace < T > ( T packet , Client sender , PacketReplacer < T > packetReplacer ) where T : struct , IPacket {
2022-03-01 21:08:53 +00:00
foreach ( Client client in Clients . Where ( client = > sender . Id ! = client . Id ) ) packetReplacer ( sender , client , packet ) ;
2022-02-16 20:33:21 +00:00
}
2022-03-01 21:08:53 +00:00
2022-02-16 00:35:38 +00:00
public async Task Broadcast < T > ( T packet , Client sender ) where T : struct , IPacket {
2022-03-11 05:59:02 +00:00
IMemoryOwner < byte > memory = MemoryPool < byte > . Shared . RentZero ( Constants . HeaderSize + packet . Size ) ;
2022-02-04 09:45:38 +00:00
PacketHeader header = new PacketHeader {
Id = sender ? . Id ? ? Guid . Empty ,
2022-03-11 05:59:02 +00:00
Type = Constants . PacketMap [ typeof ( T ) ] . Type ,
PacketSize = packet . Size
2022-02-04 09:45:38 +00:00
} ;
FillPacket ( header , packet , memory . Memory ) ;
await Broadcast ( memory , sender ) ;
}
2022-03-15 05:59:23 +00:00
public Task Broadcast < T > ( T packet ) where T : struct , IPacket {
return Task . WhenAll ( Clients . Where ( c = > c . Connected ) . Select ( async client = > {
IMemoryOwner < byte > memory = MemoryPool < byte > . Shared . RentZero ( Constants . HeaderSize + packet . Size ) ;
PacketHeader header = new PacketHeader {
Id = client . Id ,
Type = Constants . PacketMap [ typeof ( T ) ] . Type ,
PacketSize = packet . Size
} ;
FillPacket ( header , packet , memory . Memory ) ;
await client . Send ( memory . Memory , client ) ;
memory . Dispose ( ) ;
} ) ) ;
}
2022-02-04 09:45:38 +00:00
/// <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-03-01 21:08:53 +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 ) {
2022-03-11 05:59:02 +00:00
memory = memoryPool . Rent ( Constants . HeaderSize ) ;
2022-03-22 09:20:49 +00:00
2022-03-12 05:42:51 +00:00
async Task < bool > Read ( Memory < byte > readMem , int readSize , int readOffset ) {
readSize + = readOffset ;
2022-03-11 05:59:02 +00:00
while ( readOffset < readSize ) {
int size = await socket . ReceiveAsync ( readMem [ readOffset . . readSize ] , SocketFlags . None ) ;
2022-02-15 21:17:18 +00:00
if ( size = = 0 ) {
// treat it as a disconnect and exit
Logger . Info ( $"Socket {socket.RemoteEndPoint} disconnected." ) ;
if ( socket . Connected ) await socket . DisconnectAsync ( false ) ;
2022-03-12 01:08:50 +00:00
return false ;
2022-02-15 21:17:18 +00:00
}
readOffset + = size ;
}
2022-03-12 01:08:50 +00:00
return true ;
2022-02-04 09:45:38 +00:00
}
2022-03-12 05:42:51 +00:00
if ( ! await Read ( memory . Memory [ . . Constants . HeaderSize ] , Constants . HeaderSize , 0 ) )
2022-03-13 21:27:53 +00:00
break ;
2022-03-11 05:59:02 +00:00
PacketHeader header = GetHeader ( memory . Memory . Span [ . . Constants . HeaderSize ] ) ;
2022-03-12 05:42:51 +00:00
Range packetRange = Constants . HeaderSize . . ( Constants . HeaderSize + header . PacketSize ) ;
if ( header . PacketSize > 0 ) {
IMemoryOwner < byte > memTemp = memory ; // header to copy to new memory
2022-03-12 01:08:50 +00:00
memory = memoryPool . Rent ( Constants . HeaderSize + header . PacketSize ) ;
2022-03-12 05:42:51 +00:00
memTemp . Memory . Span [ . . Constants . HeaderSize ] . CopyTo ( memory . Memory . Span [ . . Constants . HeaderSize ] ) ;
2022-03-11 05:59:02 +00:00
memTemp . Dispose ( ) ;
2022-03-12 05:42:51 +00:00
if ( ! await Read ( memory . Memory , header . PacketSize , Constants . HeaderSize ) )
2022-03-13 21:27:53 +00:00
break ;
2022-03-11 05:59:02 +00:00
}
2022-03-12 05:42:51 +00:00
2022-03-13 11:14:01 +00:00
// if (header.Type is not PacketType.Player and not PacketType.Cap and not PacketType.Capture)Logger.Info($"Got your mom {header.Id} {header.Type} 0x{header.PacketSize:X} 0x{memory.Memory.Length:X} 0x{header.Size:X}");
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-03-12 05:42:51 +00:00
connect . Deserialize ( memory . Memory . Span [ packetRange ] ) ;
2022-02-04 09:45:38 +00:00
lock ( Clients ) {
2022-03-06 23:19:49 +00:00
client . Name = connect . ClientName ;
2022-03-22 09:20:49 +00:00
if ( Clients . Count ( x = > x . Connected ) = = Settings . Instance . Server . MaxPlayers ) {
2022-03-22 09:17:48 +00:00
client . Logger . Error ( $"Turned away as server is at max clients" ) ;
memory . Dispose ( ) ;
goto disconnect ;
}
2022-03-22 09:20:49 +00:00
2022-02-04 09:45:38 +00:00
bool firstConn = false ;
switch ( connect . ConnectionType ) {
2022-03-13 11:14:01 +00:00
case ConnectPacket . ConnectionTypes . FirstConnection : {
2022-02-04 09:45:38 +00:00
firstConn = true ;
2022-03-22 09:17:48 +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 ;
}
2022-03-22 09:20:49 +00:00
2022-02-04 09:45:38 +00:00
break ;
}
2022-03-13 11:14:01 +00:00
case ConnectPacket . ConnectionTypes . Reconnecting : {
2022-02-11 16:16:19 +00:00
client . Id = header . Id ;
2022-02-04 09:45:38 +00:00
if ( FindExistingClient ( header . Id ) is { } newClient ) {
2022-03-15 21:37:23 +00:00
if ( newClient . Connected ) throw new Exception ( $"Tried to join as already connected user {header.Id}" ) ;
2022-02-04 09:45:38 +00:00
newClient . Socket = client . Socket ;
client = newClient ;
} else {
firstConn = true ;
2022-03-13 11:14:01 +00:00
connect . ConnectionType = ConnectPacket . 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-03-11 05:59:02 +00:00
IMemoryOwner < byte > tempBuffer = MemoryPool < byte > . Shared . RentZero ( Constants . HeaderSize + ( other . CurrentCostume . HasValue ? Math . Max ( connect . Size , other . CurrentCostume . Value . Size ) : connect . Size ) ) ;
2022-02-10 01:44:50 +00:00
PacketHeader connectHeader = new PacketHeader {
2022-02-10 01:42:46 +00:00
Id = other . Id ,
2022-03-11 05:59:02 +00:00
Type = PacketType . Connect ,
PacketSize = connect . Size
2022-02-10 01:42:46 +00:00
} ;
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-03-13 11:14:01 +00:00
ConnectionType = ConnectPacket . 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-03-11 05:59:02 +00:00
await client . Send ( tempBuffer . Memory [ . . ( Constants . HeaderSize + connect . Size ) ] , 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 ;
2022-03-11 05:59:02 +00:00
connectHeader . PacketSize = other . CurrentCostume . Value . Size ;
2022-02-11 04:25:47 +00:00
MemoryMarshal . Write ( tempBuffer . Memory . Span , ref connectHeader ) ;
2022-03-11 05:59:02 +00:00
other . CurrentCostume . Value . Serialize ( tempBuffer . Memory . Span [ Constants . HeaderSize . . ( Constants . HeaderSize + connectHeader . PacketSize ) ] ) ;
await client . Send ( tempBuffer . Memory [ . . ( Constants . HeaderSize + connectHeader . PacketSize ) ] , null ) ;
2022-02-11 04:25:47 +00:00
}
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-03-01 21:08:53 +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-03-11 05:59:02 +00:00
costumePacket . Deserialize ( memory . Memory . Span [ Constants . HeaderSize . . ( Constants . HeaderSize + costumePacket . Size ) ] ) ;
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-03-11 05:59:02 +00:00
packet . Deserialize ( memory . Memory . Span [ Constants . HeaderSize . . ( Constants . HeaderSize + packet . Size ) ] ) ;
2022-02-16 20:33:21 +00:00
if ( PacketHandler ? . Invoke ( client , packet ) is false ) {
memory . Dispose ( ) ;
continue ;
}
2022-03-01 21:08:53 +00:00
}
catch ( Exception e ) {
2022-02-16 00:35:38 +00:00
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-03-01 21:08:53 +00:00
}
catch ( Exception e ) {
if ( e is SocketException { SocketErrorCode : SocketError . ConnectionReset } ) {
2022-03-12 02:16:10 +00:00
client . Logger . Info ( $"Disconnected from the server: Connection reset" ) ;
2022-02-04 09:45:38 +00:00
} else {
2022-03-12 02:16:10 +00:00
client . Logger . Error ( $"Disconnecting due to exception: {e}" ) ;
2022-02-15 20:26:50 +00:00
if ( socket . Connected ) Task . Run ( ( ) = > socket . DisconnectAsync ( false ) ) ;
2022-02-04 09:45:38 +00:00
}
memory ? . Dispose ( ) ;
}
2022-03-22 09:20:49 +00:00
2022-03-22 09:17:48 +00:00
disconnect :
2022-03-12 02:16:10 +00:00
Logger . Info ( $"Client {socket.RemoteEndPoint} ({client.Name}/{client.Id}) disconnected from the server" ) ;
2022-02-04 09:45:38 +00:00
2022-03-15 20:44:46 +00:00
// Clients.Remove(client)
client . Connected = false ;
2022-03-12 05:42:51 +00:00
try {
client . Dispose ( ) ;
2022-03-22 09:20:49 +00:00
}
catch { /*lol*/
}
2022-02-08 16:15:31 +00:00
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 ) ;
}
2022-03-22 09:20:49 +00:00
}