Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
passerà alla scrittura di programmi Java che si connettono ai servizi di rete. Si vedrà come si
possono ottenere informazioni da un web server e come inviare posta elettronica utilizzando un
programma Java
Connessione a un server
Prima di scrivere il primo programma di networking, conviene imparare a utilizzare uno strumento
di debug specifico già a disposizione di tutti, cioè telnet, uno strumento preinstallato nella maggior
parte dei sistemi, sia UNIX che Windows. E’ possibile utilizzare telnet per connettersi a un
computer remoto, ma lo si può utilizzare con altri servizi offerti da host internet.
telnet time-A.timefreq.bldrdoc.gov 13
Verrà riportata una riga analoga a quella riportata nella seguente figura
Ci si è collegati al servizio “ora del giorno” che viene offerto costantemente da un particolare
server messo a disposizione dal National Institute of Standards and Technology di Boulder, in
Colorado, e propone la misura del tempo effettuata con un orologio atomico al cesio. Ovviamente
il tempo riportato non è esattamente preciso a causa dei ritardi di trasmissione in rete.
Per convenzione, il servizio “ora del giorno” è sempre collegato alla porta numero 13.
NB: nella terminologia delle reti, una porta (port) non è un dispositivo fisico ma un’astrazione che
facilita la comunicazione tra un server e un client, come si può vedere nella seguente figura
Il software del server in questione viene eseguito in modo continuo sul computer remoto, in
attesa che il traffico in rete chieda di comunicare con la porta 13. Quando il sistema operativo del
computer remoto (server) riceve un pacchetto di rete che contiene una richiesta di connessione
alla porta numero 13, attiva il processo di ascolto del server e instaura la connessione con il client.
Questa rimane attiva fino a quando una delle due parti non la conclude.
Quando si inizia una sessione telnet con il comando telnet time-A.timefreq.bldrdoc.gov 13,
una parte del software di rete, eseguita dai server DNS, è in grado di convertire la stringa
“time-A.timefreq.bldrdoc.gov” nel corrispondente Internet Protocol (IP), 132.163.4.104.
Dopo aver instaurato la connessione, il programma remoto rinvia una riga di dati e chiude
la connessione.
Ovviamente, in generale i client e i server instaurano una comunicazione più estesa prima che una
delle due parti chiuda la connessione.
Si tratta dello stesso processo che il browser web svolge per ottenere una pagina web;
Primo programma rete: SocketTest
Il programma d’esempio SocketTest esegue le stesse operazioni che si effettuano con telnet: si
connette a una porta e visualizza ciò che trova.
1. La prima riga apre un socket, un’astrazione del software di rete che permette la
comunicazione da e verso questo programma. Prende come parametri l’indirizzo
remoto e il numero della porta. Se la connessione non ha successo, viene generata
un’eccezione UnknownHostException. Se il problema è di altro tipo, si verifica invece
un’eccezione IOException.
2. Dopo aver letto il socket, nella seconda riga, il metodo getInputStream() della classe
Socket restituisce un oggetto InpuStream che si può utilizzare come qualsiasi altro
stream.
a. Utilizza uno Scanner per leggere una riga di caratteri inviata dal server.
Nei programmi reali non si vuole solo leggere da un socket, perché i metodi di lettura si bloccano
fino a quando i dati sono disponibili. Se non si può raggiungere l’host l’applicazione aspetta a
lungo e in sostanza si rimane alla mercé del timeout impostato dal sistema operativo sottostante.
Si può invece decidere che il timeout debba assumere un valore ragionevole per una particolare
applicazione, poi, per impostarlo si chiama il metodo setSoTimeout specificando un valore
espresso in millisecondi:
try {
Scanner in = new Scanner(s.getInputStream());
String line = in.nextLine();
}
catch (InterruptedIOException e) {
// reagisce al timeout
}
Si deve considerare un’altra situazione di timeout. Il costruttore Socket(String host, int port) può
bloccare per un tempo indefinito fino a quando si stabilisce una connessione iniziale. Questo
problema può essere risolto costruendo prima un socket non connesso e poi impostando una
connessione con un timeout:
Di solito non ci si deve preoccupare troppo degli indirizzi Internet, ovvero degli indirizzi numerici
dell’host composti da 4 byte (oppure 16 byte in Ipv6), per esempio 132.163.4.102. Tuttavia, si può
utilizzare la classe InetAddress se è necessario convertire i nomi degli host in indirizzi Internet.
Il metodo statico getByName restituisce un oggetto InetAddress dell’host, come nel seguente
esempio:
Alcuni nomi di host che prevedono un traffico intenso corrispondono a diversi indirizzi Internet, in
modo da distribuire il carico di lavoro. Per esempio il nome dell’host google.com corrisponde a
dodici diversi indirizzi Internet. Si possono ottenere tutti gli indirizzi Internet di un host con il
metodo statico getAllByName:
Dopo aver stabilito una connessione con questa porta inviando la richiesta corretta
attraverso la rete, questo metodo restituisce un oggetto Socket che rappresenta la
connessione stabilita.
Si può utilizzare questo oggetto per ottenere gli stream di input e/o di output come segue:
Qualsiasi cosa il server invii allo stream di output diventa l’input del programma client,
mentre l’ output del programma client finisce nello stream di input del server.
In tutti gli esempi di questo capitolo si trasmette del testo attraverso i socket, per cui si
trasformano gli stream in scanner e writer:
Il messaggio contenuto nel comando viene visualizzato sullo schermo del terminale client
quando si utilizza telnet per connettersi a questo programma server sulla porta 8189.
incoming.close();
Riepilogo
Ogni programma server, per esempio un server web http, continua ad eseguire il ciclo indicato di
seguito:
1. Riceve un comando dal client (“passami queste informazioni”) attraverso uno stream di dati in
ingresso.
L’indirizzo 127.0.0.1 è un indirizzo speciale, l’indirizzo locale, che indica il computer locale.
Ogni volta che si sa che il programma ha stabilito una nuova connessione sul socket, cioè quando
ha successo la chiamata, si avvia un nuovo thread che si prende cura della connessione tra il server
e quel client. Il programma principale può tornare ad aspettare una connessione successiva.
Perché ciò possa accadere, il ciclo principale del server deve essere simile al seguente:
while (true) {
Socket incoming = s.accept();
Runnable r = new ThreadedEchoHandler(incoming);
Thread t = new Thread(r);
t.start();
}
Dato che ogni connessione avvia un nuovo thread, più client possono connettersi al server
contemporaneamente. Si può verificare questa situazione compilando il programma di esempio
del server riportato nella classe ThreadedEchoServer.
Mezza chiusura
Quando un programma client invia una richiesta al server, il server deve essere in grado di stabilire
quando si verifica la fine della richiesta. Per questo motivo, molti protocolli Internet, per esempio
SMTP, sono orientati alla riga, mentre altri protocolli contengono un’intestazione che specifica la
dimensione dei dati della richiesta. Negli altri casi, può essere indicata la fine dei dati della
richiesta tramite la mezza chiusura: si può chiudere lo stream di output di un socket, lasciando
aperto il socket in modo che si possa leggere la risposta.
Quando ci si connette a un socket, il thread corrente si blocca fino a quando la connessione non
viene stabilita oppure si verifica un timeout. Analogamente accade quando si leggono i dati
attraverso un socket.
Nelle applicazioni interattive, si preferisce offrire agli utenti un’opzione che permetta di
semplificare la cancellazione di una connessione socket che non sembra produrre i risultati
desiderati. Tuttavia, se un thread si blocca su un socket che non risponde, non è possibile
sbloccarlo chiamando interrupt.
Un canale non prevede stream associati, ma ha i metodi read e write che utilizzano oggetti Buffer.
Questi metodi sono dichiarati nelle interfacce ReadableByteChannel e WritableByteChannel
Se non si vogliono usare i buffer, si può utilizzare la classe Scanner per leggere (stream di input) da
un oggetto SocketChannel perché Scanner ha un costruttore con un parametro
ReadableBytechannel:
Per trasformare un canale in uno stream di output, si utilizza il seguente metodo statico:
Questo è tutto. Ogni volta che si interrompe un thread durante un’operazione di apertura, lettura
o scrittura, l’operazione non si blocca ma viene conclusa con un’eccezione.
A partire da JDK 1.4, il pacchetto java.net distingue tra URL (Uniform Resource Locator) e URI
(Uniform Resource Identifier).
Un URI è un costrutto puramente sintattico che specifica, attraverso le varie parti di una stringa,
una risorsa web.
mailto:cay@horstmann.com
il quale non individua alcunché, in quanto non ci sono dati da individuare mediante questo
identificatore. Un URI di questo tipo prende il nome di URN (Uniform Resource Name).
Nella libreria Java la classe URI non ha metodi che permettono di accedere alla risorsa indicata
dall’identificatore. Il suo solo scopo è quello di eseguire il parsing.
Per confermare il fatto che il parsing non è affatto banale, è sufficiente considerare la
complessità dei seguenti URI:
http://maps.yahoo.com/py/maps.py?csz=Cupertino+CA
ftp://username:password@ftp.yourserver.com/pub/file.txt
Un URL è un tipo particolare di URI, cioè quella parte che contiene informazioni sufficienti per
individuare una risorsa.
Invio di e-mail
Il programma d’esempio MailTest è un semplice programma di posta elettronica che sfrutta le API
JavaMail.