Introduzione
La
volta scorsa avevamo visto una panoramica di alcuni servizi per le applicazioni
distribuite. Questa Introdurremo le Socket, un meccanismo per comunicare
in rete, indipendentemente dal dispositivo fisico tramite il quale effettuiamo
la connessione.
Ricordiamo
brevemente il paradigma Client-Server: un programma chiamato Client richiede
dei servizi ad un altro programma chiamato Server. Quest’ultimo è
in ascolto di richieste da parte dei client, esegue tali richieste con
le risorse che ha a disposizione, e rispedisce, se richiesto i risultati
al client. Tali programmi possono risiedere anche su computer diversi:
tutto questo è trasparente ad entrambi.
Esistono
tuttora molte applicazioni, soprattutto in Internet, che fanno uso di questo
paradigma:
-
Telnet:
se sulla nostra macchina si ha disposizione il programma telnet (programma
client), è possibile operare su un computer remoto come si opera
su un computer locale. Questo è possibile se sulla macchina remota
è presente un programma server in grado di esaudire le richieste
del client telnet.
-
Ftp: tramite
un client ftp si possono copiare e cancellare file su un computer remoto,
purché qui sia presente un server ftp.
-
Web: il
browser è un cliente web, che richiede pagine web ai vari computer
su cui è installato un web server, che esaudirà le
richieste spedendo la pagina desiderata.
Il
meccanismo di astrazione per la comunicazione in rete è rappresentato
dal paradigma socket presentato per la prima volta nella Berkeley Software
Distribution (BSD) della University of California a Berkeley.
Una
socket è come una porta di comunicazione e non è molto diversa
da una presa elettrica: tutto ciò che è in grado di
comunicare tramite il protocollo standard TCP/IP, può collegarsi
ad una socket e comunicare tramite questa porta di comunicazione,
allo stesso modo in cui un qualsiasi apparecchio che funziona a corrente
può collegarsi ad una presa elettrica e sfruttare la tensione messa
a disposizione. Nel caso delle socket, invece dell’elettricità,
nella rete viaggiano pacchetti TCP/IP. Tale protocollo e le socket forniscono
quindi un’astrazione, che permette di far comunicare dispositivi diversi
che utilizzano lo stesso protocollo.
Ovviamente
Java mette a disposizione alcune classi per l’utilizzo delle socket, tra
cui la classe Socket. Usando questa classe un client può stabilire
un canale di comunicazione con un host remoto. Si potrà comunicare
attraverso questo canale
utilizzando
stream particolari, specializzati per le socket.
Vediamo
adesso quali sono le operazioni che un client ed un server devono effettuare
con le socket per stabilire una connessione (nel caso del client) e per
accettare connessioni (nel caso del server).
Il client
Quindi
un client, per comunicare con un host remoto usando il protocollo TCP/IP,
dovrà creare per prima cosa un oggetto Socket con tale host. Si
dovrà specificare l’indirizzo IP dell’host, e il numero di porta.
Sull’host remoto dovrà esserci un server che è in "ascolto"
su tale porta. La classe Socket mette a disposizione due costruttori:
Socket(
String host, int port ) throws IOException
Socket(
InetAddress address, int port ) throws IOException
InetAddress
è una classe di utilità per la gestione di indirizzi Internet
(ad esempio ottenere l’indirizzo IP numerico, dato un indirizzo sotto forma
di stringa).
Una
volta creato un oggetto Socket, si possono ottenere gli stream ad esso
associati tramite i metodi della classe Socket:
InputStream
getInputStream() throws IOException
OutputStream
getOutputStream() throws IOException
A questo
punto la comunicazione può avere inizio: il client può scrivere
sull’OutputStream, come si fa con un normale stream, e ricevere dati dal
server leggendo dall’InputStream.
Di
seguito viene mostrato un estratto di codice (quasi pseudo codice) che
vorrebbe dare un'idea delle operazioni da compiere per aprire una socket
con un server che si trova ad un certo indirizzo (host) ed è in
ascolto su una certa porta (port).
protected
String host;
protectedint
port;
protected
DataInputStream in;
protected
DataOutputStream out;
protected
Socket connect () throws IOException {
System.err.println
("Connessione a " + host + ":"
+ port + "...");
Socket socket
= new Socket (host, port);
System.err.println
("Connessione avvenuta.");
out = new
DataOutputStream (socket.getOutputStream ());
in = new
DataInputStream (socket.getInputStream ());
return
socket;
}
Effettuata
la connessione possiamo ottenere gli stream associati con i metodi della
classe Socket getOutputStream e getInputStream. Creiamo poi un DataOutputStream
ed un DataInputStream su tali stream ottenuti (effettivamente per ottimizzare
la comunicazione in rete si dovrebbe prima creare uno stream bufferizzato
sullo stream di output, ma questi dettagli al momento non ci interessano).
A
questo punto, se ad esempio abbiamo stabilito una connessione con un web
server, si deve richiedere il file al server e quindi si spedisce
tale richiesta tramite lo stream di output:
out.writeBytes
("GET " + file + " HTTP/1.0\r\n\r\n");
A
questo punto non resta che mettersi in attesa, sullo stream di input, dell’invio
dei dati da parte del server:
while
((input = in.readLine ()) != null)
Il server
Vediamo
adesso quali sono le operazioni tipiche di un programma Server.
Un
server rimane in attesa di connessioni su una certa porta, ed ogni volta
che un client si connette a tale porta, il server ottiene una socket, tramite
la quale può comunicare col client. Il meccanismo messo a disposizione
da Java per questo è la classe ServerSocket, tramite la quale il
server può appunto accettare connessioni dai client attraverso la
rete. I passi tipici di un server saranno quindi:
-
creare
un oggetto di classe ServerSocket specificando un numero di porta locale,
cioè la porta in cui il server rimarrà in ascolto
di richieste di connessioni.
-
attendere
(tramite il metodo accept di suddetta classe) connessioni dai client
-
usare
la socket ottenuta ad ogni connessione, per comunicare col client.
Infatti
il metodo accept della classe ServerSocket crea un oggetto Socket per ogni
connessione. Il server potrà poi comunicare, come fa un client:
estraendo gli stream di input ed output dalla socket.
I
costruttore della classe ServerSocket sono:
ServerSocket(int
port) throws IOException
ServerSocket(int
port, int count) throws IOException
A
parte il parametro che specifica la porta (già spiegato), il parametro
count permette di specificare il numero di richieste di connessioni messe
in coda dal sistema operativo (il default per questo parametro è
50). Infatti dopo che il ServerSocket è stato creato il sistema
operativo si mette subito in attesa di connessioni; queste richieste saranno
messe in una coda e saranno rimosse una per volta, ad ogni chiamata, da
parte del server, del metodo
Socket
accept() throws IOException
Quindi
count non limita il numero di connessioni che un server è in grado
di mantenere, ma il numero di richieste di connessioni che verranno messe
in coda, se il server ci mette molto per accettare nuove connessioni. Infine
se come numero di porta viene specificato 0 il sistema operativo seleziona
un numero di porta valido e non occupato. E’ comunque possibile ottenere
il numero di porta sul quale il ServerSocket è in ascolto di connessioni
utilizzando il metodo
int
getLocalPort()
La
socket ottenuta dal metodo accept è uguale a quella ottenuta dal
client alla connessione, e quindi potrà essere utilizzata allo stesso
modo per recuperare gli stream ad essa associata e per comunicare col client.
E'
bene far notare che se il server accetta più connessioni (di solito
è quello che succede: spesso il server è multithreaded),
il server otterrà una socket differente per ogni client.
L’ultimo
metodo della classe ServerSocket è
void
close() throws IOException
Che
chiude il ServerSocket, ma non le connessioni che sono stare stabilite
(queste dovranno essere chiuse chiamando il metodo close della classe Socket).
Tipicamente quando una socket viene chiusa da un lato, sull’altro lato
si avrà una IOException.
Anche
l'esempio di codice che segue vuole semplicemente mostrare un'idea del
codice Java che un server esegue per mettersi in attesa di connessioni
da parte di client. Una volta ottenuta una connessione il server stampa
semplicemente un messaggio di benvenuto.
System.out.println ("Server in partenza
sulla porta " + port);
ServerSocket server = new ServerSocket
(port);
System.out.println ("Server partito sulla
porta " + server.getLocalPort() ) ;
System.out.println ("In attesa di connessioni...");
Socket client = server.accept ();
System.out.println ("Richiesta di connessione
da " + client.getInetAddress () ) ;
InputStream i = client.getInputStream ();
OutputStream o = client.getOutputStream ();
PrintStream p = new PrintStream
(o) ;
p.println("BENVENUTI.");
Conclusioni
Andrea
vi mostrerà adesso qualche semplice esempio funzionante di utilizzo
di Socket; vedrete come in Java è molto semplice instaurare una
comunicazione in rete fra un client ed un server. Provate a fare la stessa
cosa in C, e vedrete che le operazioni da compiere sono molte di più...
P.S.
I listati Java sono stati formattati col programma java2html scaricabile
al sito
http://www.gnu.org/software/java2html/java2html.html. |