Skip to content

Applicativo client-server riguardante la gestione delle prenotazioni di un fast-food, realizzato interamente utilizzando Java e JavaRMI.

Notifications You must be signed in to change notification settings

zanderic/terminale-fastfood

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 

Repository files navigation

Gestione Prenotazioni Fast Food

Introduzione

L’obiettivo di questa relazione è spiegare come si è proceduto nella realizzazione di un progetto client-server riguardante la gestione delle ordinazioni in un fast-food.
Per una maggior organizzazione, abbiamo scelto di separare il lavoro in due grandi blocchi: una parte riguarda la cucina con i relativi cuochi, mentre l’altra si occupa della sala con i tavoli e i camerieri. Entrambe le componenti fanno riferimento ad un’interfaccia comune, i cui metodi sono stati implementati in classi apposite e richiamati tutti nella generica classe Metodi. Quindi l’interfaccia è stata arricchita durante la stesura del codice di tutti i metodi necessari per un corretto funzionamento del progetto, i quali sono stati implementati nella classe Cucina (se riguardavo la cucina) e nella classe Sala (se riguardavano la sala). Ognuno di questi viene richiamato nella classe Metodi, unica classe che estende UnicasteRemoteObjects e implementa l’interfaccia. Da come si può intuire quindi, la nostra scelta progettuale si è basata sull’utilizzo di Java RMI.
Ovviamente la comunicazione tra camerieri e cucina ruota tutta intorno a delle liste. L’accesso a queste liste è esclusivo, ciò significa che se ad esempio un cameriere inserisce un elemento in una coda condivisa, nessun’altro potrà accedervi finché non finisce di eseguire tale operazione. Con Java è possibile implementare tale meccanismo utilizzando il comando synchronized, definito nell’intestazione del metodo. Viene quindi simulata una delle questioni più importanti nei sistemi moderni: la concorrenza.
Dopo aver compilato tutti i file sorgente, è possibile eseguire i vari client (cuochi e camerieri) su più macchine diverse che si appoggiano alla principale, contentente il server.

UTILIZZO DEL PROGRAMMA

  1. Scompattare l'archivio ProgettoSO.zip
  2. Compilare tutti i file .java in ProgettoSO/src/server
  3. Compilare tutti i file .java in ProgettoSO/src/client
  4. Avviamento Server: lanciare il comando "java Server" in ProgettoSO/src/server
  5. Avviamento Client ClientCuoco: lanciare il comando "java ClientCuoco nomemacchina" in ProgettoSO/src/client
  6. Avviamento Client Cameriere: lanciare il comando "java Cameriere nomemacchina" in ProgettoSO/src/client
  7. Programma avviato, è possibile iniziare ad interagirci!

N.B. "nomemacchina" va sostituito con il nome della macchina sulla quale è salvato il client

Server e connessione con il Client

La classe Server, oltre a creare il server vero e proprio e a stabilire la connessione principale, si occupa di importare il menù del ristorante e di salvarlo in una stringa, che poi verrà passata alla classe Cameriere in remoto.
Il menù del ristorante viene preso in input dal file “lista.txt” che si trova nella cartella del Server. Ogni riga/prodotto di questo file viene scannerizzata e divisa in strighe prendendo come separatore lo spazio, stringhe che sono salvate in una semplice lista. Fatto questo, il programma prende l’indice del prodotto dalla prima stringa e il suo nome dalla seconda fino all’ultima, concatenandole tutte utilizzando il metodo concat(). Il prodotto quindi è completo, può essere creato attraverso l’indice e la stringa finale e salvato nell’array predisposto. Il passo successivo consiste nell’utilizzare l’oggetto StringBuilder men e, attraverso il metodo append(), costruire una lunga stringa contenente tutti i prodotti. Una volta analizzato tutto il file di input, l’oggetto men potrà essere convertito in String e salvato in prodo, che risulterà essere una stringa avente tutti i prodotti del menù catalogati e distribuiti, uno per riga.
La classi ClientCuoco e Cameriere che contengono il riferimento all'oggetto remoto, in qualità di Client si occupano di stabilire una connessione con il server mediante la seguente riga:

String name = "rmi://" + args[0] + ":" + 1099 + "/Metodi";

L’utente dovrà inserire da riga di comando il nome del Client da far partire e l’hostname del computer che ospita il server, ad esempio:

java ClientCuoco doncurzio

Il motivo per cui abbiamo optato per l’inserimento dell’hostname anziché dell’indirizzo IP del computer è dovuto al fatto che abbiamo riscontrato difficoltà, nel ricavare sui computer di laboratorio l’IP dell’interfaccia ethernet. Inoltre ci è sembrato più intuitivo l’inserimento di un nome “umano” anziché dell’indirizzo macchina. Si veda il file README.txt per ulteriori delucidazioni riguardo l'avvio dell'applicativo. L’oggetto remoto è definito nella classe Metodi.java. Questa classe contiene i riferimenti a tutti i metodi utilizzati dal server. Per mantenere coerenza tra le funzioni dei vari metodi e per organizzare al meglio il nostro lavoro, abbiamo fatto si che l’implementazione di questi metodi si trovi in altre classi, come Cucina.java per i metodi riguardanti la cucina e Sala.java per i metodi riguardanti i camerieri. Lo stesso vale per l’organizzazione dei metodi dichiarati nell’interfaccia.

Classi Prodotto e Tavolo

Essendo questo un applicativo destinato ad essere utilizzato nei ristoranti e dovendo quindi maneggiare ed interagire con pietanze, bevande e tutto ciò che concerne il mondo della ristorazione, abbiamo prima di tutto deciso di categorizzare ciascun prodotto che sarà immesso nel sistema e definirlo come un object avente due parametri in ingresso: int indice, che andrà a definire il numero con cui viene salvato quell’oggetto e String tipo, che ne definirà il nome. Ogni prodotto inoltre porta con sé due booleani inizialmente settati a false, preparing e ready, i quali vengono utilizzati in cucina per riconoscere il livello di preparazione raggiunto dall’oggetto in questione. Questa categorizzazione è stata implementata nella classe Prodotto. Quindi, per rendere le ordinazioni dei clienti facilmente processabili dalla cucina dopo essere state raccolte dal cameriere, abbiamo definito una classe a parte, nominata Tavolo. In questa classe definiamo un’ordinazione con un oggetto Tavolo appunto, costituito da una LinkedList di oggetti Prodotto, che rappresenta l’insieme delle bevande e/o pietanze richieste, e da un int idTavolo, che si riferisce al numero del tavolo al quale questa ordinazione appartiene. La scelta della LinkedList è stata dettata dalla logica e dalla semplicità, non avendo bisogno di un accesso diretto nella struttura dati abbiamo subito scartato gli array.

Classe Coda

La classe Coda è una delle classe cardini del progetto. La struttura che abbiamo decidso di utilizzare e che ci sembrava politically correct per il nostro caso è una coda di tipo FIFO - First In First Out: inserimento in coda ed estrazione in testa, quindi. Gli elementi accettati sono oggetti di tipo Tavolo, rappresentanti ordinazioni da processare in cucina o comande completate da servire ai tavoli. Grazie alla sua implementazione è stato possibile definire una serie di liste principali (come ordinazioniInCucina e ordinazioniCucinate) su cui poter accedere in maniera sincronizzata, grazie agli strumenti messi a disposizione di Java. Tra i metodi definiti, oltre al costruttore, in cui è inizializzata una LinkedList di tipo generico, ci sono una serie di metodi opportunamente commentati nella classe. In particolare è giusto spendere due parole per il metodo push( ): tale metodo permette di aggiungere un elemento alla lista. Da notare che nella sua implementazione è chiamato il metodo notifyAll, senza il quale alcuni metodi come getFirst( ) e readFirst( ) non potrebbero funzionare; entrambi infatti, hanno bisogno che vi sia almeno un elemento contenuto. Quando ciò non si verifica, la coda è bloccata dal wait( ). Quindi in definitiva, grazie al notifyAll viene sbloccato il wait( ) ed eseguita l'operazione per cui sono stati implementati. Ognuno dei metodi è sincronizzato. Ciò significa che vi è un accesso esclusivo per ogni operazione definita dai metodi. Volendo andare più nello specifico, c'è da considerare il metodo avviso(). Questo metodo, una volta superato il synchronized che precede il controllo della coda ordinazioniCucinate, chiama un wait( ) su quest’ultima coda. Non appena un’ordinazione viene completata in cucina e quindi aggiunta alla coda attraverso la chiamata del metodo push( ), il metodo Avviso( ) viene sbloccato e procede alla visualizzione della notifica sui terminali servendosi di un semplice System.out.println( ).

Sala e Camerieri

La parte che tratteremo ora riguarda il cameriere e la sua interazione con il Server. Il Client che esegue la parte del cameriere, viene avviato richiamando la classe Cameriere e inserendo come parametro da riga di comando il nome della macchina sulla quale risiede il Server. Una volta configurata la connessione, al cameriere giungeranno diverse informazioni riguardanti il ristorante, necessarie per l’intera operatività del sistema. Tra queste ci sono il numero dei tavoli a disposizione e il menù del locale, che verrà inviato solo una volta all’inizio e salvato in una variabile. La scelta di questa soluzione unica è dettata da un’esigenza di aumento d’efficienza sia per il Client che per il Server, in quanto è sicuramente più performante richiamare solo una volta un metodo, che verrà utilizzato per più operazioni, anziché richiamarlo per ogni singola operazione. Ultimato lo scambio di informazioni, viene lanciato il thread Display, che si occuperà di far apparire a video le opzioni selezionabili dal cameriere, proprio come se si stesse tenendo in mano un palmare da comanda. Successivamente, sempre nella classe Cameriere, verrà lanciato un thread incaricato di eseguire il metodo Avviso( ), il quale funge da servizio notifica al cameriere: nel caso venga evasa un’ordinazione in cucina, giungerà su tutti i terminali in sala una notifica riguardo questo evento, quindi ogni cameriere sarà libero, all’azione successiva, di decidere di consegnare al tavolo la comanda appena completata oppure no. La coda che fa da ponte tra la cucina e la sala, e che quindi viene sempre tenuta sotto controllo dai camerieri, è la coda ordinazioniCucinate, definita dalla classe Coda . Proseguendo con la classe Display, qui vengono configurate le operazioni che può eseguire un cameriere: chiudere la sessione lavorativa (equivalente a chiudere la connessione con il server), prendere un’ordinazione o consegnare ordinazioni pronte ai tavoli. L’utente potrà scegliere tra queste operazioni in un menù che comparirà sul palmare, digitando rispettivamente i numeri 0, 1 o 2. Ad ogni opzione selezionata, tranne per la prima, verrà lanciato un thread apposito che si occuperà di gestire l’operazione richiesta. In questo caso, la scelta di utilizzare due diversi thread, è stata implementata al fine di evitare che il singolo problema di un’operazione comprometta tutto il programma e al fine di rendere il codice più chiaro e meno ripetitivo. Una volta digitato il numero 1, espressione della volontà di prendere un’ordinazione da un tavolo, verrà lanciato il thread Ordinazione. Esso si occuperà di gestire l’operazione sopracitata fino alla consegna della comanda in cucina. Le variabili di classe utilizzate per portare a termine l’operazione sono tre: max, serve per sapere quanti sono i tavoli nel ristorante, tavServito, variabile imprescindibile che lega ogni comanda al tavolo che l’ha generata e la LinkedList prodottiOrdinati, che raccoglierà tutti gli oggetti Prodotto ordinati dal cliente. Il cameriere inizialmente dovrà selezionare il tavolo per il quale intende prendere la comanda. Qui si genera il primo problema di sincronizzazione: ci possono essere più camerieri che tentano di prendere la comanda contemporaneamente allo stesso tavolo, ma quest’azione dev’essere esclusiva e riservata a solo uno di loro. Questo problema è stata risolto mediante l’ausilio dell’array boolean postiOccupati che, come il nome può far intendere, riunisce i tavoli del ristorante in un elenco, segnandoli in tempo reale come occupati o liberi. Detto i l’indice dell’array, i + 1 sarà il corrispettivo numero di tavolo controllato: se il suo booleano sarà true, il posto sarà occupato, al contrario sarà false. Le operazioni di lettura e scrittura su questa struttura dati sono regolate dal synchronized che Java mette a disposizione. Solo un cameriere per volta può eseguire operazioni, indifferentemente che si tratti di lettura o scrittura. Qualora un cameriere inserisca l’ID di un tavolo di cui se ne sta già occupando un altro collega, verrà mostrato a display un messaggio di errore e verrà invitato a scegliere un altro tavolo tra quelli disponibili nel ristorante. Solo nel momento in cui il cameriere conclude l’ordinazione del tavolo inviandola in cucina, rilascia il lock e un altro cameriere potrà occuparsi di quel tavolo, se il cliente ne avrà bisgno. Alla chiamata del metodo ordinazione() quindi, il lock viene rilasciato, e un collega, se chiamato in causa dal cliente, anche lo stesso che ha appena ordinato, potrà accogliere una nuova ordinazione acquisendo il lock sulla struttura dati. Da notare, che il rilascio del lock è stato posizionato in questo punto e non al momento della consegna delle ordinazioni al tavolo, per garantire la possibilità ai clienti di richiedere ulteriori ordinazioni dopo la prima. Sarà ora necessario inserire le proprie scelte mediante indici che fanno riferimento ai prodotti disponibili nel menù comparso a video. Qualora l’indice inserito non rientri nel range di quelli disponibili o sia di un tipo diverso dal numerico, comparirà un errore e si richiederà di riprovare con indici validi. Se si ordina una bevanda, verrà chiesto di inserire un ulteriore indice per specificare se la richiesta prevede bevanda con ghiaccio (indice numero 1) o senza ghiaccio (indice numero 2). Anche in questo caso si effettua il medesimo tipo di controllo sugli indici. Quando il tavolo avrà terminato l’ordinazione, il cameriere dovrà semplicemente digitare il numero 0 e verrà mostrato a display un sunto dell’ordinazione. Successivamente l’ordinazione sarà processata e inviata alla cucina mediante il metodo ordinazione(), incaricato di prendere la lista dei prodotti ordinati e l’ID del tavolo associato, trasformarli in oggetto Tavolo ed inviare quest’ultimo nella coda ordinazioniInCucina, pronta per essere analizzata dai cuochi in servizio. Se al termine della fase di immissione dei prodotti il tavolo non ha ordinato nulla, il sistema segnalerà l’errore e chiederà di ordinare almeno un prodotto. La possibilità di richiamare tutti questi metodi nella classe Cameriere permette agli addetti alla sala di inserire le ordinazioni nelle due code comuni del ristorante, che fungono da canali unidirezionali di comunicazione: ordinazioniInCucina collega sala a cucina e ordinazioniCucinate collega cucina a sala. Più in particolare, vorremmo far notare che quando viene richiamato il metodo remoto ordinazione( ), verranno inviati al server la lista dei prodotti ordinati e l’indice del tavolo servito. L’invio dei prodotti ordinati, essendo oggetti di tipo Prodotto, è stato possibile mediante l’ausilio del Serializable che Java mette a disposizione. Una volta completata l’ordinazione e visualizzato il riassunto della stessa, ricomparirà il menù principale con le solite scelte possibili: disconnettersi dal server, prendere un’ordinazione o consegnare un’ordinazione al tavolo. Digitando il numero 2 da tastierino numerico, partirà il thread Consegna. Questo, inizialmente controllerà se ci sono ordinazioni pronte da consegnare verificando la lunghezza della coda ordinazioniCucinate. Se nulla, visualizzerà un messaggio di errore, altrimenti consegnerà l’ordinazione al cliente facendo un getFirst() dalla suddetta lista per estrarre in testa l’oggetto Tavolo ed ottenendo il numero corretto del tavolo facendo un getId( ) sull’oggetto estratto. Completata l’ordinazione, verrà nuovamente visualizzando il menù principale.

Cucina e Cuochi

  • Attenzione: i termini “Vassoio” e “Tavolo” sono equivalenti. Il nome Tavolo è il nome della classe che gestisce le ordinazioni. La classe Tavolo contiene una lista di Prodotti; I camerieri prendono ordinazioni ai Tavoli (viene creato un oggetto Tavolo, che all’interno contiene la lista dei prodotti da preparare). I cuochi operano su questa lista, che per praticità chiameremo “vassoio”.

Tutto ciò che concerne il lavoro nella cucina del fast-food risiede nella classe Cuoco, che viene lanciata sotto forma di thread dal client ClientCuoco. L’interazione client-server inizia con un messaggio di benvenuto da parte del server. Il cuoco può scegliere se proseguire o uscire dal programma. In caso di proseguimento, viene memorizzato l’ID del vassoio attualmente in preparazione in cucina. Le nostre scelte implementative nella strutturazione della cucina sono state le seguenti:

  • I cuochi possono lavorare solo su un vassoio alla volta (il vassoio “attuale”);
  • Ogni vassoio è contrassegnato da un id. L’id aumenta ogni volta che un vassoio viene completato dai cuochi. Durante la preparazione dei prodotti, il client controlla ripetutamente se l'id del vassoio è rimasto lo stesso. In caso negativo, al completamento della preparazione del prodotto questo sarà messo in magazzino e al cuoco verrà presentato il nuovo vassoio;
  • Nel caso in cui il vassoio attuale si svuoti mentre un cuoco prepara un prodotto, e non siano presenti altri vassoi da preparare, il prodotto del cuoco verrà messo in magazzino e il cuoco verrà messo in attesa di un altro cuoco;
  • I cuochi possono preparare solo i prodotti presenti nel vassoio;
  • In altre parole, un cuoco non può decidere in maniera autonoma di preparare un prodotto che non è presente tra quelli richiesti nel vassoio 'attuale';
  • Se durante la scelta del prodotto da preparare altri cuochi completano altri prodotti, comportando di conseguenza un cambio dello stato del vassoio, al cuoco verrà chiesto di riconfermare la propria selezione.

In base all'esito dell'operazione il server invierà una array di risposta al client del cuoco. L'array conterrà due oggetti:

  • Un oggetto di tipo Prodotto (che sarà il prodotto che dovrà preparare il cuoco);
  • Una stringa che avviserà il cuoco nel caso il prodotto sia già presente in magazzino.

Il motivo per cui viene inviato dal server un oggetto di tipo Prodotto è che questo rappresenta gli 'ingredienti' che il cuoco dovrà usare per preparare il prodotto e re-inviarlo al Server. Questa scelta implementativa, che può sembrare un pò forzata, si è basata sul fatto che volevamo far si che server e client si scambiassero un oggetto di tipo Prodotto. La classe Prodotto infatti sarà presente sia nel lato client sia nel lato server, implementerà l'interfaccia Serializable e conterrà lo stesso UID. Abbiamo diviso i possibili esiti in 5 casi, che sono singolarmente spiegati fra i commenti nella classe Cucina.java. Non escludiamo che alcuni di questi casi avrebbero potuto essere raggruppati, al fine di 'sfoltire' codice e commenti. Ciononostante abbiamo preferito distinguerli singolarmente per evidenziare i possibili esiti in situazioni di 'concorrenza' nella preparazione dei prodotti tra più cuochi. In caso un cuoco scelga un prodotto che è contenuto nel magazzino, questo sarà automaticamente dichiarato pronto e prelevato dal magazzino. Le notifiche al cuoco funzionano in questo modo: qualora non siano presenti vassoi da preparare, e un cuoco faccia richiesta dell'elenco dei prodotti attuali, viene attivato il 'servizio notifica'. Il servizio notifica è attivato solo 'su richiesta' del cuoco (a differenza della notifica ai camerieri, che è invocata ripetutamente poiché si trova in un ciclo while(true)). Quando attivo, il client si mette 'in ascolto' e, qualora arrivi un nuovo vassoio da preparare, mostra un messaggio al cuoco avvisandolo dell'arrivo del vassoio. Il servizio notifica viene attivato solo una volta (facendo più volte richiesta della lista dei prodotti quando non è presente nessun vassoio NON si attiva più volte il servizio notifica) per mezzo di un booleano che viene settato false quando il servizio non è attivo e true quando è attivo. Quando il servizio notifica è attivo, il server invoca un wait() sulla coda dei vassoi (oggetti Tavolo). Quando verranno prese le ordinazioni da un nuovo tavolo, un nuovo vassoio da preparare sarà inserito nella lista. Ciò comporterà un notifyAll() nella classe coda.java che risveglierà il wait(), facendo in modo che il server invii al client il messaggio di notifica.

Magazzino

Il magazzino è implementato con una LinkedList di tipo Prodotto. Ogni qual volta un cuoco completa la preparazione di un Prodotto che non è più richiesto, questo viene inserito nella lista Magazzino. Ad esempio, un oggetto viene depositato in magazzino quando

  • Due o più cuochi preparano lo stesso prodotto, e questo è richiesto solo una volta;
  • Un cuoco sta preparando un prodotto ma nel frattempo il vassoio attuale è stato. completato Di conseguenza, ogni qual volta un cuoco decida di preparare un prodotto che è già contenuto nel magazzino, questo verrà tolto dal magazzino ed utilizzato dal cuoco. Ciò comporterà l'immediata preparazione di quel prodotto. La lista magazzino è una lista a condivisa da tutti i cuochi. Qualunque cuoco può depositarvi prodotti o utilizzarli.

Progetto SISTEMI OPERATIVI
Riccardo Zandegiacomo De Lugan, Antonio Faienza, Flavio Colonna e Mirco Lacalandra
CdL in Informatica per il Management
A.S. 2013/2014, Settembre 2014

About

Applicativo client-server riguardante la gestione delle prenotazioni di un fast-food, realizzato interamente utilizzando Java e JavaRMI.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published