10. Tworzenie aplikacji rozproszonych CORBA
10.1. Wprowadzenie
W poprzednim rozdziale omówiliśmy, jak tworzyć aplikacje rozproszone w Javie przy użyciu pakietu RMI. W niniejszym rozdziale zajmiemy się tym samym zagadnieniem, tym razem w oparciu o architekturę CORBA. CORBA (Common Object Request Broker Architecture) to specyfikacja zdefiniowana przez OMG (Object Management Group), zrzeszającą wiele przedsiębiorstw z branży informatycznej. CORBA definiuje „magistralę programową” dostępną dla aplikacji napisanych w różnych językach:

Zobaczymy, że tworzenie aplikacji rozproszonej przy użyciu CORBA jest zbliżone do metody stosowanej w Javie RMI: koncepcje są do siebie podobne. CORBA ma tę zaletę, że zapewnia interoperacyjność z aplikacjami napisanymi w innych językach.
10.2. Proces tworzenia aplikacji CORBA
10.2.1. Wprowadzenie
Aby opracować aplikację klient-serwer CORBA, wykonamy następujące kroki:
- napisanie interfejsu serwera za pomocą IDL (Interface Definition Language)
- wygenerowanie klas „szkieletowych” i „stubowych” serwera
- napisanie kodu serwera
- napisanie klienta
- kompilacja wszystkich klas
- uruchomienie katalogu usług CORBA
- uruchomienie serwera
- uruchomienie klienta
Jako pierwszy przykład weźmiemy serwer echo, który był już używany w kontekście RMI. Dzięki temu czytelnik będzie mógł dostrzec różnice między tymi dwiema metodami.
Aplikacja została przetestowana z wykorzystaniem jdk1.2.
10.2.2. Tworzenie interfejsu serwera
Podobnie jak w przypadku Java RMI, z punktu widzenia klienta serwer jest definiowany przez swój interfejs. Jeśli klasy implementujące serwer nie są potrzebne klientowi, to klasy jego interfejsu już tak. Podczas gdy w przypadku Java RMI wykorzystywano interfejs Java, z którego powstawały klasy „szkieletowe” i „stubowe” serwera, architektura Java CORBA wymaga opisu interfejsu w języku innym niż Java. Interfejs ten spowoduje utworzenie kilku klas, z których niektóre będą wykorzystywane przez klienta, a inne przez serwer.
Opis interfejsu echo będzie wyglądał następująco:
Opis interfejsu zostanie zapisany w pliku echo.idl. Jest on napisany w języku IDL (Interface Definition Language) systemu OMG. Aby można było z niego korzystać, musi zostać przeanalizowany przez program, który utworzy pliki źródłowe w języku używanym do tworzenia aplikacji CORBA. W tym przypadku wykorzystamy program idltojava.exe, który na podstawie powyższego interfejsu utworzy pliki źródłowe .java niezbędne dla aplikacji. Program idltojava.exe nie jest dostarczany wraz z programem JDK. Można go pobrać ze strony firmy Sun http://java.sun.com.
Przeanalizujmy kilka wierszy poprzedniego interfejsu idl:
odpowiada pakietowi echo w Javie. Kompilacja interfejsu spowoduje utworzenie pakietu Java o nazwie echo c.a.d – katalogu zawierającego klasy Java.
odpowiada interfejsowi iSrvEcho w Javie. Spowoduje utworzenie interfejsu Java.
odpowiada instrukcji Java String echo(String msg). Typy w języku IDL nie odpowiadają dokładnie typom w języku Java. Odpowiedniki zostaną przedstawione nieco dalej w tym rozdziale. W języku IDL parametry funkcji mogą być parametrami wejściowymi (in), wyjściowymi (out) lub wejściowo-wyjściowymi (inout). W tym przypadku metoda echo przyjmuje parametr wejściowy msg, który jest ciągiem znaków, i zwraca ciąg znaków jako wynik.
Powyższy interfejs należy do naszego serwera echo. Przypomnijmy, że interfejs zdalny opisuje metody obiektu serwera dostępne dla klientów. W tym przypadku dla klientów dostępna będzie wyłącznie metoda echo.
10.2.3. Kompilacja interfejsu serwera IDL
Po zdefiniowaniu interfejsu serwera generowane są odpowiednie pliki 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
Opcja -fno-cpp służy do wskazania, że nie należy używać preprocesora (najczęściej stosowanego w przypadku języków C/C++). Kompilacja pliku echo.idl tworzy podkatalog echo zawierający następujące pliki:
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
Plik iSrvEcho.java to plik Java opisujący interfejs serwera:
/* * Plik: ./ECHO/ISRVECHO.JAVA * Źródło: ECHO.IDL * Data: poniedziałek, 15 marca 1999 r., godz. 13:56:08 * Autor: D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 18 sierpnia 1998 r. 16:25:34 */
package echo;
public interface iSrvEcho
extends org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity {
String echo(String msg)
;
}
Widać, że jest to niemal dosłowne tłumaczenie interfejsu IDL. Jeśli zechcemy zapoznać się z zawartością pozostałych plików, takich jak .java, znajdziemy tam bardziej złożone informacje. Oto, co mówi dokumentacja na temat roli tych różnych plików:
interfejs serwera
implementuje powyższy interfejs iSrvEcho. Jest to klasa abstrakcyjna, „szkielet” serwera, dostarczająca serwerowi funkcje CORBA niezbędne dla aplikacji rozproszonej.
Jest to obraz („stub”) serwera, z którego będzie korzystał klient. Zapewnia on klientowi funkcje CORBA umożliwiające nawiązanie połączenia z serwerem.
Zapewnia metody niezbędne do zarządzania odwołaniami do obiektów CORBA
Zapewnia metody niezbędne do zarządzania parametrami wejścia i wyjścia metod interfejsu.
10.2.4. Kompilacja klas wygenerowanych na podstawie interfejsu IDL
Warto skompilować powyższe klasy. W innym przykładzie zobaczymy, że można tu wykryć błędy wynikające z nieprawidłowego działania generatora idltojava. W tym przypadku wszystko przebiega pomyślnie, a po kompilacji w katalogu pakietu echo znajdują się następujące pliki:
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. Zapis serwera
10.2.5.1. Implementacja interfejsu iSrvEcho
Wcześniej zdefiniowaliśmy interfejs iSrvEcho. Teraz tworzymy klasę implementującą ten interfejs. Będzie ona pochodną klasy _iSrvEchoImplbase.java, która, jak wspomniano powyżej, już implementuje interfejs iSrvEcho.
// zaimportowane pakiety
import echo.*;
// klasa implementująca zdalne echo
public class srvEcho extends _iSrvEchoImplBase{
// metoda realizująca echo
public String echo(String msg){
return "[" + msg + "]";
}// koniec funkcji echo
}// koniec klasy
Kod jest zrozumiały sam w sobie. Klasa ta jest zapisana w pliku srvEcho.java w katalogu nadrzędnym względem katalogu pakietu interfejsu iSrvEcho.
Można skompilować kod, aby to sprawdzić:
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. Rejestracja klasy tworzenia serwera
Podobnie jak w przypadku aplikacji klient-serwer RMI, serwer CORBA musi zostać zarejestrowany w katalogu, aby był dostępny dla klientów. To właśnie ta procedura rejestracji różni się na etapie rozwoju w zależności od tego, czy mamy do czynienia z aplikacją CORBA, czy RMI. Oto procedura rejestracji serwera CORBA typu echo zapisana w pliku serveurEcho.java:
// importowane pakiety
import echo.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;
//----------- klasa serveurEcho
public class serveurEcho{
// ------- main: uruchamia serwer echo
// składnia pg machineAnnuaire portAnnuaire nomService
// maszyna: maszyna obsługująca katalog CORBA
// port: port katalogu CORBA
// nomService: nazwa usługi do zarejestrowania
public static void main(String arg[]){
// czy argumenty są obecne?
if(arg.length!=3){
System.err.println("Syntaxe : pg machineAnnuaire portAnnuaire nomService");
System.exit(1);
}
// pobieramy argumenty
String machine=arg[0];
String port=arg[1];
String nomService=arg[2];
try{
// do pracy potrzebny jest obiekt CORBA
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
ORB orb=ORB.init(initORB,null);
// dodajemy usługę do katalogu usług
// będzie się nazywać srvEcho
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
NameComponent nc= new NameComponent(nomService,"");
NameComponent path[]={nc};
// tworzymy serwer i przypisujemy go do usługi o nazwie srvEcho
srvEcho serveurEcho=new srvEcho();
ncRef.rebind(path,serveurEcho);
orb.connect(serveurEcho);
// monitorowanie
System.out.println("Serveur d'écho prêt");
// oczekiwanie na żądania klientów
java.lang.Object sync=new java.lang.Object();
synchronized(sync){
sync.wait();
}
} catch(Exception e){
// wystąpił błąd
System.err.println("Erreur " + e);
e.printStackTrace(System.err);
}
}// stan główny
}// serveurEcho
Poniżej wyjaśniamy ogólne zasady uruchamiania serwera, nie wchodząc w szczegóły, które na pierwszy rzut oka mogą wydawać się skomplikowane. Z poprzedniego przykładu należy zapamiętać główne założenia, które powtórzą się w przypadku każdego serwera CORBA.
10.2.5.2.1. Parametry serwera
Serwer CORBA musi zarejestrować się w usłudze katalogowej działającej na określonym komputerze i porcie. Nasza aplikacja otrzyma te dwie informacje jako parametry. Zarejestrowana w ten sposób usługa musi posiadać nazwę, która będzie trzecim parametrem.
10.2.5.2.2. Utworzenie obiektu dostępu do usługi katalogowej CORBA
Aby połączyć się z usługą katalogową i zarejestrować nasz serwer echo, potrzebujemy obiektu o nazwie ORB (Object Request Broker), uzyskanego za pomocą następującej metody klasy:
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
W przykładzie wykorzystano następującą sekwencję w celu uzyskania obiektu ORB:
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
ORB orb=ORB.init(initORB,null);
Zastosowano następujące pary (parametr, wartość):
("-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.
Drugi parametr metody init pozostawiono na wartości null. Gdyby pierwszy parametr również pozostawiono na wartości null, zastosowana para (komputer, port) byłaby domyślna (localhost,900).
10.2.5.2.3. Rejestracja serwera w katalogu usług CORBA
Rejestracja serwera w katalogu odbywa się poprzez wykonanie następujących czynności:
// usługa zostaje dodana do katalogu usług
// będzie nosił nazwę srvEcho
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
NameComponent nc= new NameComponent(nomService,"");
NameComponent path[]={nc};
// tworzymy serwer i przypisujemy go do usługi srvEcho
srvEcho serveurEcho=new srvEcho();
ncRef.rebind(path,serveurEcho);
orb.connect(serveurEcho);
Pierwsza część kodu polega na przygotowaniu nazwy usługi. Nazwa ta jest w kodzie oznaczona zmienną path. Nazwa usługi składa się z kilku elementów:
- komponent początkowy objRef, obiekt generyczny, który należy przekształcić do typu NamingContext, w tym przypadku ncRef.
- nazwa usługi, w tym przypadku nomService, która została przekazana jako parametr do serwera
Te składniki nazwy (NameComponent) są zebrane w tablicy, w tym przypadku path. To właśnie ta tablica precyzyjnie „naznacza” utworzoną usługę. Po utworzeniu nazwy pozostaje
- przypisać ją do instancji serwera (wcześniej utworzonej klasy srvEcho)
srvEcho serveurEcho=new srvEcho();
- i zapisać go w katalogu
10.2.5.3. Kompilacja klasy uruchamiającej serwer
Kompilujemy poprzednią klasę:
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. Wpis klienta
10.2.6.1. Kod
Tworzymy klienta w celu przetestowania naszej usługi echo. Przekażemy klientowi te same trzy parametry, co serwerowi:
Maszyna: maszyna, na której znajduje się katalog usług CORBA
Port: port, na którym działa ten katalog
nomService: nazwa usługi echo
Klient łączy się z usługą echo, a następnie prosi użytkownika o wpisanie wiadomości za pomocą klawiatury. Wiadomości te są wysyłane do serwera echo, który je odsyła. Przebieg tej wymiany jest wyświetlany na ekranie.
Klient usługi echo o nazwie CORBA jest bardzo podobny do już napisanego klienta RMI. Również w tym przypadku klient musi połączyć się z usługą katalogową, aby uzyskać odwołanie do obiektu serwera, z którym chce się połączyć. Różnica między tymi dwoma klientami polega wyłącznie na tym. Oto kod klienta echo o nazwie CORBA:
10.2.6.2. Połączenie klienta z serwerem
Powyższy klient CORBA łączy się z serwerem za pomocą instrukcji:
// nawiązuje się połączenie z serwerem echo
iSrvEcho serveurEcho=getServeurEcho(machine,port,nomService);
Po zakończeniu tej operacji klient posiada odwołanie do serwera echo. Następnie klient o numerze CORBA nie różni się od klienta o numerze RMI. Prywatna metoda zapewniająca połączenie z serwerem jest następująca:
// zaimportowane pakiety
import java.io.*;
import echo.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
// ---------- klasa cltEcho
public class cltEcho {
public static void main(String arg[]){
// składnia: cltEcho machineAnnuaire portAnnuaire nazwa_serwisu
// maszyna: maszyna, na której działa katalog usług CORBA
// port: port, na którym działa katalog usług
// nomService: nazwa usługi echa
// weryfikacja argumentów
if(arg.length!=3){
System.err.println("Syntaxe : pg machineAnnuaire portAnnuaire nomservice");
System.exit(1);
}
// pobieranie parametrów
String machine=arg[0];
String port=arg[1];
String nomService=arg[2];
// nawiązywanie połączenia z serwerem echo
iSrvEcho serveurEcho=getServeurEcho(machine,port,nomService);
// komunikacja klient-serwer
BufferedReader in=null;
String msg=null;
String reponse=null;
iSrvEcho serveur=null;
try{
// otwarcie strumienia klawiatury
in=new BufferedReader(new InputStreamReader(System.in));
// pętla odczytu wiadomości do wysłania na serwer echo
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
while(! msg.equals("fin")){
// wysyłanie wiadomości do serwera i odbieranie odpowiedzi
reponse=serveurEcho.echo(msg);
// monitorowanie
System.out.println("Réponse serveur : " + reponse);
// następna wiadomość
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
}// while
// koniec
System.exit(0);
// obsługa błędów
} catch (Exception e){
System.err.println("Erreur : " + e);
System.exit(2);
}// try
}// main
// ---------------------- getServeurEcho
private static iSrvEcho getServeurEcho(String machine, String port,
String nomService){
// żądanie referencji z serwera echo
// numer śledzenia
System.out.println("--> Connexion au serveur CORBA en cours...");
// numer referencyjny serwera echa
iSrvEcho serveurEcho=null;
try{
// żądany jest obiekt CORBA do pracy
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
ORB orb=ORB.init(initORB,null);
// wykorzystuje się usługę katalogową w celu zlokalizowania serwera echa
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
// poszukiwana usługa nosi nazwę srvEcho – wysyłamy do niej żądanie
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
// zwracamy odwołanie do serwera
return serveurEcho;
}// getServeurEcho
}// klasa
Widzimy te same sekwencje kodu, co na serwerze:
- tworzymy obiekt ORB, który pozwoli nam połączyć się z katalogiem usług CORBA
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
ORB orb=ORB.init(initORB,null);
- definiujemy różne składniki nazwy usługi echo
org.omg.CORBA.Object objRef=orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
NameComponent nc= new NameComponent(nomService,"");
NameComponent path[]={nc};
- zwracamy się do serwisu katalogowego o identyfikator usługi echa (w tym miejscu różnimy się od serwera)
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. Testy
10.2.7.1. Uruchomienie usługi katalogowej
Na komputerze z systemem Windows uruchamiamy usługę katalogową w następujący sposób:
co powoduje uruchomienie usługi katalogowej na porcie 1000 tego komputera.
Usługa katalogowa tnameserv generuje komunikat na ekranie, który wygląda następująco:
Initial Naming Context:
IOR:000000000000002849444c3a6f6d672e6f72672f436f734e616d696e672f4e616d696e67436f
6e746578743a312e3000000000010000000000000030000100000000000a69737469612d30303900
044700000018afabcafe000000027620dd9a000000080000000000000000
TransientNameServer: setting port for initial object references to: 1000
Jest to mało czytelne, ale zwróćmy uwagę na ostatni wiersz: usługa działa na porcie 1000.
10.2.7.2. Uruchomienie serwera echo
Usługa echo jest uruchamiana z trzema parametrami:
E:\data\java\corba\ECHO>start j:\jdk12\bin\java serveurEcho localhost 1000 srvEcho
Serwer wyświetla:
10.2.7.3. Uruchomienie klienta na tym samym komputerze, na którym znajduje się serwer
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. Uruchomienie klienta na komputerze z systemem Windows innym niż serwer
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. Przykład 2: serwer SQL
10.3.1. Wprowadzenie
W tym miejscu ponownie omawiamy kod serwera SQL, który został już omówiony w kontekście Java RMI, aby ponownie podkreślić podobieństwa i różnice między tymi dwiema metodami. Przypomnijmy rolę tego serwera SQL: znajduje się on na komputerze z systemem Windows i umożliwia klientom zdalnym korzystanie z publicznych baz danych ODBC tego komputera z systemem Windows.

Klient CORBA może wykonać trzy operacje:
- połączyć się z wybraną bazą danych
- wysyłać zapytania SQL
- zamknąć połączenie
Serwer wykonuje zapytania SQL od klienta i wysyła mu wyniki. Jest to jego podstawowe zadanie i dlatego nazywamy go serwerem SQL. Stosujemy różne etapy omówione wcześniej w przypadku serwera echo.
10.3.2. Tworzenie interfejsu serwera IDL
Dla przypomnienia przytoczymy interfejs RMI, którego używaliśmy dla serwera:
import java.rmi.*;
// interfejs zdalny
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;
}
Rola poszczególnych metod była następująca:
Connect: klient łączy się ze zdalną bazą danych, podając sterownik, adres URL JDBC, a także swój identyfikator id i hasło mdp w celu uzyskania dostępu do tej bazy. Serwer zwraca mu ciąg znaków wskazujący wynik połączenia:
executeSQL: klient żąda wykonania zapytania SQL w bazie, z którą jest połączony. Określa znak, który ma oddzielać pola w zwracanych wynikach. Serwer zwraca tablicę ciągów znaków:
dla zapytania o aktualizację bazy, gdzie n oznacza liczbę zaktualizowanych wierszy
jeśli zapytanie spowodowało błąd
jeśli zapytanie nie zwróciło żadnych wyników
jeśli zapytanie zwróciło wyniki. Wiersze zwrócone w ten sposób przez serwer stanowią wyniki zapytania.
Close: klient zamyka połączenie ze zdalną bazą danych. Serwer zwraca ciąg znaków wskazujący wynik tego zamknięcia:
Interfejs serwera IDL będzie wyglądał następująco:
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();
};// interfejs
};// moduł
Jedyną nowością w porównaniu z tym, co widzieliśmy w przypadku interfejsu serwera echo IDL, jest użycie słowa kluczowego sequence. Słowo to pozwala zdefiniować tablicę jednowymiarową. Definicja odbywa się w dwóch etapach:
- zdefiniowanie typu określającego tablicę, w tym przypadku „resultats”:
Słowo kluczowe typedef jest dobrze znane programistom języka C/C++: pozwala ono zdefiniować nowy typ. W tym przypadku typ resultats jest zdefiniowany jako równoważny typowi sequence<string>, c.a.d, czyli dynamicznej (o nieokreślonej wielkości) tablicy ciągów znaków.
- Wykorzystanie nowego typu tam, gdzie jest to potrzebne
Metoda executeSQL zwraca zatem tablicę ciągów znaków.
10.3.3. Kompilacja interfejsu serwera IDL
Poprzedni interfejs IDL zostaje umieszczony w pliku srvSQL.idl. Kompilujemy ten plik:
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
Widać, że w wyniku kompilacji powstał katalog o nazwie modułu interfejsu IDL (srvSQL). Przyjrzyjmy się zawartości tego katalogu:
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
Przypominamy, że pliki Helper i Holder to klasy związane z parametrami wejścia-wyjścia oraz wynikami metod interfejsu zdalnego. Katalog srvSQL zawiera wszystkie pliki .java związane z interfejsem interSQL zdefiniowanym w pliku .idl. Zawiera on również pliki związane z typem resultats utworzonym w interfejsie IDL.
Plik interSQL.java jest plikiem Java interfejsu naszego serwera. Ważne jest, aby sprawdzić, czy to, co zostało wygenerowane automatycznie, odpowiada naszym oczekiwaniom. Wygenerowany plik interSQL.java wygląda następująco:
/* * Plik: ./SRVSQL/INTERSQL.JAVA * Od: SRVSQL.IDL * Data: piątek, 19 marca 09:59:48 1999 * Autor: D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 18 sierpnia 1998 r. 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()
;
}
Widzimy, że mamy ten sam interfejs, co w przypadku klienta-serwera RMI. Możemy więc kontynuować. Skompilujmy wszystkie te pliki .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. Kod serwera SQL
Teraz piszemy kod serwera SQL. Przypomnijmy, że ta klasa musi pochodzić od klasy abstrakcyjnej _nomInterfaceImplBase, powstałej w wyniku kompilacji pliku IDL. Pomijając tę szczególną cechę oraz sekwencje kodu związane z rejestracją usługi w katalogu, kod serwera CORBA jest identyczny z kodem serwera RMI:
// zaimportowane pakiety
import java.sql.*;
import java.util.*;
import srvSQL.*;
// klasa SQLServant
public class SQLServant extends _interSQLImplBase{
// dane globalne klasy
private Connection DB;
// --------------- połączenie
public String connect(String pilote, String url, String id,
String mdp){
// połączenie z bazą danych poprzez adres URL przy użyciu sterownika
// autoryzacja przy użyciu identyfikatora id i hasła mdp
String resultat=null; // wynik metody
try{
// ładowanie sterownika
Class.forName(pilote);
// żądanie połączenia
DB=DriverManager.getConnection(url,id,mdp);
// ok
resultat="200 Connexion réussie";
} catch (Exception e){
// błąd
resultat="500 Echec de la connexion (" + e + ")";
}
// koniec
return resultat;
}
// ------------- executeSQL
public String[] executeSQL(String requete, String separateur){
// wykonuje zapytanie SQL w bazie DB
// i umieszcza wyniki w tablicy ciągów znaków
// dane niezbędne do wykonania zapytania
Statement S=null;
ResultSet RS=null;
String[] lignes=null;
Vector resultats=new Vector();
String ligne=null;
try{
// utworzenie kontenera zapytania
S=DB.createStatement();
// wykonanie zapytania
if (! S.execute(requete)){
// zapytanie o aktualizację
// zwracana jest liczba zaktualizowanych wierszy
lignes=new String[1];
lignes[0]="100 "+S.getUpdateCount();
return lignes;
}
// było to zapytanie o dane
// pobieranie wyników
RS=S.getResultSet();
// liczba pól w zestawie wyników
int nbChamps=RS.getMetaData().getColumnCount();
// przetwarzamy je
while(RS.next()){
// tworzenie wiersza wyników
ligne="101 ";
for (int i=1;i<nbChamps;i++)
ligne+=RS.getString(i)+separateur;
ligne+=RS.getString(nbChamps);
// dodanie do wektora wyników
resultats.addElement(ligne);
}// while
// koniec przetwarzania wyników
// zwolnienie zasobów
RS.close();
S.close();
// zwracanie wyników
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){
// błąd
lignes=new String[1];
lignes[0]="500 " + e;
return lignes;
}// try-catch
}// executeSQL
// --------------- zamknij
public String close(){
// zamyka połączenie z bazą danych
String resultat=null;
try{
DB.close();
resultat="200 Base fermée";
} catch (Exception e){
resultat="500 Erreur à la fermeture de la base ("+e+")";
}
// zwraca wynik
return resultat;
}
}// klasa SQLServant
Klasa ta znajduje się w pliku SQLServant.java, który kompilujemy:
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. Tworzenie programu uruchamiającego serwer SQL
Poprzednia klasa reprezentuje serwer SQL po uruchomieniu. Wcześniej należy go zarejestrować w katalogu usług CORBA. Podobnie jak w przypadku usługi echo, zrobimy to za pomocą specjalnej klasy, której w momencie wykonywania przekażemy trzy parametry:
Maszyna: maszyna, na której znajduje się katalog usług CORBA
Port: port, na którym działa ten katalog
nomService: nazwa usługi SQL
Kod tej klasy jest niemal identyczny z kodem klasy, która wykonywała tę samą czynność dla usługi echo. Wyróżniliśmy pogrubioną czcionką wiersz, który różni się między tymi dwiema klasami: nie tworzy ona tego samego obiektu serwera. Widzimy zatem, że mechanizm uruchamiania serwera pozostaje niezmienny. Jeśli wyodrębnimy ten mechanizm do osobnej klasy, tak jak zrobiono to tutaj, staje się on niemal niezauważalny dla programisty.
// zaimportowane pakiety
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;
//----------- klasa serveurSQL
public class serveurSQL{
// ------- main: uruchamia serwer SQL
public static void main(String arg[]){
// serveurSQL usługa portu maszynowego
//czy liczba argumentów jest prawidłowa
if(arg.length!=3){
System.err.println("Syntaxe : pg machineAnnuaireCorba portAnnuaireCorba nomService");
System.exit(1);
}
// pobieramy argumenty
String machine=arg[0];
String port=arg[1];
String nomService=arg[2];
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
try{
// potrzebujemy obiektu CORBA do pracy
ORB orb=ORB.init(initORB,null);
// dodajemy usługę do katalogu usług
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
NameComponent nc= new NameComponent(nomService,"");
NameComponent path[]={nc};
// tworzymy serwer i przypisujemy go do usługi srvSQL
SQLServant serveurSQL=new SQLServant();
ncRef.rebind(path,serveurSQL);
orb.connect(serveurSQL);
// monitorowanie
System.out.println("Serveur SQL prêt");
// oczekiwanie na żądania klientów
java.lang.Object sync=new java.lang.Object();
synchronized(sync){
sync.wait();
}
} catch(Exception e){
// wystąpił błąd
System.err.println("Erreur " + e);
e.printStackTrace(System.err);
}
}// stan główny
}// srvSQL
Kompilujemy tę nową klasę:
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. Zapis klienta
Klient serwera CORBA jest wywoływany z następującymi parametrami:
machine: maszyna, na której znajduje się katalog usług CORBA
port: port, na którym działa ten katalog
nomService: nazwa usługi SQL
sterownik: sterownik, którego serwer SQL ma używać do zarządzania wybraną bazą danych
urlBase: adres URL JDBC bazy danych, którą ma zarządzać
id: identyfikator klienta lub null, jeśli nie ma identyfikatora
mdp: hasło klienta lub null, jeśli nie ma hasła
separator: znak, którego serwer SQL musi używać do oddzielania pól w wierszach wyników zapytania
Oto przykład możliwych parametrów:
gdzie:
machine: komputer, na którym znajduje się katalog usług CORBA
port: port, na którym działa ten katalog
srvSQL: srvSQL, nazwa serwera CORBA
sterownik: sun.jdbc.odbc.JdbcOdbcDriver, standardowy sterownik baz danych z interfejsem ODBC
urlBase: jdbc:odbc:articles, aby korzystać z bazy „articles” zadeklarowanej na liście baz publicznych ODBC na komputerze z systemem Windows
id: null, brak identyfikatora
hasło: null, brak hasła
separator: , pola wyników będą oddzielone przecinkiem
Po uruchomieniu z powyższymi parametrami klient wykonuje następujące kroki:
- łączy się z komputerem machine na porcie port, aby zażądać usługi CORBA srvSQL
- wysyła żądanie połączenia z bazą danych artykułów
- prosi użytkownika o wpisanie zapytania SQL na klawiaturze
- wysyła ją do serwera SQL
- wyświetla na ekranie wyniki zwrócone przez serwer
- ponownie prosi użytkownika o wpisanie zapytania SQL na klawiaturze. Zakończy działanie po zakończeniu wprowadzania zapytania.
Poniżej znajduje się kod Java klienta. Komentarze powinny wystarczyć do jego zrozumienia. Można zauważyć, że:
- kod jest identyczny z kodem klienta RMI, który już analizowaliśmy. Różni się on procesem wysyłania zapytania do katalogu, który został wyodrębniony do metody getServeurSQL.
- Metoda getServeurSQL jest identyczna z metodą napisaną dla klienta echo
Widać zatem, że:
- klient CORBA różni się od klienta RMI jedynie sposobem nawiązywania połączenia z serwerem
- sposób ten jest identyczny dla wszystkich klientów CORBA
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
import java.io.*;
public class clientSQL {
// dane ogólne klasy
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[]){
// składnia: cltSQL maszyna port separator sterownik adres URL identyfikator hasło
// komputer port: komputer i port z katalogu usług CORBA, z którym należy się skontaktować
// usługa: nazwa usługi
// sterownik: sterownik, którego należy użyć dla bazy danych, z której będą pobierane dane
// urlBase: adres URL JDBC bazy danych, z której ma być pobierane dane
// id: identyfikator użytkownika
// mdp: hasło użytkownika
// separator: ciąg znaków oddzielający pola w wynikach zapytania
// sprawdzanie liczby argumentów
if(arg.length!=8)
erreur(syntaxe,1);
// inicjalizacja parametrów połączenia z bazą danych
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];
// parametry usługi katalogowej CORBA
String[] initORB={"-ORBInitialHost",arg[0],"-ORBInitialPort",arg[1]};
// klient CORBA – żądanie referencji serwera SQL
interSQL serveurSQL=getServeurSQL(machine,port,service);
// komunikacja klient-serwer
String requete=null;
String reponse=null;
String[] lignes=null;
String codeErreur=null;
try{
// otwarcie strumienia klawiatury
in=new BufferedReader(new InputStreamReader(System.in));
// śledzenie
System.out.println("--> Connexion à la base de données en cours");
// pierwsze żądanie połączenia z bazą danych
reponse=serveurSQL.connect(pilote,urlBase,id,mdp);
// monitorowanie
System.out.println("<-- "+reponse);
// analiza odpowiedzi
codeErreur=reponse.substring(0,3);
if(codeErreur.equals("500"))
erreur("Abandon sur erreur de connexion à la base",3);
// pętla odczytu zapytań do wysłania na serwer SQL
System.out.print("--> Requête : ");
requete=in.readLine().toLowerCase().trim();
while(! requete.equals("fin")){
// wysyłanie zapytania do serwera i odbieranie odpowiedzi
lignes=serveurSQL.executeSQL(requete,separateur);
// monitorowanie
afficheLignes(lignes);
// kolejne żądanie
System.out.print("--> Requête : ");
requete=in.readLine().toLowerCase().trim();
}// while
// monitorowanie
System.out.println("--> Fermeture de la connexion à la base de données distante");
// zamykanie połączenia
reponse=serveurSQL.close();
// kontynuacja
System.out.println("<-- " + reponse);
// koniec
System.exit(0);
// obsługa błędów
} catch (Exception e){
erreur("Abandon sur erreur : " + e,2);
}// próba
}// main
// ----------- AfficheLignes
private static void afficheLignes(String[] lignes){
for (int i=0;i<lignes.length;i++)
System.out.println("<-- " + lignes[i]);
}// afficheLignes
// ------------ błąd
private static void erreur(String msg, int exitCode){
// wyświetlenie komunikatu o błędzie
System.err.println(msg);
// ewentualne zwolnienie zasobów
try{
in.close();
serveurSQL.close();
} catch(Exception e){}
// zakończenie działania
System.exit(exitCode);
}// błąd
// ---------------------- getServeurSQL
private static interSQL getServeurSQL(String machine, String port, String service){
// żądanie referencji serwera SQL
// maszyna: maszyna z katalogu usług CORBA
// port: port katalogu usług CORBA
// usługa: nazwa usługi CORBA, o którą należy poprosić
// śledzenie
System.out.println("--> Connexion au serveur CORBA en cours...");
// numer identyfikacyjny serwera SQL
interSQL serveurSQL=null;
// parametry usługi katalogowej CORBA
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
try{
// żądamy obiektu CORBA do pracy – w tym celu przekazujemy port
// nasłuchowego katalogu usług CORBA
ORB orb=ORB.init(initORB,null);
// korzystamy z katalogu usług, aby zlokalizować serwer SQL
org.omg.CORBA.Object objRef=
orb.resolve_initial_references("NameService");
NamingContext ncRef=NamingContextHelper.narrow(objRef);
// szukana usługa nazywa się srvSQL – wysyłamy do niej żądanie
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
// zwracamy odwołanie do serwera
return serveurSQL;
}// getServeurSQL
}// klasa
Skompilujmy klasę klienta:
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
Jesteśmy gotowi do przeprowadzenia testów.
10.3.7. Testy
10.3.7.1. Pré-requis
Zakładamy, że baza danych o nazwie „Artykuły” (ACCESS) jest publicznie dostępna na komputerze z systemem Windows serwera SQL:

Baza ta ma następującą strukturę:
nazwa | typ |
code | 4-znakowy kod artykułu |
nom | nazwa (ciąg znaków) |
prix | jego cena (rzeczywista) |
stock_actu | aktualny stan magazynowy (liczba całkowita) |
stock_mini | minimalny stan magazynowy (liczba całkowita), poniżej którego należy uzupełnić zapasy danego artykułu |
10.3.7.2. Uruchomienie usługi katalogowej
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. Uruchomienie serwera SQL
Uruchamiamy serwer SQL:
W oknie wyświetla się komunikat DOS:
10.3.7.4. Uruchomienie klienta na tym samym komputerze co serwer
Oto wyniki uzyskane przy uruchomieniu klienta na tym samym komputerze co serwer:
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
--> Zapytanie: select nazwa, 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
--> Zapytanie: delete from articles where stock_mini<7
<-- 100 1
--> Zapytanie: select nazwa, 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. Uruchomienie klienta na komputerze innym niż serwer
Oto wyniki uzyskane przy użyciu klienta na komputerze innym niż serwer:
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
--> Zapytanie: 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. Odpowiedniki IDL – JAVA
Poniżej przedstawiamy powiązania między typami prostymi IDL i JAVA:
typ IDL | typ Java |
boolean | boolean |
char | char |
wchar | char |
bajt | byte |
ciąg znaków | java.lang.String |
wstring | java.lang.String |
short | short |
unsigned short | short |
long | int |
unsigned long | int |
long long | long |
unsigned long long | long |
float | float |
double | double |
Przypomnijmy, że aby zdefiniować tablicę elementów typu T w interfejsie IDL, stosuje się instrukcję:
a następnie używa się instrukcji nomType w celu odwołania się do typu tablicy. Tak więc w interfejsie serwera SQL zastosowano deklarację:
, aby resultats odnosiło się do tablicy String[] w Javie.