2010-11-18 4 views
5

Sto scrivendo un gioco multipiattaforma con funzionalità di rete (utilizzando SFML e RakNet) e sono arrivato al punto in cui ho compilato il server sul mio server Ubuntu e ho avviato un client sul mio Mac. Tutto lo sviluppo è fatto sul mio Mac, quindi inizialmente ho testato il server su questo, e ha funzionato bene.Creazione e utilizzo di una struttura multipiattaforma in C++

Sto inviando struct s in rete e quindi semplicemente li restituisco da char * a (ad esempio) inet::PlayerAdded. Ora questo ha funzionato bene (per la maggior parte), ma la mia domanda è: funzionerà sempre? Sembra un approccio molto fragile. La struttura sarà sempre la stessa anche su altre piattaforme, Windows, ad esempio? Cosa raccomanderesti?

#pragma pack(push, 1) 
struct Player 
{ 
    int dir[2]; 
    int left; 
    float depth; 
    float elevation; 
    float velocity[2]; 
    char character[50]; 
    char username[50]; 
}; 

// I have been added to the game and my ID is back 
struct PlayerAdded: Packet 
{ 
    id_type id; 
    Player player; 
}; 
#pragma pack(pop) 
+2

Per il downvoter seriale, qualche motivo particolare? (Il downvote di Steve sembra particolarmente eclatante) – KevinDTimm

+0

I punti non hanno senso in un posto come questo. Non me ne preoccuperei. –

+1

Non mi importa dei punti, voglio sapere perché ogni risposta (tranne una, la meno utile di tutte) ha avuto un downvote. Soprattutto quando una delle risposte (downvoted) era chiaramente migliore. – KevinDTimm

risposta

5

Come molte altre risposte, sconsiglio di inviare dati binari non elaborati se è possibile evitarlo. Qualcosa come Boost seriale o Google Protobuf farà un buon lavoro senza troppi sovraccarichi.

Ma è certamente possibile creare strutture binarie multipiattaforma, è fatto tutto il tempo ed è un modo molto valido per lo scambio di dati. Stratificare una "struttura" su quei dati ha senso. Devi comunque stare molto attento al layout, fortunatamente la maggior parte dei compilatori ti offre molte opzioni per farlo. "pack" è una di queste opzioni e si prende cura di molte cose.

È inoltre necessario fare attenzione alle dimensioni dei dati. Semplice includere stdint.h e utilizzare i tipi di dimensione fissa come uint32_t. Prestare attenzione ai valori in virgola mobile poiché non tutte le architetture condivideranno lo stesso valore, per il float a 32 bit probabilmente lo fanno. Anche per endianess la maggior parte delle architetture useranno lo stesso, e se non lo fanno si può semplicemente capovolgerlo sul client che è diverso.

+0

Anche se la maggior parte delle risposte sono state davvero eccezionali, penso che questo sia quello che mi ha aiutato di più. Penso che farò un tentativo, e se mai colpirò una barriera con questo metodo saprò cosa usare. – ErikPerik

9

questo non funzionerà se (tra le altre cose) si tenta di farlo dalla macchina little-endian a big-endian macchina come la corretta rappresentazione int sarà invertito tra i due.

Questo potrebbe anche fallire se l'allineamento o l'imballaggio della struttura cambia da macchina a macchina. Cosa succede se si dispone di alcune macchine a 64 bit e alcune a 32 bit?

È necessario utilizzare un'adeguata libreria di serializzazione portatile come Boost.Serialization o Google Protocol Buffers per assicurarsi di disporre di un protocollo filo (ovvero formato di dati trasmissibili) che può essere decodificato correttamente indipendentemente dall'hardware.

Una volta la cosa bella dei buffer di protocollo è che è possibile comprimere i dati in modo trasparente utilizzando un flusso compatibile con ZLIB che è anche compatibile con i flussi di protobuf. L'ho fatto davvero, funziona bene. Immagino che altri flussi di decoratore possano essere usati in modo analogo per migliorare o ottimizzare il protocollo di base del filo secondo necessità.

2

La risposta a "... disposto lo stesso anche su altre piattaforme ..." è generalmente no. Questo è così anche se vengono affrontati problemi come CPU e/o endianness differenti.

Diversi sistemi operativi (anche sulla stessa piattaforma hardware) potrebbero utilizzare diverse rappresentazioni di dati; questo è normalmente chiamato "piattaforma ABI" ed è diverso tra ad es. Windows a 32 bit/64 bit, Linux a 32 bit/64 bit, MacOSX.

'#pragma pack' è solo metà strada, perché oltre le restrizioni di allineamento possono esserci differenze di dimensione del tipo di dati. Ad esempio, "long" su Windows a 64 bit è a 32 bit mentre è a 64 bit su Linux e MacOSX.

Detto questo, il problema ovviamente non è nuovo ed è già stato risolto in passato: la procedura remota chiamata standard (RPC) contiene meccanismi per definire le strutture di dati in modo indipendente dalla piattaforma e come codificare/decodificare "buffer" che rappresentano queste strutture. Si chiama "XDR" (eXternal Data Representation). Vedi RFC1832. Come da programmazione, questa ruota è stata reinventata più volte; che tu converta in XML, fai il livello basso con XDR, usa google :: protobuf, boost o Qt :: Variant, c'è molto da scegliere.

Su un lato puramente di implementazione: per semplicità, basti pensare che "unsigned int" ovunque sia allineato a 32 bit a un limite di 32 bit; se puoi codificare tutti i tuoi dati come array di valori a 32 bit, l'unico problema di esternalizzazione che devi affrontare è endianness.