10. 分布式应用程序构建 CORBA
10.1. 简介
在上一章中,我们学习了如何使用 RMI 包创建 Java 分布式应用程序。 本章将探讨相同的问题,但这次采用的是 CORBA 架构。CORBA(通用对象请求代理架构)是由 OMG(对象管理组)定义的一套规范,该组织汇集了全球众多IT企业。 CORBA定义了一个“软件总线”,支持使用不同语言编写的应用程序访问:

我们将看到,使用 CORBA 构建分布式应用程序的方法与 Java RMI 所采用的方法非常相似:其概念具有相似性。 CORBA 的优势在于能够与其他语言编写的应用程序实现互操作。
10.2. CORBA 应用程序的开发流程
10.2.1. 简介
要开发一个 CORBA 客户端-服务器应用程序,我们将遵循以下步骤:
- 使用 IDL(接口定义语言)编写服务器接口
- 生成服务器的“骨架”和“存根”类
- 编写服务器
- 编写客户端
- 编译所有类
- 启动 CORBA 服务目录
- 启动服务器
- 启动客户端
我们首先以 RMI 环境中已使用的回显服务器为例。读者由此可以了解这两种方法之间的区别。
该应用程序已在 jdk1.2 环境中经过测试。
10.2.2. 编写服务器接口
与 Java RMI 类似,对于客户端而言,服务器由其接口定义。如果客户端不需要实现服务器的类,那么它就需要该接口的类。 Java RMI 采用 Java 接口生成服务器的“骨架”和“存根”类,而 Java CORBA 架构则要求使用 Java 以外的语言来描述该接口。 该接口将生成多个类,其中部分由客户端使用,部分由服务器使用。
回显接口的描述如下:
该接口描述将存储在 echo.idl 文件中。它使用 IDL(接口定义语言)编写,该语言属于 OMG 框架。 为了能够使用,该描述必须由一个程序进行解析,该程序将使用开发CORBA应用程序所用的语言生成源文件。 在此,我们将使用程序 idltojava.exe,该程序将基于上述接口生成应用程序所需的 .java 源文件。 程序 idltojava.exe 并未随 JDK 一起提供。可从 Sun 网站 http://java.sun.com 获取。
让我们分析一下前面 idl 接口中的几行代码:
相当于 Java 中的 package echo。编译该接口将生成包含 Java 类的目录 echo c.a.d。
等同于 Java 的接口 iSrvEcho。将生成一个 Java 接口。
等同于 Java 语句 String echo(String msg)。IDL 语言的类型与 Java 语言的类型并不完全对应。本章稍后将介绍这些对应关系。 在 IDL 语言中,函数的参数可以是输入(in)、输出(out)或输入输出(inout)参数。 在此,方法 echo 接收一个 msg 类型的输入参数(该参数为字符串),并返回一个字符串作为结果。
前面的接口是我们的回显服务器的接口。需要提醒的是,远程接口描述了客户端可访问的服务器对象的方法。在此,只有方法 echo 对客户端可用。
10.2.3. 编译服务器的 IDL 接口
定义好服务器接口后,将生成相应的 Java 文件。
E:\data\java\corba\ECHO>dir *.idl
ECHO IDL 78 15/03/99 13:56 ECHO.IDL
E:\data\java\corba\ECHO>d:\javaidl\idltojava.exe -fno-cpp echo.idl
选项 -fno-cpp 用于指示无需使用预处理器(通常与 C/C++ 一起使用)。 编译文件 echo.idl 会生成一个名为 echo 的子目录,其中包含以下文件:
E:\data\java\corba\ECHO>dir echo
_ISRVE~1 JAV 1 095 17/03/99 17:19 _iSrvEchoStub.java
ISRVEC~1 JAV 311 17/03/99 17:19 iSrvEcho.java
ISRVEC~2 JAV 825 17/03/99 17:19 iSrvEchoHolder.java
ISRVEC~3 JAV 1 827 17/03/99 17:19 iSrvEchoHelper.java
_ISRVE~2 JAV 1 803 17/03/99 17:19 _iSrvEchoImplBase.java
文件 iSrvEcho.java 是描述服务器接口的 Java 文件:
/*
* 文件:./ECHO/ISRVECHO.JAVA
* 来源:ECHO.IDL *
日期:1999年3月15日 星期一 13:56:08 * B
y: D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 1998年8月18日 16:25:34 */
pa
ckage echo; p
ublic interface iSrvEcho
extends org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntit
y { String echo(Stri
n
g msg) ; }
可以看出,这几乎是 IDL 接口的逐字翻译。如果好奇查看其他文件 .java 的内容,会发现更复杂的内容。以下是文档中关于这些不同文件作用的说明:
服务器接口
实现了上述接口。这是一个抽象类,作为服务器的“骨架”,为服务器提供分布式应用程序所需的CORBA功能。
这是客户端将使用的服务器“存根”。它为客户端提供 CORBA 功能,以便连接到服务器。
提供管理 CORBA 对象引用所需的方法
提供了用于管理接口方法的输入/输出参数所需的方法。
10.2.4. 编译由接口 IDL 生成的类
建议编译上述类。在另一个示例中,我们将看到此处可能因生成器 idltojava 运行异常而发现错误。此处一切正常,编译完成后,在 echo 包的目录中将包含以下文件:
E:\data\java\corba\ECHO\echo>dir
_ISRVE~1 JAV 1 095 17/03/99 17:19 _iSrvEchoStub.java
ISRVEC~1 JAV 311 17/03/99 17:19 iSrvEcho.java
ISRVEC~2 JAV 825 17/03/99 17:19 iSrvEchoHolder.java
ISRVEC~3 JAV 1 827 17/03/99 17:19 iSrvEchoHelper.java
_ISRVE~2 JAV 1 803 17/03/99 17:19 _iSrvEchoImplBase.java
_ISRVE~1 CLA 2 275 18/03/99 11:25 _iSrvEchoImplBase.class
_ISRVE~2 CLA 1 383 18/03/99 11:25 _iSrvEchoStub.class
ISRVEC~1 CLA 251 18/03/99 11:25 iSrvEcho.class
ISRVEC~2 CLA 2 078 18/03/99 11:25 iSrvEchoHelper.class
ISRVEC~3 CLA 858 18/03/99 11:25 iSrvEchoHolder.class
10.2.5. 服务器写入
10.2.5.1. 实现 iSrvEcho 接口
我们在上面定义了接口 iSrvEcho。现在,我们将编写实现该接口的类。该类将继承自类 _iSrvEchoImplbase.java,如上所述,该类已经实现了接口 iSrvEcho。
// 导入的包
import echo.*;
// 实现远程回显的类
public class srvEcho extends _iSrvEchoImplBase{
// 实现回显的方法
public String echo(String msg){
return "[" + msg + "]";
}// 回显结束
}// 类结束
代码不言自明。该类保存在文件 srvEcho.java 中,位于接口 iSrvEcho 包所在目录的父目录下。
可以编译以进行验证:
E:\data\java\corba\ECHO>j:\jdk12\bin\javac srvEcho.java
E:\data\java\corba\ECHO>dir
ECHO IDL 78 15/03/99 13:56 ECHO.IDL
SRVECH~1 CLA 488 18/03/99 11:30 srvEcho.class
SRVECH~1 JAV 252 15/03/99 14:02 srvEcho.java
ECHO <REP> 17/03/99 17:19 echo
10.2.5.2. 编写服务器创建类
与客户端-服务器应用程序 RMI 一样,CORBA 服务器必须注册到目录中,才能被客户端访问。 在开发层面,正是这一注册流程因应用程序类型(CORBA 或 RMI)的不同而有所差异。 以下是 CORBA 回显服务器的注册信息,该信息已记录在文件 serveurEcho.java 中:
// 导入的包 import echo.
*; import org.
omg.CosNaming.*; import org
.omg.CosNaming.NamingContextPackage.*; import or
g.omg.CORBA.*; //-----
------ 类 serveurEcho public clas
s serveurEcho{ // ------
- main:启动回显服务器 // pg 语法 machineAnnuaire
portAnnuaire nomService // 机器:支持目录的机器 CORBA // 端口:目
录端口 CORBA // nomService:要注册的服务名称 public sta
tic void main(String arg[]){ // 参
数是否存在? if(arg.length!=3){ System.err.prin
t ln("Syntaxe : pg machineAnnuaire portA
nnuaire nomService"); System
.exit(1); } // 获
取参数 String machine=arg[0]; String port=arg[1]; String nomService=arg[2];
try{
// 我们需要一个 CORBA 对象来工作
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
ORB orb=ORB.init(initORB,null);
// 将服务添加到服务目录中
// 该服务将命名为 srvEcho
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
NameComponent nc= new NameComponent(nomService,"");
NameComponent path[]={nc};
// 创建服务器并将其与服务 srvEcho 关联
srvEcho serveurEcho=new srvEcho();
ncRef.rebind(path,serveurEcho);
orb.connect(serveurEcho);
// 监控
System.out.println("Serveur d'écho prêt");
// 等待客户端请求
java.lang.Object sync=new java.lang.Object();
synchronized(sync){
sync.wait();
}
} catch(Exception e){
// 发生错误
System.err.println("Erreur " + e);
e.printStackTrace(System.err);
}
}// 主进程
}// serveurEcho
下面我们将概述服务器的启动流程,暂不深入探讨乍看之下较为复杂的细节。请记住前例中的要点,这些要点在所有 CORBA 服务器中都会出现。
10.2.5.2.1. 服务器参数
CORBA服务器必须向运行在指定机器和端口上的目录服务进行注册。我们的应用程序将接收这两项数据作为参数。注册的服务必须有一个名称,这将作为第三个参数。
10.2.5.2.2. 创建访问目录服务的对象 CORBA
为了访问目录服务并注册我们的回显服务器,我们需要一个名为 ORB(对象请求代理)的对象,该对象可通过以下类方法获取:
Args : tableau de paires de chaînes de caractères, chaque paire étant de la forme (paramètre,valeur)
Prop : propriétés de l’application
该示例使用以下序列来获取对象 ORB:
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
ORB orb=ORB.init(initORB,null);
使用的 (参数, 值) 对如下:
("-ORBInitialHost",machine) : ce couple précise la machine ou opère l’annuaire des services CORBA, ici la machine passée en paramètre au serveur.
("-ORBInitialPort",port ) : ce couple précise le port ou opère l’annuaire des services CORBA, ici le port passé en paramètre au serveur.
方法 init 的第二个参数被设为 null。如果第一个参数也设为 null,则使用的 (主机,端口) 对将默认为 (localhost,900)。
10.2.5.2.3. 将服务器注册到服务目录中 CORBA
将服务器注册到目录中需执行以下操作:
// 将服务添加到服务目录中 // 它将命名为 srvEcho org.omg.C
ORBA.Object objRef=
orb.resolve_initial_refere
n ces("NameService"); NamingContext ncRef=
N amingContextHelper.narrow(objRef); NameComponent
n c= new NameComponent(nomService,""); NameComp
o nent path[]={nc}; // 创建服务
器并将其与服务 srvEcho 关联 srvEcho serveurEcho=new srvEcho();
ncRef.rebind(path,serveurEch
o ); orb.connect(serveurEcho);
代码的第一部分用于准备服务名称。该名称在代码中由变量 path 表示。服务名称包含多个组成部分:
- 初始组件 objRef,这是一个通用对象,需要转换为类型 NamingContext,此处为 ncRef。
- 服务名称,此处为 nomService,该名称已作为参数传递给服务器
名称的这些组成部分(NameComponent)被汇集到一个数组中,此处为 path。正是这个数组精确地“命名”了所创建的服务。名称创建完成后,还需
- 将其与服务器的一个实例(即之前构建的类 srvEcho)关联
srvEcho serveurEcho=new srvEcho();
- 将其注册到目录中
10.2.5.3. 编译服务器启动类
编译上述类:
E:\data\java\corba\ECHO>j:\jdk12\bin\javac serveurEcho.java
E:\data\java\corba\ECHO>dir
ECHO IDL 78 15/03/99 13:56 ECHO.IDL
SERVEU~1 CLA 1 793 18/03/99 13:18 serveurEcho.class
SERVEU~1 JAV 1 806 16/03/99 15:38 serveurEcho.java
SRVECH~1 CLA 488 18/03/99 11:30 srvEcho.class
SRVECH~1 JAV 252 15/03/99 14:02 srvEcho.java
ECHO <REP> 17/03/99 17:19 echo
10.2.6. 客户留言
10.2.6.1. 代码
我们编写一个客户端来测试我们的回显服务。我们将向客户端传递与服务器相同的三个参数:
机器:存放服务目录的机器CORBA
端口:该目录运行的端口
nomService:回显服务的名称
客户端连接到回显服务,然后提示用户通过键盘输入消息。这些消息会被发送至回显服务器,并由服务器将其原样返回。屏幕上会实时显示该交互过程。
回显服务的客户端 CORBA 与之前编写的客户端 RMI 非常相似。同样,该客户端必须连接到目录服务,以获取其想要连接的服务器对象的引用。这两个客户端之间的区别仅在于此。 以下是回显客户端 CORBA 的代码:
10.2.6.2. 客户端与服务器的连接
上述 CORBA 客户端通过以下语句连接到服务器:
此操作完成后,客户端将获得回显服务器的引用。随后,客户端 CORBA 与客户端 RMI 并无区别。确保与服务器建立连接的私有方法如下:
// 导入的软件包
import java.io.*;
import echo.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
// ---------- 类 cltEcho
public class cltEcho {
public static void main(String arg[]){
// 语法:cltEcho machineAnnuaire portAnnuaire 服务名称
// 机器:服务目录运行的机器 CORBA
// 端口:服务目录运行的端口
// nomService:回显服务名称
// 参数验证
if(arg.length!=3){
System.err.println("Syntaxe : pg machineAnnuaire portAnnuaire nomservice");
System.exit(1);
}
// 获取参数
String machine=arg[0];
String port=arg[1];
String nomService=arg[2];
// 与回显服务器建立连接
iSrvEcho serveurEcho=getServeurEcho(machine,port,nomService);
// 客户端-服务器交互
BufferedReader in=null;
String msg=null;
String reponse=null;
iSrvEcho serveur=null;
try{
// 打开键盘流
in=new BufferedReader(new InputStreamReader(System.in));
// 读取待发送至回显服务器的消息循环
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
while(! msg.equals("fin")){
// 向服务器发送消息并接收响应
reponse=serveurEcho.echo(msg);
// 后续处理
System.out.println("Réponse serveur : " + reponse);
// 下一条消息
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
}// while
// 结束
System.exit(0);
// 错误处理
} catch (Exception e){
System.err.println("Erreur : " + e);
System.exit(2);
}// try
}// main
// ---------------------- getServeurEcho
private static iSrvEcho getServeurEcho(String machine, String port,
String nomService){
// 请求回显服务器的引用
// 跟踪
System.out.println("--> Connexion au serveur CORBA en cours...");
// 回显服务器的参考号
iSrvEcho serveurEcho=null;
try{
// 请求一个 CORBA 对象以供处理
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
ORB orb=ORB.init(initORB,null);
// 使用目录服务定位回显服务器
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
// 所查找的服务名为 srvEcho - 正在请求该服务
NameComponent nc= new NameComponent(nomService,"");
NameComponent path[]={nc};
serveurEcho=iSrvEchoHelper.narrow(ncRef.resolve(path));
} catch (Exception e){
System.err.println("Erreur lors de la localisation du serveur d'écho ("
+ e + ")");
System.exit(10);
}// try-catch
// 返回服务器引用
return serveurEcho;
}// getServeurEcho
}// 类
这里出现了与服务器中相同的代码序列:
- 创建一个 ORB 对象,它将使我们能够连接到 CORBA 服务目录
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
ORB orb=ORB.init(initORB,null);
- 定义回显服务名称的各个组成部分
org.omg.CORBA.Object objRef=orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
NameComponent nc= new NameComponent(nomService,"");
NameComponent path[]={nc};
- 向目录服务请求回显服务的引用(这与服务器处理方式不同)
10.2.6.3. Compilation
E:\data\java\corba\ECHO>j:\jdk12\bin\javac cltEcho.java
E:\data\java\corba\ECHO>dir
CLTECH~1 CLA 2 599 18/03/99 13:51 cltEcho.class
CLTECH~1 JAV 2 907 16/03/99 16:15 cltEcho.java
ECHO IDL 78 15/03/99 13:56 ECHO.IDL
SERVEU~1 CLA 1 793 18/03/99 13:18 serveurEcho.class
SERVEU~1 JAV 1 806 16/03/99 15:38 serveurEcho.java
SRVECH~1 CLA 488 18/03/99 11:30 srvEcho.class
SRVECH~1 JAV 252 15/03/99 14:02 srvEcho.java
ECHO <REP> 17/03/99 17:19 echo
10.2.7. 测试
10.2.7.1. 启动目录服务
在 Windows 机器上,我们按以下方式启动目录服务:
这将使目录服务在该机器的 1000 端口上启动。
目录服务 tnameserv 会显示如下内容:
Initial Naming Context:
IOR:000000000000002849444c3a6f6d672e6f72672f436f734e616d696e672f4e616d696e67436f
6e746578743a312e3000000000010000000000000030000100000000000a69737469612d30303900
044700000018afabcafe000000027620dd9a000000080000000000000000
TransientNameServer: setting port for initial object references to: 1000
虽然不太好读,但请记住最后一行:该服务在1000端口上运行。
10.2.7.2. 启动回声服务器
回声服务启动时需指定三个参数:
E:\data\java\corba\ECHO>start j:\jdk12\bin\java serveurEcho localhost 1000 srvEcho
服务器显示:
10.2.7.3. 在与服务器相同的计算机上启动客户端
E:\data\java\corba\ECHO>j:\jdk12\bin\java cltEcho localhost 1000 srvEcho
--> Connexion au serveur CORBA en cours...
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin
10.2.7.4. 在非服务器端的Windows计算机上启动客户端
E:\data\java\corba\ECHO>j:\jdk12\bin\java cltEcho tahe.istia.univ-angers.fr 1000 srvEcho
--> Connexion au serveur CORBA en cours...
Message : abcd
Réponse serveur : [abcd]
Message : efgh
Réponse serveur : [efgh]
Message : fin
10.3. 示例 2:一台服务器 SQL
10.3.1. 简介
在此,我们将重新探讨此前在 Java 环境(RMI)中已分析过的 SQL 服务器,旨在进一步突出这两种方法的共同点及其差异。 我们重申该服务器 SQL 的作用:它位于一台 Windows 机器上,允许远程客户端利用该 Windows 工作站的公共数据库 ODBC。

CORBA客户端可执行3项操作:
- 连接到其选定的数据库
- 发送 SQL 请求
- 关闭连接
服务器执行客户端的 SQL 请求并向其发送结果。这是其核心功能,因此我们称其为 SQL 服务器。我们将之前在回显服务器上介绍的各个步骤应用到此处。
10.3.2. 编写服务器的 IDL 接口
回顾一下我们之前用于服务器的 RMI 接口:
import java.rmi.*;
// 远程接口 public interf
ace interSQL extends Remote{ public Stri
ng connect(String pilote, String url, String id, String mdp) throws j
ava.rmi.RemoteException; public
String[] executeSQL(String requete, String separateur) thr
ows java.rmi.RemoteException; p
ublic String close()
throws java.rmi.RemoteExceptio
n; }
各方法的作用如下:
Connect:客户端连接到远程数据库,并提供驱动程序、URL JDBC 以及其用户名 id 和密码 mdp 以访问该数据库。服务器返回一个字符串,表示连接结果:
executeSQL:客户端请求在已连接的数据库上执行查询 SQL。它指定了返回结果中字段之间的分隔符。服务器返回一个字符串数组:
用于数据库更新请求,其中 n 表示被更新的行数
若查询引发错误
若查询未返回任何结果
如果查询产生了结果。服务器返回的这些行即为查询的结果行。
Close:客户端关闭与远程数据库的连接。服务器返回一个字符串,指示此次关闭的结果:
服务器的 IDL 接口如下:
module srvSQL{
typedef sequence<string> resultats;
interface interSQL{
string connect(in string pilote, in string urlBase, in string id, in string mdp);
resultats executeSQL(in string requete, in string separateur);
string close();
};// 接口
};// 模块
与回显服务器的 IDL 接口相比,唯一的新变化是使用了 sequence 关键字。该关键字用于定义一维数组。定义过程分为两步:
- 定义一个类型来标识数组,此处为 resultats:
关键字 typedef 在 C/C++ 程序员中广为人知:它用于定义一种新类型。 在此,类型 resultats 被定义为与类型 sequence<string>(即 c.a.d)等价,即一个动态(无固定尺寸)的字符串数组。
- 在需要的地方使用新类型
因此,方法 executeSQL 返回一个字符串数组。
10.3.3. 编译服务器的 IDL 接口
将上述 IDL 接口放入文件 srvSQL.idl 中。编译该文件:
E:\data\java\corba\sql>d:\javaidl\idltojava -fno-cpp srvSQL.idl
E:\data\java\corba\sql>dir
SRVSQL IDL 275 19/03/99 9:59 srvSQL.idl
SRVSQL <REP> 19/03/99 9:41 srvSQL
可以看到,编译生成了一个名为 IDL(srvSQL)的接口模块目录。让我们看看该目录的内容:
E:\data\java\corba\sql>dir srvSQl
RESULT~1 JAV 833 19/03/99 10:00 resultatsHolder.java
RESULT~2 JAV 1 883 19/03/99 10:00 resultatsHelper.java
_INTER~1 JAV 2 474 19/03/99 10:00 _interSQLStub.java
INTERS~1 JAV 448 19/03/99 10:00 interSQL.java
INTERS~2 JAV 841 19/03/99 10:00 interSQLHolder.java
INTERS~3 JAV 1 855 19/03/99 10:00 interSQLHelper.java
_INTER~2 JAV 4 535 19/03/99 10:00 _interSQLImplBase.java
需要提醒的是,文件 Helper 和 Holder 是与远程接口方法的输入/输出参数及返回结果相关的类。 目录 srvSQL 包含所有与 .idl 文件中定义的接口 interSQL 相关的 .java 文件。 该目录还包含与在 IDL 接口中创建的 resultats 类型相关的文件。
文件 interSQL.java 是我们服务器接口的 Java 文件。务必检查自动生成的内容是否符合预期。生成的 interSQL.java 文件如下:
/*
* 文件:./SRVSQL/INTERSQL.JAVA
* 来源:SRVSQL.IDL *
日期:1999年3月19日 星期五 09:59:48 * 生
成于: D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 1998年8月18日 16:25:34 */
pa
ckage srvSQL; p
ublic interface interSQL
extends org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntit
y { String connect(String pilote, String urlBase, String id, Stri
n
g mdp) ; String[] executeSQL(String requete, String
s eparateur) ;
String close() ; }
可以看出,该界面与用于 RMI 客户端-服务器的界面相同。因此我们可以继续。现在编译所有这些 .java 文件:
E:\data\java\corba\sql\srvSQL>j:\jdk12\bin\javac *.java
Note: _interSQLImplBase.java uses or overrides a deprecated API. Recompile with
"-deprecation" for details.
1 warning
E:\data\java\corba\sql\srvSQL>dir *.class
_INTER~1 CLA 3 094 19/03/99 10:01 _interSQLImplBase.class
_INTER~2 CLA 1 953 19/03/99 10:01 _interSQLStub.class
INTERS~1 CLA 430 19/03/99 10:01 interSQL.class
INTERS~2 CLA 2 096 19/03/99 10:01 interSQLHelper.class
INTERS~3 CLA 870 19/03/99 10:01 interSQLHolder.class
RESULT~1 CLA 2 047 19/03/99 10:01 resultatsHelper.class
RESULT~2 CLA 881 19/03/99 10:01 resultatsHolder.class
10.3.4. 编写 SQL 服务器
现在我们编写服务器代码 SQL。需要提醒的是,该类必须继承自由编译文件 IDL 生成的抽象类 _nomInterfaceImplBase。 除了这一特点,以及与将服务注册到目录相关的代码段之外,服务器 CORBA 的代码与服务器 RMI 的代码完全相同:
// 导入的包
import java.sql.*;
import java.util.*;
import srvSQL.*;
// 类 SQLServant
public class SQLServant extends _interSQLImplBase{
// 类的全局数据
private Connection DB;
// --------------- 连接
public String connect(String pilote, String url, String id,
String mdp){
// 通过驱动程序连接到数据库 url
// 使用用户名 id 和密码 mdp 进行身份验证
String resultat=null; // 方法结果
try{
// 加载驱动程序
Class.forName(pilote);
// 连接请求
DB=DriverManager.getConnection(url,id,mdp);
// 成功
resultat="200 Connexion réussie";
} catch (Exception e){
// 错误
resultat="500 Echec de la connexion (" + e + ")";
}
// 结束
return resultat;
}
// ------------- executeSQL
public String[] executeSQL(String requete, String separateur){
// 在数据库 DB 上执行查询 SQL
// 并将结果放入字符串数组中
// 执行查询所需的数据
Statement S=null;
ResultSet RS=null;
String[] lignes=null;
Vector resultats=new Vector();
String ligne=null;
try{
// 创建查询容器
S=DB.createStatement();
// 执行查询
if (! S.execute(requete)){
// 更新查询
// 返回已更新的行数
lignes=new String[1];
lignes[0]="100 "+S.getUpdateCount();
return lignes;
}
// 这是一条查询请求
// 获取结果
RS=S.getResultSet();
// Resultset中的字段数
int nbChamps=RS.getMetaData().getColumnCount();
// 对结果集进行处理
while(RS.next()){
// 创建结果行
ligne="101 ";
for (int i=1;i<nbChamps;i++)
ligne+=RS.getString(i)+separateur;
ligne+=RS.getString(nbChamps);
// 将结果添加到结果向量中
resultats.addElement(ligne);
}// while
// 结束结果处理
// 释放资源
RS.close();
S.close();
// 返回结果
int nbLignes=resultats.size();
if (nbLignes==0){
lignes=new String[1];
lignes[0]="501 Pas de résultats";
} else {
lignes=new String[resultats.size()];
for(int i=0;i<lignes.length;i++)
lignes[i]=(String) resultats.elementAt(i);
}//if
return lignes;
} catch (Exception e){
// 错误
lignes=new String[1];
lignes[0]="500 " + e;
return lignes;
}// try-catch
}// executeSQL
// --------------- 关闭
public String close(){
// 关闭与数据库的连接
String resultat=null;
try{
DB.close();
resultat="200 Base fermée";
} catch (Exception e){
resultat="500 Erreur à la fermeture de la base ("+e+")";
}
// 返回结果
return resultat;
}
}// 类 SQLServant
该类位于我们正在编译的文件 SQLServant.java 中:
E:\data\java\corba\sql>dir
SRVSQL IDL 275 19/03/99 9:59 srvSQL.idl
SRVSQL <REP> 19/03/99 9:41 srvSQL
SQLSER~1 JAV 2 941 15/03/99 9:09 SQLServant.java
E:\data\java\corba\sql>j:\jdk12\bin\javac SQLServant.java
E:\data\java\corba\sql>dir *.class
SQLSER~1 CLA 2 568 19/03/99 10:19 SQLServant.class
10.3.5. 编写SQL服务器启动程序
上述类表示启动后的 SQL 服务器。在此之前,它必须先注册到 CORBA 服务目录中。与回显服务类似,我们将使用一个特殊类来完成此操作,并在运行时向其传递三个参数:
主机:存放 CORBA 服务目录的主机
端口:该目录运行的端口
nomService:服务名称 SQL
该类的代码与为 echo 服务执行相同操作的类几乎完全一致。我们已将两类之间唯一的差异行用粗体标出:它不会创建相同的服务器对象。由此可见,服务器启动机制始终保持一致。如果像这里所做的那样,将该机制封装到一个类中,它对开发者而言几乎是透明的。
// 导入的包
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;
//----------- 类 serveurSQL
public class serveurSQL{
// ------- 主程序:启动服务器 SQL
public static void main(String arg[]){
// serveurSQL 机器端口服务
//参数数量是否正确
if(arg.length!=3){
System.err.println("Syntaxe : pg machineAnnuaireCorba portAnnuaireCorba nomService");
System.exit(1);
}
// 获取参数
String machine=arg[0];
String port=arg[1];
String nomService=arg[2];
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
try{
// 我们需要一个 CORBA 对象才能工作
ORB orb=ORB.init(initORB,null);
// 将服务添加到服务目录中
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
NameComponent nc= new NameComponent(nomService,"");
NameComponent path[]={nc};
// 创建服务器并将其与服务关联 srvSQL
SQLServant serveurSQL=new SQLServant();
ncRef.rebind(path,serveurSQL);
orb.connect(serveurSQL);
// 后续操作
System.out.println("Serveur SQL prêt");
// 等待客户端请求
java.lang.Object sync=new java.lang.Object();
synchronized(sync){
sync.wait();
}
} catch(Exception e){
// 发生错误
System.err.println("Erreur " + e);
e.printStackTrace(System.err);
}
}// 主进程
}// srvSQL
我们编译这个新类:
E:\data\java\corba\sql>j:\jdk12\bin\javac serveurSQL.java
E:\data\java\corba\sql>dir *.class
SQLSER~1 CLA 2 568 19/03/99 10:19 SQLServant.class
SERVEU~1 CLA 1 800 19/03/99 10:33 serveurSQL.class
10.3.6. 客户端写入
调用服务器客户端 CORBA 时,使用以下参数:
机器:存放 CORBA 服务目录的机器
端口:该目录运行的端口
nomService:服务名称 SQL
驱动程序:SQL服务器用于管理目标数据库的驱动程序
urlBase:待管理数据库的 URL JDBC
id:客户端标识,若无标识则为 null
mdp:客户端密码,若无密码则为 null
分隔符:SQL 服务器用于分隔查询结果行中各字段的字符
以下是一个可能的参数示例:
其中:
machine:存放 CORBA 服务目录的机器
端口:该目录运行的端口
srvSQL:srvSQL,服务器名称 CORBA
驱动程序:sun.jdbc.odbc.JdbcOdbcDriver, 带ODBC接口的数据库常用驱动程序
urlBase:jdbc:odbc:articles,用于使用在Windows机器的公共数据库列表ODBC中声明的articles数据库
id:null,无标识
mdp:null,无密码
分隔符:, 结果字段将用逗号分隔
使用上述参数启动后,客户端将执行以下步骤:
- 连接到机器 machine 的端口 port,以请求服务 CORBA srvSQL
- 请求连接到商品数据库
- 请求用户通过键盘输入查询 SQL
- 将其发送至服务器 SQL
- 它在屏幕上显示服务器返回的结果
- 再次提示用户在键盘上输入请求 SQL。当请求结束时,程序将停止。
客户端的Java代码如下。注释应足以说明其含义。可以发现:
- 该代码与之前研究过的客户端代码 RMI 完全相同。其区别在于向目录服务发起请求的过程,该过程被封装在方法 getServeurSQL 中。
- 方法 getServeurSQL 与为回显客户端编写的方法完全相同
因此我们可以看到:
- CORBA 客户端与 RMI 客户端的唯一区别仅在于其联系服务器的方式
- 对于所有 CORBA 客户端,这种方式是相同的
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
import java.io.*;
public class clientSQL {
// 类的全局数据
private static String syntaxe =
"syntaxe : cltSQL machine port service pilote urlBase id mdp separateur";
private static BufferedReader in=null;
private static interSQL serveurSQL=null;
public static void main(String arg[]){
// 语法:cltSQL 机器 端口 分隔符 驱动程序 url id 密码
// 主机端口:待联系的服务目录 CORBA 中的主机及端口
// 服务:服务名称
// 驱动程序:用于操作数据库的驱动程序
// urlBase:要使用的数据库的 JDBC URL
// id:用户ID
// mdp:其密码
// 分隔符:查询结果中字段的分隔字符串
// 参数数量验证
if(arg.length!=8)
erreur(syntaxe,1);
// 初始化数据库连接参数
String machine=arg[0];
String port=arg[1];
String service=arg[2];
String pilote=arg[3];
String urlBase=arg[4];
String id, mdp, separateur;
if(arg[5].equals("null")) id=""; else id=arg[5];
if(arg[6].equals("null")) mdp=""; else mdp=arg[6];
if(arg[7].equals("null")) separateur=" "; else separateur=arg[7];
// 目录服务参数 CORBA
String[] initORB={"-ORBInitialHost",arg[0],"-ORBInitialPort",arg[1]};
// 客户端 CORBA - 请求服务器引用 SQL
interSQL serveurSQL=getServeurSQL(machine,port,service);
// 客户端-服务器对话
String requete=null;
String reponse=null;
String[] lignes=null;
String codeErreur=null;
try{
// 打开键盘流
in=new BufferedReader(new InputStreamReader(System.in));
// 跟踪
System.out.println("--> Connexion à la base de données en cours");
// 首次连接数据库请求
reponse=serveurSQL.connect(pilote,urlBase,id,mdp);
// 跟踪
System.out.println("<-- "+reponse);
// 响应分析
codeErreur=reponse.substring(0,3);
if(codeErreur.equals("500"))
erreur("Abandon sur erreur de connexion à la base",3);
// 读取要发送至 SQL 服务器的请求循环
System.out.print("--> Requête : ");
requete=in.readLine().toLowerCase().trim();
while(! requete.equals("fin")){
// 向服务器发送请求并接收响应
lignes=serveurSQL.executeSQL(requete,separateur);
// 跟踪
afficheLignes(lignes);
// 下一个请求
System.out.print("--> Requête : ");
requete=in.readLine().toLowerCase().trim();
}// while
// 跟踪
System.out.println("--> Fermeture de la connexion à la base de données distante");
// 关闭连接
reponse=serveurSQL.close();
// 后续
System.out.println("<-- " + reponse);
// 结束
System.exit(0);
// 错误处理
} catch (Exception e){
erreur("Abandon sur erreur : " + e,2);
}// try
}// main
// ----------- AfficheLignes
private static void afficheLignes(String[] lignes){
for (int i=0;i<lignes.length;i++)
System.out.println("<-- " + lignes[i]);
}// afficheLignes
// ------------ 错误
private static void erreur(String msg, int exitCode){
// 显示错误信息
System.err.println(msg);
// 可能释放资源
try{
in.close();
serveurSQL.close();
} catch(Exception e){}
// 退出
System.exit(exitCode);
}// 错误
// ---------------------- getServeurSQL
private static interSQL getServeurSQL(String machine, String port, String service){
// 请求服务器引用 SQL
// 机器:服务目录中的机器 CORBA
// 端口:服务目录的端口 CORBA
// 服务:要请求的服务名称 CORBA
// 跟踪
System.out.println("--> Connexion au serveur CORBA en cours...");
// 服务器引用 SQL
interSQL serveurSQL=null;
// 目录服务 CORBA 的参数
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
try{
// 请求一个 CORBA 对象以进行操作——为此需要通过端口
// 连接 CORBA 服务目录的监听端口
ORB orb=ORB.init(initORB,null);
// 使用目录服务定位服务器 SQL
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
// 所查找的服务名为 srvSQL - 向其发起请求
NameComponent nc= new NameComponent(service,"");
NameComponent path[]={nc};
serveurSQL=interSQLHelper.narrow(ncRef.resolve(path));
} catch (Exception e){
System.err.println("Erreur lors de la localisation du serveur SQL ("
+ e + ")");
System.exit(10);
}// try-catch
// 返回服务器引用
return serveurSQL;
}// getServeurSQL
}// 类
现在编译客户端类:
E:\data\java\corba\sql>j:\jdk12\bin\javac clientSQL.java
E:\data\java\corba\sql>dir *.class
SQLSER~1 CLA 2 568 19/03/99 10:19 SQLServant.class
SERVEU~1 CLA 1 800 19/03/99 10:33 serveurSQL.class
CLIENT~1 CLA 3 774 19/03/99 10:45 clientSQL.class
我们已准备好进行测试。
10.3.7. 测试
10.3.7.1. Pré-requis
假设名为“Articles”的数据库 ACCESS 在服务器 SQL 的 Windows 机器上公开可用:

该数据库的结构如下:
名称 | 类型 |
code | 4位商品编码 |
nom | 名称(字符串) |
prix | 其价格(实际) |
stock_actu | 当前库存(整数) |
stock_mini | 最低库存(整数),低于该数值需补货 |
10.3.7.2. 目录服务启动
E:\data\java\corba\sql>start j:\jdk12\bin\tnameserv -ORBInitialPort 1000
Le service d’annuaire est lancé sur le port 1000. Il affiche dans une fenêtre DOS quelque chose du genre :
Initial Naming Context:
IOR:000000000000002849444c3a6f6d672e6f72672f436f734e616d696e672f4e616d696e67436f
6e746578743a312e3000000000010000000000000030000100000000000a69737469612d30303900
052800000018afabcafe000000027693d3fd000000080000000000000000
TransientNameServer: setting port for initial object references to: 1000
10.3.7.3. 启动服务器 SQL
启动 SQL 服务器:
它在 DOS 窗口中显示:
10.3.7.4. 在与服务器位于同一台机器上启动客户端
以下是在与服务器位于同一台机器上的客户端上获得的结果:
E:\data\java\corba\sql>j:\jdk12\bin\java clientSQL localhost 1000 srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Connexion au serveur CORBA en cours...
--> Connexion à la base de données en cours
<-- 200 Connexion réussie
--> 查询:select 名称, stock_actu, stock_mini from 文章
<-- 101 vélo,31,8
<-- 101 arc,9,8
<-- 101 canoé,7,7
<-- 101 fusil,9,8
<-- 101 skis nautiques,13,8
<-- 101 essai3,13,9
<-- 101 cachalot,6,6
<-- 101 léopard,7,7
<-- 101 panthère,7,7
--> 查询:delete from articles where stock_mini<7
<-- 100 1
--> 查询:select 姓名, stock_actu, stock_mini from 文章
<-- 101 vélo,31,8
<-- 101 arc,9,8
<-- 101 canoé,7,7
<-- 101 fusil,9,8
<-- 101 skis nautiques,13,8
<-- 101 essai3,13,9
<-- 101 léopard,7,7
<-- 101 panthère,7,7
--> Requête : fin
--> Fermeture de la connexion à la base de données distante
<-- 200 Base fermée
10.3.7.5. 在非服务器机器上启动客户端
以下是在与服务器不同的机器上运行客户端时获得的结果:
E:\data\java\corba\sql>j:\jdk12\bin\java clientSQL tahe.istia.univ-angers.fr 1000 srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Connexion au serveur CORBA en cours...
--> Connexion à la base de données en cours
<-- 200 Connexion réussie
--> 查询:select * from articles
<-- 101 a300,v_lo,1202,31,8
<-- 101 d600,arc,5000,9,8
<-- 101 d800,canoé,1502,7,7
<-- 101 x123,fusil,3000,9,8
<-- 101 s345,skis nautiques,1800,13,8
<-- 101 f450,essai3,3,13,9
<-- 101 z400,léopard,500000,7,7
<-- 101 g457,panthère,800000,7,7
--> Requête : fin
--> Fermeture de la connexion à la base de données distante
<-- 200 Base fermée
10.4. IDL 与 JAVA 的对应关系
此处列出简单类型 IDL 与 JAVA 之间的对应关系:
类型 IDL | Java类型 |
boolean | boolean |
字符 | 字符 |
wchar | char |
字节 | 字节 |
字符串 | java.lang.String |
wstring | java.lang.String |
short | short |
无符号短整型 | short |
long | int |
无符号长整型 | int |
长长 | long |
无符号长长整型 | long |
float | float |
double | double |
请注意,要在接口 IDL 中定义一个类型为 T 的数组,需使用以下语句:
随后使用 nomType 来引用该数组的类型。因此,在服务器接口 SQL 中,我们使用了以下声明:
,使得 resultats 在 Java 中指向数组 String[]。