(from: http://www.cs.swan.ac.uk/~csneal/InternetComputing/ThreadCallBack.html)
7.1. Background
So far, all the RMI examples you have seen have been very straightforward - the client needs to invoke some service on a server, and is prepared to wait until the service has finished before proceeding. But what if the service will take some time, and the client wishes to get on with something else while it waits? Or if the activities of the server are to some degree independent of main function of the client? The first situation is often the case when dealing with GUI code - it is not considered good practice to freeze up a GUI while it performs some slow operation.Also, suppose there are multiple clients - what happens if more than one tries to access the service at the same time? We will deal with this issue first, as it is the simplest.
7.2. RMI Threads
We will start off with an RMI version of the Fibonacci server - if you recall the socket version worked quite happily with multiple clients. First we need an interface:import java.rmi.Remote; import java.rmi.RemoteException; public interface Fib extends Remote { int getNextFib() throws RemoteException; }
import java.rmi.*; import java.rmi.server.*; public class FibServer extends UnicastRemoteObject implements Fib { private int F1 ; private int F2 ; public FibServer() throws RemoteException { F1 = 1; F2 = 2; } public int getNextFib() throws RemoteException { int temp = F1; F1 = F2; F2 = temp + F2; return temp; } public static void main (String args[]) { try { FibServer server = new FibServer(); Naming.rebind("FIB", server); } catch (java.net.MalformedURLException e) { System.out.println("Malformed URL " + e.toString()); } catch (RemoteException e) { System.out.println("Communication error " + e.toString()); } } }
import java.rmi.*; import java.lang.*; public class FibClient { public static void main (String args[]) { Fib serverObject; Remote RemoteObject; /* Should put in security manager */ try { /* See if the object is there... */ String name = "rmi://localhost/FIB"; RemoteObject = Naming.lookup(name); serverObject = (Fib)RemoteObject; for (int i = 0; i < 100; i++) { System.out.println(serverObject.getNextFib() + "\n"); } } catch (Exception e) { System.out.println("Error in invoking object method " + e.toString() + e.getMessage()); e.printStackTrace(); } } }
To make things easier you can download the interface, server and client.
7.2.1. Running...
Compile the application in the usual way - locally on one machine is sufficient - and try connecting to the server with two (or more) clients. First impressions... Great! It works! However... Look more closely. You will notice that the server is only generating one set of Fibonacci numbers, which are being shared between the clients. This is because we have only created one server object, and the RMI runtime is simply scheduling multiple calls to the same object. You can think of the server starting up multiple threads, one for each client, and then each of those threads calls the (single) server object. The actual situation is a bit more complex than this, but that need not concern us here.In the case of the Fibonacci server, this is not the behaviour we want or expect - however, in most actual applications, it is. We typically want to provide parallel, distributed access to a common data set. Of course, the usual problems of parallel access to a common data set - especially when one or more clients can alter the data - still apply. But the good news is that in most circumstances, multiple RMI clients can work without any explicit threading code needing to be written on the server (though you may still want to thread the server for, say, performance reasons). However, even though we have not included it in the example above, in general the same issues relating to controlling access to shared data exist as with our socket based example - and we'll consider this in more detail in the next chapter.
7.3. Callbacks
Most people learn to program using a sequential model - they write some program that reads some input, and it waits until that input arrives before proceeding. Much real code does not work like this - it is event driven. Such code will some how set up bits of code that get called when certain events occur - in Java this commonly happens with GUI code and we talk about registering event handlers, or event listeners. A generalization of this idea is the callback - code that is called in application (or client) A by application (or server) B as a result of something that A did previously.The following simple (and silly) example illustrates the problem. We have the following RMI interface:
import java.rmi.*; public interface Fruit1 extends Remote { public String nextFruit() throws RemoteException ; }
import java.rmi.*; import java.rmi.server.*; public class Main extends UnicastRemoteObject implements Fruit1 { private String fruity[]={"apple","orange","strawberry","mango"}; int i = 3; public Main() throws RemoteException { } public String nextFruit() throws RemoteException { try { i++; if (i == 4) { i = 0; } Thread.sleep(3000); } catch(Exception e){ System.out.println("Oh no!!!"); }; return fruity[i]; } public static void main(String[] args) { try { Main server = new Main(); Naming.rebind("FRUIT", server); } catch (java.net.MalformedURLException e) { System.out.println("Malformed URL for MessageServer name " + e.toString()); } catch (RemoteException e) { System.out.println("Communication error " + e.toString()); } } }
import java.rmi.*; public class FruitClient1 extends javax.swing.JFrame { private javax.swing.JLabel activityLabel; private javax.swing.JTextField fruitLabel; private javax.swing.JButton getFruit; private Fruit1 fruit; public FruitClient1() { initComponents(); LabelChanger lb = new LabelChanger(activityLabel); lb.start(); try { Remote remoteObject = Naming.lookup("FRUIT"); fruit = (Fruit1)remoteObject ; } catch(Exception e){ System.out.println("Oh no!!!"); }; } private void initComponents() { /* GUI initialization omitted */ } private void getFruitActionPerformed(java.awt.event.ActionEvent evt) { try { fruitLabel.setText(fruit.nextFruit()); } catch(Exception e){ System.out.println("Oh no!!!"); }; } public static void main(String args[]) { /* code to run application omitted */ } } class LabelChanger extends Thread { private javax.swing.JLabel label; public LabelChanger(javax.swing.JLabel lb){ label = lb; } public void run() { while (true){ try { label.setText("Hello There!"); Thread.sleep(1000); label.setText(" "); Thread.sleep(1000); } catch(Exception e){ System.out.println("Oh no!!!"); }; } } }
You can download the interface, server and client.
Compile and run the application - it works, but the GUI freezes when the button is clicked until the string is returned. This is not good.
7.3.1. Aside: Threads and Swing
Note that there is an important issue here - we have created a Swing user interface, and then used it with threaded code. Strictly speaking, Swing is not thread safe - that is, things can go badly wrong if we do this, and we should really follow the appropriate procedures to produce thread safe code. In practice, because we are not writing to any GUI component from more than one thread, there is no practical problem.7.3.2. A Simple Solution: Polling
The obvous thing we need to do is somehow get the GUI to start the operation on the server, but not wait for it to finish. Instead, we somehow want the GUI to be notified when the server has finished. The problem with our current model is that we have a clear cut distinction between server and client - the client asks the server to do things, not the other way around. Thinking about this, a simple solution is a technique called polling - where the client periodically asks the server 'have you finished yet?'. When the server says yes, the client calls another method to retrieve the new value. Here is the new interface.import java.rmi.*; public interface Fruit2 extends Remote { public void nextFruit() throws RemoteException ; public boolean doneYet() throws RemoteException; public String getFruit() throws RemoteException; }
import java.rmi.*; import java.rmi.server.*; import java.lang.*; import java.util.*; public class Main extends UnicastRemoteObject implements Fruit2 { DoFruit df = new DoFruit(); // A bit flaky... public Main() throws RemoteException { } public void nextFruit() throws RemoteException { df = new DoFruit(); df.start(); } public boolean doneYet() throws RemoteException { return df.doneYet(); } public String getFruit() throws RemoteException { return df.getFruit(); } public static void main(String[] args) { try { Main server = new Main(); Naming.rebind("FRUIT", server); } catch (java.net.MalformedURLException e) { System.out.println("Malformed URL for MessageServer name " + e.toString()); } catch (RemoteException e) { System.out.println("Communication error " + e.toString()); } } } class DoFruit extends Thread { private String fruity[]={"apple","orange","strawberry","mango"}; private static int i = 3; private boolean done = false; public void run() { try { done = false; i++; if (i == 4) { i = 0; } Thread.sleep(3000); done = true; } catch(Exception e){ System.out.println("Oh no!!!"); }; } public String getFruit() { done = false; return fruity[i]; } public boolean doneYet() { return done; } }
import java.rmi.*; public class FruitClient2 extends javax.swing.JFrame { private javax.swing.JLabel activityLabel; private javax.swing.JTextField fruitLabel; private javax.swing.JButton getFruit; private Fruit2 fruit; private FruitChecker testFruit; public FruitClient2() { initComponents(); LabelChanger lb = new LabelChanger(activityLabel); lb.start(); try { Remote remoteObject = Naming.lookup("FRUIT"); fruit = (Fruit2)remoteObject ; } catch (Exception e){} testFruit = new FruitChecker(fruitLabel, fruit); testFruit.start(); } private void initComponents() { /* GUI initialization */ } private void getFruitActionPerformed(java.awt.event.ActionEvent evt) { try { fruit.nextFruit(); } catch(Exception e){ System.out.println("Oh no!!!"); }; } public static void main(String args[]) { /* run the client */ } } class LabelChanger extends Thread { //Same as before } class FruitChecker extends Thread { private javax.swing.JTextField textfield; private Fruit2 remoteFruit; public FruitChecker(javax.swing.JTextField tf, Fruit2 f) { textfield = tf; remoteFruit = f; } public void run() { while (true) { try { Thread.sleep(100) ; if (remoteFruit.doneYet()) { textfield.setText(remoteFruit.getFruit()); } } catch(Exception e){ System.out.println("Oh no!!!"); }; } } }
Compile and run the application - it works, and the GUI remains responsive (a bit pointless in this case, but you can imagine applications where other, local, operations could proceed while the server was busy). The only real problem with polling that - potentially - it is resource expensive. In our case, this is not really an issue - but it is possible to consider cases where this would be a problem.
7.3.3. A Better Solution - Callbacks
It would be better, in general, if we could somehow get rid of the step in which the client asks if the server has finished, and just get the server to invoke some method on the client. The way to do this is to make the client an RMI server as well - by extention, the main RMI server also becomes an RMI client. This now seems to get complicated - at first thought, you need to run the RMI registry and a web server on the client as well as the server. This is only partly true - you need the web server, but not the registry. The reason you don't need the RMI registry is because its function is to enable clients to locate services. This is not necessary in this case because the calling client already knows where the service is (it's running it) and so it can simply pass a reference to its own remote callback service as a parameter when it first calls the server. This solution needs two more files: as well as the interface, server and client, we also need a new interface for the callback server and a new class to implement the callback server. Here is the main server interface - notice the polling method is now gone, but that the nextFruit method now takes a parameter of class (actually interface) Notify. This will be a reference to the callback server on the client.import java.rmi.*; public interface Fruit3 extends Remote { public void nextFruit(Notify n) throws RemoteException ; public String getFruit() throws RemoteException; }
import java.rmi.*; public interface Notify extends Remote { public void doneIt() throws RemoteException; }
import java.rmi.*; import java.rmi.server.*; import java.lang.*; import java.util.*; public class Main extends UnicastRemoteObject implements Fruit3 { DoFruit df; public Main() throws RemoteException { } public void nextFruit(Notify n) throws RemoteException { df = new DoFruit(n); df.start(); } public String getFruit() throws RemoteException { return df.getFruit(); } public static void main(String[] args) { try { Main server = new Main(); Naming.rebind("FRUIT", server); } catch (java.net.MalformedURLException e) { System.out.println("Malformed URL for MessageServer name " + e.toString()); } catch (RemoteException e) { System.out.println("Communication error " + e.toString()); } } } class DoFruit extends Thread { private String fruity[]={"apple","orange","strawberry","mango"}; private static int i = 3; private Notify notify; public DoFruit(Notify n) { notify = n; } public String getFruit() { return fruity[i]; } public void run() { try { i++; if (i == 4) { i = 0; } Thread.sleep(3000); notify.doneIt(); } catch(Exception e){ System.out.println("Oh no!!!"); }; } }
Here is the client code:
import java.rmi.*; import java.rmi.server.*; public class FruitClient3 extends javax.swing.JFrame { private javax.swing.JLabel activityLabel; private javax.swing.JTextField fruitLabel; private javax.swing.JButton getFruit; private Fruit3 fruit; private FruitChecker notifyFruit; public FruitClient3() { initComponents(); LabelChanger lb = new LabelChanger(activityLabel); lb.start(); try { Remote remoteObject = Naming.lookup("FRUIT"); fruit = (Fruit3)remoteObject ; notifyFruit = new FruitChecker(fruitLabel, fruit); } catch(Exception e){ System.out.println("Oh no!!!"); }; } private void initComponents() { /* GUI initialization */ } private void getFruitActionPerformed(java.awt.event.ActionEvent evt) { try { fruit.nextFruit(notifyFruit); } catch(Exception e){ System.out.println("Oh no!!!"); }; } public static void main(String args[]) { /* start GUI */ } } class LabelChanger extends Thread { //Same as before }
import java.rmi.*; import java.rmi.server.*; public class FruitChecker extends UnicastRemoteObject implements Notify { private javax.swing.JTextField textfield; private Fruit3 remoteFruit; public FruitChecker(javax.swing.JTextField tf, Fruit3 f) throws RemoteException { textfield = tf; remoteFruit = f; } public void doneIt() { try { textfield.setText(remoteFruit.getFruit()); } catch(Exception e){ System.out.println("Oh no!!!"); } } }
Compile and run the application. Which is best? In general, callbacks are a 'nicer' solution: they are bit more complex, and need a bit more code, but consume less resources in practice. However, the need to run a web server will rule out this solution in some cases.
1 comment:
Hi nice readding your post
Post a Comment