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 环境中已经用过的 echo 服务器作为第一个示例。这将使读者能够看到这两种方法之间的区别。
该应用程序已在 JDK 1.2 上经过测试。
10.2.2. 编写服务器接口
与 Java RMI 类似,服务器是通过其与客户端相关的接口来定义的。虽然客户端并不需要实现服务器的具体类,但必须使用其接口定义的类。 Java RMI 使用 Java 接口来生成服务器的“骨架”和“存根”类,而 Java CORBA 架构则要求使用 Java 以外的语言来描述该接口。该接口将生成多个类,其中一些由客户端使用,另一些则由服务器使用。
echo 接口的描述如下:
该接口描述将存储在 echo.idl 文件中。它采用 OMG 的 IDL(接口定义语言)编写。 要使其可用,必须由一个程序对其进行解析,该程序将生成用于开发 CORBA 应用程序的语言的源文件。在此,我们将使用 idltojava.exe 程序,它将根据上述接口为应用程序生成必要的 .java 源文件。idltojava.exe 程序未包含在 JDK 中。可从 Sun 网站 http://java.sun.com 下载。
让我们分析前文 IDL 接口中的几行代码:
等同于 Java **包 echo**。编译该接口将生成 Java 包 echo,即一个包含 Java 类的目录。
等同于 Java **接口 iSrvEcho**。它将生成一个 Java 接口。
等同于 Java 语句 **String echo(String msg)**。IDL 语言中的类型与 Java 语言中的类型并不完全对应。本章后文将对此进行说明。 在 IDL 语言中,函数的参数可以是输入 (**in**)、输出 (**out**) 或输入输出 (**inout**) 参数。在此*,echo* 方法接收一个名为 *msg* 的输入参数(字符串类型),并返回一个字符串作为结果。
上述接口即为我们的 echo 服务器的接口。请注意,远程接口描述了客户端可访问的服务器对象的方法。在此,客户端仅可访问 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 文件:
/*
* File: ./ECHO/ISRVECHO.JAVA
* From: ECHO.IDL
* Date: Mon Mar 15 13:56:08 1999
* By: D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 Aug 18 1998 16:25:34
*/
package echo;
public interface iSrvEcho
extends org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity {
String echo(String msg)
;
}
我们可以看到,这几乎是对 IDL 接口的逐字翻译。如果你好奇到去查看其他 .java 文件的内容,你会发现更复杂的东西。以下是文档中关于这些不同文件作用的说明:
服务器接口
实现了前面的 iSrvEcho 接口。这是一个抽象类,是服务器的“骨架”,为服务器提供了分布式应用程序所需的 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 接口。
// imported packages
import echo.*;
// class implementing remote echo
public class srvEcho extends _iSrvEchoImplBase{
// method performing the echo
public String echo(String msg){
return "[" + msg + "]";
}// fine echo
}// end of class
代码不言自明。该类保存在 iSrvEcho 接口包的父目录下的 srvEcho.java 文件中。
您可以编译它以进行验证:
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 应用程序而有所不同。以下是注册在 serverEcho.java 文件中的 echo CORBA 服务器的注册过程:
// imported packages
import echo.*
; import org.omg.CosNaming.
*; import org.omg.CosNaming.NamingContextPackage
.*; import org.omg.CORB
A.*; //----------- class serveu
rEcho public class serveu
rEcho{ // ------- main: launches the ech
o server // syntax pg machineAnnuaire portAnnuaire n
omService // machine: machine supporting CORBA
directory // port: directory port
CORBA // nomService: name of the service t
o be registered public static void
main(String arg[]){ // are th
e arguments there?
if(arg.length!=3){ System.err.println("Syntaxe : pg machineAnnuaire por
tAnnuaire nomSe
r
vice"); System.exit(1); }
// retrieve the argu
ments String machi
ne=arg[0]; String port=arg[1]; String nomService=arg[2];
try{
// you need a CORBA object to work
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
ORB orb=ORB.init(initORB,null);
// put the service in the service directory
// it will be called srvEcho
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
NameComponent nc= new NameComponent(nomService,"");
NameComponent path[]={nc};
// create the server and associate it with the srvEcho service
srvEcho serveurEcho=new srvEcho();
ncRef.rebind(path,serveurEcho);
orb.connect(serveurEcho);
// follow-up
System.out.println("Serveur d'écho prêt");
// waiting for customer requests
java.lang.Object sync=new java.lang.Object();
synchronized(sync){
sync.wait();
}
} catch(Exception e){
// there has been an error
System.err.println("Erreur " + e);
e.printStackTrace(System.err);
}
}// hand
}// 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 服务目录中注册服务器
在目录中注册服务器需要执行以下操作:
// put the service in the service directory //
it will be called srvEcho
org.omg.CORBA.Object o
b jRef= orb.resolve_initial_references("
N ameService"); NamingContext ncRef=NamingContextHe
l per.narrow(objRef); NameComponent nc= new Nam
e Component(nomService,"");
NameComponent path[]={nc}; // create the server a
nd associate it with the srvEcho s
e rvice srvEcho serveurEcho=new
srvEcho(); ncRef.rebind(path,serveurEcho); orb.connect(serveurEcho);
代码的第一部分涉及服务名称的准备。该名称在代码中由变量 path 表示。一个服务名称由几个组成部分构成:
- 初始组件 objRef,这是一个通用对象,必须强制转换为 NamingContext 类型,此处为 ncRef。
- 服务名称,此处为 `serviceName`,该名称作为参数传递给了服务器
这些名称组件(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. 代码
我们正在编写一个客户端来测试我们的回显服务。我们将向客户端传递与向服务器传递时相同的三个参数:
Machine:存放 CORBA 服务目录的机器
端口:该目录运行的端口
serviceName:回显服务的名称
客户端连接到回显服务,然后提示用户在键盘上输入消息。这些消息会被发送至回显服务器,服务器再将它们发回。屏幕上会显示此对话的日志。
回显服务的 CORBA 客户端与之前编写的 RMI 客户端非常相似。同样,客户端必须连接到目录服务,以获取其希望连接的服务器对象的引用。这两个客户端之间的区别仅此而已。以下是 CORBA 回显客户端的代码:
10.2.6.2. 将客户端连接到服务器
上面的 CORBA 客户端使用以下语句连接到服务器:
// on fait la liaison avec le serveur d'écho
iSrvEcho serveurEcho=getServeurEcho(machine,port,nomService);
此操作完成后,客户端持有对回显服务器的引用。从这一刻起,CORBA 客户端与 RMI 客户端并无二致。建立与服务器连接的私有方法如下:
// imported packages
import java.io.*;
import echo.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
// ---------- class cltEcho
public class cltEcho {
public static void main(String arg[]){
// syntax : cltEcho machineAnnuaire portAnnuaire nameservice
// machine: machine where the CORBA service directory operates
// port: port where the service directory operates
// nomService: echo service name
// argument verification
if(arg.length!=3){
System.err.println("Syntaxe : pg machineAnnuaire portAnnuaire nomservice");
System.exit(1);
}
// parameters are retrieved
String machine=arg[0];
String port=arg[1];
String nomService=arg[2];
// link to echo server
iSrvEcho serveurEcho=getServeurEcho(machine,port,nomService);
// client-server dialogue
BufferedReader in=null;
String msg=null;
String reponse=null;
iSrvEcho serveur=null;
try{
// open keyboard flow
in=new BufferedReader(new InputStreamReader(System.in));
// loop for reading msg to be sent to echo server
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
while(! msg.equals("fin")){
// send msg to server and receive response
reponse=serveurEcho.echo(msg);
// follow-up
System.out.println("Réponse serveur : " + reponse);
// next msg
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
}// while
// it's over
System.exit(0);
// error management
} catch (Exception e){
System.err.println("Erreur : " + e);
System.exit(2);
}// try
}// hand
// ---------------------- getServeurEcho
private static iSrvEcho getServeurEcho(String machine, String port,
String nomService){
// requests an echo server reference
// follow-up
System.out.println("--> Connexion au serveur CORBA en cours...");
// echo server reference
iSrvEcho serveurEcho=null;
try{
// we request a CORBA object to work with
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
ORB orb=ORB.init(initORB,null);
// use the directory service to locate the echo server
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
// the service required is called srvEcho - it is requested
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 the reference to the server
return serveurEcho;
}// getServeurEcho
}// class
我们看到与服务器端相同的代码序列:
- 我们创建一个 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};
- 我们向目录服务请求对 echo 服务的引用(这与服务器端的实现不同)
10.2.6.3. 编译
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 客户端可以执行三项操作:
- 连接到其选定的数据库
- 发送 SQL 查询
- 关闭连接
服务器执行客户端的 SQL 查询并将结果发回给客户端。这是其主要功能,因此我们称其为 SQL 服务器。我们应用之前在 echo 服务器中介绍过的各个步骤。
10.3.2. 编写服务器的 IDL 接口
作为提醒,以下是我们用于服务器的 RMI 接口:
import java.rmi.*;
// remote interface p
ublic interface interSQL extends Remote{
public String connect(String pilote, String url, String id, String mdp
) throws java.rmi.RemoteExcept
ion; public String[] executeSQL(String requete, String separa
teur) throws java.rmi.RemoteE
xception; public Str
ing close() throws java.rmi.Re
moteException; }
各种方法的作用如下:
Connect:客户端连接到远程数据库,提供驱动程序、JDBC URL 以及访问数据库所需的用户名和密码。服务器返回一个字符串,表示连接结果:
executeSQL:客户端请求在其连接的数据库上执行 SQL 查询。它指定了返回结果中字段之间的分隔符。服务器返回一个字符串数组:
(用于数据库更新查询,其中 n 表示更新行的数量)
如果查询引发了错误
如果查询未返回任何结果
如果查询返回了结果。服务器返回的行即为该查询的结果行。
关闭:客户端关闭与远程数据库的连接。服务器返回一个字符串,表示此次关闭的结果:
服务器的 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();
};// interface
};// module
与我们之前看到的 Echo 服务器的 IDL 接口相比,唯一的新特性是使用了 sequence 关键字。该关键字允许您定义一维数组。定义过程分为两步:
- 定义一个类型来标识该数组,此处为 results:
typedef 关键字对 C/C++ 程序员来说并不陌生:它允许你定义一个新类型。在此,类型 resultats 被定义为与类型 sequence<string> 等价,即一个字符串的动态(无固定大小)数组。
- 在需要的地方使用该新类型
因此,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 接口中创建的结果类型相关的文件。
interSQL.java 文件是本服务器接口的 Java 文件。务必验证自动生成的内容是否符合预期。生成的 interSQL.java 文件如下:
/*
* File: ./SRVSQL/INTERSQL.JAVA
* From: SRVSQL.IDL
* Date: Fri Mar 19 09:59:48 1999
* By: D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 Aug 18 1998 16:25:34
*/
package srvSQL;
public interface interSQL
extends org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity {
String connect(String pilote, String urlBase, String id, String mdp)
;
String[] executeSQL(String requete, String separateur)
;
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 Server
接下来我们将编写 SQL 服务器代码。请注意,该类必须继承自编译 IDL 文件生成的抽象类 _nomInterfaceImplBase。除了这一特殊要求,并且不包括与在目录中注册服务相关的代码段外,CORBA 服务器代码与 RMI 服务器代码完全相同:
// imported packages
import java.sql.*;
import java.util.*;
import srvSQL.*;
// class SQLServant
public class SQLServant extends _interSQLImplBase{
// global class data
private Connection DB;
// --------------- connect
public String connect(String pilote, String url, String id,
String mdp){
// connection to url database via driver
// identification with identity id and password mdp
String resultat=null; // result of the method
try{
// loading the driver
Class.forName(pilote);
// connection request
DB=DriverManager.getConnection(url,id,mdp);
// ok
resultat="200 Connexion réussie";
} catch (Exception e){
// error
resultat="500 Echec de la connexion (" + e + ")";
}
// end
return resultat;
}
// ------------- executeSQL
public String[] executeSQL(String requete, String separateur){
// executes a SQL query on the DB database
// and puts the results in an array of strings
// data required to execute the request
Statement S=null;
ResultSet RS=null;
String[] lignes=null;
Vector resultats=new Vector();
String ligne=null;
try{
// create query container
S=DB.createStatement();
// request execution
if (! S.execute(requete)){
// update request
// returns the number of lines updated
lignes=new String[1];
lignes[0]="100 "+S.getUpdateCount();
return lignes;
}
// it was a query request
// retrieve results
RS=S.getResultSet();
// number of Resultset fields
int nbChamps=RS.getMetaData().getColumnCount();
// we exploit them
while(RS.next()){
// create results line
ligne="101 ";
for (int i=1;i<nbChamps;i++)
ligne+=RS.getString(i)+separateur;
ligne+=RS.getString(nbChamps);
// add to results vector
resultats.addElement(ligne);
}// while
// end of results processing
// free up resources
RS.close();
S.close();
// we return the results
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){
// error
lignes=new String[1];
lignes[0]="500 " + e;
return lignes;
}// try-catch
}// executeSQL
// --------------- close
public String close(){
// closes database connection
String resultat=null;
try{
DB.close();
resultat="200 Base fermée";
} catch (Exception e){
resultat="500 Erreur à la fermeture de la base ("+e+")";
}
// return result
return resultat;
}
}// class 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 Server 启动程序
前面的类代表已启动的 SQL Server。在此之前,必须将其注册到 CORBA 服务目录中。与 echo 服务一样,我们将使用一个特殊类来完成此操作,并在运行时向该类传递三个参数:
Machine:存放 CORBA 服务目录的机器
Port:该目录运行的端口
serviceName:SQL服务的名称
该类的代码与执行回显服务相同任务的类几乎完全一致。我们已将两类之间不同的那行代码加粗标出:它并未创建相同的服务器对象。因此,我们可以看到,服务器启动机制保持不变。如果我们将该机制封装到一个类中(如本例所示),它对开发人员而言便几乎是透明的。
// imported packages
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;
//----------- class serveurSQL
public class serveurSQL{
// ------- main: launches the SQL server
public static void main(String arg[]){
// serveurSQL machine port service
//do we have the right number of arguments
if(arg.length!=3){
System.err.println("Syntaxe : pg machineAnnuaireCorba portAnnuaireCorba nomService");
System.exit(1);
}
// retrieve the arguments
String machine=arg[0];
String port=arg[1];
String nomService=arg[2];
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
try{
// you need a CORBA object to work
ORB orb=ORB.init(initORB,null);
// put the service in the service directory
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
NameComponent nc= new NameComponent(nomService,"");
NameComponent path[]={nc};
// create the server and associate it with the srvSQL service
SQLServant serveurSQL=new SQLServant();
ncRef.rebind(path,serveurSQL);
orb.connect(serveurSQL);
// follow-up
System.out.println("Serveur SQL prêt");
// waiting for customer requests
java.lang.Object sync=new java.lang.Object();
synchronized(sync){
sync.wait();
}
} catch(Exception e){
// there has been an error
System.err.println("Erreur " + e);
e.printStackTrace(System.err);
}
}// hand
}// 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 服务目录的机器
端口:该目录运行的端口
服务名称:SQL 服务的名称
驱动程序:SQL 服务器必须用于管理目标数据库的驱动程序
baseUrl:待管理数据库的 JDBC URL
id:客户端 ID,若无 ID 则为 null
password: 客户端密码,若无密码则为 null
separator:SQL 服务器必须用于分隔查询结果行中字段的字符
以下是一个可能的参数示例:
其中:
机器:存放 CORBA 服务目录的机器
端口:该目录运行的端口
srvSQL:srvSQL,即 SQL 服务器的 CORBA 名称
驱动程序:sun.jdbc.odbc.JdbcOdbcDriver, 适用于具有 ODBC 接口的数据库的标准驱动程序
urlBase:jdbc:odbc:articles,用于使用 Windows 机器上公共 ODBC 数据库列表中声明的 articles 数据库
id: null,无 ID
password: null,无密码
分隔符:, 结果字段将用逗号分隔
使用上述参数启动后,客户端将执行以下步骤:
- 它连接到机器 machine 的端口 port,以请求 CORBA 服务 srvSQL
- 请求连接到 articles 数据库
- 它要求用户在键盘上输入一个 SQL 查询
- 将其发送至 SQL 服务器
- 它在屏幕上显示服务器返回的结果
- 它再次提示用户在键盘上输入SQL查询。当查询结束时,程序将停止。
以下是 Java 客户端代码。注释应足以帮助理解。请注意:
- 该代码与之前学习的 RMI 客户端代码完全相同。其区别在于从目录请求服务的过程,该过程被封装在 getServeurSQL 方法中。
- getServeurSQL 方法与为 echo 客户端编写的方法完全相同
因此我们可以看出:
- CORBA 客户端与 RMI 客户端的区别仅在于其联系服务器的方式
- 该方法对所有 CORBA 客户端而言都是相同的
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
import java.io.*;
public class clientSQL {
// global class data
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[]){
// syntax : cltSQL machine port separator driver url id mdp
// machine port: machine & service directory port CORBA to contact
// department: department name
// driver: driver to be used for the database to be processed
// urlBase: jdbc url of the database to be used
// id: user identity
// mdp: password
// separator: string separating fields in query results
// check number of arguments
if(arg.length!=8)
erreur(syntaxe,1);
// init database connection parameters
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];
// directory service parameters CORBA
String[] initORB={"-ORBInitialHost",arg[0],"-ORBInitialPort",arg[1]};
// client CORBA - request a server reference SQL
interSQL serveurSQL=getServeurSQL(machine,port,service);
// client-server dialogue
String requete=null;
String reponse=null;
String[] lignes=null;
String codeErreur=null;
try{
// open keyboard flow
in=new BufferedReader(new InputStreamReader(System.in));
// follow-up
System.out.println("--> Connexion à la base de données en cours");
// initial database connection request
reponse=serveurSQL.connect(pilote,urlBase,id,mdp);
// follow-up
System.out.println("<-- "+reponse);
// response analysis
codeErreur=reponse.substring(0,3);
if(codeErreur.equals("500"))
erreur("Abandon sur erreur de connexion à la base",3);
// loop for reading requests to be sent to the server SQL
System.out.print("--> Requête : ");
requete=in.readLine().toLowerCase().trim();
while(! requete.equals("fin")){
// send request to server and receive response
lignes=serveurSQL.executeSQL(requete,separateur);
// follow-up
afficheLignes(lignes);
// following request
System.out.print("--> Requête : ");
requete=in.readLine().toLowerCase().trim();
}// while
// follow-up
System.out.println("--> Fermeture de la connexion à la base de données distante");
// close the connection
reponse=serveurSQL.close();
// follow-up
System.out.println("<-- " + reponse);
// end
System.exit(0);
// error management
} catch (Exception e){
erreur("Abandon sur erreur : " + e,2);
}// try
}// hand
// ----------- AfficheLignes
private static void afficheLignes(String[] lignes){
for (int i=0;i<lignes.length;i++)
System.out.println("<-- " + lignes[i]);
}// afficheLignes
// ------------ error
private static void erreur(String msg, int exitCode){
// error msg display
System.err.println(msg);
// possible release of resources
try{
in.close();
serveurSQL.close();
} catch(Exception e){}
// we leave
System.exit(exitCode);
}// error
// ---------------------- getServeurSQL
private static interSQL getServeurSQL(String machine, String port, String service){
// requests a server reference SQL
// machine: service directory machine CORBA
// port: service directory port CORBA
// service: service name CORBA to request
// follow-up
System.out.println("--> Connexion au serveur CORBA en cours...");
// server reference SQL
interSQL serveurSQL=null;
// directory service parameters CORBA
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
try{
// a CORBA object is required to work - to do this, we pass the port
// service directory listening CORBA
ORB orb=ORB.init(initORB,null);
// use the directory service to locate the SQL server
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
// the service required is called srvSQL - it is requested
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 the reference to the server
return serveurSQL;
}// getServeurSQL
}// class
现在我们来编译客户端类:
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. 先决条件
我们假设在 SQL Server 的 Windows 机器上,有一个名为 Articles 的 ACCESS 数据库可供公共访问:

该数据库具有以下结构:
名称 | 类型 |
代码 | 4位项目代码 |
名称 | 其名称(字符串) |
price | 其价格(实际) |
当前库存 | 当前库存(整数) |
min_stock | 最低库存(整数),低于该数值时必须补货 |
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 ,
--> Connection to server CORBA in progress...
--> Current database connection
<-- 200 Successful connection
--> Requête : select nom, stock_actu, stock_mini from articles
<-- 101 bicycle,31,8
<-- 101 arc,9,8
<-- 101 canoeing,7,7
<-- 101 rifle,9,8
<-- 101 water skis,13.8
<-- 101 test3,13,9
<-- 101 sperm whale,6,6
<-- 101 leopard,7,7
<-- 101 panther,7,7
--> Requête : delete from articles where stock_mini<7
<-- 100 1
--> Requête : select nom, stock_actu, stock_mini from articles
<-- 101 bicycle,31,8
<-- 101 arc,9,8
<-- 101 canoeing,7,7
<-- 101 rifle,9,8
<-- 101 water skis,13.8
<-- 101 test3,13,9
<-- 101 leopard,7,7
<-- 101 panther,7,7
--> Query: end
--> Closing the remote database connection
<-- 200 Closed base
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 ,
--> Connection to server CORBA in progress...
--> Current database connection
<-- 200 Successful connection
--> Requête : select * from articles
<-- 101 a300,v_lo,1202,31,8
<-- 101 d600,arc,5000,9,8
<-- 101 d800,canoe,1502,7,7
<-- 101 x123,rifle,3000,9,8
<-- 101 s345,water skis,1800,13,8
<-- 101 f450,test3,3,13,9
<-- 101 z400,leopard,500000,7,7
<-- 101 g457,panther,800000,7,7
--> Query: end
--> Closing the remote database connection
<-- 200 Closed base
10.4. IDL - JAVA 对应关系
此处提供简单 IDL 与 Java 类型之间的映射:
IDL 类型 | Java 类型 |
boolean | 布尔型 |
字符 | char |
wchar | 字符 |
字节 | byte |
字符串 | java.lang.String |
wstring | java.lang.String |
short | short |
无符号短整型 | short |
long | int |
无符号长整型 | int |
long long | long |
无符号长长整型 | long |
float | float |
双精度 | double |
请注意,要在 IDL 接口中定义一个类型为 T 的元素数组,我们需要使用以下语句:
随后我们使用 TypeName 来指代该数组类型。因此,在 SQL Server 接口中,我们使用了以下声明:
这样,results 在 Java 中就指代一个 String[] 数组。