分布式编程技术的基本思想:客户计算机产生一个请求,然后将这个请求通过网络发送到服务器。服务器处理这个请求,并发送回一个针对该客户端的响应,供客户端进行分析。
客户端和服务端之间用代理进行通讯,客户端调用代理进行常规的方法调用,而客户端代理与服务端代理进行联系,服务端代理以常规方式调用服务器对象上的方法。
代理之间通信技术:
1.RMI,Java的远程方法调用技术,支持Java的分布式对象之间的方法调用。
2.CORBA,通过对象请求代理架构,支持任何编程语言编写的对象之间的方法调用。CORBA使用Internet Inter-ORB协议(IIOP)支持对象间通信。
3.Web服务架构是一个协议集,有时统一描述为WS-*。独立于编程语言,使用基于XML的通信格式。用于传输对象的格式则是简单对象访问协议(SOAP)。
若互相通信的程序都是由Java实现的,那么CORBA与WS-*的通用性和复杂性统统是不需要的。远程方法调用(RMI)专门针对Java应用之间的通信。
分布式计算的关键是远程方法调用。在一台机器(称为客户端)上的某些代码希望调用在另一台机器(远程对象)上的某个对象的一个方法。要实现这点,方法的参数必须以某种方式传递到另一台机器上,而服务器必须得到通知,去定位远程对象并执行要调用的方法,并且必须将返回值传递回去。
存根和参数编组
当客户代码要在远程对象上调用一个远程方法时,实际上调用的是代理对象上的一个普通方法,此代理对象称为存根(stub)。
存根位于客户端机器上,而非服务器上,它知道如何通过网络与服务器联系。存根将远程方法所需的参数打包成一组字节。队参数编码的过程叫参数编组,参数编组的目的的将参数转换成适合在虚拟机之间进行传递的格式。在RMI协议中,对象是使用序列化机制来进行编码的,在SOAP协议中,对象被编码为XML。
客户端的存根方法构造一个信息块,由以下部分组成:
被使用的远程对象的标识符;
被调用的方法的描述;
编组后的参数。
然后存根将此消息发送给服务器。服务器接收对象执行以下动作:
定位要调用的远程对象;
调用所需的方法,并传递客户端提供的参数;
捕获返回值或该调用参生的异常;
将返回值编组,打包返回给客户端存根。
客户端存根对来自服务器端的返回值或异常进行反编组,就成为了调用存根的返回值。
RMI编程模型简单实例
接口
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* 简单仓库的远程借口
* 远程对象的借口必须扩展Remote接口。接口中的所有方法必须声明抛出RemoteException异常,因为远程调用总是存在失败的可能。
*/
public interface Warehouse extends Remote {
double getPrice(String description) throws RemoteException;
}
实现接口
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Map;
/**
* 实现简单仓库远程借口的类
*该类是远程方法调用的目标。继承UnicastRemoteObject,这个类的构造器可以让它的对象供远程访问。
*有时候不继承UnicastRemoteObject,在这种情况下需手动初始化远程对象,并将它们传给静态的exportObject方法。在远程对象的构造器中调用exportObject方法:UnicastRemoteObjcet.exportObject(this,0);
*/
public class WarehouseImpl extends UnicastRemoteObject implements Warehouse {
private Map<String,Double> prices;
public WarehouseImpl() throws RemoteException{
prices = new HashMap<String,Double>();
prices.put("Blackwell Toaster",24.95);
prices.put("ZapXpress Microwave", 49.95);
}
public double getPrice(String description) throws RemoteException {
Double price = prices.get(description);
return price == null ? 0 : price;
}
}
构造并注册WarehouseImpl对象
import java.rmi.RemoteException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
/**
* 第一个远程对象总要通过某种方式进行定位,JDK提供了自举注册服务。
* 服务器程序使用自举注册服务来注册至少一个远程对象,要注册一个远程对象,需要一个RMI URL和一个对实现对象的引用。
* RMI的URL以rmi:开头,后接服务器以及一个可选的端口号,接着是远程对象的名字。例如:rmi://regserver.mycompany.com:99/central_warehouse
* 默认情况下,主机名是localhost,端口为1099。服务器告诉注册表在给定位置将该对象关联或“绑定”到这个名字。
* 下面代码将一个WarehouseImpl对象注册到了同一个服务器上的RMI注册表中:
WarehouseImpl centralWarehouse = new WarehouseImpl();
Context namingContext = new InitialContext();
namingContext.bind("rmi:central_warehouse", centralWarehouse);
*
*/
public class WarehouseServer {
public static void main(String[] args) throws RemoteException,NamingException{
System.out.println("Constructing server implementation...");
WarehouseImpl centralWarehouse = new WarehouseImpl();
System.out.println("Binding server implementation to registry...");
Context namingContext = new InitialContext();
namingContext.bind("rmi:central_warehouse", centralWarehouse);
System.out.println("Waiting for invocations from clients...");
}
}
客户端获得远程对象
import java.rmi.RemoteException;
import java.util.Enumeration;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameClassPair;
import javax.naming.NamingException;
public class WarehouseClient {
public static void main(String[] args) throws NamingException,RemoteException {
Context namingContext = new InitialContext();
System.out.println("RMI registry bindings:");
//通过下面的调用枚举所有注册过的RMI对象
//NameClassPair是一个助手类,它包含绑定对象的名字和该对象所属类的名字
Enumeration<NameClassPair> e = namingContext.list("rmi://localhost/");
while(e.hasMoreElements()) {
System.out.println(e.nextElement().getName());//打印注册对象的名字
}
//客户端通过下面方式来指定服务器和远程对象的名字,以此获得访问远程对象所需的存根
String url = "rmi://localhost/central_warehouse";;
Warehouse centralWarehouse = (Warehouse)namingContext.lookup(url);
String descr = "Blackwell Toaster";
double price = centralWarehouse.getPrice(descr);
System.out.println(descr+":"+price);
}
}
部署RMI简单实例
创建两个目录分别用于启动服务器和客户端的类
server/
WarehouseServer.class
Warehouse.class
WarehouseImpl.class
client/
WarehouseClient.class
Warehouse.class
部署RMI应用时,通常需要动态地将类交付为运行程序,其中一个例子就是RMI注册表。注册表的一个实例要服务许多不同的RMI应用。当注册表启动时,无法预测将来会参生的所有注册表请求。RMI注册表会动态地加载之前从未遇到过的所有远程接口的类文件。
动态交付的类文件是通过标准的Web服务器发布的,服务器程序需使Warehouse.class文件对于RMI注册表来说是可获得的,所以将该文件放到第三个目录download中:
download/
Warehouse.class
部署应用,服务器、RMI注册表、Web服务器和客户端可以定位到四台不同的机器上。(由于一台机器部署测试例子没看懂,所以具体一台机器测试没做笔记)
扩展
javax.naming.InitialContext
InitialContext() 构建一个命名上下文,用来访问RMI注册表。
javax.naming.Context
static Object lookup(String name) 返回给定名字的对象。如果该名字尚未绑定则抛出NamingException异常。
static void bind(String name,Object obj) 将name和obj对象绑定。如果该对象已经绑定则抛出NameAlreadyBoundException异常。
static void unbind(String name) 解除该名字的绑定。解除一个不存在的绑定是合法的。
static void rebind(String name,Object obj) 将name和obj对象绑定。替换掉以前的绑定。
NamingEnumeration<NameClassPair> list(String name) 返回一个枚举列表,列出所有匹配的绑定对象。使用“rmi:”调用该方法可以列出所有MRI对象。
javax.naming.NameClassPair
String getName() 获取已命名对象的名字。
String getClassName() 获取已命名对象所属的类名。
java.rmi.Naming
static Remote lookup(String url) 返回URL对应的远程对象。如果该名字尚未绑定,抛NotBoundException异常。
static void bind(String name,Remote obj) 将name和远程对象obj绑定。如果该对象已经绑定抛AlreadyBoundException异常。
static void unbind(String name) 解除该名字的绑定。如果该名字没有绑定抛NotBound异常。
static void rebind(String name,Remote obj) 将name和远程对象obj绑定。替换掉以前的绑定。
static String[] list(String url) 参数url指定了某个注册表,返回注册表中的所有URL组成的字符串数组。