Esiste un modello per scrivere un server a turni che comunica con n client su socket?


14

Sto lavorando su un server di gioco generico che gestisce i giochi per un numero arbitrario di client TCP collegati in rete durante una partita. Ho un "design" hackerato con un nastro isolante che funziona, ma sembra fragile e non flessibile. Esiste un modello consolidato per la scrittura di comunicazioni client / server che sia solido e flessibile? (In caso contrario, come miglioreresti quello che ho di seguito?)

All'incirca ho questo:

  • Durante l'impostazione di un gioco, il server ha un thread per ogni socket del giocatore che gestisce le richieste sincrone da un client e le risposte dal server.
  • Una volta che il gioco procede, tuttavia, tutti i thread tranne un sonno, e quel thread scorre tutti i giocatori uno alla volta comunicando la loro mossa (in una richiesta-risposta inversa).

Ecco un diagramma di quello che ho attualmente; fare clic per una versione più grande / leggibile o PDF da 66 kB .

Diagramma sequenziale del flusso

I problemi:

  • Richiede ai giocatori di rispondere esattamente a turno con esattamente il messaggio giusto. (Suppongo che potrei lasciare che ogni giocatore risponda con una merda casuale e andare avanti solo quando mi danno una mossa valida.)
  • Non consente ai giocatori di parlare con il server a meno che non sia il loro turno. (Potrei avere il server inviare loro aggiornamenti su altri giocatori, ma non elaborare una richiesta asincrona.)

Requisiti finali:

  • Le prestazioni non sono fondamentali. Questo sarà usato principalmente per i giochi non in tempo reale, e principalmente per mettere gli IA l'uno contro l'altro, non per gli umani nervosi.

  • Il gioco sarà sempre a turni (anche se con una risoluzione molto alta). Ogni giocatore ottiene sempre una mossa elaborata prima che tutti gli altri giocatori facciano un turno.

L'implementazione del server sembra essere in Ruby, se questo fa la differenza.


Se si utilizza un socket TCP per client, questo limite non dovrebbe (65535-1024) client?
o0 '.

1
Perché è un problema, Lohoris? Molte persone faranno fatica a ottenere 6000 utenti simultanei, non importa 60000.
Kylotan,

@Kylotan Indeed. Se avrò più di 10 IA simultanei rimarrò sorpreso. :)
Phrogz,

Per curiosità, quale strumento hai usato per creare quel diagramma?
Matthias,

@matthias Purtroppo, era troppo lavoro personalizzato in Adobe Illustrator.
Phrogz,

Risposte:


10

Non sono sicuro di cosa tu voglia ottenere esattamente. Ma c'è un modello che viene utilizzato costantemente nei server di gioco e può aiutarti. Usa le code dei messaggi.

Per essere più specifici: quando i client inviano messaggi al server, non elaborarli immediatamente. Piuttosto, analizzali e mettili in coda per questo specifico client. Quindi, in alcuni loop principali (forse anche su un altro thread), passare a turno tutti i client, recuperare i messaggi dalla coda ed elaborarli. Quando l'elaborazione indica che il turno di questo client è terminato, passa al successivo.

In questo modo, i clienti non sono tenuti a lavorare rigorosamente turn-by-turn; solo abbastanza velocemente da avere qualcosa in coda prima che il client venga elaborato (puoi ovviamente aspettare il client o saltare il suo turno se è in ritardo). E puoi aggiungere il supporto per le richieste asincrone aggiungendo una coda "asincrona": quando un client invia una richiesta speciale, questa viene aggiunta a questa coda speciale; questa coda viene verificata ed elaborata più spesso di quella dei client.


1

I thread hardware non si adattano abbastanza bene da rendere "uno per giocatore" un'idea ragionevole per un numero di giocatori a 3 cifre, e la logica di sapere quando svegliarli è una complessità che crescerà. Un'idea migliore è quella di trovare un pacchetto I / O asincrono per Ruby che ti consenta di inviare e ricevere dati senza dover mettere in pausa un intero thread del programma mentre ha luogo l'operazione di lettura o scrittura. Questo risolve anche il problema di aspettare che i giocatori rispondano, poiché non avrai discussioni sospese su un'operazione di lettura. Invece il tuo server può semplicemente controllare se un limite di tempo è scaduto e quindi notificare l'altro giocatore di conseguenza.

Fondamentalmente "I / O asincrono" è lo "schema" che stai cercando, sebbene non sia realmente uno schema, più un approccio. Invece di chiamare esplicitamente 'read' su un socket e mettere in pausa il programma fino all'arrivo dei dati, si imposta il sistema per chiamare il proprio gestore 'onRead' ogni volta che ha i dati pronti e si continua l'elaborazione fino a quel momento.

ogni presa ha un turno

Ogni giocatore ha un turno e ogni giocatore ha un socket che invia i dati, che è un po 'diverso. Un giorno potresti non desiderare un socket per giocatore. Potresti non usare affatto i socket. Mantenere separate le aree di responsabilità. Scusate se questo sembra un dettaglio non importante, ma quando confondete concetti nel vostro progetto che dovrebbero essere diversi, allora diventa più difficile trovare e discutere approcci migliori.


1

Esiste sicuramente più di un modo per farlo, ma personalmente salterei completamente i thread separati e utilizzerei solo un loop di eventi. Il modo in cui lo fai dipenderà in qualche modo dalla libreria I / O che stai utilizzando, ma sostanzialmente il tuo loop del server principale sarà simile al seguente:

  1. Configurare un pool di connessioni e una presa di ascolto per le nuove connessioni.
  2. Aspetta che succeda qualcosa.
  3. Se qualcosa è una nuova connessione, aggiungila al pool.
  4. Se qualcosa è una richiesta di un client, controlla se è qualcosa che puoi gestire immediatamente. Se sì, fallo; in caso contrario, inseriscilo in una coda e (facoltativamente) invia un riconoscimento al client.
  5. Controlla anche se c'è qualcosa nella coda che puoi gestire ora; in tal caso, fallo.
  6. Tornare al passaggio 2.

Ad esempio, supponiamo che tu abbia n clienti coinvolti in un gioco. Quando poi il primo n − 1 di loro invia le loro mosse, basta controllare che la mossa appaia valida e rispedire un messaggio dicendo che la mossa è stata ricevuta ma stai ancora aspettando che altri giocatori si muovano. Dopo che tutti e n i giocatori si sono mossi, elabori tutte le mosse che hai salvato e invii i risultati a tutti i giocatori.

È inoltre possibile perfezionare questo schema per includere i timeout: la maggior parte delle librerie di I / O dovrebbe avere un meccanismo di attesa fino all'arrivo di nuovi dati o al termine di un determinato periodo di tempo.

Ovviamente, potresti anche implementare qualcosa del genere con thread individuali per ogni connessione, facendo in modo che quei thread trasmettano tutte le richieste che non possono gestire direttamente a un thread centrale (uno per gioco o uno per server) che esegue un ciclo come mostrato sopra, tranne per il fatto che parla con i thread del gestore connessioni piuttosto che direttamente con i client. Se trovi questo più semplice o più complicato dell'approccio a thread singolo dipende davvero da te.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.