Introduzione
Il
package java.net, cuore del networking in Java, ruota attorno alle classi
che consentono di utilizzare i Socket, libreria standard per la programmazione
di rete. Questo package contiene inoltre un supporto di più alto
livello per l'apertura di connessioni di rete basate sull'incapsulazione
di un protocollo associata ad una data URL. Ad esempio è possibile
aprire URL web utilizzando il protocollo HTTP in modo trasparente per il
programmatore. L'importante è disporre del supporto per un dato
protocollo. È anche possibile implementare il supporto per nuovi
protocolli integrandolo completamente nella struttura del package.
In
questo articolo prenderemo dapprima in considerazione i Socket per poi
occuparci del supporto di più alto livello e delle classi accessorie
presenti nel package.
Gli
indirizzi e i Socket
Per
effettuare una comunicazione è necessario poter identificare il
mittente e il destinatario; ciò significa conoscere gli indirizzi
di entrambi. Nel caso della comunicazione su Internet lo schema di indirizzamento
è quello previsto dal protocollo TCP/IP: esistono gli indirizzi
IP sotto forma di quattro byte (e.g. 127.0.0.1) e i nomi simbolici che
in seguito al processo di risoluzione del nome vengono fatti corrispondere
ad indirizzi IP (e.g. www.mokabyte.it).
Il
package java.net offre una classe dedicata alla rappresentazione degli
indirizzi: la classe InetAddress. In molti casi non sarà necessario
ricorrere a questa classe per indicare un computer e basterà fornire
una stringa contenente l'indirizzo in una delle due forme ammesse: indirizzo
IP o nome simbolico. La classe InetAddress contiene comunque molti metodi
utili per la manipolazione degli indirizzi e risulta utile in molte situazioni.
Poiché
su un computer possono essere attivi più servizi in grado di comunicare
in rete (si pensi ad esempio ai servizi HTTP e FTP), specificare il semplice
indirizzo di un computer non è sufficiente per realizzare una comunicazione:
è necessario specificare anche il servizio. In TCP/IP questo si
fa specificando un intero tra 0 e 65535 conosciuto col nome di porta.
Ai vari servizi sono associate porte convenzionali (che però possono
anche cambiare); ad esempio al protocollo HTTP, utilizzato dai browser
per il trasferimento delle pagine Web, è associata la porta 80.
Sulla porta 25 avviene l'invio della posta. Sulla porta 21 viene servito
il protocollo FTP necessario al trasferimento dei file.
Nel
modello client-server, tipicamente utilizzato per la realizzazione
delle comunicazioni, sulla macchina server è in esecuzione un processo
che attende connessioni su una data porta. I processi che aprono porte
per accettare connessioni devono essere eseguiti con i privilegi di amministratore
se il numero di porta è inferiore a 1024, per questo motivo quando
si realizzano servizi in proprio spesso si usano porte con numero superiore
a 1024.
I
Socket consentono di aprire un canale di comunicazione tra due computer
collegati in rete. Una volta aperto il canale la responsabilità
della comunicazione tra i due programmi che partecipano nella connessione
ricade interamente sul programmatore.
Esistono
due tipi di connessione: una basata su messaggi e l'altra basata su un
canale di comunicazione vero e proprio. Queste due modalità di comunicazione
corrispondono alle connessioni UDP e TCP offerte dal protocollo TCP/IP
utilizzato effettivamente per la comunicazione.
Nei
paragrafi seguenti tratteremo questi due tipi di comunicazione più
nel dettaglio.
La
comunicazione con messaggi
La
modalità di comunicazione basata su messaggi (e realizzata col protocollo
UDP) consente di inviare singoli messaggi da un computer all'altro. Un
messaggio consiste in un array di byte che vengono inviati dal mittente
con le informazioni necessarie a recapitarlo. I messaggi sono inviati uno
indipendentemente dall'altro e quindi possono giungere a destinazione in
un ordine differente da quello con cui sono stati inviati. Inoltre non
viene fornita alcuna garanzia sul fatto che un messaggio sia effettivamente
recapitato al destinatario; se però il messaggio arriva a destinazione
è sicuramente corretto, un messaggio che si è corrotto durante
il tragitto è considerato perso.
La
modalità di comunicazione basata sullo scambio di messaggi è
alla base della comunicazione con un canale e risulta decisamente più
efficiente di qeust'ultima. Se la non affidabilità del protocollo
non costituisce un problema e si devono inviare messaggi questo tipo di
comunicazione è da preferirsi.
Le
classi che consentono di comunicare con uno scambio di messaggi nel package
dedicato al networking sono le classi il cui nome inizia per Datagram:
DatagramPacket
DatagramSocket
DatagramSocketImpl
Solo le
prime due classi sono effettivamente utilizzate per la comunicazione poiché
l'ultima, introdotta con la versione 1.1 del linguaggio, serve solo per
cambiare l'implementazione di questo tipo di socket.
Il
messaggio viene preparato con la creazione di un oggetto della classe DatagramPacket
che viene costruito fornendo un array di byte che rappresenterà
il contenuto del pacchetto. L'indirizzo di destinazione del pacchetto potrà
essere anche fornito durante la costruzione del pacchetto ma dovrà
essere noto al momento del suo invio. La costruzione di un pacchetto avviene
come segue:
...
//
Indirizzo di destinazione: indica la macchina locale
InetAddress
dest = InetAddress.getByName("localhost");
String
msg = "Messaggio";
byte[]
data = msg.getBytes();
int
port = 8768;
DatagramPacket
p = new DatagramPacket(data, data.length, dest, port);
Per
poter inviare il messaggio preparato è necessario aprire un Socket
di tipo DatagramSocket. Se il socket è utilizzato solo per inviare
si può delegare al costruttore dell'oggetto la scelta della porta
da utilizzare per la comunicazione. Se al contrario si intende ricevere
bisogna anche specificare la porta a cui il DatagramSocket è collegato.
Il messaggio dell'esempio precedente può essere inviato come segue:
...
DatagramSocket
s = new DatagramSocket();
s.send(p);
...
Ovviamente
vanno raccolte le eventuali eccezioni sollevate dall'invio del pacchetto.
La
ricezione di un pacchetto avviene semplicemente creando il DatagramSocket
associato alla porta da cui si vogliono ricevere i pacchetti. Si utilizza
poi il metodo receive per attendere un messaggio e quindi riceverlo. Ad
esempio la ricezione del messaggio inviato negli esempi precedenti avviene
come segue:
...
DatagramSocket
srv = new DatagramSocket(8768);
DatagramPacket
p = new DatagramPacket(new byte[100], 100);
srv.receive(p);
...
Osserviamo
come venga preparato un pacchetto in cui accogliere i dati ricevuti. Se
il pacchetto è troppo piccolo i dati in eccesso sono eliminati dal
supporto di rete. Quando la chiamata a receive termina il pacchetto p conterrà
i dati inviati.
La
comunicazione con canali
La
comunicazione basata su canali consente a due programmi in esecuzione su
due computer differenti di scambiarsi flussi di dati. Una volta stabilita
la connessione i due programmi possono inviare dati lungo il canale come
se si trattasse della scrittura di dati in un file. Ciascuno dei due programmi
ha a disposizione quindi di un InputStream da cui ricevere i dati e di
un OutputStream in cui inserire i dati da inviare.
Contrariamente
alla comunicazione basata su messaggi quella basata su canale garantisce
che i dati siano ricevuti nello stesso ordine in cui sono stati inviati
e che non vi siano perdite di informazione. Il protocollo che offre il
supporto alla realizzazione dei canali è chiamato TCP ed è
implementato a partire dal protocollo a messaggi chiamato IP il cui funzionamento
è molto simile a UDP di cui abbiamo già parlato.
Le
classi che consentono la creazione di canali sono le seguenti:
-
Socket
-
SocketImpl
-
ServerSocket
-
SocketPermission
Come nel
caso della classe DatagramSocketImpl la classe SocketImpl è riservata
a chi vuole cambiare l'implementazione dei Socket. L'architettura dei Socket
rappresenta la tipica struttura client-server adottata nella programmazione
di rete: un programma, detto servente, attende connessioni su una
data porta. Il ciclo di attesa di questo programma è il seguente:
...
int
port = 9067;
ServerSocket
svr = new ServerSocket(port);
Socket
s = svr.accept();
communicate(s);
...
Il
programma attende quindi una richiesta di connessione sulla porta indicata.
Quando arriva una richiesta il metodo accept restituisce un Socket che
potrà essere utilizzato per la comunicazione del cliente che ne
fa richiesta.
Cerchiamo
ora di capire come il cliente contatta il server:
...
String
server = "localhost";
int
port = 9067;
Socket
s = new Socket(server, port);
...
Resta
quindi da capire come ottenere i due canali per effettuare la comunicazione
tra il cliente ed il server:
...
InputStream
in = s.getInputStream();
OutputStream
out = s.getOutputStream();
//
Usa gli stream
s.close();
...
Sia
il server che il cliente ottengono i due stream e, conoscendo il protocollo,
ovverosia le regole da seguire per la comunicazione, si inviano dati e
li ricevono sui due stream. Se entrambi i programmi sono Java è
possibile e risulta essere molto utile l'impiego di filtri che elevano
la comunicazione su un canale come DataInputStream e DataOutputStream.
Si possono anche serializzare gli oggetti utilizzando classi come ObjectInputStream
e ObjectOutputStream.
Per
concludere la nostra panoramica sui Socket non resta che dire che la classe
SocketPermission serve a specificare, per fini di sicurezza, diritti sull'accesso
a risorse di rete.
La classe URL
& co.
Come
abbiamo accennato all'inizio dell'articolo il package java.net è
strutturato in due parti: la prima si occupa delle connessioni e la abbiamo
già vista; la seconda si occupa di modellare il concetto di URL
e di protocollo in modo da astrarre dai dettagli di implementazione che
invece sono presenti nell'uso dei Socket.
Una
URL individua un computer della rete, una porta un protocollo ed il nome
di una risorsa. Siamo tutti abituati a scrivere nel nostro browser
URL come http://www.mokabyte.it/ spesso senza ricordare quante informazioni
racchiudano al loro interno. L'idea di base è quella di rappresentare
il concetto di URL in una classe ed invocare una classe accessoria che
sappia gestire il protocollo in essa definito.
La
classe URL rappresenta una URL. Attraverso essa è possibile ottenere
le varie informazioni ad essa associate: il computer che ospita la risorsa,
la porta, il nome della risorsa e il protocollo da utilizzare. Una volta
creato un oggetto URL è possibile ricorrere al metodo openConnection
per ottenere un oggetto di tipo URLConnection che consente di gestire la
connessione. Poiché l'apertura della connessione potrebbe richiedere
più informazioni è possibile impostare queste informazioni
prima di effettuare la connessione vera e propria.
Lo
schema di interazione è il seguente:
...
URL
u = new URL("http://www.mokabyte.it/");
URLConnection
c = u.openConnection();
InputStream
in = c.getInputStream();
//
Legge la pagina Web
...
Come
si può osservare da questo esempio molti dettagli della comunicazione
sono nascosti dalla connessione. Il difetto di questo approccio è
però legato alla disponibilità di classi che implementino
un protocollo. Per il protocollo HTTP se ne occupa la classe HttpURLConnection.
Questo stile di programmazione non ha mai avuto un grosso successo a causa
della semplicità della programmazione basata su Socekt e sui limiti
imposti dal modello nei dettagli dei quali non ci addentreremo.
Altre
classi accessorie
Il
package contiene altre classi accessorie, alcune legate alla classe URL
sono degli oggetti specifici all'estensione del meccanismo e alla gestione
del contenuto della comunicazione. Queste classi sono:
-
URLClassLoader
-
URLDecoder
-
URLEncoder
-
URLStreamHandler
-
JarURLConnection
-
ContentHandler
Le classi
Authenticator e PasswordAuthentication si occupano poi dell'autenticazione
basata su password. Infine la classe MulticastSocket consente di realizzare
un particolare tipo di Socket in grado di inviare lo stesso messaggio a
più clienti alla volta. Questo tipo di Socket richiede un particolare
supporto della rete e degli indirizzi IP particolari. Per questo tipo di
servizi conviene consultare la documentazione on-line.
Conclusioni
In
questo articolo abbiamo fatto una panoramica del package java.net, cuore
del networking in Java. Si potevano dire molte altre cose su questo package
ma ciò che è stato detto dovrebbe essere sufficiente per
chi si avvicina per concluderne lo studio sapendo come muoversi. |