我刚开始学习如何使用RMI,我有一个问题。我有以下目录结构:
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
以下是 ComputePi 类中的main
方法:
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();
}
以下是 ComputeEngine 类中的main
方法:
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();
}
以下是executeTask
方法,同样位于 ComputeEngine 类中:
public <T> T executeTask(Task<T> task) throws RemoteException {
if (task == null) {
throw new IllegalArgumentException("task is null");
}
return task.execute();
}
RMI注册表和服务器启动就好了。以下是服务器的参数:
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
以下是客户的参数:
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
但是,当我尝试运行客户端时,我收到以下异常:
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
但是如果我将 Pi.class 文件添加到服务器目录:
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
该计划有效。我的问题是, Pi.class 真的需要在服务器上才能让我的程序运行吗?我的理解是(如果我错了,请纠正我)我将该类的实例发送到服务器,服务器将知道如何处理它,即它不关心实现。有人能解释一下RMI在我的案例中是如何运作的吗?对此,我真的非常感激。谢谢!
答案 0 :(得分:13)
我在同一个网络中用两台PC试过这个例子。一个用Java 1.7.0_40作为服务器,另一个用Java 1.7.0_45作为客户端。这两台PC都是基于Windows的。我遇到了denshaotoko提出的同样问题。
解决方案是:
服务器端:
C:\>start rmiregistry -J-Djava.rmi.server.useCodebaseOnly=false
Java 7需要-J-Djava.rmi.server.useCodebaseOnly选项,因为默认值为true,这意味着RMI Registry不会查找除启动目录之外的其他代码库。然后启动服务器的下一步将失败。详情请见此处: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
同样,java.rmi.server.useCodebaseOnly应该设置为false。否则,服务器将不使用客户端提供的代码库。然后客户端将获得类未找到的异常。主机名192.168.1.124是服务器的IP地址
你应该得到“ComputeEngine bound”
客户方:
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
我trid文件:/ URL但没有成功。我认为原因很简单。有太多安全限制使服务器无法访问客户端PC上的文件。所以我将Pi.class文件放在我的网络服务器上,该服务器位于rmi目录下的http://54.200.126.244
。我的Web服务器使用Apache。任何PC都可以访问http://54.200.126.244/rmi/
,以便彻底解决问题。
最后,您应该能够使用相同的命令从任何目录启动rmiregistry,服务器和客户端。否则,即使您可以成功,某些设置仍可能仍然不正确。例如,如果从包含“compute”目录的目录启动rmiregistry(在我的例子中是C:\ rmi),rmiregistry将直接从它的起始目录加载Compute.class和Task.class,因此-Djava的设置.rmi.server.codebase = file:/ c:/rmi/compute.jar变得无用。
答案 1 :(得分:8)
您正在尝试发送服务器未知的类的序列化对象。
执行时:
Pi task = new Pi(Integer.parseInt(args[1]));
BigDecimal pi = comp.executeTask(task);
服务器实际上并不知道Pi
是什么。由于Pi
类是API的一部分,因此它也应该加载到服务器上。
当我有一个需要远程执行某些东西的应用程序时,使用例如RMI
,Spring Remoting或类似的东西,我将我的项目划分为3个项目:API
,Server和Client。 API项目将包含与功能相关的所有接口和模型类(此项目将产生jar,并且或多或少与您的计算机JAR相似)。服务器将导入API JAR,将实现接口并通过远程层(就像您对服务器所做的那样)提供服务,并像客户端一样使用客户端。
使用序列化时,双方必须知道类本身。然后传输的是对象的状态,以便在另一侧重建它。
序列化是RMI用于在两者之间传递对象的机制 JVM,作为从客户端到a的方法调用中的参数 服务器或作为方法调用的返回值。
A bit of Serialization on RMI William Grosso(2001年10月)。并且here了解更多信息。
答案 2 :(得分:4)
我的问题是,Pi.class是否真的需要在服务器上才能使我的程序正常工作?我的理解是(如果我错了,请纠正我)我将该类的实例发送到服务器,服务器将知道如何处理它,即它不关心实现。
你理解正确。编译时,Pi.class不需要在服务器上,但服务器确实需要在运行时下载它! (Pi必须可序列化)
问题是:服务器如何知道何时需要下载Pi.class?
答案是:通过客户端提供的java.rmi.server.codebase设置的值。客户端必须设置java.rmi.server.codebase选项。你必须说出Pi.class的位置。将Pi.class的副本放在公共目录中进行部署是一种常见习惯。因此,完整的解决方案是:
结构:
compute.jar
client\
|-org\
| |-examples\
| |-rmi\
| |client\
| |--> ComputePi // client main
| |--> Pi // implements Task
|-deploy\
| |-org\
| |-examples\
| |-rmi\
| |-client\ // directory that will contain the deployment copy of Pi.class
|--> client.policy
server\
|-org\
| |-examples\
| |-rmi\
| |-engine\
| |--> ComputeEngine // server main, implements Compute
|--> server.policy
其中compute.jar是先前创建的jar文件
cd C:\Users\Public\RMI\
javac compute\Compute.java compute\Task.java
jar cvf compute.jar compute\*.class
您是否在java文件中正确设置了包和导入命令?因为您修改了教程的原始结构......
编译服务器:
C:\Users\Public\RMI\> cd server
C:\Users\Public\RMI\server> javac -cp ..\compute.jar org\examples\rmi\engine\ComputeEngine.java
编译客户端:
C:\Users\Public\RMI\> cd client
C:\Users\Public\RMI\client> javac -cp ..\compute.jar org\examples\rmi\client\ComputePi.java org\examples\rmi\client\Pi.java
将Pi.class移动到部署目录
C:\Users\Public\RMI\> cp client\org\examples\rmi\client\Pi.class client\deploy
运行rmi注册表。如果您使用的是java 7,请按照muyong的建议设置选项-J-Djava.rmi.server.useCodebaseOnly = false
C:\Users\Public\RMI\> start rmiregistry -J-Djava.rmi.server.useCodebaseOnly=false
运行服务器。如果您使用的是java 7,请按照muyong的建议设置选项-J-Djava.rmi.server.useCodebaseOnly = false
C:\Users\Public\RMI\> cd server
C:\Users\Public\RMI\server> java -cp .;..\compute.jar
-Djava.rmi.server.useCodebaseOnly=false
-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
运行客户端。 注意:注意java.rmi.server.codebase设置(记住结论/)
C:\Users\Public\RMI\> cd client
C:\Users\Public\RMI\client> java -cp .;..\compute.jar
-Djava.rmi.server.codebase=file:/c:/Users/Public/RMI/client/deploy/
-Djava.security.policy=client.policy
org.examples.rmi.client.Compute.Pi 127.0.0.1 45
让我知道它是否有效!
P.S。我不使用Windows操作系统而是使用Linux,我可能会在'\'和'/'
之间产生混淆答案 3 :(得分:2)
来自jdk 1.7 useCodebaseOnly的默认值为true,这意味着它不会寻找其他代码库,除非它位于同一目录中。
设置此vm参数 -Djava.rmi.server.useCodebaseOnly = false 以运行服务器和客户端,并同时提供codebase和hostname的路径。见下面的例子。
下面是我的实现,它是根据我的目录结构。对于在windows中运行替换:(冒号)用; (分号)。
java -cp classes:classes / compute.jar -Djava.rmi.server.useCodebaseOnly = false -Djava.rmi.server.codebase = url:http://localhost:4800/ -Djava.rmi.server.hostname = localhost -Djava.security.policy = client.policy client.ComputePi localhost 45
答案 4 :(得分:-1)
我认为您为客户端指定的代码库不正确:
-Djava.rmi.server.codebase="file:/C:/Users/Public/RMI/compute.jar"
这无助于服务器找到client.Pi。
“Java Tutorial RMI”指定
-Djava.rmi.server.codebase=file:/c:/home/jones/public_html/classes/
这就是有一个client / Pi.class的目录(即如果有一个跟随教程,“jones”写了客户端)。
不幸的是,即使遵循我自己的建议,也就是在我指定的情况下
-Djava.rmi.server.codebase=file:/h:/rmi-example/jones/src/
当我启动客户端时,我会得到与您相同的异常。
尚未解决。我希望java选项-verbose:class
能够解决这个问题。
答案 5 :(得分:-1)
同时在键盘上按Win + R快捷键。这将打开“运行”对话框。 ... 在“运行”框中键入以下命令:rundll32.exe sysdm.cpl,EditEnvironmentVariables
然后给您当前目录