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 环境中已经用过的 echo 服务器作为第一个示例。这将使读者能够看到这两种方法之间的区别。

该应用程序已在 JDK 1.2 上经过测试。

10.2.2. 编写服务器接口

与 Java RMI 类似,服务器是通过其与客户端相关的接口来定义的。虽然客户端并不需要实现服务器的具体类,但必须使用其接口定义的类。 Java RMI 使用 Java 接口来生成服务器的“骨架”和“存根”类,而 Java CORBA 架构则要求使用 Java 以外的语言来描述该接口。该接口将生成多个类,其中一些由客户端使用,另一些则由服务器使用。

echo 接口的描述如下:

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

该接口描述将存储在 echo.idl 文件中。它采用 OMG 的 IDL(接口定义语言)编写。 要使其可用,必须由一个程序对其进行解析,该程序将生成用于开发 CORBA 应用程序的语言的源文件。在此,我们将使用 idltojava.exe 程序,它将根据上述接口为应用程序生成必要的 .java 源文件。idltojava.exe 程序未包含在 JDK 中。可从 Sun 网站 http://java.sun.com 下载。

让我们分析前文 IDL 接口中的几行代码:

module echo
等同于 Java **包 echo**。编译该接口将生成 Java 包 echo,即一个包含 Java 类的目录。
interface iSrvEcho
等同于 Java **接口 iSrvEcho**。它将生成一个 Java 接口。
string echo(in string msg)
等同于 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.java

服务器接口

_iSrvEchoImplbase.java

实现了前面的 iSrvEcho 接口。这是一个抽象类,是服务器的“骨架”,为服务器提供了分布式应用程序所需的 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 接口。

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

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 服务目录中注册服务器

在目录中注册服务器需要执行以下操作:

     // 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();
  • 并在目录中注册它

    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. 代码

我们正在编写一个客户端来测试我们的回显服务。我们将向客户端传递与向服务器传递时相同的三个参数:

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 服务的引用(这与服务器端的实现不同)

serveurEcho=iSrvEchoHelper.narrow(ncRef.resolve(path));

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

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 客户端可以执行三项操作:

  • 连接到其选定的数据库
  • 发送 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 以及访问数据库所需的用户名和密码。服务器返回一个字符串,表示连接结果:

    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 ...
如果查询返回了结果。服务器返回的行即为该查询的结果行。

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

    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();
    };// interface
};// module

与我们之前看到的 Echo 服务器的 IDL 接口相比,唯一的新特性是使用了 sequence 关键字。该关键字允许您定义一维数组。定义过程分为两步:

  • 定义一个类型来标识该数组,此处为 results
typedef sequence<string> resultats;

typedef 关键字对 C/C++ 程序员来说并不陌生:它允许你定义一个新类型。在此,类型 resultats 被定义为与类型 sequence<string> 等价,即一个字符串的动态(无固定大小)数组。

  • 在需要的地方使用该新类型
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 接口中创建的结果类型相关的文件。

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 服务器客户端时需传入以下参数:

    machine port nomServiceAnnuaire pilote urlBase id mdp separateur

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

端口:该目录运行的端口

服务名称:SQL 服务的名称

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

baseUrl:待管理数据库的 JDBC URL

id:客户端 ID,若无 ID 则为 null

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

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

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

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

其中:

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

端口:该目录运行的端口

srvSQLsrvSQL,即 SQL 服务器的 CORBA 名称

驱动程序sun.jdbc.odbc.JdbcOdbcDriver, 适用于具有 ODBC 接口的数据库的标准驱动程序

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

id: null,无 ID

password: null,无密码

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

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

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

Image

该数据库具有以下结构:

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

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

    typedef   sequence<T> nomType;

随后我们使用 TypeName 来指代该数组类型。因此,在 SQL Server 接口中,我们使用了以下声明:

    typedef sequence<string> resultats;

这样,results 在 Java 中就指代一个 String[] 数组。