Sei sulla pagina 1di 11

Si può iniziare questo capitolo rivedendo i concetti di base che riguardano il networking, poi si

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.

 Per avviare il programma di solito si digita ‘telnet’ da riga di comando.

 Di seguito è riportato un esempio di ciò che si può fare. Si digiti il comando

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.

 Il software invia successivamente una richiesta di connessione a questo computer alla


porta 13.

 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.

Esempio: richiedere il sorgente di una pagina web

Di seguito è riportato un altro esempio, analogo al precedente. Si considerino i passi indicati di


seguito:

1. Si utilizza telnet per connettersi a java.sun.com sulla porta 80.

2. Si digita il comando GET / HTTP/1.0.

3. A questo punto, si preme due volte il tasto Invio.

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.

 Le istruzioni fondamentali di questo semplice programma sono le seguenti:

Socket socket = new Socket(“time-A.timefreq.gov”, 13);


InputStream inStream = socket.getInputStream();

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.

3. Dopo aver agganciato lo stream, il programma esegue le seguenti attività:

a. Utilizza uno Scanner per leggere una riga di caratteri inviata dal server.

b. Visualizza ogni riga sull’output standard.

Questo procede fino a quando lo stream si conclude e il server si scollega.

La classe Socket è interessante e semplice da utilizzare in quanto la tecnologia Java nasconde le


maggiori complessità che riguardano l’impostazione di una connessione di rete e l’invio di dati
tramite questa connessione. Il pacchetto java.net mette a disposizione in sostanza la stessa
interfaccia di programmazione che si utilizza per lavorare con un file.
Timeout del socket

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:

Socket s = new Socket(. . .);


s.setSoTimeout(10000); // timeout dopo 10 secondi

In questo caso le successive operazioni di lettura o scrittura generano un’eccezione


SocketTimeoutException quando si raggiunge il timeout prima che l’operazione si sia conclusa. Si
può pertanto rilevare l’eccezione e reagire al timeout che si è verificato:

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:

Socket s = new Socket();


s.connect(new InetSocketAddress(host, port), timeout);
Indirizzi Internet

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:

InetAddress address = InetAddress.getByName(“time-A.timefreq.bldrdoc.gov”);

L’oggetto InetAddress restituito incapsula la sequenza di 4 byte 132.163.4.104

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:

InedAddress[] addresses = InetAddress.getAllByName(host);

Infine, a volte è necessario conoscere l’indirizzo dell’host locale. Se si chiede semplicemente


l’indirizzo di localhost, si acquisisce sempre 127.0.0.1, che non è molto utile. Si utilizza invece il
metodo statico getLocalHost per acquisire l’indirizzo del proprio host locale.

InetAddress address = InetAddress.getLocalHost();

L’esempio InetAddressTest è un semplice programma che visualizza l’indirizzo Internet dell’host


locale se non si specificano parametri nella riga di comando, oppure tutti gli indirizzi Intenet di un
altro host specificato nella riga di comando come segue:

java InetAddressTest java.sun.com


Implementazione dei server
Dopo aver implementato un semplice client di rete, in grado di ricevere dati da Internet, si può
passare a implementare un semplice server che possa inviare informazioni ai client. Dopo aver
avviato il programma server, questi aspetta che un client si colleghi alla porta; si sceglie la porta
8189, che non viene utilizzata da nessuno dei servizi standard.

 La classe ServerSocket definisce il socket. Si consideri la seguente istruzione che stabilisce


un server che controlla la porta 8189:

ServerSocket s = new ServerSocket(8189);

 Successivamente diremo al programma di aspettare un tempo indefinito, cioè fino a


quando un client non si connette alla porta, con la seguente istruzione

Socket incoming = s.accept();

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:

InputStream inStream = incoming.getInputStream();


OutputStream outStream = incoming.getOutputStream();

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:

Scanner in = new Scanner(inStream);


PrintWriter out = new PrintWriter(outStream, true /* autoflush */);

 Si può inviare ad esempio al client un messaggio di saluto:

out.println(“Hello! Enter BYE to exit.”);

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.

 Alla fine, si chiude il socket in entrata

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.

2. In qualche modo si ricavano le informazioni richieste.

3. Si inviano le informazioni al client attraverso uno stream di dati in uscita.

Programma di esempio: EchoServer

Si può verificare il funzionamento compilando ed eseguendo il programma di esempio EchoServer.

 Utilizziamo telnet per connetterci al server e alla porta indicati di seguito:


Server: 127.0.0.1
Porta: 8189

L’indirizzo 127.0.0.1 è un indirizzo speciale, l’indirizzo locale, che indica il computer locale.

Se si è connessi direttamente a Internet, chiunque può connettersi dall’esterno al server di


ripetizione (echo), purché conosca l’indirizzo IP e il numero della porta.
Servire più client

Si consideri di voler permettere a più client contemporaneamente di connettersi al server. Di solito


un server viene eseguito di continuo su un computer server e i client che si connettono ad Internet
possono utilizzare questo server contemporaneamente. Il rifiuto di connessioni multiple permette
a un client di monopolizzare il servizio rimanendo connesso per lungo tempo. La situazione può
essere migliorata grazie ai prodigi dei thread.

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();
}

La classe ThreadedEchoHandler implementa Runnable e contiene il ciclo di comunicazione con il


client nel suo metodo run:

class ThreadedEchoHandler implements Runnable {


...
public void run() {
try {
InputStream inStream = incoming.getInputStream();
OutputStream outStream = incoming.getOutputStream();
// elabora l’input e invia la riposta
incoming.close();
}
catch (IOException e) { // gestisce l’eccezione }
}
}

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.

Il programma da client assume la seguente forma:

Socket socket = new Socket(host, port);


Scanner in = new Scanner(socket.getInputStream());
PrintWriter writer = new PrintWriter(socket.getOutputStream());
// invia i dati della richiesta
writer.print(. . .):
writer.flush(. . .);
socket.shutdownOutput();
// ora il socket è chiuso a metà
// legge i dati della risposta
while (in.hasNextLine() != null) { String line = in.nextLine(); . . . }
socket.close();
Socket interrompibili

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.

Per interrompere il funzionamento di un socket, si utilizza SocketChannel, una funzionalità del


pacchetto java.nio.

SocketChannel channel = SocketChannel.open(new InetSocketAddress(host, port));

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:

Scanner in = new Scanner(channel);

Per trasformare un canale in uno stream di output, si utilizza il seguente metodo statico:

OutputStream outStream = Channels.newOutputStream(channel);

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.

Il programma d’esempio InterruptibleSocketTest mostra come un thread che legge da un server


possa essere interrotto. Il server invia uno stream di numeri casuali al client, ma se si attiva la
casella di controllo Busy, il server simula di essere occupato e non invia dati. Premendo cancel si
interrompe la connessione.
URL e URI

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.

Un esempio di URI può essere

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.

Le classi URL e URLConnection vengono utilizzate per il recupero di informazioni da un sito


remoto. Si può costruire un oggetto URL da una stringa:

Url url = new URL(stringaURL);

Se si vuole semplicemente prendere il contenuto della risorsa, si può utilizzare il metodo


openStream della classe URL. Questo metodo produce un oggetto InputStream e viene utilizzato
come al solito, per esempio si può costruire un oggetto Scanner:

InputStream inStream = url.openStream()


Scanner in = new Scanner(inStream);

Invio di e-mail

Il programma d’esempio MailTest è un semplice programma di posta elettronica che sfrutta le API
JavaMail.

Potrebbero piacerti anche