Skip to content

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:

Image

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:

  1. napisanie interfejsu serwera za pomocą IDL (Interface Definition Language)
  2. wygenerowanie klas „szkieletowych” i „stubowych” serwera
  3. napisanie kodu serwera
  4. napisanie klienta
  5. kompilacja wszystkich klas
  6. uruchomienie katalogu usług CORBA
  7. uruchomienie serwera
  8. 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:

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

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:

module echo

odpowiada pakietowi echo w Javie. Kompilacja interfejsu spowoduje utworzenie pakietu Java o nazwie echo c.a.d – katalogu zawierającego klasy Java.

interface iSrvEcho

odpowiada interfejsowi iSrvEcho w Javie. Spowoduje utworzenie interfejsu Java.

string echo(in string msg)

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:

iSrvEcho.java

interfejs serwera

_iSrvEchoImplbase.java

implementuje powyższy interfejs iSrvEcho. Jest to klasa abstrakcyjna, „szkielet” serwera, dostarczająca serwerowi funkcje CORBA niezbędne dla aplikacji rozproszonej.

_iSrvEchoStub.java

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.

iSrvEchoHelper.java

Zapewnia metody niezbędne do zarządzania odwołaniami do obiektów CORBA

iSrvEchoHolder.java

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:

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

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
    ncRef.rebind(path,serveurEcho);
    orb.connect(serveurEcho);

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

10.2.7.1. Uruchomienie usługi katalogowej

Na komputerze z systemem Windows uruchamiamy usługę katalogową w następujący sposób:

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

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:

    Serveur d’écho prêt

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.

Image

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:

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

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:

    100 n

dla zapytania o aktualizację bazy, gdzie n oznacza liczbę zaktualizowanych wierszy

    500 msg d’erreur

jeśli zapytanie spowodowało błąd

    501 Pas de résultats

jeśli zapytanie nie zwróciło żadnych wyników

    101 ligne1
    101 ligne2
    101 ...

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:

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

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”:
typedef sequence<string> 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
resultats executeSQL(in string requete, in string separateur);

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 port nomServiceAnnuaire pilote urlBase id mdp separateur

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:

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

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
connect(‘’sun.jdbc.odbc.JdbcOdbcDriver’’, ‘‘jdbc:odbc:articles’’, ’’’’, ’’’’)
  • prosi użytkownika o wpisanie zapytania SQL na klawiaturze
  • wysyła ją do serwera SQL
executeSQL(requete, ’’,’’);
  • 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:

Image

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:

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

W oknie wyświetla się komunikat DOS:

    Serveur SQL prêt

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ę:

    typedef   sequence<T> nomType;

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ę:

    typedef sequence<string> resultats;

, aby resultats odnosiło się do tablicy String[] w Javie.