12

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がどのように機能しているかを説明できますか?ほんとうにありがとう。ありがとう!

4

6 に答える 6

13

この例は、同じネットワーク内の 2 台の PC で試しました。1 つは Java 1.7.0_40 がサーバーとして機能し、もう 1 つは Java 1.7.0_45 がクライアントとして機能します。どちらの PC も Windows ベースです。伝承男が提起した同じ問題に遭遇しました。

解決策は次のとおりです。

サーバ側:

C:\>start rmiregistry -J-Djava.rmi.server.useCodebaseOnly=false

Java 7 では -J-Djava.rmi.server.useCodebaseOnly オプションが必要です。これは、デフォルト値が true であるためです。これは、RMI レジストリが開始元のディレクトリ以外の他のコード ベースを検索しないことを意味します。その後、サーバーを開始する次のステップは失敗します。詳細はこちら: 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 バインド」を取得する必要があります。

クライアント側:

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 

file:/ URL を試しましたが、成功しませんでした。理由は簡単だと思います。サーバーがクライアント PC 上のファイルにアクセスできないようにするセキュリティ制限が多数あります。http://54.200.126.244そこで、rmi ディレクトリの下にある Web サーバーに Pi.class ファイルを配置しました。私の Web サーバーは Apache を使用しています。どのPCからでもアクセスできるhttp://54.200.126.244/rmi/ので問題がスッキリ解決。

最後に、同じコマンドを使用して、任意のディレクトリから rmiregistry、サーバー、およびクライアントを起動できるはずです。そうしないと、成功しても一部の設定が正しくない可能性があります。たとえば、「compute」ディレクトリ (私の場合は C:\rmi) を含むディレクトリから rmiregistry を開始すると、rmiregistry は開始ディレクトリから Compute.class と Task.class を直接ロードするため、-Djava の設定は.rmi.server.codebase=file:/c:/rmi/compute.jar が使えなくなります。

于 2013-11-29T17:17:03.343 に答える
8

サーバーに認識されていないクラスのシリアル化されたオブジェクトを送信しようとしています。

実行すると:

  Pi task = new Pi(Integer.parseInt(args[1]));
  BigDecimal pi = comp.executeTask(task);

サーバーは が何であるかを本当に知りませんPi。クラスは API の一部であるためPi、サーバーにもロードする必要があります。

たとえばRMI、Spring Remoting などを使用して何かをリモートで実行する必要があるアプリケーションがある場合、プロジェクトを 3 つのプロジェクトAPI(サーバー、クライアント) に分割します。API プロジェクトには、機能に関連するすべてのインターフェイスとモデル クラスが含まれます (このプロジェクトは jar になり、コンピューターの JAR とほぼ同じになります)。サーバーは API JAR をインポートし、インターフェイスを実装し、(サーバーで行ったように) リモート レイヤーを介してサービスを利用できるようにし、クライアントで行ったようにクライアントを利用できるようにします。

シリアル化を使用する場合、クラス自体が両側で認識されている必要があります。次に転送されるのは、反対側でオブジェクトを再構築するためにオブジェクトの状態です。

シリアル化は、クライアントからサーバーへのメソッド呼び出しの引数として、またはメソッド呼び出しからの戻り値として、JVM 間でオブジェクトを渡すために RMI によって使用されるメカニズムです。

William Grossoによる RMI での連載(2001 年 10 月)。そして、ここにもう少し情報があります。

于 2012-06-30T10:31:32.100 に答える
5

私の質問は、私のプログラムが動作するためには、Pi.class がサーバー上に存在する必要があるのですか? 私の理解では (間違っていたら訂正してください)、そのクラスのインスタンスをサーバーに送信すると、サーバーはそれをどう処理するかを知っています。つまり、実装は気にしません。

あなたは正しく理解しました。Pi.class は、コンパイル時にサーバー上にある必要はありませんが、サーバーは実行時にダウンロードする必要があります。(Pi はシリアライズ可能でなければなりません)

問題は、サーバーが必要なときに Pi.class をダウンロードする場所をどのように知るかということです。

その答えは、クライアントが提供する java.rmi.server.codebase 設定の値によるものです。クライアントは java.rmi.server.codebase オプションを設定する必要があります。Pi.class の場所を指定する必要があります。デプロイのために Pi.class のコピーを public ディレクトリに置くのは一般的な習慣です。したがって、完全な解決策は次のとおりです。

  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
    

    ここで、compute.jar は以前に作成した jar ファイルです

    cd C:\Users\Public\RMI\
    javac compute\Compute.java compute\Task.java
    jar cvf compute.jar compute\*.class
    

    Java ファイルでパッケージおよびインポート コマンドを正しく設定しましたか? チュートリアルの元の構造を変更したため...

  2. サーバーをコンパイルします。

    C:\Users\Public\RMI\> cd server
    C:\Users\Public\RMI\server> javac -cp ..\compute.jar org\examples\rmi\engine\ComputeEngine.java
    
  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
    
  4. Pi.class を deploy ディレクトリに移動します。

    C:\Users\Public\RMI\> cp client\org\examples\rmi\client\Pi.class client\deploy
    
  5. rmi レジストリを実行します。Java 7 を使用している場合は、muyong の提案に従って、オプション -J-Djava.rmi.server.useCodebaseOnly=false を設定します。

    C:\Users\Public\RMI\> start rmiregistry -J-Djava.rmi.server.useCodebaseOnly=false
    
  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
    
  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
    

それがうまくいくかどうか教えてください!

Ps 私は Windows OS ではなく Linux を使用しています。'\' と '/' を混同していた可能性があります。

于 2014-01-05T19:49:36.757 に答える