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:
- un errore di comunicazione indica inequivocabilmente
una caduta del server o un partizionamento della rete che
renda la comunicazione al server non più possibile
- 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:
- il
RoomServerAgent
o il ServerAgent
che ha individuato l'errore invoca serverDisconnected(URL
del server caduto) sul suo MainServer
- 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
- 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)
- 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:
- 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.
- Simula la terminazione di tutte le partite
in esecuzione ospitate sul server caduto
- 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
|