Nonostante l'importanza di Java stia crescendo e che l'interesse nei confronti del networking sia sempre maggiore, la programmazione di rete in Java è ancora per molti aspetti oscura. Eppure è molto semplice. In questo articolo gli autori si concentrano su tutto ciò che ha a che fare con il protocollo UDP e con le classi Java necessarie a utilizzarlo.
Introduzione
Spesso si preferisce ancora scrivere applicazioni di rete in C o C++, e in alcuni casi limitati questo è effettivamente consigliabile, ma lo si può fare benissimo anche in Java in modo più facile e veloce.
Per affrontare l'articolo non è necessario essere specialisti della rete, ma una conoscenza dei concetti basilari è comunque richiesta, dato che vengono richiamati termini comuni della programmazione di rete.
Gli autori fanno subito una nota dolente: la rete è l'area meno supportata dalla maggior parte delle implementazioni Java dell'epoca. Non ci sono errori rilevanti relativi al networking, ma, se si può scegliere l'implementazione, è meglio optare per quella della Sun per Solaris.
A questo punto il lettore potrebbe chiedersi perché avventurarsi nella programmazione di rete in Java. Basta dare uno sguardo alla lista di classi offerte da java.net per trattare problematiche di rete. Java fornisce infatti ottime astrazioni alle problematiche di rete e non bisogna essere dei guru del settore. Attenzione però: Java, almeno in questo contesto, conosce le reti basate su sistemi IP.
In rete i dati viaggiano per mezzo di pacchetti chiamati datagrammi. Nelle reti IP ogni datagramma contiene un'intestazione lunga dai 20 ai 60 byte ed un insieme di dati che può arrivare fino a 65515 byte.
L'User Datagram Protocol, ovvero UDP
UDP, User Datagram Protocol, è un protocollo di trasporto semplice, senza connessione, che si basa sul trasferimento di datagrammi. La sua descrizione completa è nel documento RFC768; qui gli autori ne danno una breve descrizione per poter cominciare subito a sperimentare con le classi Java.
Ogni operazione di output di un processo che usa il trasporto UDP produce un datagramma UDP che viene incapsulato in un datagramma IP. Il protocollo UDP non è affidabile: invia i datagrammi ma non garantisce che arrivino a destinazione. Sono quindi gli applicativi che lo utilizzano a doversi preoccupare dell'affidabilità del servizio.
La testata UDP è relativamente semplice:
0 7 8 15 16 23 24 31
+--------+--------+--------+--------+
| Source | Destination |
| Port | Port |
+--------+--------+--------+--------+
| | |
| Length | Checksum |
+--------+--------+--------+--------+
|
| data octets ...
+---------------- ...
I campi Source Port e Destination Port rappresentano ovviamente sorgente e destinazione del pacchetto. Il campo Length contiene la lunghezza dell'intero datagramma UDP, cioè testata più dati, in byte. Il valore minimo è 8. Il campo Checksum è opzionale.
Il checksum è un campo di controllo end-to-end: viene calcolato dal mittente e verificato dal ricevitore. Se il pacchetto UDP è errato, viene scartato silenziosamente, senza generazione di messaggi d'errore. L'opzionalità del checksum implica maggiore velocità quando disabilitato, ma può naturalmente fornire sorgenti irrecuperabili di errore.
Casi d'uso del protocollo UDP
Se si lavora nel campo dell'integrazione di dispositivi per la rilevazione di campo e computer, ad esempio sensori di temperatura, rilevatori badge o dispositivi biometrici, ci si troverà molto probabilmente a dover utilizzare il protocollo UDP.
Perché usare UDP e non il più affidabile TCP/IP o addirittura HTTP? La risposta è che UDP è più facile da implementare a livello di firmware e quindi costa meno ai costruttori in termini di sviluppo.
UDP è, nelle parole degli autori, il cugino povero di TCP: molto veloce ma poco affidabile. Quando lo si usa per inviare dati non si può sapere se sono arrivati né se sono arrivati nell'ordine nel quale sono partiti. Si può usare per implementare un protocollo di trasferimento file come NFS o TFTP, e anche il multicasting si affida a UDP per la sperimentazione.
Con UDP è l'applicazione che si deve preoccupare dell'affidabilità, prendendosi cura dei pacchetti mancanti o fuori sequenza. Ciò significa molto lavoro: si può fare, ma prima conviene pensare se si poteva usare TCP.
Le classi fondamentali da conoscere sono DatagramPacket e DatagramSocket.
Finalmente un po' di codice
Supponiamo di avere un personal computer collegato in rete ad uno di questi dispositivi ambientali. Dobbiamo aprire un socket sulla stessa porta sulla quale il dispositivo è in ascolto:
private DatagramSocket serverSocket;
private int serverPort;
serverSocket = new DatagramSocket(serverPort);
Ora che abbiamo il nostro oggetto socket per l'UDP, possiamo impostare il timeout a 5 secondi:
serverSocket.setSoTimeout(5000);
Supponiamo ora di voler spedire un segnale al dispositivo, ad esempio il classico segnale di inizializzazione. Per fare questo dobbiamo costruire un oggetto di tipo DatagramPacket usando il costruttore:
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
DatagramPacket packet = new DatagramPacket(data, data.length, address, serverPort);
Quindi lo inviamo al dispositivo:
serverSocket.send(packet);
A questo punto la cosa successiva da fare è aspettare il segnale di ritorno dal dispositivo, che potrebbe essere un echo, un messaggio di errore o altro, a seconda del dispositivo e dell'architettura scelta.
Creiamo quindi il DatagramPacket vuoto che conterrà il pacchetto ricevuto:
DatagramPacket packet =
new DatagramPacket(new byte[512], 512);
Per metterci in ascolto sul socket utilizziamo il metodo receive, che è bloccante, cioè blocca il flusso del programma finché non riceve un pacchetto o scade il timeout:
try {
serverSocket.receive(packet);
} catch (java.io.InterruptedIOException e) {
// gestiamo il timeout
}
Infine è sempre buona norma rimettere le cose al loro posto prima di chiudere l'applicazione, che in questo caso significa chiudere il socket con il metodo close:
serverSocket.close();
Gli autori insistono sul fatto che, con UDP, bisogna tagliare le soluzioni su misura caso per caso, con un occhio di riguardo ai dispositivi con cui si deve colloquiare. Inoltre suggeriscono di non disseminare le applicazioni di oggetti DatagramPacket, ma di progettare sempre classi wrapper, così da rendere più semplice la manutenzione quando si incontrano classi deprecate o bug di libreria.
L'approfondimento
Dopo gli stralci di codice, l'articolo passa a un approccio più ragionato e sistematico a quanto Java mette a disposizione per implementare UDP.
La classe DatagramPacket mette a disposizione due costruttori fondamentali, uno per ricevere dati dalla rete e l'altro per inviarli:
public DatagramPacket(byte[] buff, int len)
public DatagramPacket(byte[] buff, int len, InetAddress addr, int port)
Nel primo caso i dati si troveranno in buff; nel secondo si crea un datagramma per inviare il pacchetto di byte buff all'host di indirizzo addr e alla porta port.
Per operare su un oggetto DatagramPacket si utilizzano poi i seguenti metodi:
public synchronized InetAddress getAddress()
public synchronized int getPort()
public synchronized byte[] getData()
public synchronized int getLength()
La classe DatagramSocket serve invece per inviare o ricevere un datagramma. I costruttori principali sono:
public DatagramSocket() throws SocketException
public DatagramSocket(int port) throws SocketException
public DatagramSocket(int port, InetAddress addr) throws SocketException
Il primo è legato ad una porta anonima, dato che la porta di destinazione è parte del DatagramPacket. Tra i metodi più importanti della classe:
public void send(DatagramPacket o) throws IOException
public void receive(DatagramPacket p) throws IOException
public int getLocalPort()
public void close()
public synchronized void setSoTimeout(int timeout) throws SocketException
public synchronized int getSoTimeout() throws IOException
Conclusione
L'articolo mostra come sia facile e produttivo realizzare applicazioni che implementano il protocollo UDP con Java, individuandone al tempo stesso i punti di forza e le debolezze. Probabilmente, osservano gli autori, chi ha a che fare con software di questa tipologia vedrà progressivamente i costruttori spostarsi verso protocolli più evoluti come TCP o HTTP, ma nel contesto italiano dell'epoca si sentirà parlare di UDP ancora a lungo.
Quindi, concludono, occhio ai datagrammi.