Skip to content

10. 分布式应用程序构建 CORBA

10.1. 简介

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

Image

我们将看到,使用 CORBA 构建分布式应用程序的方法与 Java RMI 所采用的方法非常相似:其概念具有相似性。 CORBA 的优势在于能够与其他语言编写的应用程序实现互操作。

10.2. CORBA 应用程序的开发流程

10.2.1. 简介

要开发一个 CORBA 客户端-服务器应用程序,我们将遵循以下步骤:

  1. 使用 IDL(接口定义语言)编写服务器接口
  2. 生成服务器的“骨架”和“存根”类
  3. 编写服务器
  4. 编写客户端
  5. 编译所有类
  6. 启动 CORBA 服务目录
  7. 启动服务器
  8. 启动客户端

我们首先以 RMI 环境中已使用的回显服务器为例。读者由此可以了解这两种方法之间的区别。

该应用程序已在 jdk1.2 环境中经过测试。

10.2.2. 编写服务器接口

与 Java RMI 类似,对于客户端而言,服务器由其接口定义。如果客户端不需要实现服务器的类,那么它就需要该接口的类。 Java RMI 采用 Java 接口生成服务器的“骨架”和“存根”类,而 Java CORBA 架构则要求使用 Java 以外的语言来描述该接口。 该接口将生成多个类,其中部分由客户端使用,部分由服务器使用。

回显接口的描述如下:

module echo{
    interface iSrvEcho{
        string echo(in string msg);
    };
};

该接口描述将存储在 echo.idl 文件中。它使用 IDL(接口定义语言)编写,该语言属于 OMG 框架。 为了能够使用,该描述必须由一个程序进行解析,该程序将使用开发CORBA应用程序所用的语言生成源文件。 在此,我们将使用程序 idltojava.exe,该程序将基于上述接口生成应用程序所需的 .java 源文件。 程序 idltojava.exe 并未随 JDK 一起提供。可从 Sun 网站 http://java.sun.com 获取。

让我们分析一下前面 idl 接口中的几行代码:

module echo

相当于 Java 中的 package echo。编译该接口将生成包含 Java 类的目录 echo c.a.d。

interface iSrvEcho

等同于 Java 的接口 iSrvEcho。将生成一个 Java 接口。

string echo(in string msg)

等同于 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 的内容,会发现更复杂的内容。以下是文档中关于这些不同文件作用的说明:

iSrvEcho.java

服务器接口

_iSrvEchoImplbase.java

实现了上述接口。这是一个抽象类,作为服务器的“骨架”,为服务器提供分布式应用程序所需的CORBA功能。

_iSrvEchoStub.java

这是客户端将使用的服务器“存根”。它为客户端提供 CORBA 功能,以便连接到服务器。

iSrvEchoHelper.java

提供管理 CORBA 对象引用所需的方法

iSrvEchoHolder.java

提供了用于管理接口方法的输入/输出参数所需的方法。

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(对象请求代理)的对象,该对象可通过以下类方法获取:

ORB ORB.init(String [] args, Properties prop)

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();
  • 将其注册到目录中
    ncRef.rebind(path,serveurEcho);
    orb.connect(serveurEcho);

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 客户端通过以下语句连接到服务器:

         // 与回显服务器建立连接 iSrvEcho serveurEcho=getSe
        rveurEcho(machine,port,nomService);

此操作完成后,客户端将获得回显服务器的引用。随后,客户端 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};
  • 向目录服务请求回显服务的引用(这与服务器处理方式不同)
serveurEcho=iSrvEchoHelper.narrow(ncRef.resolve(path));

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 机器上,我们按以下方式启动目录服务:

E:\data\java\corba\ECHO>start j:\jdk12\bin\tnameserv -ORBInitialPort 1000

这将使目录服务在该机器的 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

服务器显示:

    Serveur d’écho prêt

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。

Image

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 以访问该数据库。服务器返回一个字符串,表示连接结果:

    200 - Connexion réussie
    500 - Echec de la connexion

executeSQL:客户端请求在已连接的数据库上执行查询 SQL。它指定了返回结果中字段之间的分隔符。服务器返回一个字符串数组:

    100 n

用于数据库更新请求,其中 n 表示被更新的行数

    500 msg d’erreur

若查询引发错误

    501 Pas de résultats

若查询未返回任何结果

    101 ligne1
    101 ligne2
    101 ...

如果查询产生了结果。服务器返回的这些行即为查询的结果行。

Close:客户端关闭与远程数据库的连接。服务器返回一个字符串,指示此次关闭的结果:

    200 Base fermée
    500 Erreur lors de la fermeture de la base (msg d’erreur)

服务器的 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 sequence<string> resultats;

关键字 typedef 在 C/C++ 程序员中广为人知:它用于定义一种新类型。 在此,类型 resultats 被定义为与类型 sequence<string>(即 c.a.d)等价,即一个动态(无固定尺寸)的字符串数组。

  • 在需要的地方使用新类型
resultats executeSQL(in string requete, in string separateur);

因此,方法 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

需要提醒的是,文件 HelperHolder 是与远程接口方法的输入/输出参数及返回结果相关的类。 目录 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 时,使用以下参数:

    machine port nomServiceAnnuaire pilote urlBase id mdp separateur

机器:存放 CORBA 服务目录的机器

端口:该目录运行的端口

nomService:服务名称 SQL

驱动程序:SQL服务器用于管理目标数据库的驱动程序

urlBase:待管理数据库的 URL JDBC

id:客户端标识,若无标识则为 null

mdp:客户端密码,若无密码则为 null

分隔符:SQL 服务器用于分隔查询结果行中各字段的字符

以下是一个可能的参数示例:

    localhost 1000 srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,

其中:

machine:存放 CORBA 服务目录的机器

端口:该目录运行的端口

srvSQL:srvSQL,服务器名称 CORBA

驱动程序sun.jdbc.odbc.JdbcOdbcDriver, 带ODBC接口的数据库常用驱动程序

urlBasejdbc:odbc:articles,用于使用在Windows机器的公共数据库列表ODBC中声明的articles数据库

id:null,无标识

mdp:null,无密码

分隔符, 结果字段将用逗号分隔

使用上述参数启动后,客户端将执行以下步骤:

  • 连接到机器 machine 的端口 port,以请求服务 CORBA srvSQL
  • 请求连接到商品数据库
connect(‘’sun.jdbc.odbc.JdbcOdbcDriver’’, ‘‘jdbc:odbc:articles’’, ’’’’, ’’’’)
  • 请求用户通过键盘输入查询 SQL
  • 将其发送至服务器 SQL
executeSQL(requete, ’’,’’);
  • 它在屏幕上显示服务器返回的结果
  • 再次提示用户在键盘上输入请求 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 机器上公开可用:

Image

该数据库的结构如下:

名称
类型
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 服务器:

E:\data\java\corba\sql>start j:\jdk12\bin\java serveurSQL localhost 1000 srvSQL

它在 DOS 窗口中显示:

    Serveur SQL prêt

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 的数组,需使用以下语句:

    typedef   sequence<T> nomType;

随后使用 nomType 来引用该数组的类型。因此,在服务器接口 SQL 中,我们使用了以下声明:

    typedef sequence<string> resultats;

,使得 resultats 在 Java 中指向数组 String[]