I am just starting to learn how to use RMI, and I have a question. I have the following directory structure:
compute.jar
client
|
org\examples\rmi\client
|--> ComputePi // client main
|--> Pi // implements Task
org\examples\rmi\compute
|--> Compute // interface
|--> Task // interface
server
|
org\examples\rmi\engine
|--> ComputeEngine // server main, implements Compute
org\examples\rmi\compute
|--> Compute // interface
|--> Task // interface
Here's the main
method in the ComputePi class:
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
}
try {
String name = "Compute";
// args[0] = 127.0.0.1, args[1] is irrelevant
Registry registry = LocateRegistry.getRegistry(args[0], 0);
Compute comp = (Compute) registry.lookup(name);
Pi task = new Pi(Integer.parseInt(args[1]));
BigDecimal pi = comp.executeTask(task);
System.out.println(pi);
}
catch (Exception e) {
System.err.println("ComputePi exception:");
e.printStackTrace();
}
Here's the main
method in the ComputeEngine class:
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
}
try {
String name = "Compute";
Compute engine = new ComputeEngine();
Compute stub = (Compute) UnicastRemoteObject.exportObject(engine, 0);
Registry registry = LocateRegistry.getRegistry();
registry.rebind(name, stub);
System.out.println("ComputeEngine bound.");
}
catch (Exception e) {
System.err.println("ComputeEngine exception: ");
e.printStackTrace();
}
Here's the executeTask
method, also in the ComputeEngine class:
public <T> T executeTask(Task<T> task) throws RemoteException {
if (task == null) {
throw new IllegalArgumentException("task is null");
}
return task.execute();
}
The RMI registry and server start up just fine. Here are the params for the server:
C:\Users\Public\RMI\server>set CLASSPATH=
C:\Users\Public\RMI\server>start rmiregistry
C:\Users\Public\RMI\server>java -Djava.rmi.server.codebase="file:/C:/Users/Public/RMI/compute.jar" -Djava.rmi.server.hostname=127.0.0.1 -Djava.security.policy=server.policy org.examples.rmi.engine.ComputeEngine
Here are the params for the client:
C:\Users\Public\RMI\client>java -Djava.rmi.server.codebase="file:/C:/Users/Public/RMI/compute.jar" -Djava.security.policy=client.policy org.examples.rmi.client.ComputePi 127.0.0.1 45
However, I get the following exception when I try to run the client:
java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:
java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
java.lang.ClassNotFoundException: org.examples.rmi.client.Pi
at sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
at sun.rmi.transport.Transport$1.run(Unknown Source)
at sun.rmi.transport.Transport$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.Transport.serviceCall(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(Unknown Source)
at sun.rmi.transport.StreamRemoteCall.executeCall(Unknown Source)
at sun.rmi.server.UnicastRef.invoke(Unknown Source)
at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(Unknown Source)
at java.rmi.server.RemoteObjectInvocationHandler.invoke(Unknown Source)
at $Proxy0.executeTask(Unknown Source)
at org.examples.rmi.client.ComputePi.main(ComputePi.java:38)
Caused by: java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
java.lang.ClassNotFoundException: org.examples.rmi.client.Pi
at sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
at sun.rmi.transport.Transport$1.run(Unknown Source)
at sun.rmi.transport.Transport$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.Transport.serviceCall(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.ClassNotFoundException: org.examples.rmi.client.Pi
at java.net.URLClassLoader$1.run(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Unknown Source)
at sun.rmi.server.LoaderHandler.loadClass(Unknown Source)
at sun.rmi.server.LoaderHandler.loadClass(Unknown Source)
at java.rmi.server.RMIClassLoader$2.loadClass(Unknown Source)
at java.rmi.server.RMIClassLoader.loadClass(Unknown Source)
at sun.rmi.server.MarshalInputStream.resolveClass(Unknown Source)
at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)
at java.io.ObjectInputStream.readClassDesc(Unknown Source)
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)
at sun.rmi.server.UnicastRef.unmarshalValue(Unknown Source)
... 11 more
But if I add the Pi.class file to the server directory:
server
|
org\examples\rmi\engine
|--> ComputeEngine // server main, implements Compute
org\examples\rmi\compute
|--> Compute // interface
|--> Task // interface
org\examples\rmi\client
|--> Pi // same as Pi for client
The program works. My question is, does Pi.class really need to be on the server for my program to work? My understanding is (and please correct me if I'm wrong) that I send an instance of that class to the server, and the server would know what to do with it, i.e. it doesn't care about the implementation. Can someone explain how RMI is working in my case? I really appreciate it. Thanks!
I tried this example with two PC in same network. One with Java 1.7.0_40 working as server, another with Java 1.7.0_45 as client. Both PCs are Windows based. I met the same problem raised by denshaotoko.
The solution is:
Server side:
C:\>start rmiregistry -J-Djava.rmi.server.useCodebaseOnly=false
The -J-Djava.rmi.server.useCodebaseOnly option is needed for Java 7 since the default value is true, which means the RMI Registry will not look for other code base except the directory it is started from. Then the next step starting the server will fail. Details see here: http://docs.oracle.com/javase/7/docs/technotes/guides/rmi/enhancements-7.html
C:\>java -cp c:\rmi;c:\rmi\compute.jar -Djava.rmi.server.useCodebaseOnly=false -Djava.rmi.server.codebase=file:/c:/rmi/compute.jar -Djava.rmi.server.hostname=192.168.1.124 -Djava.security.policy=c:\rmi\server.policy engine.ComputeEngine
Again the java.rmi.server.useCodebaseOnly should be set to false. Otherwise the server won't use the codebase provided by the client. Then client side will get the class not found exception. The hostname of 192.168.1.124 is the server's IP address
You should get "ComputeEngine bound"
Client side:
C:\>java -cp c:\rmi;c:\rmi\compute.jar -Djava.rmi.server.codebase=http://54.200.126.244/rmi/ -Djava.security.policy=c:\rmi\client.policy client.ComputePi 192.168.1.124 45
I trid the file:/ URL but not successful. I think the reason is simple. There're so many security limits that make the server not possible to access a file on the client PC. So I put the Pi.class file on my web server which is at http://54.200.126.244
under rmi directory. My web server use Apache. Any PC can access http://54.200.126.244/rmi/
so the problem is solved cleanly.
Finally, you should be able to start the rmiregistry, the server and the client from any directory using the same commands. Otherwise, some settings may still not be correct even if you can succeed. For example, if you start rmiregistry from the directory contain the "compute" directory (in my case is C:\rmi), the rmiregistry will directly load Compute.class and Task.class from it's starting directory, so the setting of -Djava.rmi.server.codebase=file:/c:/rmi/compute.jar become useless.