与现有服务器的连接
人们常说,RMI主要是“从Java到Java”,但这种说法掩盖了这样一个事实:Java可使用被称为JNI的本机方法接口,很容易地与现有和原有系统连接。JNI和RMI的混合使用与任何其它Java程序一样简单。您可使用JDBC,再结合RMI,与现有的关系数据库连接。也就是说,您可使用RMI连接二层次和三层次系统--即使双方都不是用Java 编写的亦可。这样做有很大的好处和优势,下面会详细阐述。但首先让我们看一看它是如何完成的。
假定您现在有一台在关系数据库中存储了有关客户订单信息的服务器。在任何多层次系统中,您都得设计一个远程接口,以便于客户机访问服务器--利用作为Remote 接口的RMI:
import java.rmi.*;
import java.sql.SQLException;
import java.util.Vector;
public interface OrderServer extends Remote {
Vector getUnpaid() throws RemoteException, SQLException;
void shutDown() throws RemoteException;
// ... other methods (getOrderNumber, getShipped, ...)
}
java.sql包包含JDBC包。每个远程方法均可被服务器采用实际数据库的JDBC调用,或通过采用其它数据库访问机制的本机方法实现。上面所示的方法返回一个Order (订单) 对象的Vector (列表)。Order (订单)就是在您的系统中定义的、用来保存客户订单的类。
本节将介绍如何使用JDBC实现getUnpaid,和如何使用JNI 实施shutDown。
JDBC -- 连接数据库
使用JDBC实现getUnpaid的OrderServerImpl如下:
import java.rmi.*;
import java.rmi.server.*;
import java.sql.*;
import java.util.Vector;
public class OrderServerImpl
extends UnicastRemoteObject
implements OrderServer
{
Connection db; // connection to the db
PreparedStatement unpaidQuery; // unpaid order query
OrderServerImpl() throws RemoteException, SQLException {
db = DriverManager.getConnection("jdbc:odbc:orders");
unpaidQuery = db.preparedStatement("…");
}
public Vector getUnpaid() throws SQLException {
ResultSet results = unpaidQuery.executeQuery();
Vector list = new Vector();
while (results.next())
list.addElement(new Order(results));
return list;
}
public native void shutDown();
}
其中大多是JDBC任务。除了以Order开始的类型是您的系统的一部分类型以外,您所看到的所有类型均为JDBC或 RMI的一部分。构造函数初始化OrderServerImpl对象,创建与jdbc URL中所规定的数据库的连接( Connection)。有了这个连接,我们就可以使用prepareStatement定义一个能找到所有未付款订单的查询。在此还可为其它方法定义其它查询。OrderServerImpl作为数据库在同一个系统上运行,而且还可能是在同一个进程中(下面将讨论shutDown)。
当getUnpaid方法在RMI服务器对象OrderServerImpl上被调用后,就会执行预先编译的查询,并返回包含所有匹配元素的JDBC ResultSet对象。随后,我们为结果集中的每个项目创建新的Order对象,并将其添加到Vector对象中( Java 的动态数组)。在结束读取结果后,我们将这个向量返回给客户机,后者可将结果显示给用户,或者其他相关的人。
JNI -- 本机方法
RMI服务器和客户机可利用本机方法与现有的和原有的系统连接。您可使用本机方法实现不能直接访问数据库的远程方法,或者通过采用现有代码更简单地实现。您可使用本机接口JNI编写C和C++程序,以实现?Java方法并 Java对象上调用该方法。用本机方法实现shutDown的程序如下:
JNIEXPORT void JNICALL
Java_OrderServerImpl_shutDown(JNIEnv *env, jobject this)
{
jclass cls;
jfieldID fid;
DataSet *ds;
cls = (*env)->GetObjectClass(env, this);
fid = (*env)->GetFieldID(env, cls, "dataSet", "J");
ds = (DataSet *) (*env)->GetObjectField(env, this, fid);
/* With a DataSet pointer we can use the original API */
DSshutDown(ds);
}
这是假定了现有服务器通过其API定义的DataSet类型得到了引用。指向服务器DataSet的指针存储在dataSet域中。当客户机调用shutDown时,服务器的shutDown方法就会被调用。因为在服务器实现中声明了要用本机方法来实现shutDown 方法,所以,RMI将直接调用这个本机方法。这个本机方法找到对象的dataSet域,得到其值,并用它调用现有API 的函数DSshutDown。
Sun公司目前正与ILOG公司合作,开发一种称作TwinPeaks 的产品。TwinPeaks将能够兼容目前的C和C++ API,并生成 Java类,该Java类包含了到Java类中API的调用。这样,您就能从Java调用现有的任何API。TwinPeaks面市后,将有可能完全使用Java (而非JNI调用)编写诸如shutDown这样的方法。
体系结构
RMI系统可为分布式面向对象计算提供简单而又直接的基础。其体系结构可允许对服务器和引用类型进行扩展,从而使RMI能以连续的方式添加功能。
当服务程序被输出后,其引用类型就被定义。在上面的例子中,我们将服务器作为UnicastRemoteObject服务器输出,即点到点非复制服务器。对这些对象的引用对于这类服务器非常合适。不同类型的服务器有不同的引用句法。例如,MulticastRemoteObject就有允许复制服务的引用句法。
当客户机收到向服务器的引用后,RMI就会下载一个可将该引用上的调用转换为面向服务器的远程调用的存根。如图3所示,存根使用对象序列化法将参数编组到方法中,并通过网络将经过编组的调用送到服务器。在服务器端,RMI系统接收调用,并连接到一个框架上,而框架则负责解除参数编组并在服务器上调用该方法的实现。当服务器的执行完成后,无论返回一个值或抛出一个例外,框架通过对结果进行编组,并向客户机的存根发送一个应答。存根解除应答编组,并根据需要返回一个值或抛出一个例外。存根和框架是用服务器的实现生成的,通常使用的是程序rmic。存根使用引用与框架进行会话。这种体系结构使引用能够定义通信的属性。用于 UnicastRemoteObject服务器的引用与在特定主机和端口上运行的单个服务器对象进行通信。凭借存根/引用的分离功能,RMI就能添加新的引用类型。处理复制服务器的引用可将服务器请求多路发送给一组正确的复制程序,汇集响应,再根据多种响应返回正确的结果。如果服务程序没有在虚拟机上运行,则另一个引用类型可激活该程序。客户机可透明地与所有这些引用类型共同工作。
保密与安全
在执行RMI请求时,安全是绝对有保证的。RMI可在客户机与服务器之间提供安全信道,并可将下载的执行程序放入安全的“沙箱(sandbox)”中运行,从而保护您的系统免遭不明客户机可能的攻击。
首先,必须定义您的安全需求,这非常重要。如果您正在安全的企业网内部执行诸如ComputeServer这样的程序,则您只需知道谁在使用计算环路即可,这样您就能对滥用系统的人进行跟踪。如果您需要提供商业计算服务器,则您就需要防止多种恶意的破坏。这些都会影响接口的设计--在企业内部,您可能只要求每个Task对象都配备人名和部门编号,以便跟踪。而在商业领域中,您可能需要更高的安全性,包括数字签名身份识别和某些能帮助您剔除会耗费超过分配于其时间的恶意任务的合同语言。
在客户机和服务器之间您可能需要有一个安全信道。 RMI可使您能提供可创建包括加密插口(socket)在内的您所需要的任何类型插口的插口工厂。从JDK 1.2开始,您将能够指定服务器插口所提供的服务的要求(通过给出对这些要求的描述)。这种新技术可在小应用程序上采用,而多数浏览器都拒绝承诺设置插口工厂。插口要求可包括加密和其它要求。
下载的类也存在安全问题。Java通过SecurityManager对象处理安全问题,而该对象可传递所有与安全有关操作的判断,如打开文件和网络连接等。RMI通过要求您在输出任何服务对象或在服务器上调用任何方法之前安装安全管理器,以使用这个标准的Java机制。RMI提供与小应用程序(无文件存取,只是连接到发出主机等)限制相同的 RMISecurityManager类型。这样可防止下载的执行程序从计算机上读取或写入数据、或与防火墙后面的其它系统连接。您也可编写和安装自己的安全管理程序对象,以执行不同的安全限制。
防火墙
RMI为防火墙后面的客户机提供了与远程服务器进行通信的方法。这样可使您使用RMI在因特网上部署客户程序,如在用于万维网上的小应用程序中。穿越客户机的防火墙会使通信速度降低,所以RMI成功地采用速度最快的技术连接客户机与服务器。当客户机首次依次尝试下列三种可能的方法,试图与服务器建立通信时,该技术即被UnicastRemoteObject的引用所发现
用插口(socket)直接与服务器的端口通信。
如果失败,则建立连接到服务器主机和端口上的URL,并在该URL上使用HTTP POST请求,将信息作为POST的主体发送给框架。如果成功,则post的结果就是框架对存根的响应。
如果又失败,则用端口80 (标准HTTP端口)建立连接到服务器主机的URL,并使用能够将?MI请求发送给服务器的 CGI程序。
这三种技术哪一种首先成功,将来就会首先被用来与服务器进行通信。如果这些技术都不成功,则远程方法调用失败。
这种三步策略使客户机能够以尽可能高的效率实现通信,并大多使用直接插口连接。在没有安装防火墙的系统上,或在企业内部防火墙后的通信中,客户机将使用插口直接连接到服务器上。二级通信技术的速度比直接通信明显地要慢得多,但允许您编写能在因特网和万维网上广泛使用的客户程序。