遠程方法調用(RMI)原理與示例


RMI介紹

  遠程方法調用(RMI)顧名思義是一台機器上的程序調用另一台機器上的方法。這樣可以大致知道RMI是用來干什么的,但是這種理解還不太確切。RMI是Java支撐分布式系統的基石,例如著名的EJB組件。RMI是遠程過程調用(RPC)的一種面向對象實現,RMI底層是通過socket通信和對象序列化技術來實現的。這里引用Wikipedia對RMI的介紹:

The Java Remote Method Invocation (Java RMI) is a Java API that performs remote method invocation, the object-oriented equivalent of remote procedure calls (RPC), with support for direct transfer of serialized Java classes and distributed garbage collection.

  1. The original implementation depends on Java Virtual Machine(JVM) class representation mechanisms and it thus only supports making calls from one JVM to another. The protocol underlying this Java-only implementation is known as Java Remote Method Protocol (JRMP).
  2. In order to support code running in a non-JVM context, a CORBA version was later developed.

Usage of the term RMI may denote solely the programming interface or may signify both the API and JRMP, IIOP, or another implementation, whereas the term RMI-IIOP (read: RMI over IIOP) specifically denotes the RMI interface delegating most of the functionality to the supporting CORBA implementation.
The basic idea of Java RMI, the distributed garbage-collection (DGC) protocol, and much of the architecture underlying the original Sun implementation, come from the 'network objects' feature of Modula-3.

RMI基本原理

  RMI的目的就是要使運行在不同的計算機中的對象之間的調用表現得像本地調用一樣。RMI 應用程序通常包括兩個獨立的程序:服務器程序和客戶機程序。RMI 需要將行為的定義與行為的實現分別定義, 並允許將行為定義代碼與行為實現代碼存放並運行在不同的 JVM 上。在 RMI 中, 遠程服務的定義是存放在繼承了 Remote 的接口中。遠程服務的實現代碼存放在實現該定義接口的類中。RMI 支持兩個類實現一個相同的遠程服務接口: 一個類實現行為並運行在服務器上, 而另一個類作為一個遠程服務的代理運行在客戶機上。客戶程序發出關於代理對象的調用方法, RMI 將該調用請求發送到遠程 JVM 上, 並且進一步發送到實現的方法中。實現方法將結果發送給代理, 再通過代理將結果返回給調用者。

  RMI 構建三個抽象層, 高層覆蓋低層, 分別負責Socket通信, 參數和結果的序列化和反序列化等工作。存根( Stub) 和骨架( Skeleton) 合在一起形成了 RMI 構架協議。下面的引用層被用來尋找各自的通信伙伴, 在這一層還有一個提供名字服務的部分, 稱為注冊表( registry) 。最下一層是傳輸層, 是依賴於 TCP/IP 協議實現客戶機與服務器的互聯。
  

  當客戶端調用遠程對象方法時, 存根負責把要調用的遠程對象方法的方法名及其參數編組打包,並將該包向下經遠程引用層、傳輸層轉發給遠程對象所在的服務器。通過 RMI 系統的 RMI 注冊表實現的簡單服務器名字服務, 可定位遠程對象所在的服務器。該包到達服務器后, 向上經遠程引用層, 被遠程對象的 Skeleton 接收, 此 Skeleton 解析客戶包中的方法名及編組的參數后, 在服務器端執行客戶要調用的遠程對象方法, 然后將該方法的返回值( 或產生的異常) 打包后通過相反路線返回給客戶端, 客戶端的 Stub 將返回結果解析后傳遞給客戶程序。事實上, 不僅客戶端程序可以通過存根調用服務器端的遠程對象的方法, 而服務器端的程序亦可通過由客戶端傳遞的遠程接口回調客戶端的遠程對象方法。在分布式系統中, 所有的計算機可以是服務器, 同時又可以是客戶機。

 

RMI應用示例

  Remote 接口用於標識其方法可以從非本地虛擬機上調用的接口。任何遠程對象都必須直接或間接實現此接口。只有在“遠程接口”(擴展 java.rmi.Remote 的接口)中指定的這些方法才可遠程使用。 也就是說需要遠程調用的方法必須在擴展Remote接口的接口中聲名並且要拋出RemoteException異常才能被遠程調用。遠程對象必須實現java.rmi.server.UniCastRemoteObject類,這樣才能保證客戶端訪問獲得遠程對象時,該遠程對象將會把自身的一個拷貝序列化后以Socket的形式傳輸給客戶端,此時客戶端所獲得的這個拷貝稱為“存根”,而服務器端本身已存在的遠程對象則稱之為“骨架”。其實此時的存根是客戶端的一個代理,用於與服務器端的通信,而骨架也可認為是服務器端的一個代理,用於接收客戶端的請求之后調用遠程方法來響應客戶端的請求。 遠程對象的接口和實現必須在客戶端和服務器端同時存在並且保持一致才行。

 

實現代碼:

package com.wxisme.rmi;

import java.rmi.Remote;
import java.rmi.RemoteException;

/**
 *@Description:<p>遠程接口定義</p>
 *@author 王旭
 *@time 2016年3月14日 下午4:53:48
 */
public interface HelloDefine extends Remote {
    
    public String helloWorld() throws RemoteException;
    
    public String sayHello(String name) throws RemoteException;
    
}
package com.wxisme.rmi;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

/**
 *@Description:<p>遠程接口實現</p>
 *@author 王旭
 *@time 2016年3月14日 下午4:57:50
 */
public class HelloDefineImp extends UnicastRemoteObject implements HelloDefine {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    public HelloDefineImp() throws RemoteException {
        super();
    }

    public String helloWorld() throws RemoteException {
        return "Hello AlphaGo!";
    }

    public String sayHello(String name) throws RemoteException {
        return "Hello" + name +"!";
    }

}
package com.wxisme.rmi;

import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

/**
 *@Description:<p>服務端綁定</p>
 *@author 王旭
 *@time 2016年3月14日 下午4:59:33
 */
public class HelloServer {
    
    HelloDefine hello;
    
    public void server() throws RemoteException, MalformedURLException, AlreadyBoundException {
        hello = new HelloDefineImp();
        
        //遠程對象注冊表實例
        LocateRegistry.createRegistry(8888);
        //把遠程對象注冊到RMI注冊服務器上
        Naming.bind("rmi://localhost:8888/Hello", hello);
        System.out.println("server:對象綁定成功!");
    }

}
package com.wxisme.rmi;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

/**
 *@Description:<p>客戶端調用</p>
 *@author 王旭
 *@time 2016年3月14日 下午5:08:51
 */public class HelloClient {
    
    public HelloDefine hello;
    
    public void client() throws MalformedURLException, RemoteException, NotBoundException {
        //在RMI注冊表中查找指定對象
        hello = (HelloDefine) Naming.lookup("rmi://localhost:8888/Hello");
        //調用遠程對象方法
        System.out.println("client:");
        System.out.println(hello.helloWorld());
        System.out.println(hello.sayHello("神之一手"));
    }

}
package com.wxisme.rmi;

import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

import org.junit.Test;

/**
 *@Description:<p>測試</p>
 *@author 王旭
 *@time 2016年3月14日 下午5:14:36
 */
public class RMITest {

    @Test
    public void testServer() throws RemoteException, MalformedURLException, AlreadyBoundException {
        HelloServer server = new HelloServer();
        server.server();
        while(true);
    }
    
    @Test
    public void testClient() throws MalformedURLException, RemoteException, NotBoundException {
        HelloClient client = new HelloClient();
        client.client();
    }

}

運行結果:

 

 

 

 

 

 

 

 

 

 

 

參考資料:

Wikipedia RMI:https://en.wikipedia.org/wiki/Java_remote_method_invocation

Java RMI 框架:http://haolloyin.blog.51cto.com/1177454/332426/

《基於 RMI 的文件上傳與下載的實現》程曉錦, 徐秀花


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM