RMI远程方法调用
客户与服务器角色
所有的分布式编程思想都很简单:客户计算机产生一个请求,然后将这个请求通过网络发送到服务器。服务器处理这个请求并返回一个针对该客户端的相应,供客户端进行分析。
我们想要的机制:
客户端程序员以常规的方式进行方法调用,无需关心将数据发送到网络上或者解析响应之类的问题。解决办法是在客户端为远程对象安装一个代理。
同样实现服务端的程序员也不需要考虑与客户端之间的通信。解决办法是在服务端安装第二个代理对象。该服务端代理和客户端代理通信,并且它将以常规方式调用服务器对象上的方法。
代理之间的通信:
- CORBA:通用对象请求代理架构,支持 任何语言 编写的对象之间的方法调用。
- Web服务架构是一个协议集,有时统一描述为 WS-*。它也是独立于编程语言的,不过它使用基于XML的通信格式。用于传输对象的格式则是 简单对象访问协议(SOAP)。
- RMI,Java的远程方法调用技术,支持Java的分布式对象之间的方法调用。
远程方法调用
存根与参数编组
我们将 客户机 上的代理对象称为 存根(stub)。它知道如何通过网络与服务器联系。
存根会将远程方法所需的参数打包成一组字节。对参数编码的过程称作 参数编组(parameter marshalling),参数编组的目的是为了将参数编码为适合在虚拟机之间传输的格式。
客户端的存根方法构造了一个信息块,它有一下几部分组成:
- 被调用的远程对象的标识符
- 被调用的方法的描述
- 编组后的参数
服务端接收器执行一下操作:
- 定位要调用的远程对象
- 调用所需方法,并传递客户端提供的参数
- 捕获返回值或该调用产生的异常
- 将返回值编组,打包送回给客户端存根
客户端存根对来自服务端的返回值或异常进行反编组,其结果就作为调用存根的返回值。
RMI编程模型
接口与实现
public interface Warehouse extends Remote{
double getPrice(String description) throws RemoteException;
}
远程对象的接口必须扩展java.rmi.Remot
接口;接口中的所有方法还必须抛出RemoteException
异常。
在 客户端 我们需要提供一个实现了上面远程接口的类:
public class WarehoseImpl extends UnicastRemoteObject implements Warehouse{
public WarehouseImpl() throws RemoteException{
...
}
public double getPrice(String description) throws RemoteException{
...
return price;
}
}
这个实现类继承了UnicastRemoteObject
,这个类的构造器使得它的对象可供远程访问。如果我们的实现类已经继承了其它的类,从未不法继承UnicastRemotObject
类时,我们可以在实现类的构造方法中调用下面的语句:
UnicastRemoteObject.exportObject(this,0);
第二个参数是0,表示任何适合的端口都可以用来监听客户端连接。
RMI注册表
要访问服务器上的远程对象时,客户端首先需要一个本地的存根对象。客户端应该怎么去的这个存根呢?最普通的方法就是调用另一个服务对象上的一个远程方法,以返回值的方式去的存根对象。获取存根的第一个远程对象总是要通过某种方式进行定位,否则这就成了先有鸡还是现有蛋的问题了。JDK提供了RIM注册服务,允许我们注册远程对象:
//在1099端口上开启注册服务
Registry registry = LocateRegistry.createRegistry(1099);
//将WarehouseImpl对象注册到RMI注册表中
WarehoseImpl warehoseImpl = new WarehoseImpl();
registry.bind("warehouse",warehoseImpl);
客户端:
Registry registry = LocateRegistry.getRegistry(host);//端口默认为1099
//Registry registry = LocateRegistry.getRegistry(host,port);
String[] servicesName = registry.list();//获取所有的服务名称
Warehouse warehouse = (Warehouse)registry.lookup("warehouse");//获取存根
warehouse.getPrice("apple");//调用远程方法