MokaByte 57 - 9mbre 2001 
Corso CORBA
X ed ultima parte: Callback CORBA
di
Gianluca Morello
Una semplice Chat CORBA con il pattern Observer ed invocazione callback

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


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