Java RMI和ClassNotFoundException

BJ *_*ruz 10 java client rmi

我刚刚开始学习如何使用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
Run Code Online (Sandbox Code Playgroud)

这是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();
}
Run Code Online (Sandbox Code Playgroud)

这是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();
}
Run Code Online (Sandbox Code Playgroud)

这是executeTask方法,也在ComputeEngine类中:

  public <T> T executeTask(Task<T> task) throws RemoteException {
    if (task == null) {
      throw new IllegalArgumentException("task is null");
    }
    return task.execute();
  }
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

以下是客户的参数:

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
Run Code Online (Sandbox Code Playgroud)

但是,当我尝试运行客户端时,我收到以下异常:

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
Run Code Online (Sandbox Code Playgroud)

但是,如果我将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
Run Code Online (Sandbox Code Playgroud)

该计划有效.我的问题是,Pi.class真的需要在服务器上才能让我的程序运行吗?我的理解是(如果我错了,请纠正我)我将该类的实例发送到服务器,服务器将知道如何处理它,即它不关心实现.有人能解释一下RMI在我的案例中是如何运作的吗?对此,我真的非常感激.谢谢!

muy*_*ong 13

我在同一个网络中用两台PC试过这个例子.一个用Java 1.7.0_40作为服务器,另一个用Java 1.7.0_45作为客户端.这两台PC都是基于Windows的.我遇到了denshaotoko提出的同样问题.

解决方案是:

服务器端:

C:\>start rmiregistry -J-Djava.rmi.server.useCodebaseOnly=false
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

java.rmi.server.useCodebaseOnly应该再次设置为false.否则,服务器将不使用客户端提供的代码库.然后客户端将获得类未找到的异常.主机名192.168.1.124是服务器的IP地址

你应该得到"ComputeEngine绑定"

客户端:

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 
Run Code Online (Sandbox Code Playgroud)

我trid文件:/ URL但不成功.我认为原因很简单.有太多的安全限制使服务器无法访问客户端PC上的文件.所以我将Pi.class文件放在我的网络服务器上,该服务器位于http://54.200.126.244rmi目录下.我的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变得无用.


Fra*_*eth 8

您正在尝试发送服务器未知的类的序列化对象.

执行时:

  Pi task = new Pi(Integer.parseInt(args[1]));
  BigDecimal pi = comp.executeTask(task);
Run Code Online (Sandbox Code Playgroud)

服务器真的不知道是什么Pi.由于Pi该类是API的一部分,因此它也应该加载到服务器上.

当我有一个需要远程执行某些应用程序的应用程序时,例如使用RMISpring Remoting或类似程序,我将我的项目划分为3个项目:APIServer和Client.API项目将包含与功能相关的所有接口和模型类(此项目将产生jar,并且或多或少与您的计算机JAR相似).服务器将导入API JAR,将实现接口并通过远程层(就像您对服务器所做的那样)提供服务,并像客户端一样使用客户端.

使用序列化时,双方必须知道类本身.然后传输的是对象的状态,以便在另一侧重建它.

序列化是RMI用于在JVM之间传递对象的机制,可以是从客户端到服务器的方法调用中的参数,也可以是方法调用的返回值.

关于RMI的一些序列化作者:William Grosso(2001年10月).而这里更多的信息.


giu*_*eta 5

我的问题是,Pi.class 真的需要在服务器上才能让我的程序运行吗?我的理解是(如果我错了,请纠正我)我将那个类的实例发送到服务器,服务器会知道如何处理它,即它不关心实现。

你理解正确。Pi.class 编译时不需要在服务器上,但服务器确实需要在运行时下载它!(Pi 必须是可序列化的)

问题是:服务器在需要时如何知道从哪里下载 Pi.class?

答案是:通过客户端提供的 java.rmi.server.codebase 设置的值。客户端必须设置 java.rmi.server.codebase 选项。你必须说 Pi.class 在哪里。将 Pi.class 的副本放在公共目录中进行部署是一种常见的习惯。因此,完整的解决方案是:

  1. 结构:

    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
    
    Run Code Online (Sandbox Code Playgroud)

    其中,compute.jar 是先前创建的 jar 文件

    cd C:\Users\Public\RMI\
    javac compute\Compute.java compute\Task.java
    jar cvf compute.jar compute\*.class
    
    Run Code Online (Sandbox Code Playgroud)

    您是否正确设置了 java 文件中的 package 和 import 命令?因为你修改了教程原来的结构...

  2. 编译服务器:

    C:\Users\Public\RMI\> cd server
    C:\Users\Public\RMI\server> javac -cp ..\compute.jar org\examples\rmi\engine\ComputeEngine.java
    
    Run Code Online (Sandbox Code Playgroud)
  3. 编译客户端:

    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
    
    Run Code Online (Sandbox Code Playgroud)
  4. 将 Pi.class 移动到部署目录中

    C:\Users\Public\RMI\> cp client\org\examples\rmi\client\Pi.class client\deploy
    
    Run Code Online (Sandbox Code Playgroud)
  5. 运行 rmi 注册表。如果您使用的是 java 7,请按照 muyong 的建议设置选项 -J-Djava.rmi.server.useCodebaseOnly=false

    C:\Users\Public\RMI\> start rmiregistry -J-Djava.rmi.server.useCodebaseOnly=false
    
    Run Code Online (Sandbox Code Playgroud)
  6. 运行服务器。如果您使用的是 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
    
    Run Code Online (Sandbox Code Playgroud)
  7. 运行客户端。注意:注意 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
    
    Run Code Online (Sandbox Code Playgroud)

让我知道它是否有效!

Ps 我不使用 Windows 操作系统,而是使用 Linux,我可能会混淆“\”和“/”