Chapter 25

Remote Method Invocation

So far most of the Java programs in this tutorial have been running in one JVM. There were two exceptions: In Lesson 18 you used two JVMs while learning about socket programming, and your JDBC programs from Lesson 22 communicated with another JVM running database server. The application running on the user’s computer isn’t always allowed to access remote data directly — that’s one of the reasons distributed Java applications came into the picture. (The word distributed means having parts of the applications running on several computers.) The other reason was to provide a centralized server catering to multiple lightweight clients.

There are lots of ways to create Java distributed applications that run on more than one JVM, and Remote Method Invocation (RMI) is one of them even though it’s being used seldom these days. For example, a client Java application (JVM1) connects to a server Java application (JVM2), which connects to the DBMS that runs on a third computer. The client application knows nothing about the DBMS; it gets the data, an ArrayList of Employee objects, from the server’s application that runs in JVM2. RMI uses object serialization for the data exchange between JVM1 and JVM2.

But unlike with socket programming, where the client explicitly connects to the server, with RMI one Java class can invoke methods on Java objects that live in another (remote) JVM. Although from a syntax perspective it looks as if the caller and the server’s class are located in the same JVM, they may be thousands of miles away. The RMI client won’t have a copy of the server-side method; it’ll just have the method’s local representative — a proxy, or, using the RMI terminology, a stub.

Any RMI application consists of an RMI server, a client, and the registry (a naming service). These three components could run on three different JVMs running on different networked computers. The RMI server creates Java objects that implement business logic, registers them with the naming service, and waits for remote clients to invoke methods on them.

A client application gets a reference to a remote server object or objects from the registry and then invokes methods on this remote object. The main concept of RMI is that even though the methods are called in the client’s JVM, they are executed on the server’s! RMI supporting classes and the registry tool are included with the Java SE.

Developing Applications with RMI

This lesson is written as an illustration of a sample RMI application with a minimum theory. For a more detailed description of RMI please refer to the following website: http://download.oracle.com/javase/6/docs/technotes/guides/rmi/index.html.

Writing distributed RMI applications involves the following steps:

1. Declaring the remote interface.

2. Implementing the remote interface.

3. Writing a Java client that connects to the remote server and calls remote methods.

4. Starting the registry and registering the RMI server with it.

5. Starting the server and the client applications.

Let’s perform each of these steps by developing the RMI version of the Stock Quotes Server (see its version with sockets in Lesson 18), which will provide a client with price quotes for a specified stock. Some of the following steps could be combined, but for simplicity’s sake I’ll write a separate class for each required action.

Defining Remote Interfaces

The Java classes that you are planning to deploy on the server side have to implement remote interfaces, which declare business method(s) to be invoked remotely by RMI clients. The client’s code will look as if it’s calling local methods, but these calls will be redirected to a remote server via the RMI protocol. Following are the rules for creating remote interfaces:

  • The remote interface must declare public methods to allow clients to perform remote method invocation.
  • The remote interface must extend java.rmi.Remote.
  • Each method must declare java.rmi.RemoteException.
  • Because method arguments and returned data will be traveling across the network by means of Java serialization, their data type must be Serializable.

You start developing the server side of any distributed Java application by answering the question “What business methods have to be exposed to the client applications and what should their signatures be?” When you know the answer, define remote interfaces that declare those methods.

Listing 25-1 shows the code of the StockServer remote interface that will be implemented on the server but must exist on the client side too. This interface declares two business methods: getQuote() and getNasdaqSymbols(). The first will generate a random price quote for the specified symbol, and the second will return the list of valid stock symbols.

download.eps

Listing 25-1: StockServer interface

package com.practicaljava.lesson25;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.List; 
 
public interface StockServer extends Remote { 
  public String getQuote(String symbol) throws RemoteException; 
 
  public List<String> getNasdaqSymbols()throws RemoteException; 
}

Implementing Remote Interfaces

Although the remote interface just declares the methods, you need to create a class that will run on the server side and provide implementation for those methods. There is a special requirement to export such a class to the Java RMI run time to enable the class to receive remote calls. This is somewhat similar to binding to a port in the case of ServerSocket (see Listing 18-5), but in the case of Java RMI the server also creates a stub — a dummy class (for the client side) that contains proxies of each implemented method from remote interfaces.

The easiest way to export an RMI server instance is by extending it from java.rmi.server.UnicastRemoteObject, as in Listing 25-2. If your server has to be extended from another class you can explicitly export the server object by calling UnicastRemoteObject.export().

Listing 25-2 shows an implementation of the class StockServerImpl, which will process the client’s requests. This class generates random price quotes for the stocks located in ArrayList nasdaqSymbols.

download.eps

Listing 25-2: StockServerImpl class

package com.practicaljava.lesson25;
 
import java.rmi.*;
import java.rmi.server.*;      
import java.util.ArrayList;
 
public class StockServerImpl extends UnicastRemoteObject  implements StockServer {
  private String price=null;
  private ArrayList<String> nasdaqSymbols = new ArrayList<String>();
 
  public StockServerImpl() throws RemoteException {
    super();
   
    // Define some hard-coded NASDAQ symbols 
    nasdaqSymbols.add("AAPL");
    nasdaqSymbols.add("MSFT");
    nasdaqSymbols.add("YHOO");
    nasdaqSymbols.add("AMZN");
    nasdaqSymbols.add("MOT");
  }
 
  public String getQuote(String symbol)
                          throws RemoteException {
 
    if(nasdaqSymbols.indexOf(symbol.toUpperCase()) != -1) {
 
        // Generate a random price for valid symbols   
        price = (new Double(Math.random()*100)).toString();
    }
    return price;
  }
 
  public ArrayList<String> getNasdaqSymbols()throws RemoteException {
    return nasdaqSymbols;
  }  
}

Registering Remote Objects

To make a remote object available to clients, you need to bind it to some name in a registry, a naming service that knows where exactly in the network your RMI server StockServerImpl is running. This will allow Java clients to look up the object on the host machine by name.

Listing 25-3 depicts the code that binds the instance of StockServerImpl to port 1099 on the host machine, which is the local computer in my example. To the rest of the world this server will be known as QuoteService.

download.eps

Listing 25-3: Starting the server and binding it to a registry

package com.practicaljava.lesson25;
 
import java.rmi.Naming;
import java.rmi.RMISecurityManager;
import java.net.MalformedURLException;
import java.rmi.RemoteException;
 
public class StartServer {
 
  public static void main (String args[]) {
                      
   try {  
         
    StockServerImpl ssi = new StockServerImpl();
    Naming.rebind("rmi://localhost:1099/QuoteService",ssi);
 
}catch (MalformedURLException e1){
         System.out.println(e1.getMessage());   
   }catch(RemoteException ex) {
         ex.printStackTrace();
   }    
  }
}

There are two methods in the class java.rmi.Naming that can bind an object in the registry. The method bind() binds an RMI server to a name. It throws AlreadyBoundException if the binding already exists. The method rebind() replaces any preexisting binding with the new one. In addition to binding a server to a name, this also ensures that the clients requesting such services as getQuotes() or getNasdaqSymbols() receive their stubs — the local proxies of the remote methods.

The registry must be up and running by the time you start the program in Listing 25-3. One way to start the registry is by entering start rmiregistry in the Windows command window or rmiregistry in Mac OS.

Instead of starting the registry manually, you can start it from within the StartServer program itself by calling the following method:

LocateRegistry.createRegistry(1099);

If you know that another process has already pre-created the registry, just get its reference and bind the server to it. The getRegistry() method can be called without arguments if the RMI registry runs on the default port 1099. If this is not the case, specify the port number (5048 in the following example). The variable registry in the following code fragment is a stub to the remote object StockServerImpl:

StockServerImpl ssi = new StockServerImpl();
Registry registry = LocateRegistry.getRegistry(5048);
registry.bind("QuoteService", ssi);

Writing RMI Clients

The client program, running anywhere on the Internet, performs a lookup in the registry on the host machine (using the host machine’s domain name or IP address) and obtains a reference to the remote object. Listing 25-4 shows a sample client program. Notice the casting to the StockServer type of the data returned by the method lookup().

Even though the class StockServerImpl has been bound to the name QuoteService, because this class implements the StockServer interface we can cast the returned object to it. The variable myServer will see only the methods defined in this interface, while the class StockSertverImpl may have other public methods too.

download.eps

Listing 25-4: RMI client

package client;
 
import java.rmi.*;
import com.practicaljava.lesson25.StockServer;
 
public class Client {
 
  public static void main (String args[]) {
 
   if (args.length == 0) {
     System.out.println("
 Sample usage: java client.Client AAPL");
     System.exit(0);
   }
 
    try {  
        StockServer myServer = (StockServer) 
        Naming.lookup("rmi://localhost:1099/QuoteService");
 
      String price = myServer.getQuote(args[0]);
       if  (price != null){
         System.out.println("The price of " + args[0] +
                            " is: $" + price);
       }
       else{
          System.out.println("Invalid Nasdaq symbol. " + 
             "Please use one of these:" +  
             myServer.getNasdaqSymbols().toString());
      } 
   
 } catch (MalformedURLException ex) { 
       System.out.println(ex.getMessage()); 
    }catch (RemoteException ex2) { 
       System.out.println(ex2.getMessage()); 
    }     
  }
}

Security Considerations

Can any RMI client restrict the actions that remotely loaded code can perform on the local computer? Can the server restrict access? You can specify a security policy file containing access restrictions. For example, in the code in Listings 25-3 and 25-4 you can start the main() method with the following:

if (System.getSecurityManager() == null) {
       System.setSecurityManager(new  RMISecurityManager());
 }

The class java.rmi.RMISecurityManager extends the class java.lang.SecurityManager and provides a security context under which the RMI application executes. In RMI clients the goal is to prevent remotely loaded stub code from downloading unsecured code via remote method invocation.

The RMI client uses a file in which security policies are defined. You can use the default security file, java.policy, located in your JDK or JRE installation directory under lib/security. The default policy file gives all permissions to the code, but you can create your own file and supply it either via the command-line parameter or in the code before the security manager is set:

System.setProperty("java.security.policy", "mypolicyfile");

For a more detailed description of security policy files refer to the documentation at http://download.oracle.com/javase/1.3/docs/guide/security/PolicyFiles.html.

Java applets can also serve as RMI clients, but they don’t need RMI security managers. The only restriction on them is that they can connect only to the RMI server running on the same host on which they were deployed.

Finding Remote Objects

RMI clients find remote services by using a naming or directory service. A naming service runs on a known host and port number. The subject of naming and directory services will be covered in more detail in Lesson 31.

By now you know that an RMI server can start its own registry that offers naming services for RMI clients. The behavior of the registry is defined by the interface java.rmi.registry.Registry, and you saw an example of binding to the registry in the section “Registering Remote Objects.”

By default the RMI registry runs on port 1099, unless another port number is specified. When the client wants to invoke methods on a remote object it obtains a reference to that object by looking up the name. The lookup returns to the client a remote reference, aka stub.

The method lookup() takes the object name’s URL as an argument in the following format:

      rmi://<host_name>[:<name_service_port>]/<service_name>

host_name stands for the name of a computer on the local area network (LAN), or the name of a domain name system (DNS) on the Internet. name_service_port has to be specified only if the naming service is running on a port other than the default. service_name stands for the name of the remote object that should be bound to the registry.

Figure 25-1 illustrates the architecture of an RMI application. In the Try It section you’ll implement this architecture for our sample stock quote service.

Try It

In this exercise your goal is to start and test all the parts of the distributed Stock Server application, and you’ll run all these parts on the same computer. To emulate multiple computers you’ll open three command windows: one for the RMI client, one for the registry, and one for the server. If everything is done properly you should be able to start the RMI client with one of the stock symbols known to the server, and get a price quote back.

Lesson Requirements

You should have Java installed.

note.ai

You can download the code and resources for this Try It from the book’s web page at www.wrox.com. You can find them in the Lesson25 folder in the download.

Hints

There is an RMI plug-in for the Eclipse RMI that may be handy for developing RMI-based distributed applications. It contains a useful utility called RMI Spy that shows all outgoing and incoming method calls, and measures execution times. Another useful utility in this plug-in is Registry Inspector, which displays information about objects in the registry. The RMI plug-in is available from www.genady.net/rmi/index.html.

Step-by-Step

1. Create an Eclipse project called Lesson25 with two packages: client and com.practicaljava.lesson25.

2. In com.practicaljava.lesson25 create the interface StockServer that extends java.rmi.Remote, as in Listing 25-1.

3. In com.practicaljava.lesson25 create the class StockServerImpl that extends UnicastRemoteObject and implements the interface StockServer, as in Listing 25-2.

4. In com.practicaljava.lesson25 create the class StartServer from Listing 25-3 to start the server and bind it to the naming service.

5. In client create the class Client that will access the remote server. Follow the code from Listing 25-4.

6. Compile all the classes.

7. Open three command windows.

8. Start the RMI registry from the first command window. The naming service will listen to default port 1099. If you are running this program in Windows, use the command start rmiregistry. If you are running Mac OS, use the command rmiregistry.

Don’t expect to see any confirmations that the registry has successfully started. The absence of error messages is your confirmation.

9. In the second command window, start and register StockServer with the naming service (registry) that you just started. In the command line specify the system property java.rmi.server.codebase — this is where your StockServer compiled class is located. I created this example on my MacBook with the user name yfain11, and my Eclipse workspace was located in the directory practicalJava. The codebase command-line option starts with –D and is shown in bold.

java -classpath /Users/yfain11/practicalJava/workspace/Lesson25/bin 
-Djava.rmi.server.codebase=
file:/Users/yfain11/practicalJava/workspace/Lesson25/bin/ com.practicaljava.lesson25.StartServer

After you enter a command similar to the preceding, the server will start and bind to the registry, printing the following message in the command window:

<QuoteService> server is ready.

10. Open the third command window and switch to the bin directory — the client sub-directory should be there, as you created it in Step 1. Run the Client program, passing the stock symbol as a command line argument, and the Client will connect to your “remote” server and receive the price quote, for example:

java client.Client AAPL

Figure 25-2 shows how the three command windows look on my MacBook. They will look similar in Windows.

cd.ai

Please select Lesson 25 on the DVD with the print book, or watch online at www.wrox.com/go/fainjava to view the video that accompanies this lesson.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.62.25