天天看點

遠端方法調用(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 系統的 RMI 系統資料庫實作的簡單伺服器名字服務, 可定位遠端對象所在的伺服器。該包到達伺服器後, 向上經遠端引用層, 被遠端對象的 Skeleton 接收, 此 Skeleton 解析客戶包中的方法名及編組的參數後, 在伺服器端執行客戶要調用的遠端對象方法, 然後将該方法的傳回值( 或産生的異常) 打包後通過相反路線傳回給用戶端, 用戶端的 Stub 将傳回結果解析後傳遞給客戶程式。事實上, 不僅用戶端程式可以通過存根調用伺服器端的遠端對象的方法, 而伺服器端的程式亦可通過由用戶端傳遞的遠端接口回調用戶端的遠端對象方法。在分布式系統中, 所有的計算機可以是伺服器, 同時又可以是客戶機。

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

RMI應用示例

  Remote 接口用于辨別其方法可以從非本地虛拟機上調用的接口。任何遠端對象都必須直接或間接實作此接口。隻有在“遠端接口”(擴充 java.rmi.Remote 的接口)中指定的這些方法才可遠端使用。 也就是說需要遠端調用的方法必須在擴充Remote接口的接口中聲名并且要抛出RemoteException異常才能被遠端調用。遠端對象必須實作java.rmi.server.UniCastRemoteObject類,這樣才能保證用戶端通路獲得遠端對象時,該遠端對象将會把自身的一個拷貝序列化後以Socket的形式傳輸給用戶端,此時用戶端所獲得的這個拷貝稱為“存根”,而伺服器端本身已存在的遠端對象則稱之為“骨架”。其實此時的存根是用戶端的一個代理,用于與伺服器端的通信,而骨架也可認為是伺服器端的一個代理,用于接收用戶端的請求之後調用遠端方法來響應用戶端的請求。 遠端對象的接口和實作必須在用戶端和伺服器端同時存在并且保持一緻才行。

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

實作代碼:

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();
    }

}      

運作結果:

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

參考資料:

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

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

《基于 RMI 的檔案上傳與下載下傳的實作》程曉錦, 徐秀花