分布式计算系统要求运行在不同地址空间不同主机上的对象互相调用,在Java里提供了完整的sockets通讯接口,但sockets要求客户端和服务端必须进行应用级协议的编码交换数据,采用sockets是非常麻烦的。
  一个代替Sockets的协议是RPC(Remote Procedure Call), 它抽象出了通讯接口用于过程调用,使得编程者调用一个远程过程和调用本地过程同样方便。RPC 系统采用XDR来编码远程调用的参数和返回值。
  但RPC 并不支持对象,而EJB构造的是完全面向对象的分布式系统,所以,面向对象的远程调用RMI(Remote Method Invocation)成为必然选择。采用RMI,调用远程对象和调用本地对象同样方便。RMI采用JRMP(Java Remote Method Protocol)通讯协议,是构建在TCP/IP协议上的一种远程调用方法。

  一、RMI机制解析

  Java RMI 指的是远程方法调用 (Remote Method Invocation)。它是一种机制,能够让在某个 Java 虚拟机上的对象调用另一个 Java 虚拟机中的对象上的方法。可以用此方法调用的任何对象必须实现该远程接口。它的特点是只支持java语言编写的程序,并且允许方法返回 Java 对象以及基本数据类型,这和RPC不同,RCP是跨语言的通讯且不支持对象的概念。

  RMI 采用stubs 和 skeletons 来进行远程对象(remote object)的通讯。

  1、stub对象作为远程对象的客户端代理,运行在本地的虚拟机上,它有着和远程对象相同的的远程接口。

  当运行在本地Java虚拟机上的程序调用运行在远程Java虚拟机上的对象方法时,它首先在本地创建该对象的代理对象stub, 然后调用代理对象上匹配的方法,代理对象会作如下工作:

   A 与远程对象所在的虚拟机建立连接
   B 打包(marshal)参数并发送到远程虚拟机
   C 等待执行结果
   D 解包(unmarshal)返回值或返回的错误
   E 返回调用结果给调用程序
   F stub 对象负责调用参数和返回值的流化(serialization)、打包解包,以及网络层的通讯过程。

  2、skeleton对象运行在远程对象所在的虚拟机上,接受来自stub对象的调用。当skeleton接收到来自stub对象的调用请求后,skeleton会作如下工作:

   
   A 解包stub传来的参数
   B 调用远程对象匹配的方法
   C 打包返回值或错误发送给stub对象

  二、基本原理图

java远程请求 java远程通信_java

  三、应用

  在java的RMI中,其类图如下

java远程请求 java远程通信_远程通讯_02

 

各个类的源代码如下:

IService接口:

 

import java.rmi.Remote; 
import java.rmi.RemoteException; 
public interface IService extends Remote { 
  //声明服务器端必须提供的服务 
  String service(String content) throws RemoteException; 
}



ServiceImpl实现类:



import java.rmi.RemoteException; 
//UnicastRemoteObject用于导出的远程对象和获得与该远程对象通信的存根。 
import java.rmi.server.UnicastRemoteObject; 

public class ServiceImpl extends UnicastRemoteObject implements IService { 

  private String name; 

  public ServiceImpl(String name) throws RemoteException { 
    this.name = name; 
  } 
  @Override 
  public String service(String content) { 
    return "server >> " + content; 
  } 
}


Server类:


/* 
* Context接口表示一个命名上下文,它由一组名称到对象的绑定组成。 
* 它包含检查和更新这些绑定的一些方法。 
*/ 
import javax.naming.Context; 
/* 
* InitialContext类是执行命名操作的初始上下文。    
* 该初始上下文实现 Context 接口并提供解析名称的起始点。 
*/ 
import javax.naming.InitialContext; 
public class Server { 
  public static void main(String[] args) { 
    try { 
      //实例化实现了IService接口的远程服务ServiceImpl对象 
      IService service02 = new ServiceImpl("service02"); 
      //初始化命名空间 
      Context namingContext = new InitialContext(); 
      //将名称绑定到对象,即向命名空间注册已经实例化的远程服务对象 
      namingContext.rebind("rmi://localhost/service02", service02); 
    } catch (Exception e) { 
      e.printStackTrace(); 
    } 
    System.out.println("服务器向命名表注册了1个远程服务对象!"); 
  } 
}

Client类:

 


import javax.naming.Context; 
import javax.naming.InitialContext; 

public class Client { 
  public static void main(String[] args) { 
    String url = "rmi://localhost/"; 
    try { 
      Context namingContext = new InitialContext(); 
      // 检索指定的对象。 即找到服务器端相对应的服务对象存根 
      IService service02 = (IService) namingContext.lookup(url 
          + "service02"); 
      Class stubClass = service02.getClass(); 
      System.out.println(service02 + " 是 " + stubClass.getName() 
          + " 的实例!"); 
      // 获得本底存根已实现的接口类型 
      Class[] interfaces = stubClass.getInterfaces(); 
      for (Class c : interfaces) { 
        System.out.println("存根类实现了 " + c.getName() + " 接口!"); 
      } 
      System.out.println(service02.service("你好!")); 
    } catch (Exception e) { 
      e.printStackTrace(); 
    } 
  } 
}