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

Ми побачимо, що побудова розподіленого додатка за допомогою CORBA близька до методу, що застосовується в Java RMI: концепції схожі. CORBA має перевагу у вигляді сумісності з додатками, написаними на інших мовах.
10.2. Процес розробки додатка CORBA
10.2.1. Вступ
Для розробки клієнт-серверного додатка CORBA ми будемо дотримуватися таких етапів:
- написання інтерфейсу сервера за допомогою IDL (Interface Definition Language)
- генерація «скелетних» та «заглушкових» класів сервера
- написання сервера
- написання клієнта
- компіляція всіх класів
- запуск каталогу служб CORBA
- запуск сервера
- запуск клієнта
Як перший приклад візьмемо сервер-ехо, який вже використовувався в контексті RMI. Таким чином читач зможе побачити відмінності між цими двома методами.
Додаток було протестовано з jdk1.2.
10.2.2. Написання інтерфейсу сервера
Як і у випадку з Java RMI, з точки зору клієнта сервер визначається своїм інтерфейсом. Якщо класи, що реалізують сервер, не потрібні клієнту, то класи його інтерфейсу — потрібні. Якщо в Java RMI використовувався інтерфейс Java, що породжував «скелетні» та «заглушкові» класи сервера, то архітектура Java CORBA вимагає опису інтерфейсу мовою, відмінною від Java. Цей інтерфейс породжуватиме кілька класів, деякі з яких використовуватимуться клієнтом, а інші — сервером.
Опис інтерфейсу відгуку буде таким:
Опис інтерфейсу зберігатиметься у файлі echo.idl. Він написаний мовою IDL (Interface Definition Language) системи OMG. Щоб її можна було використовувати, її має проаналізувати програма, яка створить вихідні файли на мові, що використовується для розробки додатка CORBA. У цьому випадку ми використаємо програму idltojava.exe, яка на основі попереднього інтерфейсу створить вихідні файли .java, необхідні для роботи додатка. Програма idltojava.exe не входить до комплекту поставки JDK. Її можна завантажити з веб-сайту Sun http://java.sun.com.
Проаналізуємо кілька рядків попереднього інтерфейсу idl:
відповідає пакету echo в Java. У результаті компіляції інтерфейсу буде створено пакет Java echo c.a.d — каталог, що містить класи Java.
відповідає інтерфейсу iSrvEcho у Java. У результаті буде створено інтерфейс Java.
еквівалентний оператору Java String echo(String msg). Типи мови IDL не повністю відповідають типам мови Java. Відповідності наведено трохи далі в цьому розділі. У мові IDL параметри функції можуть бути вхідними (in), вихідними (out) або вхідно-вихідними (inout). Тут метод echo приймає вхідний параметр msg, який є символьним рядком, і повертає символьний рядок як результат.
Попередній інтерфейс належить нашому серверу-ехо. Нагадаємо, що віддалений інтерфейс описує методи об’єкта-сервера, доступні для клієнтів. У цьому випадку для клієнтів буде доступний лише метод echo.
10.2.3. Компіляція інтерфейсу IDL сервера
Після визначення інтерфейсу сервера створюються відповідні файли Java.
E:\data\java\corba\ECHO>dir *.idl
ECHO IDL 78 15/03/99 13:56 ECHO.IDL
E:\data\java\corba\ECHO>d:\javaidl\idltojava.exe -fno-cpp echo.idl
Опція -fno-cpp використовується для вказівки на те, що препроцесор не потрібно використовувати (найчастіше застосовується з C/C++). У результаті компіляції файлу echo.idl створюється підкаталог echo, у якому містяться такі файли:
E:\data\java\corba\ECHO>dir echo
_ISRVE~1 JAV 1 095 17/03/99 17:19 _iSrvEchoStub.java
ISRVEC~1 JAV 311 17/03/99 17:19 iSrvEcho.java
ISRVEC~2 JAV 825 17/03/99 17:19 iSrvEchoHolder.java
ISRVEC~3 JAV 1 827 17/03/99 17:19 iSrvEchoHelper.java
_ISRVE~2 JAV 1 803 17/03/99 17:19 _iSrvEchoImplBase.java
Файл iSrvEcho.java — це файл Java, що описує інтерфейс сервера:
/* * Файл: ./ECHO/ISRVECHO.JAVA * З: ECHO.IDL * Дата: понеділок, 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. Це абстрактний клас, «каркас» сервера, що надає серверу функціональні можливості CORBA, необхідні для розподіленої програми.
Це образ («заглушка») сервера, який використовуватиме клієнт. Він надає клієнту функціональні можливості CORBA для підключення до сервера.
Надає методи, необхідні для управління посиланнями на об’єкти CORBA
Надає методи, необхідні для управління параметрами вводу-виводу методів інтерфейсу.
10.2.4. Компіляція класів, згенерованих на основі інтерфейсу IDL
Варто скомпілювати наведені вище класи. У іншому прикладі ми побачимо, що тут можна виявити помилки, спричинені неправильною роботою генератора idltojava. У даному випадку все пройшло успішно, і після компіляції у каталозі пакета echo з’являються такі файли:
E:\data\java\corba\ECHO\echo>dir
_ISRVE~1 JAV 1 095 17/03/99 17:19 _iSrvEchoStub.java
ISRVEC~1 JAV 311 17/03/99 17:19 iSrvEcho.java
ISRVEC~2 JAV 825 17/03/99 17:19 iSrvEchoHolder.java
ISRVEC~3 JAV 1 827 17/03/99 17:19 iSrvEchoHelper.java
_ISRVE~2 JAV 1 803 17/03/99 17:19 _iSrvEchoImplBase.java
_ISRVE~1 CLA 2 275 18/03/99 11:25 _iSrvEchoImplBase.class
_ISRVE~2 CLA 1 383 18/03/99 11:25 _iSrvEchoStub.class
ISRVEC~1 CLA 251 18/03/99 11:25 iSrvEcho.class
ISRVEC~2 CLA 2 078 18/03/99 11:25 iSrvEchoHelper.class
ISRVEC~3 CLA 858 18/03/99 11:25 iSrvEchoHolder.class
10.2.5. Запис сервера
10.2.5.1. Реалізація інтерфейсу iSrvEcho
Вище ми визначили інтерфейс iSrvEcho. Тепер ми пишемо клас, що реалізує цей інтерфейс. Він буде похідним від класу _iSrvEchoImplbase.java, який, як зазначено вище, вже реалізує інтерфейс iSrvEcho.
// імпортовані пакети
import echo.*;
// клас, що реалізує віддалене відлуння
public class srvEcho extends _iSrvEchoImplBase{
// метод, що реалізує відлуння
public String echo(String msg){
return "[" + msg + "]";
}// кінець функції «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), який отримується за допомогою такого методу класу:
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();
- зареєструвати його в каталозі
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};
- запитуємо у службі каталогу посилання на службу відлуння (саме в цьому полягає відмінність від сервера)
10.2.6.3. Compilation
E:\data\java\corba\ECHO>j:\jdk12\bin\javac cltEcho.java
E:\data\java\corba\ECHO>dir
CLTECH~1 CLA 2 599 18/03/99 13:51 cltEcho.class
CLTECH~1 JAV 2 907 16/03/99 16:15 cltEcho.java
ECHO IDL 78 15/03/99 13:56 ECHO.IDL
SERVEU~1 CLA 1 793 18/03/99 13:18 serveurEcho.class
SERVEU~1 JAV 1 806 16/03/99 15:38 serveurEcho.java
SRVECH~1 CLA 488 18/03/99 11:30 srvEcho.class
SRVECH~1 JAV 252 15/03/99 14:02 srvEcho.java
ECHO <REP> 17/03/99 17:19 echo
10.2.7. Тестування
10.2.7.1. Запуск служби каталогу
На комп’ютері з ОС Windows ми запускаємо службу каталогу наступним чином:
що призводить до запуску служби каталогів на порту 1000 комп'ютера.
Служба каталогу tnameserv виводить на екран інформацію, схожу на таку:
Initial Naming Context:
IOR:000000000000002849444c3a6f6d672e6f72672f436f734e616d696e672f4e616d696e67436f
6e746578743a312e3000000000010000000000000030000100000000000a69737469612d30303900
044700000018afabcafe000000027620dd9a000000080000000000000000
TransientNameServer: setting port for initial object references to: 1000
Це важко розібрати, але звернемо увагу на останній рядок: служба працює на порту 1000.
10.2.7.2. Запуск сервера ехо
Служба ехо запускається з трьома параметрами:
E:\data\java\corba\ECHO>start j:\jdk12\bin\java serveurEcho localhost 1000 srvEcho
Сервер виводить:
10.2.7.3. Запуск клієнта на тому самому комп’ютері, що й сервер
E:\data\java\corba\ECHO>j:\jdk12\bin\java cltEcho localhost 1000 srvEcho
--> Connexion au serveur CORBA en cours...
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin
10.2.7.4. Запуск клієнта на комп'ютері з ОС Windows, відмінному від сервера
E:\data\java\corba\ECHO>j:\jdk12\bin\java cltEcho tahe.istia.univ-angers.fr 1000 srvEcho
--> Connexion au serveur CORBA en cours...
Message : abcd
Réponse serveur : [abcd]
Message : efgh
Réponse serveur : [efgh]
Message : fin
10.3. Приклад 2: сервер SQL
10.3.1. Вступ
Тут ми повертаємося до опису сервера SQL, який вже розглядали в контексті Java RMI, щоб ще раз підкреслити спільні риси та відмінності між цими двома методами. Нагадаємо про призначення цього сервера SQL: він розташований на комп’ютері з ОС Windows і дозволяє віддаленим клієнтам користуватися публічними базами даних ODBC цього комп’ютера з ОС Windows.

Клієнт 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 для доступу до цієї бази. Сервер повертає йому рядок символів, що вказує на результат підключення:
executeSQL: клієнт запитує виконання запиту SQL у базі даних, до якої він підключений. Він вказує символ, який має розділяти поля у результатах, що йому повертаються. Сервер повертає масив рядків:
для запиту на оновлення бази даних, де n — кількість оновлених рядків
якщо запит призвів до помилки
якщо запит не дав жодних результатів
якщо запит дав результати. Рядки, які сервер повертає таким чином, є результатами запиту.
Close: клієнт закриває з’єднання з віддаленою базою даних. Сервер повертає рядок, що вказує на результат цього закриття:
Інтерфейс сервера IDL матиме такий вигляд:
module srvSQL{
typedef sequence<string> resultats;
interface interSQL{
string connect(in string pilote, in string urlBase, in string id, in string mdp);
resultats executeSQL(in string requete, in string separateur);
string close();
};// інтерфейс
};// модуль
Єдина відмінність від того, що ми бачили в інтерфейсі IDL сервера-відлуння, полягає у використанні ключового слова sequence. Це ключове слово дозволяє визначити одновимірний масив. Визначення відбувається у два етапи:
- визначення типу для позначення масиву, у даному випадку — resultats:
Ключове слово typedef добре відоме програмістам на C/C++: воно дозволяє визначити новий тип. Тут тип resultats визначено як еквівалент типу sequence<string>, c.a.d — динамічного (безфіксованого розміру) масиву символьних рядків.
- Використання нового типу там, де це потрібно
Отже, метод executeSQL повертає масив рядків.
10.3.3. Компіляція інтерфейсу IDL сервера
Попередній інтерфейс IDL розміщується у файлі srvSQL.idl. Цей файл компілюється:
E:\data\java\corba\sql>d:\javaidl\idltojava -fno-cpp srvSQL.idl
E:\data\java\corba\sql>dir
SRVSQL IDL 275 19/03/99 9:59 srvSQL.idl
SRVSQL <REP> 19/03/99 9:41 srvSQL
Бачимо, що в результаті компіляції було створено каталог із назвою модуля інтерфейсу IDL (srvSQL). Давайте подивимося на вміст цього каталогу:
E:\data\java\corba\sql>dir srvSQl
RESULT~1 JAV 833 19/03/99 10:00 resultatsHolder.java
RESULT~2 JAV 1 883 19/03/99 10:00 resultatsHelper.java
_INTER~1 JAV 2 474 19/03/99 10:00 _interSQLStub.java
INTERS~1 JAV 448 19/03/99 10:00 interSQL.java
INTERS~2 JAV 841 19/03/99 10:00 interSQLHolder.java
INTERS~3 JAV 1 855 19/03/99 10:00 interSQLHelper.java
_INTER~2 JAV 4 535 19/03/99 10:00 _interSQLImplBase.java
Нагадаємо, що файли Helper та Holder — це класи, пов’язані з параметрами вводу-виводу та результатами методів віддаленого інтерфейсу. Каталог srvSQL містить усі файли .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: машина, на якій знаходиться каталог служб CORBA
порт: порт, на якому працює цей каталог
nomService: назва служби SQL
драйвер: драйвер, який повинен використовувати сервер SQL для управління потрібною базою даних
urlBase: URL-адреса JDBC бази даних, яку потрібно керувати
id: ідентифікатор клієнта або null, якщо ідентифікатор відсутній
mdp: пароль клієнта або null, якщо пароль відсутній
separateur: символ, який сервер SQL повинен використовувати для розділення полів у рядках результатів запиту
Ось приклад можливих параметрів:
де:
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
- запитує підключення до бази даних товарів
- він просить користувача ввести запит SQL з клавіатури
- відправляє його на сервер SQL
- він відображає на екрані результати, отримані від сервера
- він знову просить користувача ввести запит SQL з клавіатури. Програма зупиниться, коли запит буде завершено.
Далі наведено Java-код клієнта. Коментарів має вистачити для його розуміння. Можна помітити, що:
- код ідентичний коду клієнта RMI, який ми вже розглядали. Він відрізняється процесом запиту до сервісу каталогу, який винесено в окремий метод getServeurSQL.
- метод getServeurSQL є ідентичним тому, що написано для клієнта «echo»
Отже, бачимо, що:
- клієнт CORBA відрізняється від клієнта RMI лише способом зв’язку із сервером
- цей спосіб є однаковим для всіх клієнтів CORBA
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
import java.io.*;
public class clientSQL {
// загальні дані класу
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:

Ця база даних має таку структуру:
назва | тип |
code | 4-значний код товару |
nom | назва (рядок символів) |
prix | ціна (фактична) |
stock_actu | його поточний запас (ціле число) |
stock_mini | мінімальний запас (ціле число), при недосягненні якого необхідно поповнити товар |
10.3.7.2. Запуск довідкової служби
E:\data\java\corba\sql>start j:\jdk12\bin\tnameserv -ORBInitialPort 1000
Le service d’annuaire est lancé sur le port 1000. Il affiche dans une fenêtre DOS quelque chose du genre :
Initial Naming Context:
IOR:000000000000002849444c3a6f6d672e6f72672f436f734e616d696e672f4e616d696e67436f
6e746578743a312e3000000000010000000000000030000100000000000a69737469612d30303900
052800000018afabcafe000000027693d3fd000000080000000000000000
TransientNameServer: setting port for initial object references to: 1000
10.3.7.3. Запуск сервера SQL
Запускаємо сервер Sql:
У вікні відображається DOS:
10.3.7.4. Запуск клієнта на тій самій машині, що й сервер
Ось результати, отримані при запуску клієнта на тій самій машині, що й сервер:
E:\data\java\corba\sql>j:\jdk12\bin\java clientSQL localhost 1000 srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Connexion au serveur CORBA en cours...
--> Connexion à la base de données en cours
<-- 200 Connexion réussie
--> Запит: select ім'я, stock_actu, stock_mini from 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 використовується інструкція:
а потім використовується nomType для посилання на тип масиву. Таким чином, в інтерфейсі сервера SQL було використано оголошення:
, щоб resultats вказував на масив String[] у Java.