4.4) Multiplexing di messaggi |
Finora abbiamo sviluppato stream di messaggi piuttosto semplici, che non aggiungono particolari funzionalità ai normali stream, pur presentando già alcuni vantaggi quali:
Comunque, gli stream visti finora formano la base necessaria sulla
quale svilupparne di più sofisticati.
In particolare, ora vedremo come sviluppare degli stream di messaggi
che siano in grado di effettuare il multiplexing,
su un unico canale di comunicazione, di vari flussi di dati eterogenei
fra loro, tipicamente generati da componenti diverse di una applicazione
in maniera del tutto trasparente a tali componenti, che in genere
ignorano l'esistenza le une delle altre.
Lo scopo si raggiunge incapsulando i messaggi dentro pacchetti
nei quali la Control Information è costituita da un' etichetta
che caratterizza l'origine dei dati.
Quando tali pacchetti giungono a destinazione, i messaggi vengono
instradati alla corretta componente grazie a un meccanismo corrispondente
di demultiplexing, che
opera sulla base di tale Control Information.
In particolare, dopo aver introdotto le necessarie classi di utilità,
svilupperemo un'applicazione client-server che implementa una
chatline più raffinata di quella vista precedentemente.
Il client infatti è costituito da due parti indipendenti
fra loro, che consentono l'una di inviare testo e l'altra disegni
condividendo lo stesso canale fisico di comunicazione.
Il server ha il compito di effettuare il broadcast dei pacchetti
che riceve da un client a tutti gli altri.
Come vedremo, esso non entra nel merito di ciò che riceve,
e quindi rimarrà inalterato anche in caso si aggiungano
ulteriori moduli funzionali ai client.
4.4.1) Classe MultiplexOutputStream |
Questa classe estende MessageOutput
,
però si attacca a un altro MessageOutput
e
non a un semplice OutputStream
.
Ciò consente di effettuare il multiplexing su un qualunque
stream di messaggi (ad esempio su un QueueOutputStream
oppure un MessageOutputStream
).
Questo meccanismo ci consente di gestire più livelli di
incapsulamento, uno dentro l'altro.
In sostanza, aggiungiamo un meccanismo di etichettatura all'OutputStream
attaccato.
Dall'altra parte del canale, tale etichezza sarà usata
per determinare l'origine dei dati.
Nel client che vedremo fra breve useremo questa classe per fare
il multiplexing di due flussi di dati (che provengono dalle due
componenti separate dell'applicazione) su un unico canale di comunicazione.
La definizione della classe è la seguente (dal libro "Java
Network Programming" di Merlin Hughes et al., Mannig Publications
Co, Greenwich CT, 1997).
/* Copyright (c) 1996, 1997 Prominence Dot Com, Inc. * * See the file legal.txt in the txt directory for details. */ package prominence.msg; import java.io.*; /** * A <tt>MessageOutput</tt> that attaches to an existing <tt>MessageOutput</tt> * and attaches a multiplexing label to the header of each message that * is transmitted. * <p>The label is specified in the constructor and so one stream always * attaches the same label. * * @version 1.0 1 Nov 1996 * @author Merlin Hughes * @see prominence.msg.MultiplexInputStream */ public class MultiplexOutputStream extends MessageOutput { /** * The <tt>MessageOutput</tt> to which this is attached. */ protected MessageOutput o; /** * A <tt>ByteArrayOutputStream</tt> used to buffer the current message * contents. */ protected ByteArrayOutputStream byteO; /** * The multiplexing label. */ protected String label; /** * Creates a new <tt>MultiplexOutputStream</tt>. * @param o The <tt>MessageOutput</tt> to which to send messages * @param label The multiplexing label to be used by this stream */ public MultiplexOutputStream (MessageOutput o, String label) { super (new ByteArrayOutputStream ()); byteO = (ByteArrayOutputStream) out; this.o = o; this.label = label; } /** * Sends the current message with a multiplexing label header to the * attached <tt>MessageOutput</tt>. * @exception IOException Occurs if there is a problem sending the * message. */ public void send () throws IOException { synchronized (o) { o.writeUTF (label); byteO.writeTo (o); o.send (); } byteO.reset (); } /** * Sends the current message with a multiplexing label header to the * attached <tt>MessageOutput</tt>. * <p>If the attached <tt>MessageOutput</tt> supports targeted sending * then this method will succeed; otherwise an appropriate * <tt>IOException</tt> will be thrown. * @param dst The list of intended recipients * @exception IOException Occurs if there is a problem sending the * message or the targeted <tt>send()</tt> method is not supported by * the attached <tt>MessageOutput</tt>. */ public void send (String[] dst) throws IOException { synchronized (o) { o.writeUTF (label); byteO.writeTo (o); o.send (dst); } byteO.reset (); } } |
Note
MessageOutput
.
Solo al momento di chiamare il costruttore si è consapevoli
di aver a che fare con un MultiplexOutputStream
.
send(String[] dst)
è implementato
per predisporsi al caso in cui il MessageOutput
a
cui si attacca supporti tale metodo (spedizione multicast).
Costruttore
MessageOutput
(naturalmente
si passerà una sua classe derivata) a cui attaccarsi e
l'etichetta da usare come CI.
ByteArrayOutputStream
su cui convogliare
le scritture in attesa della spedizione.
Metodi
send()
scrive sul MessageOutput
l'etichetta (cioè la CI) e quindi il contenuto del buffer
interno (cioè il messaggio). Quindi chiama il metodo send()
del MessageOutput
, il che fa sì che quest'ultimo
invii sul canale di comunicazione:
MessageOutputStream
);
send()
, che
le scritture sul MessageOutput
e la chamata del metodo
send()
di quest'ultimo non possano essere interrotti,
per evitare, se qualcun altro condivide il MessageOutput
,
che la costruzione e l'invio del pacchetto siano interrotti prima
di essere completati. Ciò si ottiene sincronizzando tale
codice sul MessageOutput
.
4.4.2) Classe MultiplexInputStream |
Questo è lo stream di input corrispondente
a MultiplexOutputStream
.
Questa classe estende MessageInput
, si attacca a
un altro MessageInput
(non a un semplice InputStream
)
ed estrae l'etichetta da ogni messaggio che viene ricevuto.
MultiplexInputStream
L'etichetta è resa accessibile all'esterno, rendendo così
possibile determinare come deve essere elaborata l'informazione
contenuta nel messaggio.
La definizione della classe è la seguente (dal libro "Java
Network Programming" di Merlin Hughes et al., Mannig Publications
Co, Greenwich CT, 1997).
/* Copyright (c) 1996, 1997 Prominence Dot Com, Inc. * * See the file legal.txt in the txt directory for details. */ package prominence.msg; import java.io.*; /** * A <tt>MessageInput</tt> that attaches to an existing <tt>MessageInput</tt> * and strips the multiplexing label from each message that is received. * <p>The label is made publicly accessible in the <tt>label</tt> variable. * * @version 1.0 1 Nov 1996 * @author Merlin Hughes * @see prominence.msg.MultiplexOutputStream */ public class MultiplexInputStream extends MessageInput { /** * The multiplexing label of the most recently received message. */ public String label; /** * The <tt>MessageInput</tt> to which this is attached. */ protected MessageInput i; /** * Creates a new <tt>MultiplexInputStream</tt>. * @param i The <tt>MessageInput</tt> from which messages should * be received */ public MultiplexInputStream (MessageInput i) { super (i); this.i = i; } /** * Receives a new message from <tt>i</tt> and strips the multiplexing * label. The label is accessible in the <tt>label</tt> variable; the * contents of the message can be read through the usual superclass * <tt>read()</tt> methods. * @exception IOException Occurs if there is a problem receiving a * message or extracting the multiplexing label. */ public void receive () throws IOException { i.receive (); label = i.readUTF (); } } |
Note
Costruttore
MessageInput
(si passerà
di fatto una sua sottoclasse) a cui attaccarsi.
MessageInput
e quindi DataInputStream
) passandogli il MessageInput
a cui si è attaccato, per cui i normali metodi di lettura
della superclasse saranno direttamente passati a i
.
Metodi
receive()
chiama innanzitutto il metodo receive()
dell'InputStream
a cui è attaccato (che provvederà
a rimuovere la propria CI). Quindi provvede a leggere l'etichetta
(ossia la CI di multiplexing) che viene memorizzata nella variabile
pubblica label
.
read()
,
ecc.) leggeranno dal corpo del messaggio.
4.4.3) Classe Demultiplexer |
Questa è la classe che si incarica
di effettuare il demultiplexing vero e proprio.
Riceve i messaggi da un MultiplexInputStream
e, sulla
base della loro etichetta, li consegna ad uno dei MessageOutput
a cui è connesso.
In sostanza, è un thread copiatore che ha in più
la capacità di effettuare il demultiplexing dei messaggi
in arrivo. Include dei metodi per registrare i (e cancellare la
registrazione dei) MessageOutput
che devono essere
associati, in qualità di destinatari, alle etichette.
Demultiplexer
Tipicamente i MessageOutput
sono dei QueueOutputStream
,
per le ragioni che abbiamo esposto in precedenza, oppure dei DeliveryOutputStream
(che vedremo fra poco).
La definizione della classe è la seguente (dal libro "Java
Network Programming" di Merlin Hughes et al., Mannig Publications
Co, Greenwich CT, 1997).
/* Copyright (c) 1996, 1997 Prominence Dot Com, Inc. * * See the file legal.txt in the txt directory for details. */ package prominence.msg; import java.io.*; import java.util.*; /** * A class that reads messages from a <tt>MultiplexInputStream</tt> * and forwards them on to the <tt>MessageOutput</tt> identified by the * message label. * * @version 1.0 1 Nov 1996 * @author Merlin Hughes * @see prominence.msg.MessageCopier */ public class Demultiplexer extends Thread { /** * The <tt>MultiplexInputStream</tt> from which messages are read. */ protected MultiplexInputStream i; /** * The message routing table. Maps from message labels to * <tt>MessageOutput</tt>s. */ protected Hashtable routes; /** * Current <tt>Demultiplexer</tt> ID. */ static private int plexerNumber; /** * Assigns unique <tt>Demultiplexer</tt> ID's. * @return An unique <tt>Demultiplexer</tt> ID. */ static private synchronized int nextPlexerNum () { return plexerNumber ++; } /** * Creates a new <tt>Demultiplexer</tt> reading from a specified * stream. * @param i The <tt>MultiplexInputStream</tt> from which mess. should be read */ public Demultiplexer (MultiplexInputStream i) { super ("Demultiplexer-" + nextPlexerNum ()); this.i = i; routes = new Hashtable (); } /** * Registers a <tt>MessageOutput</tt> as the destination for messages * with a particular label. * @param label The message label that is to be routed * @param o The destination for such messages */ public void register (String label, MessageOutput o) { routes.put (label, o); } /** * Deregisters a particular message label. * @param label The label that is to be deregistered */ public void deregister (String label) { routes.remove (label); } /** * Routes messages from the <tt>MultiplexInputStream</tt> to the * <tt>MessageOutput</tt> identified by their labels. <pre>This method * is called by a * new thread when the superclass <tt>start()</tt> method is called. * @see java.lang.Thread#start */ public void run () { try { while (true) { i.receive (); MessageOutput o = (MessageOutput) routes.get (i.label); if (o != null) { byte[] message = new byte[i.available ()]; i.readFully (message); synchronized (o) { o.write (message); o.send (); } } } } catch (IOException ex) { ex.printStackTrace (); } } } |
Costruttore
nextPlexerNum()
).
HashTable
che servirà per correlare
le etichette ai MessageOutput
.
MultiplexInputStream
a cui è attaccato.
Metodi
nextPlexerNum()
restituisce
il valore della variabile statica plexerNumber
e
successivamente lo incrementa di 1, fornendo così il numero
progressivo per i nomi dei vari Demultiplexer
che
si volessero creare.
register(...)
riceve come parametri
un'etichetta e un MessageOutput
, e inserisce un corrispondente
nuovo elemento nella HashTable
.
deregister(...)
riceve come parametro
un'etichetta e rimuove dalla HashTable
il corrispondente
elemento.
run()
è un ciclo infinito che
realizza le funzioni di:
MultiplexInputStream
;
HashTable
l'OutputStream
corrispondente all'etichetta del messaggio;
MessageOutput
trovato prima
e chiama send()
di tale MessageOutput
;
MessageOutput
per evitare che il messaggio si possa mescolare con quelli di
altri thread che condividono il MessageOutput
prima
di essere spedito.
HashTable
,
il messaggio viene scartato.
4.4.4) Classe DeliveryOutputStream e Interfaccia Recipient |
Consegnare i messaggi in una coda e aspettarsi
che l'applicazione cerchi attivamente di prelevarli da lì,
il che richiede in genere un thread separato, non è talvolta
necessario, sopratutto per una piccola applicazione.
Una alternativa è definire un diverso tipo di MessageOutput
,
che consegna i messaggi attivamente al destinatario invece di
scriverli su uno stream (che nel caso sopracitato è la
coda).
Ciò si può implementare facendo sì che questo
nuovo tipo di MessageOutput
, quando deve spedire
un messaggio con send()
, chiami direttamente un apposito
metodo receive()
(che deve quindi essere presente)
del destinatario.
In tal modo, quando un messaggio è spedito viene immediatamente
consegnato al destinatario per l'elaborazione.
La contropartita è che il thread che invia il messaggio
con send()
(tipicamente, un thread copiatore quale
il Demultiplexer
) deve aspettare il ritorno della
receive()
del destinatario per poter proseguire la
propria elaborazione e ricevere quindi il prossimo messaggio.
La classe DeliveryOutputStream
implementa un MessageOutput
del tipo descritto.
DeliveryOutputStream
La interfaccia Recipient
dichiara il metodo receive()
,
e deve essere implementata da ogni destinatario che si voglia
attaccare ad un DeliveryOutputStream
.
4.4.4.1) Classe DeliveryOutputStream |
E' un MessageOutput
che si attacca
a un oggetto che implementa l'interfaccia Recipient
.
I messaggi sono costruiti dentro un ByteArrayOutputStream
e sono consegnati al destinatario immediatamente, dopo averli
spostati in un ByteArrayInputStream
dal quale il
destinatario provvederà a leggerli.
DeliveryOutputStream
La definizione della classe è la seguente (dal libro "Java
Network Programming" di Merlin Hughes et al., Mannig Publications
Co, Greenwich CT, 1997).
/* Copyright (c) 1996, 1997 Prominence Dot Com, Inc. * * See the file legal.txt in the txt directory for details. */ package prominence.msg; import java.io.*; /** * A <tt>MessageOutput</tt> that immediately delivers its * contents to a recipient specified in the constructor when * send() is called. * <p>The message is delivered through the <tt>Recipient</tt> interface. * * @version 1.0 1 Nov 1996 * @author Merlin Hughes * @see prominence.msg.Recipient */ public class DeliveryOutputStream extends MessageOutput { /** * A <tt>ByteArrayOutputStream</tt> used to buffer the message contents. */ protected ByteArrayOutputStream byteO; /** * The recipient of messages sent to this stream. */ protected Recipient r; /** * Creates a new <tt>DeliveryOutputStream</tt> with a specified recipient. * @param r The recipient for messages sent to this stream. */ public DeliveryOutputStream (Recipient r) { super (new ByteArrayOutputStream ()); byteO = (ByteArrayOutputStream) out; this.r = r; } /** * Delivers the current message contents to the designated recipient. */ public void send () { byte buffer[] = byteO.toByteArray (); ByteArrayInputStream bI = new ByteArrayInputStream (buffer); r.receive (new DataInputStream (bI)); byteO.reset (); } } |
Costruttore
ByteArrayOutputStream
interno.
byteO
a tale ByteArrayOutputStream
ed un'altra, in r
, all'oggetto che implementa l'interfaccia
Recipient
, che viene passato come parametro.
Metodi
send()
effettua le seguenti operazioni:
ByteArrayOutputStream
(cioè una copia
del messaggio);
ByteArrayInputStream
attaccato
alla copia del messaggio;
receive()
del destinatario,
passandogli per convenienza un DataInputStream
attaccato
al ByteArrayInputStream
e quindi al messaggio;
ByteArrayOutputStream
per consentire
la costruzione del prossimo messaggio.
4.4.4.2) Interfaccia Recipient |
E' una semplice interfaccia che dichiara un
solo metodo, receive()
.
Esso ha come parametro un DataInputStream
(che di
fatto costituisce il corpo del messaggio) dal quale, con opportune
letture, potrà essere acquisito il messaggio.
La definizione dell'interfaccia è la seguente (dal libro
"Java Network Programming" di Merlin Hughes et al.,
Mannig Publications Co, Greenwich CT, 1997).
/* Copyright (c) 1996, 1997 Prominence Dot Com, Inc. * * See the file legal.txt in the txt directory for details. */ package prominence.msg; import java.io.*; /** * The interface through which the <tt>DeliveryOutputStream</tt> delivers * messages. * * @version 1.0 1 Nov 1996 * @author Merlin Hughes * @see prominence.msg.DeliveryOutputStream */ public interface Recipient { /** * Delivers a new message to the recipient. * @param in A <tt>DataInputStream</tt> from which the message contents can * be read */ public void receive (DataInputStream in); } |
4.4.5) Client per la chatline grafica e testuale |
Vediamo ora come è fatto il client
per la chatline grafica e testuale; in seguito vedremo il server,
che peraltro è molto semplice.
Come abbiamo già detto, il client è composto di
due componenti distinte ed indipendenti, che comunicano con le
loro controparti (grazie al server) per mezzo di multiplexing
di messaggi.
Il client consiste di tre classi:
CollabTool
);
WhiteBoard
);
ChatBoard
).
4.4.5.1) Classe CollabTool |
E' la classe principale (contiene il main()
)
ed ha diverse incombenze:
ChatBoard
e WhiteBoard
),
inserirle in un frame e mostrare il tutto;
La definizione della classe è la seguente.
import java.io.*; import java.awt.*; import java.net.*; import prominence.msg.*; public class CollabTool extends Frame { protected static int id = 0; public CollabTool (InputStream i, OutputStream o) { super ("Collaborative Tool " + (id ++)); setResizable(false); Whiteboard wb = new Whiteboard (); Chatboard cb = new Chatboard (); setLayout (new GridLayout (2, 1)); add (wb); add (cb); resize (400, 500); MessageOutputStream mO = new MessageOutputStream (o); MessageInputStream mI = new MessageInputStream (i); cb.setMessageOutput (new MultiplexOutputStream (mO, "chat")); wb.setMessageOutput (new MultiplexOutputStream (mO, "wb")); Demultiplexer d = new Demultiplexer (new MultiplexInputStream (mI)); d.register ("chat", cb.getMessageOutput ()); d.register ("wb", wb.getMessageOutput ()); d.start (); } static public void main (String args[]) throws IOException { Socket socket = new Socket(args[0], 5000); new CollabTool (socket.getInputStream(),socket.getOutputStream()).show (); } } |
Note
main()
apre la connessione di rete
con il server (il cui indirizzo IP o nome DNS deve essere passato
come parametro) sul port 5000.
CollabTool
.
Costruttore
Frame
passandogli un
titolo per la finestra.
cb
(un ChatBoard
) e wb
(un WhiteBoard
) e li aggiunge al Frame
;
mO
è un MessageOutputStream
;
mI
è un MessageInptStream
.
setMessageOutput (...)
di
wb
e cb
, aggancia il loro output ad
un corrispondente MultiplexOutputStream
.
MultiplexInputStream
agganciato a mI
;
Demultiplexer
agganciato a tale MultiplexInputStream
.
Demultiplexer
gli stream di output
ai quali dovranno essere inviati i messaggi di competenza di ognuna
delle due componenti. Questo si fa chiedendo a loro quale dev'essere
lo stream di output, mediante il loro metodo getMessageOutput()
che, appunto, restituisce il MessageOutput
che ognuna
delle due componenti avrà provveduto a creare secondo le
proprie inclinazioni.
Demultiplexer
.
4.4.5.2) Classe ChatBoard |
Questa classe implementa la componente testuale
della chatline.
E' costituita da una TextArea
in cui appaiono i messaggi
precedenti e da un TextField
in cui si immette di
volta in volta un nuovo messaggio. Esso viene spedito non appena
si preme il tasto Return dentro il TextField
.
Nel contesto del client, questa componente dialoga direttamente
con le corrispondenti componenti degli altri client collegati,
ed ignora completamente l'esistenza della componente grafica.
Implementa l'interfaccia Runnable
perché un
thread gestisce l'interazione con l'utente (quello principale)
mentre un thread separato (avviato con run()
) si
occupa di ricevere i messaggi che arrivano dalla connessione di
rete.
La definizione della classe è la seguente (dal libro "Java
Network Programming" di Merlin Hughes et al., Mannig Publications
Co, Greenwich CT, 1997).
/* Copyright (c) 1996, 1997 Prominence Dot Com, Inc. * * See the file legal.txt in the txt directory for details. */ import java.io.*; import java.awt.*; import prominence.msg.*; import prominence.util.Queue; public class Chatboard extends Panel implements Runnable { protected TextArea output; protected TextField input; protected Queue q; protected Thread exec; public Chatboard () { setLayout (new BorderLayout ()); add ("Center", output = new TextArea ()); output.setEditable (false); add ("South", input = new TextField ()); q = new Queue (); exec = new Thread (this); exec.start (); } protected MessageOutput o; public void setMessageOutput (MessageOutput o) { this.o = o; } public MessageOutput getMessageOutput () { return new QueueOutputStream (q); } public boolean action (Event e, Object arg) { if (e.target == input) { try { o.writeUTF (input.getText ()); o.send (); } catch (IOException ex) { ex.printStackTrace (); } output.appendText (input.getText () + "\n"); input.setText (""); return true; } return super.action (e, arg); } public void sendMessage (String s) { try { o.writeUTF (s); o.send (); } catch (IOException ex) { ex.printStackTrace (); } } public void run () { QueueInputStream qI = new QueueInputStream (q); while (true) { try { qI.receive (); String msg = qI.readUTF (); output.appendText ("-- " + msg + "\n"); } catch (IOException ex) { ex.printStackTrace (); } } } } |
Costruttore
q
a cui verranno attaccati i 2
stream per la comunicazione col Demultiplexer
:
Demultiplexer
per depositarvi i messaggi arrivati;
Chatboard
per prelevare (e quindi leggere) i messaggi arrivati.
Metodi
setMessageOutput(MessageOutput o)
e
getMessageOutput()
, che vengono chiamati da ColabTool
in fase di creazione della ChatBoard
, predispongono
gli stream di input e output. In particolare:
setMessageOutput(MessageOutput o)
si aggancia
al MultiplexOutputStream
, creato da CollabTool
,
che serve in output;
getMessageOutput()
invece crea un QueueOutputStream
attaccato a q
e lo passa a CollabTool
,
il quale così sa dove inviare i dati prodotti dal Demultiplexer
per il ChatBoard
.
run()
, che fa partire il thread separato
di lettura dei dati in arrivo, prima di tutto crea un QueueInputStream
attaccato a q
, e poi entra in un ciclo infinito di
estrazione dei messaggi da tale stream.
action(...)
gestisce l'evento di tipo
action
nel solo TextField
, inviando
un nuovo messaggio; rimanda alla superclasse la gestione di altri
tipi di eventi.
ChatBoard
4.4.5.3) Classe WhiteBoard |
Questa classe implementa la componente grafica
della chatline.
Offre una superficie sulla quale l'utente può disegnare a mano libera. Quando l'utente:
Nel contesto del client, anche questa componente dialoga con le
corrispondenti componenti degli altri client ed ignora l'esistenza
della componente ChatBoard
.
Implementa l'interfaccia Recipient
, e quindi implementa
un metodo receive()
per la ricezione immediata dei
messaggi.
La definizione della classe è la seguente (dal libro "Java
Network Programming" di Merlin Hughes et al., Mannig Publications
Co, Greenwich CT, 1997).
/* Copyright (c) 1996, 1997 Prominence Dot Com, Inc. * * See the file legal.txt in the txt directory for details. */ import java.io.*; import java.awt.*; import prominence.msg.*; public class Whiteboard extends Canvas implements Recipient { public Whiteboard () { setBackground (new Color (255, 255, 204)); } protected MessageOutput o; public void setMessageOutput (MessageOutput o) { this.o = o; } public boolean mouseDown (Event e, int x, int y) { transmit (x, y); return super.mouseDown (e, x, y); } public boolean mouseDrag (Event e, int x, int y) { scribble (x, y); transmit (x, y); return super.mouseDrag (e, x, y); } public boolean mouseUp (Event e, int x, int y) { scribble (x, y); transmit (x, y); try { o.send (); } catch (IOException ex) { ex.printStackTrace (); } return super.mouseUp (e, x, y); } protected int oX, oY; protected void transmit (int x, int y) { try { o.writeInt (x); o.writeInt (y); } catch (IOException ex) { ex.printStackTrace (); } oX = x; oY = y; } protected void scribble (int x, int y) { Graphics g = getGraphics (); g.drawLine (oX, oY, x, y); g.dispose (); } public MessageOutput getMessageOutput () { return new DeliveryOutputStream (this); } public void receive (DataInputStream dI) { Graphics g = getGraphics (); try { int x0 = dI.readInt (), y0 = dI.readInt (); while (dI.available () > 0) { int x1 = dI.readInt (), y1 = dI.readInt (); g.drawLine (x0, y0, x1, y1); x0 = x1; y0 = y1; } } catch (IOException ex) { ex.printStackTrace (); } g.dispose (); } } |
Costruttore
WhiteBoard
è costituito da un unico Canvas
(area di disegno), istanziato automaticamente dato che WhiteBoard
estende proprio Canvas
. Il costruttore si limita
a cambiare il colore di fondo.
Metodi
ChatBoard
, i metodi setMessageOutput(MessageOutput
o)
e getMessageOutput()
, che vengono chiamati
da CollabTool
in fase di creazione della WhiteBoard
,
predispongono gli stream di I/O. In particolare:
setMessageOutput(MessageOutput o)
si aggancia
al MultiplexOutputStream
, creato da CollabTool
,
che serve in output;
getMessageOutput()
invece crea un DeliveryOutputStream
,
avente come target il WhiteBoard
stesso, e lo passa
a CollabTool
, il quale così sa dove inviare
i dati prodotti dal Demultiplexer
e destinati al
WhiteBoard
.
scribble()
disegna un trattino dall'ultima
posizione del mouse (ox, oy
) a quella corrente (x,
y
), e per far questo crea un contesto grafico che poi elimina.
transmit()
scrive nel corpo del messaggio
corrente le coordinate (x, y
) attuali, che così
si aggiungono a quelle precedenti; aggiorna quindi le coordinate
(ox, oy
) con i valori correnti.
mouseDown(...)
inizia a costruire il prossimo
messaggio, chiamando transmit(...)
;
mouseDrag(...)
mostra l'ultimo tratto disegnato
(con scribble(...)
) e poi inserisce sul messaggio
le nuove coordinate finali, chiamando transmit(...)
;
mouseUp(...)
disegna l'ultimo tratto (con scribble(...)
),
completa il messaggio (con transmit(...)
) e poi invia
effettivamente il messaggio con il metodo send()
del MultiplexOutputStream
a cui è attaccato.
receive()
legge il contenuto di un
messaggio (cioè una successione di coppie (x, y
))
e provvede ad effettuare il corrispondente disegno.
WhiteBoard
4.4.6) Server per la chatline grafica e testuale |
E' il server a cui tutti i client si connettono.
Invia in broadcast a tutti (tranne che al mittente) tutto ciò
che riceve da uno qualunque di essi.
Concettualmente (e anche strutturalmente) è identico al
server dell'esempio 9, solo che comunica attraverso l'uso di messaggi.
Si noti che i messaggi sono a loro volta pacchetti composti da:
Questo però il server lo ignora, e non ne preoccupa. Quindi,
tale server continuerà a funzionare correttamente anche
anche nel caso in cui ai client si dovessero aggiungere ultreriori
componenti funzionali, purché anch'esse comunichino fra
loro facendo uso di stream per il multiplexing dei messaggi.
Il codice è costituito da due classi. La prima, CollabServer
,
accetta richieste di connessione sul port 5000 e, ogni volta che
ne arriva una, istanzia un oggetto della classe CollabHandler
che si occupa di gestirla.
import java.net.*; import java.io.*; import java.util.*; public class CollabServer { //-------------------------------------------------- public CollabServer() throws IOException { ServerSocket server = new ServerSocket(5000); System.out.println ("Accepting connections..."); while(true) { Socket client = server.accept(); System.out.println ("Accepted from " + client.getInetAddress()); new CollabHandler(client).start(); } } //-------------------------------------------------- public static void main(String args[]) throws IOException { new CollabServer(); } } |
La seconda si occupa della gestione di una singola connessione
e dell'invio a tutte le altre, in broadcast, dei dati provenienti
da tale connessione.
import java.net.*; import java.io.*; import java.util.*; import prominence.msg.*; public class CollabHandler extends Thread { protected static Vector handlers = new Vector(); protected Socket socket; protected MessageInputStream is; protected MessageOutputStream os; //-------------------------------------------------- public CollabHandler(Socket socket) throws IOException { this.socket = socket; is = new MessageInputStream(socket.getInputStream()); os = new MessageOutputStream(socket.getOutputStream()); } //-------------------------------------------------- public void run() { try { handlers.addElement(this); //e' un metodo sincronizzato while (true) { is.receive(); byte[] buffer = new byte[is.available()]; is.readFully(buffer); broadcast(this, buffer); } } catch(IOException ex) { ex.printStackTrace(); } finally { handlers.removeElement(this); //e' un metodo sincronizzato try { socket.close(); } catch(Exception ex) { ex.printStackTrace(); } } } //-------------------------------------------------- protected static void broadcast(CollabHandler sender, byte[] buffer) { synchronized (handlers) { //ora nessuno puo' aggiungersi o abbandonare Enumeration e = handlers.elements(); while (e.hasMoreElements()) { CollabHandler c = (CollabHandler) e.nextElement(); if (c != sender) { try { c.os.write(buffer); c.os.send(); } catch(Exception ex) { c.stop(); } } } } } } |
Note
CollabTool
avvia mediante il main()
il thread principale che:
CollabHandler
) per
la gestione di ogni nuova connessione.
CollabHandler
attacca due message
stream ai due canali di comunicazione, e quando si avvia:
Vector
statico delle connessioni
attive;
broadcast()
;
Vector
delle connessioni.
broadcast()
di CollabHandler
si sincronizza sul Vector
delle connessioni e spedisce
a tutti, tranne che a se stesso, il messaggio passato come parametro.
4.5) Ulteriori estensioni di funzionalità tramite messaggi |
Abbiamo visto come l'uso e la concatenazione
di message stream costituisca un potente e versatile meccanismo
per lo sviluppo di applicazioni di rete.
Con tali conoscenze in mente, è relativamente semplice
procedere ad ulteriori estensioni di funzionalità, quali
ad esempio una trasmissione di tipo multicast,
ossia una trasmissione nella quale si specifica il sottoinsieme
dei destinatari che deve ricevere il messaggio.
Tale funzionalità può essere ottenuta con una classe
RoutingOutputStream
, che avrà il compito di
incapsulare ogni messaggio dotandolo di una CI costituita da un
vettore di stringhe che specificano ciascuna un destinatario.
RoutingOutputStream
per trasmissioni multicast,
All'altra estremità, tipicamente sul server, a valle di
un MessageInputStream
si potrà predisporre
una corrispondente classe RoutingInputStream
che
estrae la lista di destinatari e la rende disponibile all'esterno,
e una classe Router
(che ha la funzione di thread
copiatore) incaricata di inviare una copia di ogni messaggio solamente
ai corrispondenti destinatari.
Router
Con riferimento alla specifica concatenazione illustrata nella
figura 4-25, il messaggio che viene instradato dal Router
è in realtà un pacchetto contenente la CI del MultiplexOutputStream
,
e cioè l'etichetta della componente applicativa che deve
ricevere i dati.
Di conseguenza, a valle di ogni MessageOutput
in
uscita dal Router
ci potrà essere un MessageInput
,
quindi un MultiplexInputStream
ed infine un Demultiplexer
che esamina tale etichetta e smista il messaggio alla corretta
componente applicativa.
In tal modo è possibile costruire applicazioni di tipo
client-server costituite da componenti indipendenti capaci di
effettuare trasmissioni multicast, il che consente di soddisfare
praticamente qualunque esigenza applicativa.