Un
gestore multiplo
Cominciamo
subito col provare il software. Una volta scaricati questi sorgenti,
lanciate "java MultiEchoServer" per avere il server attivo:
In
questa immagine mi sono già connesso con un telnet, provate anche
voi. Dovete eseguire un telnet e connettervi all'indirizzo 127.0.0.1 (Ricordate?
Questo è l'indirizzo della vostra macchina, ovunque vi troviate)
sulla porta 2001 (di default, poi nel sorgente potete modificarla).
Se
ora lanciate un secondo telnet, e un terzo, e un quarto e vi connettete
allo stesso modo, vedrete che il server vi accetta (contrariamente alla
versione della volta scorsa). Avrete cioè una situazione simile
a questa:
Stavolta
ogni client che si connette ottiene "udienza", non viene rifiutato.
Questa
funzione viene realizzata creando un thread per ogni client che si connette.
Tale thread gestisce una singola connessione e niente altro: è bloccato
quasi sempre sulla read() dallo stream della socket.
Se
guardate il sorgente troverete una classe MultiEchoServer e una classe
Echoer (che estende Thread). MultiEchoServer è il gestore delle
connessioni in ingresso, è lui a creare un'istanza di Echoer per
ogni connessione che arriva:
while(true)
{
try
{
System.out.print("accepting... on port:"+port);
s = server.accept();
System.out.print("... accepted:"+s);
new Echoer(s).start();
System.out.println("... Echoer started.");
}
catch(Exception e)
{
e.printStackTrace();
}
}
Come
vedete da questo frammento di codice, il server resta in perenne attesa
di connessioni, ogni client che si connette (cioè quando la accept()
esce fornendo una Socket) causa la creazione di un Echoer a cui viene passata
la Socket in questione. E' l'Echoer ad occuparsi in toto della comunicazione.
Il gestore torna ad accettare nuove connessioni.
Il
brutto di questo meccanismo è che viene creato un thread per ogni
connessione accettata. Ciò può essere pesante su alcuni sistemi
operativi. Purtroppo però, che io sappia, in Java non è ancora
stata implementata la funzionalità di select(), presente
in altri ambienti (ad es. C sotto Unix). Per curiosità potete andare
a consultare le RFE (Request for FEatures) sul sito developer.javasoft.com,
Questa funzioanlità era fino a poco tempo fa nelle prime 25 in ordine
di importanza. La select() permette di assegnare una RRI (Routine di Risposta
a Interrupt) a d ogni socket aperta, in modo da ricevere una notifica quando
ci sono dati da leggere, evitando così la creazione di molti thread.
Un
chat
server
Ora
facciamo una piccola aggiunta che ci servirà come spunto per una
delle prossime puntate.
Il
server che vi ho mostrato poco fa è sì un server multi-client,
però ogni connessione viene gestita da un oggetto che non ha la
minima idea del fatto che esistano altre connessioni... Sarebbe divertente
poter gestire un piccolo chat server, in modo da permettere che
molti utenti si parlino tra loro...
Facciamo
le opportune modifiche: ci serve una struttura che "tenga in contatto"
i vari Echoer, diciamo uno Stack. Bisogna modificare ogni Echoer (ora chiamato
Chatter) per fargli mandare i caratteri ricevuti al gestore invece che
direttamente alla connessione. Bisogna modificare il gestore in modo che
"forwardi" ogni carattere ricevuto da un Chatter a tutti i Chatter collegati
in un dato momento.
Il
meccanismo è semplice: ogni Chatter, appena riceve un carattere,
lo passa al gestore chiamando broadcast():
while(true)
{
int c=i.read(); // bloccante !!!
m.broadcast(c); // lo passa al gestore per la trasmissione a tutti
// PRIMA ERA COSI'
//o.write(c); // echo
}
Il
gestore,
avendo traccia di ogni Chatter istanziato, può ciclare su tutti
i Chatter e spedire il carattere ricevuto a tutte le connessioni (realizzando
l'echo anche per il client trasmittente):
public
void broadcast(int c){
Enumeration e=chatters.elements();
while(e.hasMoreElements())
{
((Chatter)e.nextElement()).send(c);
}
}
Trasmissione di
dati complessi
Dov'è
il difetto di questo chat server? Beh, il difetto è che ogni
singolo carattere viene spedito appena arriva, non c'è bufferizzazione.
Per cui vedete il testo di ogni partecipante mescolato a quello di tutti
gli altri... Cioè il quanto di trasmissione è il singolo
carattere, non la linea o un messaggio più complesso. Bisognerebbe
trovare un sistema (un protocollo?) di trasmettere blocchi di dati, magari
etichettati col nome del partecipante, in modo da avere ad esempio un riga
intera per ogni partecipante. Esattamente come accade usando IRC.
In
una delle prossime puntate vedremo come si può, senza scrivere tanto
codice, realizzare un protocollo per trasmettere righe intere di lunghezza
variabile. In seguito trasmetteremo addirittura oggetti interi, creeremo
cioè degli oggetti messaggio a nostro piacimento e li manderemo
al gruppo in chat.
|