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

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)

  1. Classe MyGame. Essa implementerà i metodi di IGame, esplicitando quindi parametri importanti quali il numero massimo e minimo di giocatori ammessi
  2. Interfaccia IMyGameServer. Essa estenderà IGameServer e presenterà in più i metodi specifici dell'applicazione, invocabili dai clients sul server
  3. Interfaccia IMyGameClient. Essa estenderà IGameClient e presenterà in più i metodi specifici dell'applicazione invocabili dal server sui clients
  4. Classe MyGameServer, che implementerà l'interfaccia IMyGameServer
  5. 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 :

  1. recupererà tutti gli IGameClient dei giocatori coinvolti (per gli utenti remoti dovrà naturalmente rivolgersi ai corrispondenti RoomServer)
  2. creerà una nuova istanza del GameServer
  3. inserirà tale istanza nella tabella delle partite ospitate
  4. 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.

Indietro Inizio pagina Avanti
Indice   Fabio Adani e Marco Chiesi