Indice - Progetto di Reti di Calcolatori - Fabio Adani e Marco Chiesi
Progetto

Caduta di un client

Se un client cade improvvisamente o si ha una frattura della rete che renda la comunicazione tra il client ed il server a cui è connesso impossibile, i servers dovranno tenerne conto e togliere il client tra quelli connessi, per permettere anche una sua eventuale riconnessione con lo stesso nick alla rete.
A livello di server vogliamo impostare una serie di azioni di modo che l'effetto finale di una caduta di un client sia il medesimo che si avrebbe se il client stesso avesse fatto richiesta di disconnessione.

Il Server si accorge della caduta di un client quando rileva un errore di comunicazione. Quindi un'eccezione originata da un problema di comunicazione viene interpretata come una abbandono forzato del sistema da parte del client ed esso viene così forzatamente disconnesso.
Siccome la comunicazione con un client è sempre filtrata dalla classe ClientAgent, è proprio essa che se ne accorge per prima. Una volta rilevata l'eccezione il ClientAgent invoca, mediante la funzione disconnectClient(), il metodo clientDisconnected() sul MainServer locale.
Il MainServer a questo punto dovrà provvedere a fare pulizia riguardo al client e comunicare la sua disconnessione agli altri server. In pratica esso simulerà la ricezione da parte del client del comando logout() e di conseguenza farà uscire il client, in sequenza, dalle eventuali partite a cui è iscritto, dall'eventuale stanza in cui è ed infine dal sistema. I primi due passaggi sono gestiti direttamente dalla classe ClientAgent (nel metodo leave()), perché è essa che ha, istante per istante, lo stato del client sul server.

Si è visto come in condizioni normali, cioè non di guasto, se un client si disconnette mentre sta giocando una partita, il RoomServer fa in modo di invocare sul client di gioco, attraverso MainClient, il metodo callFinishMatch() dell'interfaccia IGameClient. Tramite questa invocazione si chiede al client di gioco di terminare la partita. La semantica di questo metodo sarà diversa naturalmente da gioco a gioco.
Nel caso di una caduta di un client non è evidentemente possibile nessuna invocazione di questo tipo. Occorre quindi che anche il server di gioco si accorga separatamente della caduta del client e prenda le opportune contromosse. Alcuni giochi potrebbero consentire il proseguimento della partita anche senza un giocatore (cfr. nostro Risiko), mentre altri, per esempio praticamente tutti i giochi di uno contro uno, terminerebbero forzatamente la partita (cfr. nostro PingGame)

Caduta di un server

La caduta di un server comporta conseguenze pesanti a livello di sistema. In sostanza non solo il MainServer e tutti i RoomServer del server caduto dovranno essere tolti dalla rete, ma anche tutti i clienti che erano connessi a quel server saranno forzatamente esclusi dal sistema.
Ciò che si vuole ottenere come effetto della caduta di un server è lo stesso che si avrebbe se avvenissero in sequenza le seguenti operazioni:

  • ogni utente locale al server esce dalla partita in esecuzione o in definizione
  • ogni utente locale al server esce dalla stanza di gioco in cui si trova
  • ogni utente locale al server abbandona il sistema
  • ogni RoomServer si disconnette dalla sua rete
  • il MainServer si disconnette dalla sua rete

Inoltre si vuole realizzare questo obiettivo effettuando il minor numero possibile di chiamate remote e naturalmente mantenendo la consistenza degli stati su tutti i server rimanenti.

Similmente a quanto avviene per la caduta di un client, la caduta di un server è per prima individuata da un RoomServerAgent o da un ServerAgent, in seguito ad un errore di comunicazione verso di esso. Queste sono infatti le classi che usano rispettivamente i RoomServer ed i MainServer per comunicare tra di loro. Le ipotesi severe che si sono fatte in questo progetto sono le seguenti:

  1. un errore di comunicazione indica inequivocabilmente una caduta del server o un partizionamento della rete che renda la comunicazione al server non più possibile
  2. la caduta di un RoomServer implica la caduta di tutti gli altri RoomServer e del MainServer dello stesso server NetGame

Il flusso di elaborazione che avviene in seguito alla rilevazione di una caduta è il seguente:

  1. il RoomServerAgent o il ServerAgent che ha individuato l'errore invoca serverDisconnected(URL del server caduto) sul suo MainServer
  2. il MainServerAgent invoca removeServer(URL del server caduto) su se stesso e su tutti i server remoti

Il metodo MainServer.removeServer() ha la seguente struttura

  1. individua tutti gli utenti remoti connessi a quel nodo (questa informazione è mantenuta a livello MainServer, ma non a livello di RoomServer, per questo è necessario individuare tali utenti ora per poi poterli passare ai RoomServer nelle invocazioni successive)
  2. su tutti i RoomServer locali invoca removeServer(url del server caduto, lista degli utenti del server)

Infine, il metodo RoomServer.removeServer(url del server caduto, lista degli utenti del server) effettua le seguenti operazioni:

  1. Per ogni utente della lista verifica se è nella stanza. Se sì lo elimina dalla eventuale partita a cui è iscritto e lo rimuove dalla lista degli utenti remoti.
  2. Simula la terminazione di tutte le partite in esecuzione ospitate sul server caduto
  3. Elimina il server caduto dalla lista dei server remoti

La minimizzazione delle comunicazioni si è ottenuta comunicando solo ai MainServer e non a tutti i RoomServer l'evento di disconnessione. Cioè, come si è visto, la distribuzione dell'informazione avviene a livello di MainServer e la sua trattazione per ottenere la semantica specificata avviene poi localmente ai vari MainServer e RoomServer.

Gestione ad eventi

Nel momento in cui sia stata rilevata la disconnessione di un client o di un server, com'è stato appena spiegato, è necessario modificare alcune strutture dati contenute sul server per effettuare l'opportuna pulizia dello stato, ovvero l'eliminazione dei server e dei client non più presenti. Visto che tali strutture dati sono condivise e possono essere accedute da più thread contemporaneamente, si deve fare attenzione nel momento in cui si effettuano delle modifiche. Questo problema è stato risolto facendo uso di lock. In questo caso però l'uso dei lock non è sufficiente poichè se il thread che ha rilevato la disconnessione è lo stesso che esegue la pulizia dello stato del server (questo è possibile visto che possiede già i lock necessari sulle strutture dati da modificare), può accadere che durante la modifica si lascino le strutture dati in uno stato inconsistente. Si pensi al caso del dispatcher, il quale non fa altro che eseguire dei cicli scandendo alcune strutture dati per distibuire gli eventi. Se durante uno di questi cicli la struttura dati viene modificata, il ciclo potrebbe anche non essere completata correttamente.

Pertanto, anche qui è stata adottata una soluzione ad eventi per fare in modo disaccoppiare il rilevamento della disconnessione dalla effettiva pulizia delle strutture dati. In pratica un thread che rileva una disconnessione metterà nella coda di eventi del server un opportuno evento che indica ciò che è accaduto (ClientDisconnectedEvent oppure ServerDisconnectedEvent) a seconda e sarà il gestore degli eventi ad occuparsi della effettiva modifica delle strutture dati in un secondo momento, senza interferire con il thread che aveva inizialmente rilevato il problema.

Caduta di un server di utenti

Può succedere che uno UserDBServer cada o comunque non sia più reperibile. In tal caso si vuole che tutti i MainServer che si appoggiavano a lui per l’autenticazione degli utenti rifiutino ulteriori richieste di connessione, fintanto che la comunicazione con il server è ristabilita. Per fare questo si è adottato il seguente protocollo:

  • All'avvio il MainServer tenta di connettersi al server di autenticazione
  • Nel caso non vi riesca, l'attivazione del MainServer fallisce
  • Nel momento in cui un utente tenta di accedere alla rete (login)
    • Se il server di autenticazione è connesso, il MainServer lo interroga per sapere se l'utente può accedere
    • Altrimenti tenta di riaprire una connessione con il server di autenticazione
    • Se si verifica un errore di comunicazione la connessione verrà chiusa fino al successivo tentativo
Indietro Inizio pagina Avanti
Indice   Fabio Adani e Marco Chiesi