The Source for Java Technology Collaboration


-- JohnCatherino - 18 Jan 2005

Using the ItemProxy and ClientProxy classes

Here is an example of the use of ItemProxy and ClientProxy objects. They are of critical importance when a client is behind a firewall that does not allow incoming socket connections; yet the design requires asynchronous client callbacks.

The solution is the ItemProxy and ClientProxy classes. These allow the remainder of the application development to proceed without regard to the firewall problem. When a client is behind a firewall, it requests a remote reference to a ClientProxy object from the server, and links itself to it with a local ItemProxy object.

It is probably easier to read source than the explanation, so here is a simple example client and server class. The the cajo project library file cajo.jar, will be needed for this experiment.

Note: the server should be started first, and the client should be shut down first, when finished.


// file: Client.java
import gnu.cajo.invoke.Remote;
import gnu.cajo.invoke.RemoteInvoke;
import gnu.cajo.utils.extra.ItemProxy;

public class Client {
   Thread loop;
   ItemProxy ip;
   int count;
   Object server;
   Client() throws Exception {
      server = Remote.getItem("//localhost:1198/main" rel="nofollow");
      RemoteInvoke cp =
         (RemoteInvoke)Remote.invoke(server, "getCp", null);
      ip = new ItemProxy(cp, this);
      loop = new Thread(new Runnable() {
         public void run() {
            while(!loop.isInterrupted()) {
               try {
                  Object result =
                     Remote.invoke(server, "call", "Hello " + count++);
                  System.out.println("Server call, result = " + result);
               } catch(Exception x) { // network error
                  x.printStackTrace();
                  break;
               }
               try { loop.sleep(2000); }
               catch(InterruptedException x) { break; }
            }
         }
      });
      loop.start();
      System.out.println("Client started");
   }
   public String callback(String message) {
      System.out.println("Server callback, data = " + message);
      return "Thanks!";
   }
   static Client client;
   public static void main(String args[]) throws Exception { client = new Client(); }
}


// file: Server.java
import gnu.cajo.invoke.Remote;
import gnu.cajo.invoke.RemoteInvoke
import gnu.cajo.utils.ItemServer;
import gnu.cajo.utils.extra.ClientProxy;

public class Server {
   Thread loop;
   ClientProxy cp;
   int count;
   Server() throws Exception {
      Remote.config("localhost", 1198, "localhost", 1198);
      ItemServer.bind(this, "main");
      loop = new Thread(new Runnable() {
         public void run() {
            while(!loop.isInterrupted()) {
               if (cp != null) {
                  try {
                     Object result =
                        Remote.invoke(cp, "callback", "Hello " + count++);
                     System.out.println("Client callback, result = " + result);
                  } catch(Exception x) { // network error
                     x.printStackTrace();
                     break;
                  }
               }
               try { loop.sleep(2000); }
               catch(InterruptedException x) { break; }
            }
         }
      });
      loop.start();
      System.out.println("Server started");
   }
   public String call(String message) {
      System.out.println("Client call, data = " + message);
      return "Thanks!";
   }
   public RemoteInvoke getCp() throws Exception {
      cp = new ClientProxy();
      return cp.remoteThis;
   }
   static Server server;
   public static void main(String args[]) throws Exception { server = new Server(); }
}

The server can be compiled and run as follows:

javac -classpath cajo.jar Server.java

java -classpath cajo.jar;. Server

The client can be compiled and run as follows:

javac -classpath cajo.jar Client.java

java -classpath cajo.jar;. Client

The technique by which this works is that the client's ItemProxy object creates an internal thread, which makes an outbound call to the server's ClientProxy object. This call will block until there is a server callback event. This causes callback invocations to take place over the client's outbound socket connection.

Any time the server invokes a method on its ClientProxy reference, its thread blocks, and the ItemProxy's thread wakes. The ItemProxy thread invokes the callback method on its local object, and returns the result, thereby waking the server thread, and blocking its own thread.

This may sound a little like something out of "Alice's Adventures in Wonderland" at first, but if you draw out a few exchanges on paper, then it will make sense forever. It is conceptually bizarre because the otherwise simple logic is completely reversed. Fortunately, outside of these two classes, the remaining software remains blissfully unaware; that was the whole design intent.

New: Connection Termination

Starting with release 1.66: Detection and explicit termination of a communication link is now supported.

If the client object being linked through an ItemProxy implements a method cutOff(Exception x) it will be invoked immediately following the loss of a connection with the server. This can occur for one of two reasons. First is a network failure, the server may have crashed, become unresponsive, been terminated, etc. Second is an explicit termination of the link by the server. This is accomplished by invoking the cutOff() method, newly supported by the ClientProxy object. It will immediately sever the connection between the server and the client. The Exception argument provides the client information about the reason for the termination.

The ClientProxy cutOff() method is public, to allow one more feature. A client wishing to explicitly sever its own connection with the server, can invoke this method on its ClientProxy reference. Typically no remote method invocations are made on the client's ClientProxy reference, but intentionally severing a communication link is considered to be an exceptional circumstance.

Question: has anyone tried applying this concept of RMI callbacks to firewalled clients? I thinks solution presented here relies on the assumption that calls to the same remote object are dispatched in separate (server-side) threads, thus allowing to have threads from multiple clients to wait for server's callback. But according to RMI specification 1.5 this assumption may not be true: "A method dispatched by the RMI runtime to a remote object implementation may or may not execute in a separate thread. The RMI runtime makes no guarantees with respect to mapping remote object invocations to threads."

Topic FirewalledClients . { Edit | Ref-By | Printable | Diffs r8 < r7 < r6 < r5 < r4 | More }
 XML java.net RSS

Revision r8 - 07 Jul 2008 - 18:49:20 - Main.maxim_m
Parents: WebHome > ThecajoProject