在工作中使用java编写服务的同学都有用过或者知道RMI,从RMI和RPC解决问题的出发点看,有一些是相同的,所以有相似之处,都提供了一种远程调用接口的能力,解决了分布式系统中相互之间调用的问题。不过他们之间还是有区别的。
一、RMI不是RPC
RMI是Java的一组开发分布式应用程序的API。RMI使用Java语言接口定义了远程对象,它集合了Java序列化和Java远程方法协议(Java Remote Method Protocol)。简单地说,这样使原先的程序在同一操作系统的方法调用,变成了不同操作系统之间程序的方法调用,由于J2EE是分布式程序平台,它以RMI机制实现程序组件在不同操作系统之间的通信。 但RMI的使用必须是在能够识别java代码的环境下使用,即必须有JVM的支持。因此,他只适合在java程序间的对象通信。如果不在 Java 环境下工作,或者需要与非 Java 环境通信,那么SOAP、RPC、CORAR等都是可以的。
RPC(Remote Method Invocation,远端过程调用) 与RMI的区别很明显,相比于RMI直接获取远端方法的签名,进行调用的方式,RPC使用的是C/S方式,发送请求到服务器,等待服务器返回结果。为了包装RPC的请求信息,推出了XML-RPC,客户端发送一条特定消息,该消息中必须包括名称、运行服务的程序以及输入参数。XML-RPC只能使用有限的数据类型种类和一些简单的数据结构。SOAP最主要的工作是使用标准的XML描述了RPC的请求信息(URI/类/方法/参数/返回值)。SOAP的方式,SOAP 是对如CORBA 和 RMI-IIOP 这样的重型 范例吸引人的替代。
RMI 与 RPC的相似性在于功能相似,我们可以从他们的功能图看他们的相似之处
从上面的功能框架看,RMI 和 RPC 实现的功能确实比较类似的, 当然RPC可以跨语言实现调用,功能上更为强大。如果从实现机制上看就会发现RMI 与 RPC 有很大不同。 RMI是基于类的远程调用方式,在两边有同一个类的主体和存根,而RPC 可以基于HTTP 或者别的通信协议,进行调用,在通信的数据包里,含有调用的方法,参数等序列化后的信息。
下面是RMI的机制原理图:
再来看一下RPC的机制原理:
RPC原理图与RMI原理图的最大区别在于,RMI是对象类的操作,客户端和服务端之间的通信协议已经由RMI自行解决。RPC调用,客户端与服务端之间传送的操作的数据内容,这些数据内容经过了序列化和反序列化的过程,这样的机制让RPC调用具有更为灵活的特性,因为只是通过通信(常用http,socket)协议来传送数据包,因此这种机制可以跨语言,跨平台,只要序列化和反序列化能够保证一致性,就可以实现灵活的调用。
二、RMI调用的简单例子
下面通过一个简单的例子来实践和体会RMI调用
2.1 远程对象接口和远程对象实现
对象实例:
package com.cwqsolo.rmidemo.enitity;
import java.rmi.Remote;
import java.rmi.RemoteException;
/*
* 首先定义一个继承Remote的接口
* 这个接口中定义的方法需要抛出RemoteException
*/
public interface RmiEntityHello extends Remote {
/**
* 简单的方法,无需传入参数,只返回“Hello World!"
*/
public String helloWorld() throws RemoteException;
/**
* 带参数的方法:输入参数为名字,返回“名字,Hello World!”
*/
public String sayHello2Someone(String Name) throws RemoteException;
}
定义了一个继承remote的 接口,然后还需要定义个接口的实现类,并且这个实现类需要继承UnicastRemoteObject,否则客户端调用的都是本地而非服务端。这个接口的实现类如下:
package com.cwqsolo.rmidemo.enitity;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class RmiEntityHelloImpl extends UnicastRemoteObject implements RmiEntityHello {
/**
* 因为UnicastRemoteObject的构造方法抛出了RemoteException异常,因此这里默认的构造方法必须写,必须声明抛出RemoteException异常
*/
public RmiEntityHelloImpl() throws RemoteException {
}
/**
* 简单的方法,无需传入参数,只返回“Hello World!"
*/
public String helloWorld() throws RemoteException {
return "Hello World!";
}
/**
* 带参数的方法:输入参数为名字,返回“名字,Hello World!”
*/
public String sayHello2Someone(String Name) throws RemoteException {
return "Hello->" + Name + "!";
}
}
2.2 服务端代码:
package com.cwqsolo.rmiserver;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import com.cwqsolo.rmidemo.enitity.RmiEntityHello;
import com.cwqsolo.rmidemo.enitity.RmiEntityHelloImpl;
public class HelloServer {
public static void main(String args[]) {
try {
//创建一个远程对象 ,注意对象是接口继承类
RmiEntityHello myhello = new RmiEntityHelloImpl();
//本地主机上的远程对象注册实例,并指定端口为5099,Java默认端口是1099)
//缺少注册创建,则无法绑定对象到远程注册表上
LocateRegistry.createRegistry(5099);
//把远程对象注册到RMI注册服务器上,并命名为Hello
//绑定的URL标准格式为:rmi://host:port/name(其中协议名可以省略)
Naming.bind("rmi://localhost:5099/Hello", myhello );
System.out.println(">>>>>INFO:远程RmiEntityHello对象绑定成功!");
} catch (RemoteException e) {
System.out.println("创建远程对象发生异常!");
e.printStackTrace();
} catch (AlreadyBoundException e) {
System.out.println("发生重复绑定对象异常!");
e.printStackTrace();
} catch (MalformedURLException e) {
System.out.println("发生URL畸形异常!");
e.printStackTrace();
}
}
}
2.3 客户端代码
package com.cwqsolo.rmidemo.client;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import com.cwqsolo.rmidemo.enitity.RmiEntityHello;
public class HelloClient {
public static void main(String args[]){
try {
//在RMI服务注册表中查找名称为RHello的对象,并调用其上的方法
RmiEntityHello myhello =(RmiEntityHello) Naming.lookup("rmi://localhost:5099/Hello");
System.out.println(myhello.helloWorld());
System.out.println(myhello.sayHello2Someone( "Honey") );
} catch (NotBoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
三:例子运行情况
首先启动服务端,服务端启动后,对端口进行监听,等待客户端连接
然后客户端连接并查询远程对象
最后调用远程对象的方法
服务端日志:
>>>>>INFO:远程RmiEntityHello对象绑定成功!
客户端日志:
Hello World!
Hello->Honey!
四:java rmi机制的一些实现
(这里的内容参考网络)
4.1:UnicastRemoteObject.java
基类的构造方法将远程对象发布到一个随机端口上,构造函数
protected UnicastRemoteObject(int port) throws RemoteException
{
this.port = port;
exportObject((Remote) this, port);
}
public static Remote exportObject(Remote obj, int port) throws RemoteException
{
return exportObject(obj, new UnicastServerRef(port));
}
private static Remote exportObject(Remote obj, UnicastServerRef sref) throws RemoteException
{
// if obj extends UnicastRemoteObject, set its ref.
if (obj instanceof UnicastRemoteObject) {
((UnicastRemoteObject) obj).ref = sref;
}
return sref.exportObject(obj, null, false);
}
4.2 UnicastServerRef.java
public Remote exportObject(Remote impl, Object data, boolean permanent) throws RemoteException
{
Class implClass = impl.getClass();
Remote stub;
try {
stub = Util.createProxy(implClass, getClientRef(), forceStubUse);
} catch (IllegalArgumentException e) {
throw new ExportException(
"remote object implements illegal remote interface", e);
}
if (stub instanceof RemoteStub) {
setSkeleton(impl);
}
Target target =
new Target(impl, this, stub, ref.getObjID(), permanent);
ref.exportObject(target);
hashToMethod_Map = hashToMethod_Maps.get(implClass);
return stub;
}
这里的stub变量就是我们lookup出来的远程对象的存根对象。而target保留了远程对象的信息集合:对象,存根,objId等。
4.3 TCPTransport.java
public void exportObject(Target target) throws RemoteException {
/*
* Ensure that a server socket is listening, and count this
* export while synchronized to prevent the server socket from
* being closed due to concurrent unexports.
*/
synchronized (this) {
listen();
exportCount++;
}
/*
* Try to add the Target to the exported object table; keep
* counting this export (to keep server socket open) only if
* that succeeds.
*/
boolean ok = false;
try {
super.exportObject(target);
ok = true;
} finally {
if (!ok) {
synchronized (this) {
decrementExportCount();
}
}
}
}
listen()启动监听。这里对TcpTransport加了同步,防止多个线程同时执行,同时也防止同一端口启动多次。一次TcpTransport会有一个监听端口,而一个端口上可能会发部多个远程对象。exportCount计录该TcpTransport发布了多少个对象。
public void exportObject(Target target) throws RemoteException {
target.setExportedTransport(this);
ObjectTable.putTarget(target);
}
ObjectTable为静态方法调用,那么所有的远程对象信息(target)都会放到这个对象表中。我们这个target包含了远程对象几乎所有的信息,找到他,就找到远程对象的存根对象。
4.4 bind (RegistryImpl.java )
public void bind(String name, Remote obj)
throws RemoteException, AlreadyBoundException, AccessException
{
checkAccess("Registry.bind");
synchronized (bindings) {
Remote curr = bindings.get(name);
if (curr != null)
throw new AlreadyBoundException(name);
bindings.put(name, obj);
}
}
4.5 RegistryImpl_Skel.class
case 2: // lookup(String)
{
java.lang.String $param_String_1;
try {
java.io.ObjectInput in = call.getInputStream();
$param_String_1 = (java.lang.String) in.readObject();
} catch (java.io.IOException e) {
throw new java.rmi.UnmarshalException("error unmarshalling arguments", e);
} catch (java.lang.ClassNotFoundException e) {
throw new java.rmi.UnmarshalException("error unmarshalling arguments", e);
} finally {
call.releaseInputStream();
}
java.rmi.Remote $result = server.lookup($param_String_1);
try {
java.io.ObjectOutput out = call.getResultStream(true);
out.writeObject($result);
} catch (java.io.IOException e) {
throw new java.rmi.MarshalException("error marshalling return", e);
}
break;
}
4.6 MarshalOutputStream.java
protected final Object replaceObject(Object obj) throws IOException {
if ((obj instanceof Remote) && !(obj instanceof RemoteStub)) {
Target target = ObjectTable.getTarget((Remote) obj);
if (target != null) {
return target.getStub();
}
}
return obj;
}
附:执行10000次2.174 seconds ,每秒4600次。