HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

It's widely known that an applet isn't allowed to create a network connection to a computer that's not the one from which the applet itself was loaded. This has led to the idea that two applets aren't allowed to communicate directly with each other unless they're located on the same host. This article provides a brief overview of Java's Remote Method Invocation (RMI), describes a small conferencing program and shows how direct applet-to-applet communication can be established with RMI.

RMI's Overview
Three approaches for programming distributed systems can be identified. The first one consists of using a low-level API such as the java.net package. The second one uses middleware such as OMG's CORBA. The third one consists of using a high level API such as RMI.

In a nutshell, the advantages of using RMI over other approaches are:

  • Transparency: From the programmer's perspective, remote objects and methods are somewhat like local objects and methods; the only differences are that remote classes must implement the java.rmi.Remote interface and remote methods must throw the java.rmi.RemoteException exception.
  • Integration: As RMI is a standard component of Java, it supports distributed programming in a seamless manner with the other aspects of the language.
  • Automatic object serialization: Java provides the facility to transfer data not only among hosts, but also classes and objects.
  • Automatic class loading: If an object is of a class that is not available on the given host, the class is dynamically loaded at runtime.
  • Distributed garbage collection: As objects are moved among hosts, RMI keeps track of which remote objects are no longer referenced by clients and deletes them automatically.
The ability to pass objects among hosts makes it extremely easy to program applications in which some classes are both clients and servers. For example, a method of a remote class R1 can be called with a parameter being a remote class R2. The remote class R1 will then be able to call any of the methods of the remote class R2.

A Conferencing Program
In this section I'll walk you through the complete code of a small conferencing program and show how it takes advantage of the facilities provided by RMI to achieve direct applet-to-applet communication. The program consists of two basic remote objects (implementing the "java.rmi.Remote" interface):

  • A server that accepts requests from clients who wish to participate in a discussion, and requests to identify which clients are already active
  • An applet that participates both as a client of the server and as the entity notified when other applets are ready to communicate (thus the applet is also a server)
To explain briefly, each applet registers its reference in the server, which broadcasts these references back to all other applets. All applets now have a reference to all other applets, which enables them to communicate directly without having to access the server. (To prove this, the system allows an applet to shut down the server.) Each applet displays the list of other available applets, allows the user of the system to send messages to other users and displays incoming messages.

Defining the Remote Interfaces
The first step in writing an applet or an application using RMI is to define the remote interfaces to the remote objects in the application. A remote interface extends the interface java.rmi.Remote and declares all the methods that may be invoked remotely. The two remote interfaces of the conferencing program are the Talker and TalkerServer interfaces (see Listings 1 and 2). The Talker interface is used by the applet to receive notifications from the server and messages from other applets while the TalkerServer is used by the server to receive commands from the applet. The Talker interface is implemented by the TalkerImpl class and the TalkerServer interface is implemented by the TalkerServerImpl class.

Creating the Remote Server
The next step is to define the TalkerServerImpl class that extends java.rmi.UnicastRemoteObject. UnicastRemoteObject provides support for point-to-point object references using TCP streams. The TalkerServerImpl class (see Listing 3) has a constructor, a main method and an implementation for the methods declared in the TalkerServer interface. The main method starts the remote server process, installs a security manager, creates a registry on port 2006, creates a TalkerServer remote object and finally makes this object available via the registry. The implementation of all the methods is trivial. Register adds a client to a private vector and notifies every registered client that a new member has joined the group. UnRegister notifies every client that one of them has left the session and removes it from the vector. Lookup returns a copy of the vector, and shutdown terminates the server. Although these methods are remote, they differ from local methods only in throwing RemoteException.

Implementing the Talker Interface
TalkerImpl (see Listing 4) implements the Talker interface. The constructor saves references of the applet and the current user's name. The applet is used to update the user interface; the name is used by other users of the system. Remote users call the getName method to identify the current user and call the recvMsg method to send him messages. The add and remove methods are called by the server to update the system's list of users.

Creating the User Interface
The user interface (see Listing 5) consists of the following components displayed in an applet (Figure 1):

  • A TextField to enter the name of the user
  • A List to show the names of remote users
  • A TextArea to display the sent messages
  • A TextArea to display the received message
  • Three buttons to start participating in the system, stop participating and shut down the server, respectively

Figure 1
Figure 1:

Although the code of the applet is longer than the code of the other classes, it is fairly straightforward. The init method initializes a connection with the server and contains the code necessary to lay down the components within the applet. An ActionListener is added to each button. StartListener initializes the list of persons already connected and registers the current user in the server; StopListener unregisters the user from the server. ShutdownListener allows the user to shut down the server to prove that once connections among users are established, the server is not needed anymore. A KeyListener is also added to the TextArea handling the messages to be sent. The initList, addTalker and removeTalker methods update the list of users. The addToMsg method displays an incoming message.

Installing the System
This system is based on an applet accessing a server and other applets with RMI. Therefore, it will work only with a browser supporting all of the features of JDK1.1. For this experiment I used HotJava running on several PCs while the server was running on a UNIX machine. The system is easy to configure; the only requirement is that the server must be located on the same host as the HTTP daemon. The HTML file is shown here:

<HTML>
<TITLE>Talk</title>
<APPLET code="TalkerApplet.class" width=550 height=450>
</APPLET>
</HTML>

Conclusion
In this article, I have shown how to take advantage of the facilities provided by RMI to pass remote objects in remote calls to allow both bidirectional communication between the applets and the server and among the applets themselves. This short example also demonstrates that direct applet-to-applet communication is, in fact, possible.

About the Author
Pascal Ledru is a software engineer specializing in networking applications at Aerospatiale, Inc. He is also working on his Ph.D. in computer science at the University of Alabama in Huntsville. Pascal may be reached at [email protected]

	

Listing 1.
 
import java.rmi.*; 
public interface Talker extends Remote { 
  public String getName() throws RemoteException; 
  public void recvMsg(String from, String msg) throws RemoteException; 
  public void add(Talker talker) throws RemoteException; 
  public void remove(Talker talker) throws RemoteException; 
} 

Listing 2.
 
import java.rmi.*; 
public interface TalkerServer extends Remote { 

  public void register(Talker talker) throws RemoteException; 
  public void unRegister(Talker talker) throws RemoteException; 
  public Talker[] lookup() throws RemoteException; 
  public void shutdown() throws RemoteException; 
} 

Listing 3.
 
import java.rmi.*; 
import java.rmi.server.UnicastRemoteObject; 
import java.rmi.registry.LocateRegistry; 
import java.util.*; 

public class TalkerServerImpl extends UnicastRemoteObject implements TalkerServer { 

  private Vector v = new Vector(); 

  public TalkerServerImpl() throws RemoteException { 
    super(); 
  } 

  public synchronized void register(Talker talker) throws RemoteException { 
    if (!v.contains(talker)) { 
      v.addElement(talker); 
      for (int i = 0; i < v.size(); i++) 
        ((Talker)v.elementAt(i)).add(talker); 
    } 
  } 

  public synchronized void unRegister(Talker talker) throws RemoteException { 
    for (int i = 0; i < v.size(); i++) 
      ((Talker)v.elementAt(i)).remove(talker); 
    v.removeElement(talker); 
  } 

  public synchronized Talker[] lookup() throws RemoteException { 
    Talker[] talkers = new Talker[v.size()]; 
    v.copyInto(talkers); 
    return talkers; 
  } 

  public void shutdown() throws RemoteException { 
    System.exit(0); 
  } 

  public static void main(String args[]) { 

    System.setSecurityManager(new RMISecurityManager()); 

    try { 
      System.out.println("TalkerServer.main: creating registry"); 
      LocateRegistry.createRegistry(2006); 

      System.out.println("TalkerServer.main: creating server"); 
      TalkerServerImpl talkerServer = new TalkerServerImpl(); 

      System.out.println("TalkerServer.main: binding server"); 
      Naming.rebind("//:2006/TalkerServer", talkerServer); 

      System.out.println("TalkerServer.main: done"); 
    } catch (Exception ex) { 
      System.err.println("TalkerServer.main: exception: " + ex.getMessage()); 
      ex.printStackTrace(); 
    } 

  } 

} 

Listing 4.
 
import java.rmi.*; 
import java.rmi.server.UnicastRemoteObject; 

class TalkerImpl extends UnicastRemoteObject implements Talker { 

  private TalkerApplet applet; 
  private String name; 

  public TalkerImpl(TalkerApplet applet, String name) throws RemoteException { 
    this.applet = applet; 
    this.name = name; 
  } 

  public String getName() throws RemoteException { 
    return name; 
  } 

  public void recvMsg(String from, String msg) throws RemoteException { 
    applet.addToMsg(from, msg); 
  } 

  public void add(Talker talker) throws RemoteException { 
    applet.addTalker(talker, talker.getName()); 
  } 

  public void remove(Talker talker) throws RemoteException { 
    applet.removeTalker(talker, talker.getName()); 
  } 

} 

Listing 5.
 
import java.applet.*; 
import java.awt.*; 
import java.awt.event.*; 
import java.net.*; 
import java.rmi.*; 
import java.util.*; 

public class TalkerApplet extends Applet { 

  class StartListener implements ActionListener { 
    TalkerApplet applet; 
    public StartListener(TalkerApplet applet) { 
      this.applet = applet; 
    } 
    public void actionPerformed(ActionEvent ev) { 
      field.setEditable(false); 
      start.setEnabled(false); 
      stop.setEnabled(true); 
      try { 
        initList(); 
        talker = new TalkerImpl(applet, field.getText()); 
        server.register(talker); 
      } catch (Exception ex) { 
      } 
    } 
  } 

  class StopListener implements ActionListener { 
    public void actionPerformed(ActionEvent ev) { 
      field.setEditable(true); 
      start.setEnabled(true); 
      stop.setEnabled(false); 
      try { 
        server.unRegister(talker); 
        talker = null; 
      } catch (Exception ex) { 
      } 
    } 
  } 

  class ShutdownListener implements ActionListener { 
    public void actionPerformed(ActionEvent ev) { 
      shutdown.setEnabled(false); 
      try { 
        server.shutdown(); 
      } catch (Exception ex) { 
      } 
    } 
  } 

  class TextAreaListener extends KeyAdapter { 
    public void keyReleased(KeyEvent ev) { 
      String s; 
      String s1; 
      String s2; 
      int i1; 
      int i2; 
      if (talker != null && ev.getKeyCode() == KeyEvent.VK_ENTER) { 

        // send the last line of text 
        s1 = senderText.getText(); 
        i1 = s1.lastIndexOf('\n'); 
        if (i1 == 0) s = s1; 
        else { 
          s2 = s1.substring(0, i1); 
          i2 = s2.lastIndexOf('\n'); 
          if (i2 == -1) s = s1; 
          else s = s2.substring(i2+1, s2.length()) + '\n'; 
        } 

        // and send it!! 
        String[] names = list.getSelectedItems(); 
        for (int i = 0; i < names.length; i++) 
          for (int j = 0; j < talkersNames.size(); j++) 
            if (names[i].equals((String)talkersNames.elementAt(j))) { 
              try { 
                ((Talker)talkers.elementAt(j)).recvMsg(field.getText(), s); 
              } catch (RemoteException ex) { 
              } 
            } 
      } 
    } 
  } 

  Button start, stop, shutdown; 
  TextField field; 
  List list; 
  TextArea receiverText; 
  TextArea senderText; 
  TalkerImpl talker; 
  TalkerServer server; 
  java.util.Vector talkers = new java.util.Vector(); 
  java.util.Vector talkersNames = new java.util.Vector(); 

  public void init() { 

    try { 
      server = (TalkerServer)Naming.lookup("//pluto:2006/TalkerServer"); 
    } catch (Exception ex) { 
      ex.printStackTrace(); 
      return; 
    } 

    setSize(550, 450); 

    GridBagLayout layout = new GridBagLayout(); 
    GridBagConstraints c = new GridBagConstraints(); 
    c.anchor = GridBagConstraints.NORTHWEST; 
    c.insets = new Insets(4, 4, 4, 4); 

    Panel p1 = new Panel(); 
    p1.setLayout(layout); 

    c.gridx = 0; c.gridy = 0; 
    Label label1 = new Label("Name:"); 
    layout.setConstraints(label1, c); 
    p1.add(label1); 

    c.gridx = 1; 
    field = new TextField(15); 
    layout.setConstraints(field, c); 
    p1.add(field); 

    c.gridx = 0; c.gridy = 1; 
    Label label2 = new Label("Talk to:"); 
    layout.setConstraints(label2, c); 
    p1.add(label2); 

    c.gridx = 1; 
    list = new List(5, false); 
    list.setMultipleMode(true); 
    layout.setConstraints(list, c); 
    p1.add(list); 

    c.gridx = 0; c.gridy = 2; 
    Label sender = new Label("Send message:"); 
    layout.setConstraints(sender, c); 
    p1.add(sender); 

    c.gridx = 1; 
    senderText = new TextArea(5, 50); 
    senderText.addKeyListener(new TextAreaListener()); 
    layout.setConstraints(senderText, c); 
    p1.add(senderText); 

    c.gridx = 0; c.gridy = 3; 
    Label receiver = new Label("Receive message:"); 
    layout.setConstraints(receiver, c); 
    p1.add(receiver); 

    c.gridx = 1; 
    receiverText = new TextArea(5, 50); 
    layout.setConstraints(receiverText, c); 
    p1.add(receiverText); 

    Panel p2 = new Panel(); 
    p2.setLayout(new FlowLayout(FlowLayout.CENTER)); 
    start = new Button("Start"); 
    start.addActionListener(new StartListener(this)); 
    p2.add(start); 
    stop = new Button("Stop"); 
    stop.setEnabled(false); 
    stop.addActionListener(new StopListener()); 
    p2.add(stop); 
    shutdown = new Button("Shutdown Server"); 
    shutdown.addActionListener(new ShutdownListener()); 
    p2.add(shutdown); 

    add(p1); 
    add(p2); 
    initList(); 

  } 

  void initList() { 
    Talker[] talkersArray; 
    try { 
      talkersArray = server.lookup(); 
    } catch (RemoteException ex) { 
      ex.printStackTrace(); 
      return; 
    } 
    list.removeAll(); 
    talkers.removeAllElements(); 
    talkersNames.removeAllElements(); 
    for (int i = 0; i < talkersArray.length; i++) { 
      try { 
        list.add(talkersArray[i].getName()); 
        talkers.addElement(talkersArray[i]); 
        talkersNames.addElement(talkersArray[i].getName()); 
      } catch (RemoteException ex) { 
      } 
    } 
  } 

  public void addToMsg(String from, String msg) { 
    receiverText.append(from + ": " + msg); 
  } 

  public void addTalker(Talker talker, String name) { 
    list.add(name); 
    talkers.addElement(talker); 
    talkersNames.addElement(name); 
  } 

  public void removeTalker(Talker talker, String name) { 
    list.remove(name); 
    talkers.removeElement(talker); 
  } 

} 
  
      
 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.