Esistono
situazioni in cui un client è interessato al
verificarsi di un particolare evento sul server (cambiamento
di uno stato, occorrenza di un dato errore, …). In
un ambiente distribuito, in cui tipicamente non è
possibile conoscere a priori il numero di client,
soddisfare questa necessità è problematico.
Il
meccanismo più semplice implementabile è
il polling: il client periodicamente controlla il
valore cui è interessato. Questo approccio
è poco robusto ed implica un notevole spreco
di risorse. Approcci migliori sono forniti da meccanismi
di notifica asincrona quali CORBA Event Service o
sistemi di messaggistica.
Un’ultima
possibilità è quella di utilizzare una
callback, sarà il server ad invocare direttamente
un metodo di notifica sul client o su un oggetto ad
esso associato. Questo approccio ha notevoli vantaggi
computazionali, ma può presentare difficoltà
nel caso in cui la topologia di rete veda il client
ed il server separati da un firewall.
Figura
1 — Il pattern Observer
Tralasciando
i problemi di rete, l’utilizzo di callback si presta
bene a risolvere alcuni classici problemi di programmazione
distribuita. A titolo d’esempio s’immagini la costruzione
di una semplice Chat.
I
client devono ricevere una notifica nel caso in cui
si connetta un nuovo utente, venga inviato un messaggio
o un utente lasci la chat. Si può organizzare
l’applicazione adottando un meccanismo simile a quello
della gestione degli eventi Java: i client sottoscrivono
presso il server chat un servizio di notifica.
La
soluzione classica per problemi di questo tipo è
data dal pattern Observer. Questo pattern è
pensato proprio per quelle situazioni in cui un cambiamento
di stato su un oggetto (Subject) ha potenziali impatti
su un numero imprecisato di altri oggetti (Observers).
In queste situazioni solitamente è necessario
inviare agli Observers una generica notifica.
Il
numero di Observer deve poter variare a run-time ed
il Subject non deve sapere quali informazioni sono
necessarie al generico Observer. Per questa ragione
la formulazione classica del pattern prevede che siano
definiti:
- una
classe astratta Subject che fornisca i metodi subscribe,
unsubscribe e notify
- una
classe ConcreteSubject che definisca i metodi di
accesso alle proprietà interessate
- una
classe Observer che fornisca il metodo di ricezione
notifica
- una
classe ConcreteObserver che mantenga un riferimento
al ConcreteSubject e fornisca il metodo update utilizzato
per riallineare il valore delle proprietà
Quando
un Subject cambia stato invia una notifica a tutti
i suoi Observer (quelli che hanno invocato su di lui
il metodo subscribe) in modo tale che questi possano
interrogare il Subject per ottenere le opportune informazioni
e riallinearsi al Subject.
Il
pattern Observer, anche conosciuto come publish/subscribe,
è molto utilizzato nei casi in cui è
necessario implementare meccanismi uno a n di notifica
asincrona. Anche il CORBA Event Service adotta il
pattern Observer.
Tornando
alla chat, l’implementazione del pattern può
essere leggermente semplificata facendo transitare
direttamente con la notifica il messaggio cui i client
sono interessati.
Nell’esempio
in esame ogni client si registrerà come Observer
presso il server chat (in uno scenario reale probabilmente
si definirebbe un oggetto Observer separato). Il server
invocherà il metodo di notifica su tutti gli
Observer registrati inviando loro il messaggio opportuno.
Nello scenario in esame anche l’oggetto Observer,
essendo remoto, dovrà essere attivato sull’ORB
e definito via IDL
//
IDL
module
chat {
// Forward declaration
interface SimpleChatObserver;
struct User {
string userId;
SimpleChatObserver callObj;
};
struct Message {
User usr;
string msg;
};
// OBSERVER
interface SimpleChatObserver {
// N.B. Il server non aspetta alcuna
// risposta dai vari client
oneway void callback(in Message msg);
};
// SUBJECT
interface SimpleChat {
void subscribe(in User usr);
void unsubscribe(in User usr);
void sendMessage(in Message msg);
};
};
Nell’esempio
non vengono utilizzati gli adapter e l’attivazione
viene effettuata mediante connect (quindi è
utilizzabile anche con Java IDL). Sarà il client
stesso a registrarsi presso l’ORB
package
client;
import
chat.*;
import
org.omg.CORBA.*;
import
org.omg.CosNaming.*;
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
public
class SimpleChatClient
extends _SimpleChatObserverImplBase {
private
SimpleChat chat = null;
private User user = null;
private JTextField tMsg = new JTextField();
private JButton bSend = new JButton("Send");
private JTextArea taChat = new JTextArea();
public
SimpleChatClient() {
super();
// codice di inizializzazione UI …
}
public
void init(String userId) throws Exception {
// Crea ed inizializza l'ORB
ORB orb = ORB.init((String[])null, null);
// Root naming context
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameService");
NamingContext ncRef =
NamingContextHelper.narrow(objRef);
// Utilizzo il Naming per ottenere il riferimento
NameComponent nc =
new NameComponent("SimpleChat", "");
NameComponent path[] = {nc};
chat = SimpleChatHelper.narrow(ncRef.resolve(path));
// Si registra presso l'ORB
// N.B. Il server deve essere in grado di effettuare
// un'invocazione remota del metodo callback.
orb.connect(this);
// Crea e registra user
user = new User(userId, this);
chat.subscribe(user);
}
//
Metodo remoto di notifica invocato dal server
public
void callback(Message msg) {
taChat.append("#" + msg.usr.userId
+ " - " + msg.msg + "\n");
tMsg.setText("");
}
//
Lo userId del client è passato da linea di
comando
public
static void main(String args[]) throws Exception {
SimpleChatClient sc = new SimpleChatClient();
sc.init(args[0]);
}
}
Il
riferimento al client viene fatto transitare nell’oggetto
User e registrato presso la chat dal metodo subscribe.
Per semplicità la deregistrazione del client
è associata all’evento di chiusura della finestra
(in un contesto reale sarebbe necessario un approccio
più robusto)
JFrame
f = new JFrame("SIMPLE CHAT");
f.addWindowListener(new
WindowAdapter() {
public
void windowClosing(WindowEvent e) {
try {
chat.unsubscribe(user);
System.exit(0);
} catch (Exception ex) {}
}
});
L’oggetto
SimpleChatImpl è invece avviato e registrato
presso l’ORB da un oggetto server con le modalità
consuete.
SimpleChatImpl
definisce il funzionamento della chat vera e propria
package
server;
import
chat.*;
import
java.util.Hashtable;
import
java.util.Enumeration;
public
class SimpleChatImpl extends _SimpleChatImplBase {
Hashtable h = new Hashtable();
public SimpleChatImpl() {
super();
}
// Aggiunge un utente alla chat
public synchronized void subscribe(User user) {
h.put(user.userId, user);
Message msg = new Message(user, "Has joined the Chat.");
this.sendMessage(msg);
System.out.println("Added: " + user.userId);
}
// Rimuove un utente dalla chat
public synchronized void unsubscribe(User user) {
h.remove(user.userId);
Message msg = new Message(user, "Left the Chat.");
this.sendMessage(msg);
System.out.println("Removed: " + user.userId);
}
// Invia il messaggio a tutti gli utenti
public void sendMessage(Message msg) {
User user = null;
for (Enumeration e=h.elements(); e.hasMoreElements();)
{
user = (User) e.nextElement();
// Invoca la callback
try {
user.callObj.callback(msg);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
L’esecuzione
dell’esempio segue i consueti passi. Come detto in
precedenza, un possibile problema è legato
all’eventuale presenza di firewall che inibiscano
l’invocazione da server a client.
Figura
2 — Il client chat in esecuzione
Conclusioni
In
questo articolo abbiamo realizzato una semplice chat
utilizzando callback CORBA ed il pattern Observer.
Con questo articolo abbiamo concluso il nostro corso
CORBA.
Allegati
Gli
esempi completi si possono scaricare qui
|