Organizzazione delle classi
L'organizzazione delle classi che deve
essere rispettata da ogni applicazione che voglia integrarsi
in NetGame come modulo di gioco è rappresentata graficamente
nella seguente figura. Vi è un vincolo anche sui nomi delle
classi (per permettere a NetGame di caricare automaticamente
il gioco), in particolare la figura si riferisce al caso in
cui il nome del gioco è MyGame
Le classi in grigio scuro fanno parte
o del framework NetGame (IGameServer ,
IGame , IGameClient )
o direttamente di quello Java (UnicastRemoteObject ).
UnicastRemoteObject
viene utilizzato come classe di partenza per implementare
oggetti remoti.
IGame
è un'interfaccia remota e introduce metodi necessari al RoomServer
per acquisire informazioni riguardo al gioco che ospita. In
particolare :
getMinPlayers()
e getMaxPlayers()
per ottenere rispettivamente il numero minimo e massimo di
giocatori
getName() ,
getVersion()
e getAuthor()
per ottenere rispettivamente il nome del gioco, la versione
e il nome dell'autore
getSize() per
ottenere la dimensione in KByte del gioco
IGameServer
estende l'interfaccia IGame ,
aggiungendo ad essa i metodi direttamente invocabili dal RoomServer
per impostare il gioco, iniziare una partita e finirla:
setRoom(<RoomServer
ospitante>) per collegare questa istanza del Server
del gioco al RoomServer
specificato
<IGameServer>
newInstance() per ottenere una nuova istanza del Server
del gioco. Quando un RoomServer
dovrà avviare una nuova partita lo farà creando innanzitutto
una nuova istanza, ed assegnandone ad essa la gestione. Nel
caso si voglia un unico server di gioco per ogni partita in
un RoomServer ,
si dovrà implementare questo metodo (a livello di MyGameServer )
in modo che non crei effettivamente una nuova istanza, ma
ritorni semplicemente se stessa. Questo metodo si rende necessario
in quanto non sarebbe altrimenti possibile uniformare tale
passaggio di parametri utilizzando semplicemente i costruttori
usando delle interfacce.
start(<nome della
partita>, <lista degli utenti>) per avviare
la partita
finish() per
finire la partita. Questo metodo, come vedremo, sarà normalmente
invocato dai clients
Anche IGameClient
estende l'interfaccia IGame
e ad essa aggiunge i seguenti metodi, necessari per la comunicazione
con il server di gioco :
finish()
impone al client di terminare la partita in corso
callFinish()
chiede al client di mettere in atto le operazioni per terminare
la partita.
Vi è differenza, seppur sottile tra
questi due metodi. Il primo è generalmente invocato dal server
di gioco per imporre ai client la fine della partita. Il secondo
è invece tipicamente invocato da altri componenti di NetGame
(per esempio da MainClient )
nel caso si renda necessario terminare la partita in corso.
Sviluppo di
un gioco
A questo punto, inserendosi in questa
struttura di classi, lo sviluppatore di un nuovo gioco NetGame-compatibile
a livello di interfaccia dovrà realizzare i seguenti componenti
(sempre riferendosi al caso MyGame )
- Classe
MyGame .
Essa implementerà i metodi di IGame ,
esplicitando quindi parametri importanti quali il numero
massimo e minimo di giocatori ammessi
- Interfaccia
IMyGameServer .
Essa estenderà IGameServer
e presenterà in più i metodi specifici dell'applicazione,
invocabili dai clients sul server
- Interfaccia
IMyGameClient .
Essa estenderà IGameClient
e presenterà in più i metodi specifici dell'applicazione
invocabili dal server sui clients
- Classe
MyGameServer ,
che implementerà l'interfaccia IMyGameServer
- Classe
MyGameClient ,
che implementerà l'interfaccia IMyGameClient
Chiaramente ciò non significa che l'applicazione
deve rimanere ristretta a queste classi. Si impone solamente
che esse siano presenti, con tali nomi e soddisfino questi
vincoli. In particolare si è lasciata piena libertà allo sviluppatore
riguardo al modello di comunicazione del gioco: si potrà adottare
quello più adatto al caso in questione, scegliendo eventualmente
tra un modello peer-to-peer (in cui il server interviene inizialmente
e solamente organizzando in modo opportuno le informazioni),
un modello client-server (in cui il server invece interviene
più attivamente nel gioco, eventualmente conservandone lui
solo lo stato) e modelli misti.
Di fatto il server di gioco ha tutte le informazioni necessarie,
possedendo le interfacce remote (il che significa, utilizzando
le RMI, gli stub) delle classi client del gioco, e come si
è visto queste classi le si può implementare come si vuole
di modo da ottenere tutte le informazioni necessarie.
Inoltre, come si è già accennatto, non è nemmeno obbligatorio
che ogni partita sia gestita separatamente da diverse istanze
di MyGameServer ,
infatti semplicemente implementando il metodo newInstance()
in modo che ritorni sè stesso (return
this ) e avendo cura di gestire separatamente le diverse
partite, si può tranquillamente fare in modo che vi sia per
ogni RoomServer
un unico GameServer attivo.
Avvio e terminazione
di una partita
Vediamo ora come avvengono l'avvio di
una partita e la sua terminazione.
Una partita ha inizio quando l'utente che l'ha creata (o comunque
il primo iscritto) ne richiede l'inizio e se tutto le condizioni
necessarie sono verificate (corretto numero di giocatori,
nessuna interferenza non ammissibile durante il protocollo
2PC). In tal caso il RoomServer
che dovrà ospitare la partita, e cioè quello a cui è connesso
il cliente che l'ha iniziata effettuerà in sequenza le seguenti
azioni :
- recupererà tutti gli
IGameClient
dei giocatori coinvolti (per gli utenti remoti dovrà naturalmente
rivolgersi ai corrispondenti RoomServer )
- creerà una nuova istanza del
GameServer
- inserirà tale istanza nella tabella delle
partite ospitate
- inizierà la partita invocando il metodo
start() sulla
nuova istanza creata
In realtà questo ultimo passaggio è
delicato, perché se il metodo start()
della classe server del gioco è male implementato o comunque
non prevede di ritornare prima della fine della partita, si
avrebbe un blocco del RoomServer .
Per questo in realtà l'avvio della partita è realizzato mediante
una classe accessoria, GameStarter ,
che rappresenta un Thread ed il cui compito è semplicemente
quello di eseguire il quarto punto.
Una volta avviata la partita il RoomServer
non se ne interessa più finché non termina. Un RoomServer
considera una partita finita quando viene invocato su di esso
il metodo finishMatch()
da parte del server del gioco. Quando questo avviene, il RoomServer
la toglierà dalla lista delle partite ospitate e comunicherà
l'evento, come al solito, ai clients locali ed ai servers
remoti.
Evidentemente ogni gioco terminerà in seguito a condizioni
particolari, ed eventualmente bruscamente in seguito ad errori
di comunicazione. Comunque sia, la chiamata finale, dopo aver
compiuto le opportune polizie e liberato le risorse acquisite,
è l'invocazione del metodo RoomServer.finishMatch() .
Si noti come i MainClient
vengano a sapere della terminazione della partita in corso
attraverso il RoomServer
a cui sono connessi, in quanto non c'è nessuna invocazione
diretta tra il GameClient
ed il RoomClient
che lo contiene.
Acquisizione
dinamica di giochi
Uno dei requisiti del sistema che si
sta progettando è quello di dare agli utenti la possibilità
di scaricare dal server nuovi giochi e di poterli quindi eseguire
una volta scaricati. Si è quindi in presenza di una
tecnologia COD, cioè Code On Demand, in cui il codice
eseguibile viene scaricato ed eseguito sul client. Per evitare
di dover scaricare ogni volta, si prevede che i moduli di
gioco che vengono man mano scaricati siano salvati sul file
system del nodo client per potere essere ritrovati alla successiva
esecuzione.
Per realizzare questa possibilità
è quindi necessario prevedere sia sul server che sul
client due componenti appositi utilizzati per il trasferimento
del file contenente il gioco dal server al client (visto che
l'ambiente è Java, ogni modulo sarà costituito
da un file JAR) che sono rispettivamente FileServer
e FileClient .
Il FileServer
rimane in attesa di richieste provenienti dai FileClient ,
richieste che consistono appunto nella richiesta di file che
sono presenti sul nodo server. Si tratta insomma di una sorta
di piccolo FTP.
Si può quindi riassumere il funzionamento
del client in questo modo:
- All'avvio il client controlla quali giochi sono installati
sul suo nodo (in un'ooprtuna cartella del suo file system)
- All'atto della connessione ad un server, il client richiede
al server la lista dei giochi presenti sul server
- Quando il giocatore tenta di entrare nella stanza di un
gioco
- Se il gioco è presente sia sul client che sul
server l'utente ha accesso immediato alla stanza
- Se il gioco è presente sul server ma non sul
client (oppure se la versione sul server è più
aggiornata), viene scaricato il client di quel gioco,
dopodichè l'utente accede alla stanza
- Se il gioco è presente sul client ma non sul
server, l'utente non può accedere alla stanza
Dal lato server quindi sarà necessario che l'amministratore
installi sia i moduli dei server dei giochi (che sono effettivamente
eseguiti sul nodo server) che i moduli dei client dei giochi
in modo che possano essere scaricati da parte dei client.
|