MokaByte Numero  39  - Marzo 2000
 
Networking in Java 
IV puntata
Socket, oltre il byte
di 
Andrea
Trentini
Trasmettere dati complessi: i tipi primitivi e gli oggetti interi

Vi siete domandati se è possibile evitare di convertire tutto in byte? Se non lo avete ancora fatto, fatelo. La risposta è NO, per fortuna. Le librerie di Java ci vengono in aiuto con un paio di classettine che ci permettono trasmissioni "complesse" senza spaccarsi la testa inventandosi strani protocolli

Per l'impaziente...
Potete provare subito gli esempi di questa volta: scaricateli da qui, compilate il tutto e lanciate i due file .bat ("data.bat" per i tipi primitivi e "obj.bat" per gli oggetti). Trovate un paio di screenshot più avanti. Anche se in realtà non vedrete un gran chè di speciale, dato che per apprezzare il risultato bisogna leggersi il resto dell'articolo :-)
Attenzione, per chiudere i programmi bisogna battere "^C" sul corrispondente prompt di DOS, non ho gestito il WindowEvent di chiusura della finestra (si lascia per esercizio... :-)
 
 

Una nota iniziale
In questo articoletto, dedicato alla pratica, non mi soffermerò sui dettagli implementativi, nel senso che non spiegherò il codice riga per riga. E' un sorgente così banale e soprattutto è così simile al codice che abbiamo già visto in precedenza che mi sembrerebbe offensivo... Vi racconterò soltanto quali sono le modifiche sostanziali...
Se vi sarete incuriositi al termine della lettura, vi consiglio di seguire questa serie di puntatori sicuramente interessanti:

  • la sezione "networking" del Java Tutorial
  • gli articoli pubblicati su MokaByte 35 (novembre 1999) e successivi
  • il documento sulla "serialization specification" scaricabile dal sito java.sun.com
  • la parte sulla serializzazione del mio articolo su "Serializzazione&RMI" di MB Luglio-Agosto 1999

 
 

I tipi primitivi
Rispetto all'esempio della volta scorsa il sorgente non ha cambiato struttura, se lo guardate attentamente vedrete che ho sostituito tutti gli Stream (sia Input che Output) con dei DataStream.

L'effetto di questa modifica è che ora posso usare dei metodi più intelligenti del semplice write di un singolo byte o di un array. Infatti i DataStream, incapsulando gli Stream di base, offrono le funzioni per trasmettere tutti i dati di tipo primitivo più le stringhe.
"Incapsulando" in questo caso vuol dire semplicemente che un DataStream si può istanziare solo avendo già a disposizione uno stream. Infatti per istanziare un DataStream dovete scrivere (nel caso delle socket):
new DataOutputStream(s.getOutputStream());
Le due classi DataStream (DataInputStream e DataOutputStream) sono accoppiate, infatti hanno i metodi per così dire simmetrici:
 
 DataOutputStream DataInputStream
writeInt readInt
writeFloat readFloat
writeUTF
(questo è quello per le stringhe)
readUTF
... ...

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

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

Chi volesse mettersi in contatto con la redazione può farlo scrivendo a mokainfo@mokabyte.it