MokaPackages
I packages del JDK
java.net
di
Antonio
Cisternino
il package per il networking in Java

 



In questo articolo cercheremo di viaggiare all'interno del package java.net cuore del networking in Java

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.


MokaByte rivista web su Java

MokaByte ricerca nuovi collaboratori
Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it