MokaByte
Numero 32 - Luglio Agosto 99
|
|||
|
|
II parte |
|
|
Antonio Cisternino |
|
|
In questo articolo, prosecuzione del precedente in cui ho descritto un Web Server scritto in Java, mostrerò come sia possibile estendere il server descritto in modo che divenga concorrente. |
Una piccola riflessionePerchè scrivere un web server in Java? Ormai i Web Server spuntano come funghi e uno in più non è certo una novità. Lo scopo del precedente articolo era quello di mostrare come sia possibile scrivere un server che consentisse lo scambio di dati e fosse in grado di interagire con uno o più clienti connessi via Internet.Lo scopo principale dell'articolo era quello di mostrare il ciclo base di un server qualsiasi che accetti connessioni (via TCP) e, implementando un opportuno protocollo, offra servizi via rete. Lo schema concettuale presentato è il seguente: Ill codice che eseguiva il ciclo principale del server era il seguente:<Aspetta un cliente> <Servi il cliente> <Attendi altri clienti> public void run() {Adesso qualcuno si potrebbe domandare: ma non è possibile fare meglio sfruttando i thread di java? La risposta è ovviamente affermativa: se osserviamo il codice del metodo run() la parte del codice che effettua il ciclo fondamentale del server si limita alle seguenti linee: public void run() {Il resto del codice è necessario solo per gestire la connessione e il protocollo. Potremmo pensare quindi di migliorare il servizio offerto dal nostro server offrendo la possibilità di servire più clienti in contemporanea. Ma è realmente conveniente questa scelta? Se è innegabile che servire un cliente alla volta porti alla massima efficienza su una macchina con un solo processore è anche vero che ridurre di poco questa efficienza consente di suddividere il lavoro e servire in contemporanea più clienti. Bisogna inoltre fare un'altra osservazione: quando un'applicazione cerca di accedere al disco viene sospesa in attesa che le informazioni passino dal disco alla memoria; il sistema operativo appoggia l'applicazione durante questa interazione che dura un tempo enorme rispetto alla velocità di un normale elaboratore. Quando si legge che un disco ha un tempo di accesso nell'ordine dei millisecondi si deve pensare che i tempi del processore sono mille volte più piccoli e quindi per un tempo enorme il processo che sta servendo il cliente in realtà è sospeso in attesa dei dati. Ecco quindi che servire più clienti in contemporanea può essere addirittura quasi gratis: l'accesso al disco li sospende tutti e uno alla volta poi termineranno il loro servizio. Quanto detto è vero solo per quei servizi che, come il Web, richiedono per il loro fuzionamento l'accesso a dispositivi. Se al contrario il cliente richiede al server un calcolo pesante (come ad esempio provare un certo numero di chiavi per forzare un algoritmo di crittografia) senza accessi al disco, può rivelarsi inopportuno accettare più clienti in contemporanea poiché tutti concorreranno ad usare il processore aumentando sensibilmente i tempi di risposta. In generale però un server tipicamente si occupa di accedere al filesystem o ad un database e quindi l'esecuzione del servizio per un cliente userà i dispositivi e risulterà complessivamente conveniente permettere l'accesso contemporaneo a più clienti. Nel resto dell'articolo vedremo innanzitutto come sia possibile con poco sforzo rendere concorrente il web server che abbiamo presentato cercando di capire le gioie e i dolori di una tale soluzione. Un colpo di genioAbbiamo appena scoperto che non ci piace il web server sequenziale e lo vogliamo concorrente... Come fare? Roba da suicidio: riscrivere le 245 righe di codice con questo caldo? Giammai! Effettivamente ce lo possiamo risparmiare sfruttando la programmazione OOP e introducendo, udite udite, una sola classe minimale.Nel codice che abbiamo studiato nel precedente articolo abbiamo unito la gestione del protocollo con la gestione dei clienti. Nel paragrafo precedente abbiamo visto quale in realtà sia il codice che accetta i clienti e quale quello che gestisce la connessione. Oltre a quel codice avevamo il metodo main che si occupava di avviare il server e quindi di iniziare il ciclo di ricezione dei clienti avviando un Thread che eseguiva il metodo run() della classe WebServer. Proviamo quindi a fare la seguente cosa: creiamo una nuova classe che abbia un metodo main equivalente al precedente, un metodo run() contenente solo la parte del codice che abbiamo detto essere quella necessaria per accettare i clienti. E poi? Poi riutilizziamo la classe che abbiamo già scritto con molta fatica per gestire i clienti. Per non perdere il filo del discorso però facciamo una cosa alla volta! E iniziamo con il definire una nuova classe chiamata JWebServer che a sua volta userà la classe WebServer Già definita nel precedente articolo. Secondo quanto detto la classe JWebServer dovrebbe essere (commenti a parte) come segue: public class JWebServer implements Runnable {Come si può osservare la classe mantiene le costanti che usava: queste infatti definiscono parametri comuni a tutto il server e quindi è giusto che appartengono alla classe del server. La classe WebServer che è stata declassata da web server a gestore di un singolo cliente, utilizzerà queste costanti al suo interno per poter decidere ad esempio dove si trovi la root del web server. Dobbiamo ora chiederci cosa mettiamo al posto dei punti interrogativi? Poiché abbiamo deciso di accettare più connessioni in contemporanea useremo un Thread per ogni cliente che sarà realizzato con piccole modifiche alla classe WebServer. Ecco quindi che al posto dei punti interrogativi troviamo: try {Ecco quindi che ogni volta che arriva una nuova connessione il nostro nuovo JWebServer avvia un Thread che si mette a gestire la connessione secondo lo schema già visto mentre il Thread principale si mette immediatamente in ascolto di nuove richieste. Per concludere la nostra migrazione verso il server concorrente non ci resta che da capire come vada modificata la classe WebServer perché si limiti a gestire una singola connessione senza più preoccuparsi di gestire l'arrivo dei clienti. Ovviamente il codice che abbiamo spostato relativo alla gestione dei clienti va eliminato dalla classe WebServer. Questo però non basta: se si osserva il codice che avvia il dialogo di un cliente si vede subito che viene creata un'istanza della classe WebServer a cui viene passato il riferimento al Socket che rappresenta la connessione da il cliente e il server. Quindi dovremo aggiungere il costruttore alla classe come segue: Ovviamente bisogna anche aggiungere un nuovo attributo alla classe chiamato conn e di tipo Socket. Per quanto riguarda gli altri metodi sono tutti a posto tranne il metodo run() che praticamente si riduce al corpo del ciclo for della versione precedente. Va inoltre cambiato s in conn poiché il socket di connessione al cliente viene passato come abbiamo visto al costruttore.public WebServer(Socket s) { conn = s; } Infine bisogna ricordarsi di anteporre il nome della classe alle costanti che sono state spostate dalla classe WebServer alla classe JWebServer. Ecco quindi che con pochissimo sforzo siamo riusciti a rendere concorrente un server sequenziale. Se lo provate dovreste notare un certo aumento delle prestazioni. Ma sono tutte rose e fiori? E allora perché non lo abbiamo fatto subito concorrente? La prima ragione è chiaramente la semplicità. L'introduzione della concorrenza non porta però solo miglioramenti ma anche qualche complicazione sui dati condivisi. La condivisione
di un dato
Cliente A:L'esempio in questione è alquanto banale ma dà l'idea del problema: può capitare che i due Thread accedano più volte ad un dato condiviso (il canale di log) e la loro reciproca interazione porti ad operare in modo scorretto sul dato condiviso. Questo problema può essere affrontato e risolto usando il meccanismo di sincronizzazione di Java: si dichiara un metodo synchronized (nel nostro caso addLog) e in quel metodo si eseguono tutte le operazioni necessarie (quindi entrambe le scritture) in modo che nel caso di conflitto nell'accesso al canale di log solo un Thread alla volta potrà eseguire quel metodo evitando le interferenze indesiderate. Se questo fatto nel caso del log è irrilevante risulta fondamentale se il server che stiamo scrivendo permette di accedere in modo concorrente ad una base di dati. Conclusioni
|
|
||
|
||
MokaByte ricerca
nuovi collaboratori
|
||
|