Le librerie Java permettono trasmissioni complesse oltre i semplici byte grazie a DataStream e ObjectStream. Questo articolo mostra come inviare tipi primitivi, stringhe e oggetti serializzati attraverso le socket.
Per l'impaziente
L'autore invita a provare subito gli esempi: si possono scaricare dal pacchetto reti-4_esempi.zip, compilare il tutto e lanciare i due file .bat, data.bat per i tipi primitivi e obj.bat per gli oggetti.
Per chiudere i programmi bisogna battere ^C sul corrispondente prompt di DOS, perché non è stato gestito il WindowEvent di chiusura della finestra.
Una nota iniziale
Si tratta di un articolo dedicato alla pratica, quindi l'autore non si sofferma sui dettagli implementativi riga per riga. Il sorgente è volutamente banale e molto simile al codice già visto nelle puntate precedenti. L'obiettivo è raccontare soltanto quali siano le modifiche sostanziali rispetto all'esempio base.
Tra i riferimenti consigliati:
la sezione networking del Java Tutorial;
gli articoli pubblicati su MokaByte 35 e successivi;
la serialization specification scaricabile da java.sun.com;
la parte sulla serializzazione dell'articolo dell'autore su Serializzazione & RMI.
I tipi primitivi
Rispetto all'esempio della puntata precedente, il sorgente non ha cambiato struttura: la differenza principale è che tutti gli stream, sia input sia output, sono stati sostituiti con dei DataStream.
Figura 1: Esempio di trasmissione di tipi primitivi
L'effetto di questa modifica è che ora si possono usare metodi più intelligenti del semplice write di un singolo byte o di un array. I DataStream, incapsulando gli stream di base, offrono infatti funzioni per trasmettere tutti i dati di tipo primitivo più le stringhe.
Nel caso delle socket, l'istanziazione avviene così:
new DataOutputStream(s.getOutputStream());
Le due classi DataInputStream e DataOutputStream sono accoppiate e hanno metodi simmetrici. Per esempio:
L'idea è molto semplice: invece di serializzare manualmente valori primitivi in byte e poi ricostruirli a mano lato ricevente, si lascia che siano queste classi ad occuparsi della codifica e decodifica del flusso.
Cioè se uso un metodo per scrivere, devo usare il suo gemello per leggere, altrimenti mi becco una sana eccezione a runtime.
A questo punto il nostro ChatServer è diventato più intelligente, riesce a trasmettere intere stringhe (potete divertirvi voi a trasmettere altri tipi di dati) invece dei singoli caratteri, per cui i vari utenti della chat non si intersecano più.
Perdo qualcosa? Purtroppo sì. Dal momento in cui vi sganciate dal puro canale "a singolo byte" (lo stream puro così come ve lo fornisce la Socket) non potete più utilizzare un client non-java. Infatti ho dovuto realizzare i mini-client per poter provare il tutto (se non ci credete provate a collegarvi al nuovo server con un semplice telnet e guardate cosa accade). Questo accade perché il DataStream (come anche l'ObjectStream che vedremo tra un attimo) trasmette le informazioni usando un suo protocollo, per cui i casi sono due: o lo simulate "a mano" (se usate un telnet) o scrivete un programma che "parla" lo stesso protocollo...
I tipi classe (spedire oggetti)
Con la stessa logica è possibile mandare un intero oggetto, basta costruire un ObjectOutputStream sul solito stream di base con:
new ObjectOutputStream(s.getOutputStream());
E da quel momento è possibile usare writeObject() per spedire un oggetto (quasi) qualunque, anche complesso (cioè che contenga altri oggetti a sua volta).
Figura 2: Esempio di trasmissione di oggetti serializzati
Nel nostro esempio ho costruito un oggetto Message (da spedire avanti e indietro) con un po' di dati dentro (qui ho tolto i metodi rispetto al sorgente che trovate nel file zip):
public class Message implements Serializable {
Date data;
String nome, msg;
}
Cosa ha di strano questa classe? Che implementa l'interfaccia Serializable. Gli oggetti che si possono spedire attraverso un ObjectOutputStream devono implementarla, altrimenti otterrete la solita eccezione a runtime.
Oltre il byte
Il senso della puntata è mostrare che, pur rimanendo nell'ambito delle socket, non si è costretti a ragionare sempre e soltanto per sequenze grezze di byte. Java mette a disposizione livelli di astrazione più elevati che permettono di trasmettere con relativa semplicità:
tipi primitivi;
stringhe;
oggetti interi, tramite serializzazione.
Questo sposta il problema dalla gestione manuale del protocollo di basso livello alla definizione corretta dell'ordine e del significato logico dei dati trasmessi.
Conclusione
L'articolo mostra un passaggio importante nell'uso delle socket Java: smettere di vedere la rete come puro scambio di byte e cominciare a ragionare in termini di stream strutturati e serializzazione. È un'evoluzione naturale degli esempi precedenti e prepara il terreno per protocolli applicativi più ricchi e per la trasmissione di oggetti completi.
MokaByte®
è un marchio registrato da MokaByte s.r.l.
Java®, Jini® e tutti i nomi derivati sono marchi
registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione
anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it