Skip to content

10. Побудова розподілених додатків CORBA

10.1. Вступ

У попередньому розділі ми розглянули, як створювати розподілені додатки на Java за допомогою пакета RMI. Тут ми розглянемо ту саму проблему, але цього разу з використанням архітектури CORBA. CORBA (Common Object Request Broker Architecture) — це специфікація, визначена OMG (Object Management Group), до складу якої входять численні компанії з галузі інформаційних технологій. CORBA визначає «програмну шину», доступну для додатків, написаних на різних мовах:

Image

Ми побачимо, що побудова розподіленого додатка за допомогою CORBA близька до методу, що застосовується в Java RMI: концепції схожі. CORBA має перевагу у вигляді сумісності з додатками, написаними на інших мовах.

10.2. Процес розробки додатка CORBA

10.2.1. Вступ

Для розробки клієнт-серверного додатка CORBA ми будемо дотримуватися таких етапів:

  1. написання інтерфейсу сервера за допомогою IDL (Interface Definition Language)
  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 (Interface Definition Language) системи OMG. Щоб її можна було використовувати, її має проаналізувати програма, яка створить вихідні файли на мові, що використовується для розробки додатка CORBA. У цьому випадку ми використаємо програму idltojava.exe, яка на основі попереднього інтерфейсу створить вихідні файли .java, необхідні для роботи додатка. Програма idltojava.exe не входить до комплекту поставки JDK. Її можна завантажити з веб-сайту Sun http://java.sun.com.

Проаналізуємо кілька рядків попереднього інтерфейсу idl:

module echo

відповідає пакету echo в Java. У результаті компіляції інтерфейсу буде створено пакет Java echo c.a.d — каталог, що містить класи Java.

interface iSrvEcho

відповідає інтерфейсу iSrvEcho у Java. У результаті буде створено інтерфейс 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  * Дата: понеділок, 15 березня 13:56:08 1999  *   Автор: D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 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.

// імпортовані пакети
import echo.*;

// клас, що реалізує віддалене відлуння
public class srvEcho extends _iSrvEchoImplBase{
    // метод, що реалізує відлуння
    public String echo(String msg){
        return  "["  + msg + "]";
    }// кінець функції «echo»
}// кінець класу

Код зрозумілий сам по собі. Цей клас зареєстровано у файлі 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 «echo», записаного у файлі serveurEcho.java:

// імпортовані пакети
import echo.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;

//----------- клас serveurEcho
public class serveurEcho{
    // ------- main: запускає сервер відлуння
    // синтаксис pg machineAnnuaire portAnnuaire nomService
    // машина: машина, що підтримує каталог CORBA
    // порт: порт каталогу CORBA
    // nomService: назва служби, яку потрібно зареєструвати

  public static void main(String arg[]){
    // чи є аргументи?
    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];

    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 (Object Request Broker), який отримується за допомогою такого методу класу:

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.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);

Перша частина коду полягає у підготовці імені служби. Це ім’я позначається в коді змінною 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. І в цьому випадку клієнт повинен підключитися до служби каталогу, щоб отримати посилання на об’єкт-сервер, до якого він хоче підключитися. У цьому і полягає, і лише в цьому, різниця між цими двома клієнтами. Ось код клієнта «echo» CORBA:


    

10.2.6.2. Підключення клієнта до сервера

Вищезазначений клієнт CORBA підключається до сервера за допомогою команди:

        // встановлюємо з'єднання з сервером-відлунням
        iSrvEcho serveurEcho=getServeurEcho(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 nameservice
         // машина: машина, на якій працює каталог служб 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. Вступ

Тут ми повертаємося до опису сервера SQL, який вже розглядали в контексті Java RMI, щоб ще раз підкреслити спільні риси та відмінності між цими двома методами. Нагадаємо про призначення цього сервера SQL: він розташований на комп’ютері з ОС Windows і дозволяє віддаленим клієнтам користуватися публічними базами даних ODBC цього комп’ютера з ОС Windows.

Image

Клієнт CORBA може виконувати 3 операції:

  • підключитися до обраної бази даних
  • надсилати запити SQL
  • закрити з’єднання

Сервер виконує запити SQL від клієнта та надсилає йому результати. Це його основна функція, і саме тому ми називаємо його сервером SQL. Ми застосовуємо різні етапи, розглянуті раніше на прикладі сервера-відлуння.

10.3.2. Написання інтерфейсу IDL сервера

Нагадаємо для довідки інтерфейс RMI, який ми використовували для сервера:

import java.rmi.*;

// віддалений інтерфейс
public interface interSQL extends Remote{
    public String connect(String pilote, String url, String id, String mdp)
        throws java.rmi.RemoteException;
    public String[] executeSQL(String requete, String separateur) 
        throws java.rmi.RemoteException;
    public String close()
        throws java.rmi.RemoteException;
}

Роль різних методів була такою:

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

Нагадаємо, що файли Helper та Holder — це класи, пов’язані з параметрами вводу-виводу та результатами методів віддаленого інтерфейсу. Каталог srvSQL містить усі файли .java, пов’язані з інтерфейсом interSQL, визначеним у файлі .idl. Він також містить файли, пов’язані з типом resultats, створеним в інтерфейсі IDL.

Файл interSQL.java — це Java-файл інтерфейсу нашого сервера. Важливо перевірити, чи те, що було згенеровано автоматично, відповідає нашим очікуванням. Згенерований файл interSQL.java має такий вигляд:

/*  * Файл: ./SRVSQL/INTERSQL.JAVA  * З: SRVSQL.IDL  * Дата: п’ятниця, 19 березня 09:59:48 1999  *   Автор: D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 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

Тепер ми пишемо код сервера SQL. Нагадаємо, що цей клас повинен походити від абстрактного класу _nomInterfaceImplBase, створеного в результаті компіляції файлу IDL. За винятком цієї особливості та фрагментів коду, пов’язаних із реєстрацією служби в каталозі, код сервера 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);
            // ok
            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){

        // виконує запит SQL до бази даних DB
        // та заносить результати в масив рядків

        // дані, необхідні для виконання запиту
        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();
            // кількість полів у наборі результатів
            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{
    // ------- main: запускає сервер 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

machine: машина, на якій знаходиться каталог служб CORBA

порт: порт, на якому працює цей каталог

nomService: назва служби SQL

драйвер: драйвер, який повинен використовувати сервер SQL для управління потрібною базою даних

urlBase: URL-адреса JDBC бази даних, яку потрібно керувати

id: ідентифікатор клієнта або null, якщо ідентифікатор відсутній

mdp: пароль клієнта або null, якщо пароль відсутній

separateur: символ, який сервер SQL повинен використовувати для розділення полів у рядках результатів запиту

Ось приклад можливих параметрів:

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

де:

machine: комп'ютер, на якому знаходиться каталог служб CORBA

порт: порт, на якому працює цей каталог

srvSQL: srvSQL, ім’я сервера CORBA

драйвер: sun.jdbc.odbc.JdbcOdbcDriver, стандартний драйвер для баз даних з інтерфейсом ODBC

urlBase: jdbc:odbc:articles, для використання бази «articles», заявленої у списку публічних баз ODBC на комп’ютері з Windows

id: null, ідентифікатор відсутній

пароль: null, пароль відсутній

separateur: , поля результатів будуть розділені комою

Після запуску з наведеними вище параметрами клієнт виконує такі кроки:

  • він підключається до комп’ютера machine через порт port, щоб звернутися до служби CORBA srvSQL
  • запитує підключення до бази даних товарів
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 {

    // загальні дані класу
    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: URL-адреса JDBC бази даних, з якою потрібно працювати
        // id: ідентифікатор користувача
        // mdp: його пароль
        // separateur: рядок, що розділяє поля у результатах запиту

        // перевірка кількості аргументів
        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);
        }// спроба
    }// 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

Припустимо, що база даних ACCESS під назвою «Статті» є загальнодоступною на сервері Windows SQL:

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 articles
<-- 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 articles
<-- 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
char
char
wchar
char
байт
байт
рядок
java.lang.String
wstring
java.lang.String
short
short
unsigned short
short
long
int
unsigned long
int
довгий довгий
long
unsigned long long
long
float
float
double
double

Нагадаємо, що для визначення масиву елементів типу T в інтерфейсі IDL використовується інструкція:

    typedef   sequence<T> nomType;

а потім використовується nomType для посилання на тип масиву. Таким чином, в інтерфейсі сервера SQL було використано оголошення:

    typedef sequence<string> resultats;

, щоб resultats вказував на масив String[] у Java.