5 Remote Method Invocation

Pravin Jain

epgp books

What is RMI?

 

Remote Method  Invocation.

 

That  name  gives  an  impression,  that  it  is  about invoking method on some object which is on a remote machine. RMI is actually about how  to create objects on which methods can be invoked from a remote place. How can be achieve this? We would like to have objects on which methods can be invoked from a remote place. for  eg.  let us  consider that we would  like to  have an instance of Bank  on  which we would like to be able to invoke methods from a remote place.

 

We have a RemoteObject on a machine on which we want to invoke methods from a remote place. Then this can be done by having an instance of Stub Object and Skeleton Object for this RemoteObject. The Skeleton Object will be on the same machine as the RemoteObject and the Stub Object will on the machine of the invoker. The Stub Object and the Skeleton Object would be connected using a socket, and can communicate over the socket. Now for the remote method invocation, the Stub Object has to have all the methods of the RemoteObject available, implementation of these methods will be different. ie. the methods of RemoteObject are available to the invoker on the Stub Object. When the invoker wants to invoke any method on the RemoteObject, he would simply invoke the method on the Stub Object. The implementation of the method on the Stub Object would communicate with the Skeleton over the socket connection and send the method name and the values of the parameters to the skeleton. The Skeleton on receiving the method name and parameters from the stub would now invoke the method locally on the RemoteObject. The method would return some value, The returned value would now be sent back to the Stub by the Skeleton, and the Stub in turn returns it to the invoker. In case the method on RemoteObject throws an exception the Skeleton would catch the exception object and send it to the Stub. The Stub when it receives an exception instead of return value would also throw the exception object received from the skeleton. The process of sending parameters by Stub to skeleton object is known as Marshaling of parameters. The process of receiving parameters by skeleton object is known as UnMarshaling of parameters. The marshaling and unmarshaling of parameters is done on the Socket connection, which is a stream based connection, and therefore they need to be Serializable. If any of the parameters being marshaled is not Serializable then the marshaling process would throw an exception. So initially the Stub marshalls the parameters and the skeleton unmarshalls the parameters, after invoking the method the skeleton in turn marshals the return value and the stub unmarshalls the return value. So to have remote method invocation, we need three objects. The RemoteObject, the Object which has the actual implementation of the methods, whose invocation is desired by the client. The Stub and the Skeleton classes whose objects help the client to achieve the remote invocation. Also it is necessary that the method invoker uses only Serializable objects as parameters and the implementation of methods returns values which are Serializable.

 

Let us consider an example for a banking application:

 

We might consider to have an entity called Bank.

 

What is a Bank? Bank is any object which has the methods like

 

public long openAccount(AccountType type, String name, long openBal)

public Account getAccount(long acno)

public void deposit(long acno, long amount)

public boolean withdraw(long acno, long amount)

 

The Bank is identified by the methods it has. ie. the functionality supported by the instance. In case of RMI, the Stub Object has all these methods, and so it is the Bank for the invoker. The Bank is identified by the methods and not by its implementation. We would define Bank as an interface, with the required methods, This would be implemented by the RemoteObject and the Stub Object. The RemoteObject and the Stub have common methods, ie. they implement the same interface. Lets look at the steps for defining a remote object.

 

  1. Identify the methods for remote invocation and define an interface which has the   methods. This interface has to extend from the interface Remote in the java.rmi package. All the methods in this interface must have throws RemoteException over and above the application related throws. this throws RemoteException is required since the implementation of the methods by the Stub Object can fail because of some failure in the protocol between the Stub and the Skeleton over the network. ie. the failure of the   method could be because of the failure in the communication between the Stub and the Skeleton Objects. so in case of bank example we might have and interface for Bank defined

as below:

 

package bank; import java.rmi.*;

public interface Bank extends Remote {

public long openAccount(AccountType type, String name, long openBal) throws RemoteException;

public Account getAccount(long acno) throws RemoteException; public void deposit(long acno, long amount) throws RemoteException;

public boolean withdraw(long acno, long amount) throws RemoteException;

….

}

 

2. Write an implementation class for the above interface. This would be the remote object. This   class   has   to   inherit   from   the   UnicastRemoteObject   class   from   the   java.rmi.server package. The UnicastRemoteObject class has a default constructor which throws RemoteException, so the constructor in the class must also have throws RemoteException. We inherit from the UnicastRemoteException class, to inherit the implementations of the methods of  the  Object  class,  which  would appropriate  for  the RemoteObject.

 

So,for the above example we could have a class as below:

 

package bank;

 

import java.rmi.*;

import java.rmi.server.*;

 

public class BankImpl extends UnicastRemoteObject implements Bank { private String name;

private Map<Long, Account> accountMap = new HashMap<>();

….

public BankImpl(String name) throws RemoteException {

….

}

public long openAccount(AccountType type, String name, long openBal) throws RemoteException;

public Account getAccount(long acno) throws RemoteException; public void deposit(long acno, long amount) throws RemoteException;

public boolean withdraw(long acno, long amount) throws RemoteException;

….

}

 

3. Now generate the Stub and Skeleton classes for the RemoteObject class defined in step 2. this is done by using the rmic command available with the jdk. In step 1 and 2 when we compile, we would get two class files as  Bank.class  and  BankImpl.class.  in  the  rmic  utility use the BankImpl class so command will be

 

rmic bank.BankImpl

This would generate two class files in the bank package as BankImpl_Stub.class and BankImpl_Skel.class.

 

So, as far as creating the Bank as a RemoteObject is concerned that is done.

 

Now we need to use this Remote type in an Application. In this case the application on the server would create an instance of bank.BankImpl and make it available for Remote Method Invocation. The client would have to obtain the stub for this object, for the client to initially obtain the stub object it would require some service where it can connect on the server machine. There is a Naming service available along with the jre for RMI. This is known rmiregistry.

 

At runtime the rmiregistry would have to be started first, then the application on  the  server  side would create the instance of RemoteObject and bind  it  with  the  Naming  service,  ie.  make  it available for remote invocation. rmiregistry needs to be started on the same machine as the RemoteObject The client would then have to connect to the Naming service and obtain the stub corresponding to the RemoteObject which has been bound by the  application  on  the  server  side. Now interaction wtth the rmiregistry(Naming service) would be required by the server application as well as the client. There is a class called Naming, in the java.rmi package, which has static methods which will allows interaction with the  Naming  service.  The  following  are  the  methods  in the Naming class: (all methods in this class are static)

 

static void bind(String key, Remote remoteObject)

static void rebind(String key, Remote remoteObject)

static void unbind(String key)

 

The above methods are  called by the  server applicaton only. ie. they  cannot be invoked  from remote place.

 

static Remote  lookup(String url)

 

This  method  is  invoked  by  the  client  for  obtaining  the  stub  object  for  the  given  remote  object which has been bound with the rmiregistry. The format for the url will be:

 

rmi://<host>[:<port>]/<key>

  1. assuming we  have  the  classes  bank.Bank  interface,  bank.BankImpl  and  bank.BankImpl_Stub and bank.BankImpl_Stel. then on the server the application might have code like:

public static void main(String[] args) { bank.BankImpl bi = new bank.BankImpl(….); Naming.rebind(“SBI”, bi);

}

 

The client application would then look something like:

 

public static void main(STring[] args) {

Bank bank = (Bank)Naming.lookup(“rmi://127.0.0.1/SBI”);

…..

bank……. // invoking some method on bank;

}

There is a method called list in the Naming class, with one parameter of type String.

 

This could be invoked by the server side or by the client. to konw the keys which have been bound in the Naming service.

 

The method is:

 

static String[] list(String s);

 

On the server side it would be invoked as: String[] names = Naming.list(“*”);

on the client side it would be invoked as:

 

String[] urls   = Naming.list(“rmi://127.0.0.1/*”); now look at the following code on the client side.

public static void main(String[] args) {

Bank bank = (Bank)Naming.lookup(“rmi://127.0.0.1/SBI”); Account ac = bank.openCurrentAccount(“name1”, 10000); int acno = ac.getAccountNumber();

bank.deposit(acno, 10000); ac = bank.getAccount(acno); ac.withdraw(acno, 10000); bank.passbook(acno);

}

 

Here we have created an account then deposited some amount into the account now we use the object of the account created by using the getAccount method. and then we withdraw from this account object. is this withdrawal reflected in the account managed in the bank? when we use getAccount method the account instance returned is a copy of the account on the server, and so the withdrawal is not reflected in the passbook method invocation above. ie. in case of rmi here we find pass by value, even for reference types. In rmi pass by reference is still possible. suppose when we call getAccount method, then instead of a copy of the account object, if we could get the instance of a Stub for the account object on the server. then it would be a pass by reference. ie. if the Account was a Remote type then it would be treated as pass by reference automatically. ie. if we have done the same exercise on Account as what is done for Bank. then Account would be passed by reference. ie. we should have Account interface AccountImpl class as an implementation of the Account interface and then we should have also generated the Stub and the Skeleton classes for the Account.


So for rmi, primitive types are always pass by value, reference types are also pass by value, except for the Remote types. Remote types are pass by reference, Remote types are the ones for which the Implementaion class, the Stub class and the Skeleton classes are generated.

you can view video on Remote Method Invocation
Suggested Reading:
1. Core Java Volume 2 by Cay Horstmann & Gary Cornell, Ninth Edition, Pearson Education.
2. Beginning Java Networking by Alexander V Konstantinou and others, Wrox publication.
3. Java RMI By William Grosso O’Rielly media
4. https://docs.oracle.com/javase/tutorial/rmi/index.html