一、简介

RMI (Remote Method Invocation) 允许在一个Java虚拟机中运行的对象调用在另一个Java虚拟机中运行的对象的方法。RMI用于构建分布式应用程序; 它提供了Java程序之间的远程通信。

二、架构及原理

1、RMI程序简介

RMI应用程序通常包含两个独立的程序:一个服务器和一个客户端。

典型的服务器程序创建一些远程对象,使对这些对象的引用可访问,并等待客户端调用这些对象上的方法。

典型的客户端程序获取对服务器上一个或多个远程对象的远程引用,然后调用这些对象上的方法。

RMI提供了服务器和客户端通信和来回传递信息的机制;这样的应用程序有时被称为分布式对象应用程序。

2、RMI注册表

RMI注册表(registry)是一个放置所有服务器对象的命名空间;每次服务器创建一个对象时,它都会向RMI Registry 注册这个对象(使用bind()reBind()方法)。它们使用一个唯一的绑定名称进行注册。

调用远程对象时,客户端需要该远程对象的引用;客户端可以使用远程对象的绑定名称(通过lookup()方法)从注册表中提取对象。

过程如下:

3、RMI架构图

RMI应用程序架构如下:

上图中名词解释如下:

  • Transport Layer

传输层,用于连接客户端和服务器;它管理现有的连接并建立新的连接。

  • Stub

Stub是远程对象在客户端上的表示(representation)或代理(proxy),它驻留在客户端系统中,做为客户端程序的网关。

  • Skeleton

Skeleton是驻留在服务器端的对象;Stub与Skeleton通信,将请求传递给远程对象。

  • RRL(Remote Reference Layer)

远程引用层,管理客户端对远程对象引用的层。

4、RMI工作原理

  • 当客户端对远程对象进行调用时,Stub接收到这个请求,并最终将这个请求传递给RRL。

  • 当客户端RRL接收到请求时,它调用远程对象引用的invoke()方法,将请求传递给服务器端的RRL。

  • 服务器端的RRL将请求传递给Skeleton(服务器上的代理) ,Skeleton最终调用服务器上对应的对象。

  • 执行结果会一直传递回客户端。

三、RMI程序开发

1、开发步骤

编写RMI Java应用程序的步骤大致如下:

  • 定义远程接口

  • 开发接口的实现类(远程对象)

  • 开发服务器程序

  • 开发客户端程序

  • 编译&执行

2、具体实现

  • Message类
import java.io.Serializable;

public class MyMessage implements Serializable{

	private static final long serialVersionUID = -6205209644018307258L;

	private String title;
	private String content;
	
	public MyMessage() {}
	
	public MyMessage(String title, String content) {
		super();
		this.title = title;
		this.content = content;
	}

	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	
}
  • 远程接口

新增HelloWorld接口,扩展自Remote接口,

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

public interface HelloWorld extends Remote{

	public String sendMessage(MyMessage message) throws RemoteException;
}
  • 实现类
import java.rmi.RemoteException;

public class HelloWorldImpl implements HelloWorld{

	@Override
	public String sendMessage(MyMessage message) throws RemoteException {
		String msg = String.format("Title: %s\nContent: %s", message.getTitle(), message.getContent());
		System.out.println(msg);
		return "copy that";
	}

}
  • 服务器

在服务器程序中创建一个远程对象并将其绑定到RMI注册表:

import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class MyServer {

	public static void main(String[] args) {
		try {
			//实例化实现类
			HelloWorld hw = new HelloWorldImpl();
			//暴露实现类的对象
			HelloWorld stub = (HelloWorld) UnicastRemoteObject.exportObject(hw, 0);
			Registry registry = LocateRegistry.getRegistry(5555);
			//在注册表中绑定远程对象
			registry.bind("HelloWorld", stub);
			System.out.println("Server ready.");
		} catch (RemoteException e) {
			e.printStackTrace();
		} catch (AlreadyBoundException e) {
			e.printStackTrace();
		}
	}
}
  • 客户端

客户端程序中获取远程对象并调用其方法:

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class MyClient {

	public static void main(String[] args) {
		try {
			//获取注册表
			Registry registry = LocateRegistry.getRegistry(5555);
			//在注册表中查找远程对象
			HelloWorld hw = (HelloWorld) registry.lookup("HelloWorld");
			//调用远程对象方法
			MyMessage message = new MyMessage("Hi", "I Miss You So Much");
			String result = hw.sendMessage(message);
			System.out.println(String.format("Call succeeded, the result: %s", result));
		} catch (RemoteException e) {
			e.printStackTrace();
		} catch (NotBoundException e) {
			e.printStackTrace();
		}
	}
}

3、运行

  • 编译所有Java文件
javac *.java
  • 启动rmi注册表

rmiregistry [port]

rmiregistry 5555

  • 运行服务器
java MyServer
  • 运行客户端
java MyClient

参考资料: