9. JAVA RMI
9.1. Вступ
Ми розглянули, як створювати мережеві програми за допомогою засобів зв’язку, що називаються sockets. У клієнт-серверній програмі, побудованій на основі цих засобів, зв’язком між клієнтом і сервером є протокол зв’язку, який вони використовують для взаємодії. Обидва додатки можуть бути написані на різних мовах: наприклад, Java для клієнта, Perl для сервера або будь-яка інша комбінація. Дійсно, маємо два окремі додатки, з’єднані протоколом зв’язку, відомим обом. Крім того, доступ до мережі через сокети не є прозорим для Java-додатка: він повинен використовувати клас Socket, створений спеціально для управління цими засобами зв’язку — sockets.
JAVA RMI (Remote Method Invocation) дозволяє створювати мережеві додатки з такими характеристиками:
- Клієнт-серверні додатки — це Java-додатки, розташовані на обох кінцях каналу зв’язку
- Клієнт може використовувати об’єкти, розташовані на сервері, так, ніби вони знаходяться локально
- Мережевий рівень стає прозорим: додаткам не потрібно турбуватися про те, як інформація передається з однієї точки в іншу.
Останній пункт є фактором переносимості: якщо мережевий рівень додатка RMI зміниться, сам додаток не доведеться переписувати. До нового мережевого рівня потрібно буде адаптувати лише класи RMI мови Java.
Принцип комунікації RMI полягає в наступному:
- Класичний Java-додаток написано на машині A. Він виконуватиме роль сервера. Для цього деякі його об’єкти будуть «опубліковані» на машині A, на якій виконується додаток, і таким чином стануть службами.
- Класичний Java-додаток написано на машині B. Він виконуватиме роль клієнта. Він матиме доступ до об’єктів/сервісів, опублікованих на машині A, тобто через віддалене посилання зможе оперувати ними так, ніби вони є локальними. Для цього йому потрібно знати структуру віддаленого об’єкта, до якого він хоче отримати доступ (методи та властивості).
9.2. Розглянемо приклад
Теорія, що лежить в основі інтерфейсу RMI, не є простою. Щоб краще розібратися в цьому, ми крок за кроком простежимо процес написання клієнт-серверного додатка з використанням пакета RMI для Java. Візьмемо приклад програми, який можна знайти в багатьох посібниках з RMI: клієнт викликає єдиний метод віддаленого об’єкта, який у відповідь повертає йому рядок символів. Тут ми розглянемо невеликий варіант: сервер повторює те, що йому надсилає клієнт. У цій книзі ми вже розглядали подібний приклад програми, що базується на сокетах.
9.2.1. Серверна програма
9.2.1.1. Крок 1: інтерфейс об’єкта/сервера
Віддалений об’єкт — це екземпляр класу, який повинен реалізовувати інтерфейс Remote, визначений у пакеті java.rmi. Методи об’єкта, доступні віддалено, — це ті, що оголошені в інтерфейсі, похідному від інтерфейсу Remote:
import java.rmi.*;
// віддалений інтерфейс
public interface interEcho extends Remote{
public String echo(String msg) throws java.rmi.RemoteException;
}
Тут оголошується інтерфейс interEcho, який визначає метод echo як доступний віддалено. Цей метод може генерувати виняток класу RemoteException, який об’єднує всі помилки, пов’язані з мережею.
9.2.1.2. Крок 2: написання об’єкта-сервера
На наступному етапі ми визначаємо клас, який реалізує попередній віддалений інтерфейс. Цей клас повинен бути похідним від класу UnicastRemoteObject, який має методи, що дозволяють віддалено викликати методи.
import java.rmi.*;
import java.rmi.server.*;
import java.net.*;
// клас, що реалізує віддалене відлуння
public class srvEcho extends UnicastRemoteObject implements interEcho{
// конструктор
public srvEcho() throws RemoteException{
super();
}// кінець конструктора
// метод, що реалізує відгук
public String echo(String msg) throws RemoteException{
return "[" + msg + "]";
}// кінець методу «echo»
}// кінець класу
У цьому класі ми бачимо:
- метод, що виконує ехо
- конструктор, який нічого не робить, окрім виклику конструктора батьківського класу. Він призначений для того, щоб заявити, що може генерувати виняток типу RemoteException.
Ми створимо екземпляр цього класу за допомогою методу main. Щоб об’єкт/сервіс був доступний ззовні, його потрібно створити та зареєструвати в каталозі об’єктів, доступних ззовні. Клієнт, який бажає отримати доступ до віддаленого об’єкта, діє наступним чином:
- він звертається до служби каталогу машини, на якій знаходиться потрібний йому об’єкт. Ця служба каталогу працює на порту, який клієнт повинен знати (за замовчуванням — 1099). Клієнт запитує у каталозі посилання на об’єкт/службу, вказавши її ім’я. Якщо це ім’я відповідає об’єкту/службі в каталозі, каталог повертає клієнту посилання, за допомогою якого клієнт зможе взаємодіяти з віддаленим об’єктом/службою.
- Відтепер клієнт може використовувати цей віддалений об’єкт так, ніби він є локальним
Повертаючись до нашого сервера, ми повинні створити об’єкт типу srvEcho і зареєструвати його в каталозі об’єктів, доступних ззовні. Ця реєстрація здійснюється за допомогою методу класу rebind класу Naming:
з
ім’ям: ім’я, яке буде пов’язане з віддаленим об’єктом
obj: віддалений об’єкт
Отже, наш клас srvEcho виглядає наступним чином:
import java.rmi.*;
import java.rmi.server.*;
import java.net.*;
// клас, що реалізує віддалене відлуння
public class srvEcho extends UnicastRemoteObject implements interEcho{
// конструктор
public srvEcho() throws RemoteException{
super();
}// кінець конструктора
// метод, що реалізує відлуння
public String echo(String msg) throws RemoteException{
return "[" + msg + "]";
}// кінець функції «echo»
// створення сервісу
public static void main (String arg[]){
try{
srvEcho serveurEcho=new srvEcho();
Naming.rebind("srvEcho",serveurEcho);
System.out.println("Serveur d’écho prêt");
} catch (Exception e){
System.err.println(" Erreur " + e + " lors du lancement du serveur d’écho ");
}
}// main
}// кінець класу
Коли читаєш попередню програму, складається враження, що вона зупиниться одразу після створення та збереження служби відлуння. Це не так. Оскільки клас srvEcho є похідним від класу UnicastRemoteObject, створений об’єкт виконується нескінченно: він очікує запитів клієнтів на анонімному порту, тобто такому, який система обирає залежно від обставин. Створення служби відбувається асинхронно: у цьому прикладі метод main створює службу та продовжує своє виконання: він дійсно виведе повідомлення «Сервер ехо готовий».
9.2.1.3. Крок 3: компіляція серверного додатка
На цьому етапі ми можемо скомпілювати наш сервер. Ми компілюємо файл interEcho.java з інтерфейсу interEcho, а також файл srvEcho.java з класу srvEcho. У результаті отримуємо відповідні файли .class: interEcho.class та srvEcho.class.
9.2.1.4. Крок 4: написання клієнта
Ми пишемо клієнт, якому передаємо як параметр файл URL з сервера-відлуння і який
- зчитує рядок, введений з клавіатури
- надсилає її на сервер відлуння
- виводить відповідь, яку той надсилає
- повертається до кроку 1 і зупиняється, коли введено рядок «fin».
У результаті отримуємо такий клієнт:
import java.rmi.*;
import java.io.*;
public class cltEcho {
public static void main(String arg[]){
// синтаксис: cltEcho URLService
// перевірка аргументів
if(arg.length!=1){
System.err.println("Syntaxe : pg url_service_rmi");
System.exit(1);
}
// діалог «клієнт-сервер»
String urlService=arg[0];
BufferedReader in=null;
String msg=null;
String reponse=null;
interEcho serveur=null;
try{
// відкриття потоку клавіатури
in=new BufferedReader(new InputStreamReader(System.in));
// локалізація служби
serveur=(interEcho) Naming.lookup(urlService);
// цикл зчитування повідомлень для відправки на сервер відлуння
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
while(! msg.equals("fin")){
// відправлення повідомлення на сервер та отримання відповіді
reponse=serveur.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
}// клас
У цьому клієнті немає нічого особливого, крім команди, яка запитує посилання на сервер:
Нагадаємо, що наш сервіс-відлуння було зареєстровано в каталозі сервісів машини, на якій він розташований, за допомогою команди:
Naming.rebind("srvEcho",serveurEcho);
Отже, клієнт також використовує метод класу Naming, щоб отримати посилання на сервер, яким він хоче скористатися. Використаний метод lookup приймає як параметр URL-адресу запитуваної служби. Вона має вигляд звичайної URL-адреси:
з
rmi: необов’язковий — протокол RMI
machine: ім’я або адреса IP машини, на якій працює сервер-відлуння — необов’язковий параметр, за замовчуванням localhost.
port: порт, на якому слухає служба каталогу цієї машини — необов’язковий, за замовчуванням 1099
nom_service: ім’я, під яким було зареєстровано запитувану службу (у нашому прикладі — srvEcho)
Отримується екземпляр віддаленого інтерфейсу interEcho. Якщо припустити, що клієнт і сервер знаходяться не на одній машині, то під час компіляції клієнта cltEcho.java у тому ж каталозі повинен бути файл interEcho.class, отриманий у результаті компіляції віддаленого інтерфейсу interEcho; інакше у рядках, що посилаються на цей інтерфейс, виникне помилка компіляції.
9.2.1.5. Крок 5: створення файлів .class, необхідних для клієнт-серверної програми
Щоб краще зрозуміти, що знаходиться на стороні сервера, а що — на стороні клієнта, розмістимо сервер у каталозі echo\serveur, а клієнт — у каталозі echo\client.
Каталог сервера містить такі вихідні файли:
E:\data\java\RMI\echo\serveur>dir *.java
INTERE~1 JAV 158 09/03/99 15:06 interEcho.java
SRVECH~1 JAV 759 09/03/99 15:07 srvEcho.java
Після компіляції цих двох вихідних файлів отримуємо такі файли .class:
E:\data\java\RMI\echo\serveur>dir *.class
SRVECH~1 CLA 1 129 09/03/99 15:58 srvEcho.class
INTERE~1 CLA 256 09/03/99 15:58 interEcho.class
У каталозі клієнта знаходиться такий вихідний файл:
а також файл interEcho.class, який було згенеровано під час компіляції сервера:
Після компіляції вихідного файлу отримано такі файли .class:
E:\data\java\RMI\echo\client>dir *.class
CLTECH~1 CLA 1 506 09/03/99 16:08 cltEcho.class
INTERE~1 CLA 256 09/03/99 15:59 interEcho.class
Якщо спробувати запустити клієнт cltEcho, з’явиться така помилка:
E:\data\java\RMI\echo\client>j:\jdk12\bin\java cltEcho rmi://localhost/srvEcho
Erreur : java.rmi.UnmarshalException: error unmarshalling return; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub
Якщо спробувати запустити сервер srvEcho, з’являється така помилка:
E:\data\java\RMI\echo\serveur>j:\jdk12\bin\java srvEcho
Erreur java.rmi.StubNotFoundException: Stub class not found: srvEcho_Stub; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub lors du lancement du serveur d’écho
В обох випадках віртуальна машина Java повідомляє, що не знайшла клас srvEcho_stub. Дійсно, ми ще ніколи не чули про цей клас. У клієнті локалізація сервера відбувалася за допомогою такої інструкції:
Тут urlservice — це рядок rmi://localhost/srvEcho із
Rmi — протокол RMI
Localhost — машина, на якій працює сервер — у даному випадку та сама машина, на якій працює клієнт. Зазвичай синтаксис має вигляд «машина:порт». Якщо порт не вказано, за замовчуванням використовується порт 1099. На цьому порту працює служба каталогу сервера.
srvEcho — це ім’я конкретної служби, до якої звертається запит
Під час компіляції помилок не було виявлено. Потрібно було лише, щоб файл interEcho.class віддаленого інтерфейсу був доступний.
Під час виконання віртуальна машина вимагає наявності файлу srvEcho_stub.class, якщо запитується служба srvEcho, а для служби X — як правило, файлу X_stub.class. Цей файл необхідний лише під час виконання, а не під час компіляції клієнта. Те саме стосується і сервера. Що ж це за файл?
На сервері знаходиться клас srvEcho.class, який є нашим віддаленим об’єктом/сервісом. Клієнт, навіть якщо йому не потрібен цей клас, все одно потребує свого роду його «образу», щоб мати змогу з ним спілкуватися. Насправді клієнт не надсилає свої запити безпосередньо до віддаленого об’єкта: він надсилає їх до свого локального образу srvEcho_stub.class, розташованого на тій самій машині, що й він сам. Ця локальна копія srvEcho_stub.class взаємодіє з аналогічною копією (srvEcho_stub.class), яка цього разу розміщена на сервері. Цей образ створюється на основі файлу .class із сервера за допомогою Java-інструменту під назвою rmic. У Windows використовується команда:
створить на основі файлу srvEcho.class ще два файли .class:
E:\data\java\RMI\echo\serveur>dir *.class
SRVECH~2 CLA 3 264 09/03/99 16:57 srvEcho_Stub.class
SRVECH~3 CLA 1 736 09/03/99 16:57 srvEcho_Skel.class
Серед них є файл srvEcho_stub.class, який необхідний клієнту та серверу для виконання. Також є файл srvEcho_Skel.class, роль якого наразі невідома. Створюємо копію файлу srvEcho_stub.class у каталозі клієнта та сервера і видаляємо файл srvEcho_Skel.class. Отже, маємо такі файли:
на стороні сервера:
E:\data\java\RMI\echo\serveur>dir *.class
SRVECH~1 CLA 1 129 09/03/99 15:58 srvEcho.class
INTERE~1 CLA 256 09/03/99 15:58 interEcho.class
SRVECH~1 CLA 3 264 09/03/99 16:01 srvEcho_Stub.class
на стороні клієнта:
E:\data\java\RMI\echo\client>dir *.class
CLTECH~1 CLA 1 506 09/03/99 16:08 cltEcho.class
INTERE~1 CLA 256 09/03/99 15:59 interEcho.class
SRVECH~1 CLA 3 264 09/03/99 16:01 srvEcho_Stub.class
9.2.1.6. Крок 6: Запуск клієнт-серверного додатка «echo»
Ми готові до запуску нашого клієнт-серверного додатка. Спочатку клієнт і сервер працюватимуть на одній машині. Спочатку потрібно запустити наш серверний додаток. Нагадаємо, що він:
- створює службу
- реєструє його в каталозі служб комп’ютера, на якому працює сервер «ехо»
Останній пункт вимагає наявності служби каталогу. Її запускають за допомогою команди:
rmiregistry — це служба каталогу. Тут вона запускається у фоновому режимі у вікні командного рядка Windows за допомогою команди start. Коли каталог активний, можна створити службу «echo» та зареєструвати її в каталозі служб. І тут вона запускається у фоновому режимі за допомогою команди start:
Сервер «echo» працює в новому вікні DOS і відображає, як було задано:
Залишилося лише запустити та протестувати наш клієнт:
E:\data\java\RMI\echo\client>j:\jdk12\bin\java cltEcho rmi://localhost/srvEcho
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin
9.2.1.7. Клієнт і сервер на двох різних машинах
У попередньому прикладі клієнт і сервер знаходилися на одній машині. Тепер розмістимо їх на різних машинах:
- сервер — на комп’ютері з ОС Windows
- клієнт — на комп’ютері з ОС Linux
Сервер запускається, як і раніше, на комп’ютері з Windows. На комп’ютер з Linux було перенесено файли .class клієнта:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir
total 9
drwxr-xr-x 2 serge admin 1024 Mar 10 10:02 .
drwxr-xr-x 4 serge admin 1024 Mar 10 10:01 ..
-rw-r--r-- 1 serge admin 1506 Mar 10 10:02 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
-rw-r--r-- 1 serge admin 3264 Mar 10 10:02 srvEcho_Stub.class
Клієнт запущено:
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message : msg1
Erreur : java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:
java.rmi.UnmarshalException: error unmarshalling call header; nested exception is:
java.rmi.UnmarshalException: skeleton class not found but required for client version
Отже, маємо помилку: віртуальна машина Java, судячи з усього, вимагає файл srvEcho_skel.class, який був створений утилітою rmic, але до цього моменту не використовувався. Ми створюємо його заново та також переносимо на машину під управлінням Linux:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir
total 11
drwxr-xr-x 2 serge admin 1024 Mar 10 10:17 .
drwxr-xr-x 4 serge admin 1024 Mar 10 10:01 ..
-rw-r--r-- 1 serge admin 1506 Mar 10 10:02 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
-rw-r--r-- 1 serge admin 1736 Mar 10 10:17 srvEcho_Skel.class
-rw-r--r-- 1 serge admin 3264 Mar 10 10:02 srvEcho_Stub.class
З’являється та сама помилка, що й раніше... Тож ми замислюємося і перечитуємо документацію щодо RMI. Зрештою доходимо висновку, що, можливо, саме серверу потрібен той самий файл srvEcho_Skel.class. Тож на комп’ютері з Windows ми перезапускаємо сервер із двома файлами srvEcho_Stub.class та srvEcho_Skel.class, а також :
E:\data\java\RMI\echo\serveur>dir *.class
SRVECH~1 CLA 1 129 09/03/99 15:58 srvEcho.class
INTERE~1 CLA 256 09/03/99 15:58 interEcho.class
SRVECH~2 CLA 3 264 10/03/99 9:05 srvEcho_Stub.class
SRVECH~3 CLA 1 736 10/03/99 9:05 srvEcho_Skel.class
E:\data\java\RMI\echo\serveur>start j:\jdk12\bin\java srvEcho
потім на комп’ютері з Linux ми знову тестуємо клієнт, і цього разу все працює:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1506 Mar 10 10:02 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
-rw-r--r-- 1 serge admin 3264 Mar 10 10:02 srvEcho_Stub.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin
Отже, з цього випливає, що на стороні сервера повинні бути присутні обидва файли: srvEcho_Stub.class та srvEcho_Skel.class. На стороні клієнта до цього часу був необхідний лише файл srvEcho_Stub.class. Він виявився необхідним, коли клієнт і сервер знаходилися на одній машині під управлінням Windows. У Linux його видаляємо, щоб перевірити...
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1506 Mar 10 10:02 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
*** Security Exception: No security manager, stub class loader disabled ***
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
at sun.rmi.server.RMIClassLoader.getClassLoader(RMIClassLoader.java:84)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:88)
at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at java.io.ObjectInputStream.inputObject(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:105)
at java.rmi.Naming.lookup(Naming.java:60)
at cltEcho.main(cltEcho.java:28)
Erreur : java.rmi.UnexpectedException: Unexpected exception; nested exception is:
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
Ми маємо цікаву помилку, яка, схоже, вказує на те, що віртуальна машина Java намагалася завантажити відомий клас stub, але не змогла цього зробити через відсутність «менеджера безпеки» (security manager). Здається, ми бачили щось на цю тему в документації. Повертаємося до документації... і виявляємо, що сервер повинен створити та встановити менеджер безпеки (security manager), який гарантує клієнтам, що запитують завантаження класів, їхню безпеку. За відсутності цього менеджера безпеки завантаження класів неможливе. Це здається логічним: наш клієнт під Linux звернувся до сервера з запитом на клас srvEcho_stub.class, який йому потрібен, а сервер відмовив, повідомивши, що менеджер безпеки не встановлено. Тому ми змінюємо код функції main на сервері наступним чином:
// створення служби
public static void main (String arg[]){
// встановлення менеджера безпеки
System.setSecurityManager(new RMISecurityManager());
// запуск та реєстрація служби
try{
srvEcho serveurEcho=new srvEcho();
Naming.rebind("srvEcho",serveurEcho);
System.out.println("Serveur d’écho prêt");
} catch (Exception e){
System.err.println(" Erreur " + e + " lors du lancement du serveur d’écho ");
}
}// головна
Компілюємо та створюємо файли srvEcho_stub.class та srvEcho_Skel.class за допомогою інструменту rmic. Запускаємо службу каталогу (rmiregistry), а потім сервер — і отримуємо помилку, якої раніше не було!
Erreur java.security.AccessControlException: access denied (java.net.SocketPermission 127.0.0.1:1099 connect,resolve) lors du lancement du serveur d’écho
Схоже, менеджер безпеки виявився надто ефективним. Знову переглядаємо документацію... Виявляється, що коли менеджер безпеки активний, під час запуску програми потрібно вказати її права. Це робиться за допомогою такого параметра:
start j:\jdk12\bin\java -Djava.security.policy=mypolicy srvEcho
де
java.security.policy — це ключове слово
mypolicy — це текстовий файл, що визначає права програми. У даному випадку це такий файл:
У цьому випадку програма має всі права.
Почнемо спочатку. Перейдіть до каталогу сервера та виконайте послідовно:
- запуск служби каталогу: start j:\jdk12\bin\rmiregistry
- запуск сервера: start j:\jdk12\bin\java -Djava.security.policy=mypolicy srvEcho
І цього разу сервер echo (а клієнт — ще ні) запускається правильно. Тепер ви можете провести такий експеримент:
- зупиніть сервер echo, а потім службу каталогу
- перезапустіть службу каталогу, перебуваючи в іншому каталозі, ніж той, де розташований сервер
- поверніться до каталогу сервера запустіть сервер ехо — ви отримаєте таку помилку:
Erreur java.rmi.ServerException: RemoteException occurred in server thread; nes
ted exception is:
java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub lors du lancement du serveur d’écho
З цього випливає, що каталог, з якого запускається служба каталогу, має значення. У даному випадку Java не знайшла клас srvEcho_stub.class, оскільки служба каталогу не була запущена з каталогу сервера. Під час запуску сервера можна вказати, в якому каталозі знаходяться класи, необхідні для роботи сервера:
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/serveur/
srvEcho
Команда складається з одного рядка. Ключове слово java.rmi.server.codebase використовується для вказівки URL каталогу, що містить класи, необхідні для роботи сервера. У цьому випадку цей URL визначає протокол file, який є протоколом доступу до локальних файлів, та каталог, що містить файли .class сервера. Отже, якщо виконати наступні дії:
- зупинити службу каталогу
- перезапуск служби каталогів з іншого каталогу, відмінного від каталогу сервера
- у каталозі сервера запустити його за допомогою команди (один рядок):
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/serveur/
srvEcho
Сервер запущено успішно. Тепер можна перейти до клієнта. Перевіряємо його:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1506 Mar 10 14:28 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
*** Security Exception: No security manager, stub class loader disabled ***
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
at sun.rmi.server.RMIClassLoader.getClassLoader(RMIClassLoader.java:84)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:88)
at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at java.io.ObjectInputStream.inputObject(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:105)
at java.rmi.Naming.lookup(Naming.java:60)
at cltEcho.main(cltEcho.java:31)
Erreur : java.rmi.UnexpectedException: Unexpected exception; nested exception is:
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
З’являється та сама помилка, що вказує на відсутність менеджера безпеки. Ми припускаємо, що, можливо, помилилися і що саме клієнт повинен створити свій менеджер безпеки. Ми залишаємо сервер із його менеджером безпеки, але створюємо менеджера безпеки також і для клієнта. Функція main клієнта cltEcho.java тоді виглядає так:
public static void main(String arg[]){
// синтаксис: cltEcho порт машини
// машина: машина, на якій працює сервер відлуння
// порт: порт, на якому працює каталог служб на машині служби ехо
// перевірка аргументів
if(arg.length!=1){
System.err.println("Syntaxe : pg url_service_rmi");
System.exit(1);
}
// встановлення менеджера безпеки
System.setSecurityManager(new RMISecurityManager());
// взаємодія «клієнт-сервер»
String urlService=arg[0];
BufferedReader in=null;
String msg=null;
String reponse=null;
interEcho serveur=null;
try{
....
} catch (Exception e){
....
}// try
}// main
Далі виконуємо такі дії:
- перекомпілюємо cltEcho.java
- переносимо файли .class на комп’ютер під управлінням Linux
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1506 Mar 10 14:28 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
- запускаємо клієнт
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
java.io.FileNotFoundException: /e:/data/java/rmi/echo/serveur/srvEcho_Stub.class
at java.io.FileInputStream.<init>(FileInputStream.java)
at sun.net.www.protocol.file.FileURLConnection.connect(FileURLConnection.java:150)
at sun.net.www.protocol.file.FileURLConnection.getInputStream(FileURLConnection.java:170)
at sun.applet.AppletClassLoader.loadClass(AppletClassLoader.java:119)
at sun.applet.AppletClassLoader.findClass(AppletClassLoader.java:496)
at sun.applet.AppletClassLoader.loadClass(AppletClassLoader.java:199)
at sun.rmi.server.RMIClassLoader.loadClass(RMIClassLoader.java:159)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:97)
at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at java.io.ObjectInputStream.inputObject(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:105)
at java.rmi.Naming.lookup(Naming.java:60)
at cltEcho.main(cltEcho.java:31)
File not found when looking for: srvEcho_Stub
Erreur : java.rmi.UnmarshalException: Return value class not found; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub
Незважаючи на зовнішні ознаки, ми просуваємося вперед: помилка вже інша. Ми бачимо, що клієнт зміг запросити у сервера клас srvEcho_Stub.class, але сервер його не знайшов. Отже, клієнт повинен мати менеджер безпеки, якщо хоче мати можливість запитувати класи у сервера.
Якщо розглянути попередню помилку, то бачимо, що файл srvEcho_Stub.class шукали в каталозі e:/data/java/rmi/echo/serveur/, але його не знайшли. Проте він саме там і знаходиться. Якщо уважніше придивитися до списку методів, пов’язаних із цією помилкою, можна знайти такий: sun.net.www.protocol.file.FileURLConnection.getInputStream. Схоже, клієнт відкрив потік з об’єктом типу FileURLConnection. Можна припустити, що все це пов’язано зі способом запуску нашого сервера:
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/serveur/
srvEcho
Повідомлення про помилку, схоже, посилається на значення ключового слова java.rmi.server.codebase. Якщо ще раз переглянути документацію, то можна побачити, що значення цього ключового слова у наведених прикладах завжди таке: http://.., c.a.d, а використовуваний протокол — http. Незрозуміло, як саме клієнт запитує та отримує свої класи від сервера. Можливо, він запитує їх за допомогою ключового слова URL, яке було вказано під час запуску сервера разом із ключовими словами java.rmi.server.codebase та URL. Тому ми вирішили запустити сервер за допомогою такої нової команди:
start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/echo/ srvEcho
Тепер протоколом є http. Необхідно перемістити файли .class у місце, доступне для http-сервера на комп’ютері, на якому будуть зберігатися класи. У нашому прикладі сервер працює на комп’ютері з ОС Windows із сервером HTTP PWS від Microsoft. Кореневий каталог цього сервера — d:\Inetpub\wwwroot. Тому діємо наступним чином:
- створюємо каталог d:\Inetpub\wwwroot\rmi\echo
- поміщаємо туди файли .class із сервера, а також файл mypolicy
- запускаємо веб-сервер, якщо це ще не зроблено
- перезапускаємо службу каталогу (rmiregistry)
- перезапускаємо сервер за допомогою команди
start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/echo/ srvEcho
- на комп’ютері з ОС Linux запускаємо клієнт:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1622 Mar 10 14:37 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin
Уф! Все працює. Клієнту вдалося завантажити той самий файл srvEcho_Stub.class.
Все це надихнуло нас на певні ідеї, і ми замислилися, чи клієнт, який працює на сервері під Windows, також функціонуватиме без файлу srvEcho_Stub.class. Переходимо до каталогу клієнта, видаляємо файл srvEcho_Stub.class, якщо він там є, і запускаємо клієнт так само, як і в Linux:
E:\data\java\RMI\echo\client>dir *.class
CLTECH~1 CLA 1 622 10/03/99 14:12 cltEcho.class
INTERE~1 CLA 256 09/03/99 15:59 interEcho.class
E:\data\java\RMI\echo\client>j:\jdk12\bin\java -Djava.security.policy=mypolicy cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message : nouveau message
Réponse serveur : [nouveau message]
Message : fin
9.2.1.8. Résumé
Сторона сервера Windows:
- сервер має менеджер безпеки
- він був запущений із такими параметрами: start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/echo/ srvEcho
На стороні клієнта (Linux або Windows)
- клієнт має менеджер безпеки
- на Linux він був запущений командою java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
- у Windows його запущено за допомогою j:\jdk12\bin\java -Djava.security.policy=mypolicy cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
9.2.1.9. Сервер-відлуння на Linux, клієнти на Windows та Linux
Тепер переносимо сервер на комп’ютер під управлінням Linux і тестуємо клієнти під Linux та Windows. Послідовність дій така:
- переносимо файли .class із сервера на машину під управлінням Linux
shiva[serge]:/home/admin/serge/WWW/rmi/echo/serveur#
$ dir
total 11
drwxr-xr-x 2 serge admin 1024 Mar 10 16:15 .
drwxr-xr-x 3 serge admin 1024 Mar 10 16:09 ..
-rw-r--r-- 1 serge admin 256 Mar 10 16:09 interEcho.class
-rw-r--r-- 1 serge admin 1245 Mar 10 16:09 srvEcho.class
-rw-r--r-- 1 serge admin 1736 Mar 10 16:09 srvEcho_Skel.class
-rw-r--r-- 1 serge admin 3264 Mar 10 16:09 srvEcho_Stub.class
- оскільки клас srvEcho_Stub.class буде запитуватися клієнтами, для класів сервера обрано каталог, доступний для HTTP-сервера на машині під управлінням Linux. Тут URL у цьому каталозі — це http://shiva.istia.univ-angers.fr/~serge/rmi/echo/serveur
- Служба реєстру запускається у фоновому режимі: /usr/local/bin/jdk/rmiregistry &
- сервер запускається у фоновому режимі: /usr/local/bin/jdk/bin/java
-Djava.rmi.server.codebase=http://shiva.istia.univ-angers.fr/~serge/rmi/echo/serveur/
srvEcho &
Можна протестувати клієнти. Спочатку клієнт для Windows.
- Переходимо до каталогу клієнта на комп'ютері з Windows
- запускаємо клієнт за допомогою команди:
E:\data\java\RMI\echo\client>j:\jdk12\bin\java -Djava.security.policy=mypolicy cltEcho rmi://shiva.istia.univ-angers.fr/srvEcho
Message : msg1
Réponse serveur : [msg1]
Message : fin
Перевіряємо роботу клієнта під Linux:
shiva[serge]:/home/admin/serge/java/rmi/echo/client#
$ java cltEcho srvEcho
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin
Слід зазначити, що для клієнта Linux, який працює на тій самій машині, що й сервер echo, не було потреби вказувати машину в URL запитуваної служби.
9.3. Другий приклад: сервер SQL на комп’ютері з ОС Windows
9.3.1. Проблема
У розділі JDBC ми розглянули, як працювати з реляційними базами даних. У наведених прикладах програми та використовувана база даних знаходилися на одному комп’ютері з ОС Windows. Тут ми пропонуємо створити сервер RMI на комп’ютері з ОС Windows, який дозволить віддаленим клієнтам користуватися публічними базами даних ODBC того комп’ютера, на якому розташований сервер.

Клієнт RMI може виконувати 3 операції:
- підключитися до обраної бази даних
- надсилати запити SQL
- закрити з'єднання
Сервер виконує запити SQL від клієнта та надсилає йому результати. Це його основна функція, тому ми називатимемо його сервером SQL.
Ми застосовуємо різні етапи, розглянуті раніше на прикладі сервера-відлуння.
9.3.2. Етап 1: віддалений інтерфейс
Віддалений інтерфейс — це інтерфейс, який містить перелік методів сервера RMI, доступних для клієнтів 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: клієнт підключається до віддаленої бази даних, вказуючи її pilote, url, JDBC, а також свій ідентифікатор id та пароль mdp для доступу до цієї бази. Сервер повертає йому рядок символів, що вказує на результат підключення:
executeSQL: клієнт запитує виконання запиту SQL у базі даних, до якої він підключений. Він вказує символ, який має розділяти поля у результатах, що йому повертаються. Сервер повертає масив рядків:
для запиту на оновлення бази даних, де n — кількість оновлених рядків
якщо запит призвів до помилки
якщо запит не дав жодних результатів
якщо запит дав результати. Рядки, які сервер повертає таким чином, є результатами запиту.
close: клієнт закриває з’єднання з віддаленою базою даних. Сервер повертає рядок, що вказує на результат цього закриття:
9.3.3. Крок 2: Код сервера
Далі наведено вихідний код Java для сервера SQL. Для його розуміння необхідно засвоїти принципи управління базами даних JDBC, а також побудову серверів RMI. Коментарі до програми повинні полегшити її розуміння.
// імпортовані пакети
import java.rmi.*;
import java.rmi.server.*;
import java.sql.*;
import java.util.*;
// клас srvSQL
public class srvSQL extends UnicastRemoteObject implements interSQL{
// глобальні дані класу
private Connection DB;
// ------------- конструктор
public srvSQL() throws RemoteException{
super();
}
// --------------- підключення
public String connect(String pilote, String url, String id,
String mdp) throws RemoteException{
// підключення до бази даних за 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)
throws RemoteException{
// виконує запит 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() throws RemoteException {
// закриває з'єднання з базою даних
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;
}
// ----------- main
public static void main (String[] args){
// менеджер безпеки
System.setSecurityManager(new RMISecurityManager());
// запуск служби
srvSQL serveurSQL=null;
try{
// створення
serveurSQL=new srvSQL();
// реєстрація
Naming.rebind("srvSQL",serveurSQL);
// відстеження
System.out.println("Serveur SQL prêt");
} catch (Exception e){
// помилка
System.err.println("Erreur lors du lancement du serveur SQL ("+ e +")");
}// try-catch
}// main
}// клас
9.3.4. Запуск клієнта RMI
Клієнт сервера RMI викликається з такими параметрами:
urlserviceAnnuaire: URL-адреса RMI служби каталогів, яка зареєструвала сервер SQL
драйвер: драйвер, який повинен використовувати сервер SQL для управління базою даних
urlBase: URL-адреса JDBC бази даних, яку потрібно керувати
id: ідентифікатор клієнта або null, якщо ідентифікатор відсутній
mdp: пароль клієнта або null, якщо пароль відсутній
separateur: символ, який сервер SQL повинен використовувати для розділення полів у рядках результатів запиту
Ось приклад можливих параметрів:
де:
RMI-ім'я сервера SQL
стандартний драйвер баз даних з інтерфейсом ODBC
для використання бази даних товарів, заявленої у списку загальнодоступних баз даних ODBC на комп’ютері з ОС Windows
немає ідентифікатора
без пароля
поля результатів будуть розділені комою
Після запуску з наведеними вище параметрами клієнт виконує такі кроки:
- він підключається до сервера RMI srvSQL, тобто до сервера RMI, розташованого на тій самій машині, що й клієнт
- він запитує підключення до бази даних товарів
- він просить користувача ввести запит SQL з клавіатури
- він надсилає його на сервер SQL
- він відображає на екрані результати, отримані від сервера
- він знову просить користувача ввести запит SQL з клавіатури. Програма зупиниться, коли запит буде завершено.
Далі наведено Java-код клієнта. Коментарів має вистачити для його розуміння.
import java.rmi.*;
import java.io.*;
public class cltSQL {
// глобальні дані класу
private static String syntaxe =
"syntaxe : cltSQL urlServiceAnnuaire pilote urlBase id mdp separateur";
private static BufferedReader in=null;
private static interSQL serveurSQL=null;
public static void main(String arg[]){
// синтаксис: cltSQL urlServiceAnnuaire роздільник драйвер url id пароль
// urlServiceAnnuaire: URL-адреса каталогу служб RMI, до яких слід звернутися
// драйвер: драйвер, який слід використовувати для роботи з базою даних
// urlBase : URL-адреса бази даних (JDBC) для обробки
// id: ідентифікатор користувача
// mdp: його пароль
// separateur: рядок, що розділяє поля у результатах запиту
// перевірка кількості аргументів
if(arg.length!=6)
erreur(syntaxe,1);
// ініціалізація параметрів підключення до бази даних
String urlService=arg[0];
String pilote=arg[1];
String urlBase=arg[2];
String id, mdp, separateur;
if(arg[3].equals("null")) id=""; else id=arg[3];
if(arg[4].equals("null")) mdp=""; else mdp=arg[4];
if(arg[5].equals("null")) separateur=" "; else separateur=arg[5];
// встановлення менеджера безпеки
System.setSecurityManager(new RMISecurityManager());
// діалог «клієнт-сервер»
String requete=null;
String reponse=null;
String[] lignes=null;
String codeErreur=null;
try{
// відкриття потоку клавіатури
in=new BufferedReader(new InputStreamReader(System.in));
// моніторинг
System.out.println("--> Connexion au serveur RMI en cours...");
// локалізація служби
serveurSQL=(interSQL) Naming.lookup(urlService);
// відстеження
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);
}// помилка
}// клас
9.3.5. Крок 3: створення файлів .class
- сервер скомпільовано
E:\data\java\RMI\sql\serveur>j:\jdk12\bin\javac interSQL.java
E:\data\java\RMI\sql\serveur>j:\jdk12\bin\javac srvSQL.java
E:\data\java\RMI\sql\serveur>dir *.class
INTERS~1 CLA 451 12/03/99 17:54 interSQL.class
SRVSQL~1 CLA 3 238 12/03/99 17:54 srvSQL.class
- створено файли Stub та Skel
E:\data\java\RMI\sql\serveur>j:\jdk12\bin\rmic srvSQL
E:\data\java\RMI\sql\serveur>dir *.class
INTERS~1 CLA 451 12/03/99 17:54 interSQL.class
SRVSQL~1 CLA 3 238 12/03/99 17:54 srvSQL.class
SRVSQL~2 CLA 4 491 12/03/99 17:56 srvSQL_Stub.class
SRVSQL~3 CLA 2 414 12/03/99 17:56 srvSQL_Skel.class
- файли interSQL.class, srvSQL_Stub.class, srvSQL_Skel.class переносяться до каталогу клієнта
E:\data\java\RMI\sql\client>dir
CLTSQL~1 JAV 3 486 11/03/99 11:39 cltSQL.java
INTERS~1 CLA 451 11/03/99 10:55 interSQL.class
SRVSQL~1 CLA 4 491 11/03/99 13:19 srvSQL_Stub.class
SRVSQL~2 CLA 2 414 11/03/99 13:19 srvSQL_Skel.class
- компілюємо клієнт
E:\data\java\RMI\sql\client>j:\jdk12\bin\javac cltSQL.java
E:\data\java\RMI\sql\client>dir *.class
INTERS~1 CLA 451 11/03/99 10:55 interSQL.class
CLTSQL~1 CLA 2 839 12/03/99 18:00 cltSQL.class
SRVSQL~1 CLA 4 491 11/03/99 13:19 srvSQL_Stub.class
SRVSQL~2 CLA 2 414 11/03/99 13:19 srvSQL_Skel.class
9.3.6. Крок 4: Тестування з сервером і клієнтом на одній машині під Windows
- Служба каталогу запускається в каталозі, відмінному від каталогу сервера та клієнта
- файл mypolicy розміщується у каталогах клієнта та сервера
- запускаємо сервер
E:\data\java\RMI\sql\serveur>start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/sql/serveur/ srvSQL
- запускаємо клієнт
E:\data\java\RMI\sql\client>j:\jdk12\bin\java -Djava.security.policy=mypolicy cltSQL srvSQL
sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Connexion au serveur RMI en cours...
--> Connexion à la base de données en cours
<-- 200 Connexion réussie
--> Запит: select name, stock_actu from articles order by stock_actu desc
<-- 101 vélo,31
<-- 101 essai3,13
<-- 101 skis nautiques,13
<-- 101 canoé,13
<-- 101 panthère,11
<-- 101 léopard,11
<-- 101 cachalot,10
<-- 101 fusil,10
<-- 101 arc,10
--> Запит: update articles set stock_actu=stock_actu-1 where stock_actu<=11
<-- 100 5
--> Запит: select nom, stock_actu from articles order by stock_actu asc
<-- 101 cachalot,9
<-- 101 fusil,9
<-- 101 arc,9
<-- 101 panthère,10
<-- 101 léopard,10
<-- 101 essai3,13
<-- 101 skis nautiques,13
<-- 101 canoé,13
<-- 101 vélo,31
--> Requête : fin
--> Fermeture de la connexion à la base de données distante
<-- 200 Base fermée
9.3.7. Крок 5: Тестування з сервером на комп’ютері з ОС Windows та клієнтом на комп’ютері з ОС Linux
- за необхідності зупиняємо сервер та службу каталогу
- переносимо файли .class клієнта на комп’ютер під управлінням Linux
shiva[serge]:/home/admin/serge/java/rmi/sql/client#
$ dir *.class
-rw-r--r-- 1 serge admin 2839 Mar 11 14:37 cltSQL.class
-rw-r--r-- 1 serge admin 451 Mar 11 14:37 interSQL.class
- файли з сервера розміщуються в каталозі, доступному для HTTP-сервера на комп'ютері з ОС Windows
D:\Inetpub\wwwroot\rmi\sql>dir
INTERS~1 CLA 451 11/03/99 10:55 interSQL.class
SRVSQL~1 CLA 3 238 11/03/99 13:19 srvSQL.class
SRVSQL~2 CLA 4 491 11/03/99 13:19 srvSQL_Stub.class
SRVSQL~3 CLA 2 414 11/03/99 13:19 srvSQL_Skel.class
MYPOLICY 81 08/06/98 15:01 mypolicy
- відновлюємо роботу довідкової служби
- перезапускаємо сервер з параметрами, відмінними від тих, що використовувалися в попередньому тесті
D:\Inetpub\wwwroot\rmi\sql>start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/sql/ srvSQL
- запускаємо клієнт на комп'ютері під управлінням Linux
/usr/local/bin/jdk/bin/java cltSQL rmi://tahe.istia.univ-angers.fr/srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Запит: select nom,stock_actu,stock_mini from articles order by nom
<-- 101 arc,9,8
<-- 101 cachalot,9,6
<-- 101 canoé,13,7
<-- 101 essai3,13,9
<-- 101 fusil,9,8
<-- 101 léopard,10,7
<-- 101 panthère,10,7
<-- 101 skis nautiques,13,8
<-- 101 vélo,31,8
--> Запит: update articles set stock_actu=stock_mini where stock_mini<=7
<-- 100 4
--> Запит: select ім'я, stock_actu, stock_mini from articles, упорядковано за ім'ям
<-- 101 arc,9,8
<-- 101 cachalot,6,6
<-- 101 canoé,7,7
<-- 101 essai3,13,9
<-- 101 fusil,9,8
<-- 101 léopard,7,7
<-- 101 panthère,7,7
<-- 101 skis nautiques,13,8
<-- 101 vélo,31,8
--> Requête : fin
--> Fermeture de la connexion à la base de données distante
<-- 200 Base fermée
9.3.8. Висновок
Перед нами цікава програма, оскільки вона дозволяє отримати доступ до бази даних з будь-якого комп’ютера в мережі. Цю програму цілком можна було б написати традиційним способом із використанням сокетів, що, до речі, і вимагається в одному із завдань розділу про бази даних. Якби ми написали цю програму традиційним способом:
- ми б отримали клієнт і сервер, які могли б бути написані на різних мовах
- клієнт і сервер спілкувалися б шляхом обміну рядками тексту, і їхній діалог міг би виглядати приблизно так:
де перші два параметри вказують, де знайти сервер, а наступні чотири — параметри підключення до бази даних, з якою потрібно працювати
Сервер міг би відповісти чимось на кшталт:
щоб попросити сервер виконати запит SQL до бази даних, до якої підключений клієнт. separateur — це символ, що розділяє поля у рядках відповіді.
Сервер може відповісти приблизно так
на запит щодо оновлення бази даних, де n — кількість оновлених рядків
якщо запит призвів до помилки
якщо запит не дав жодних результатів
якщо запит дав результати. Рядки, які сервер повернув у такий спосіб, є результатами запиту.
для закриття з’єднання з віддаленою базою даних. Сервер може повернути рядок, що вказує на результат цього закриття:
Звідси видно, що якщо ми здатні створити традиційний додаток із протоколом, подібним до наведеного вище, то можемо вивести можливу структуру сервера RMI. Там, де в протоколі є фраза від клієнта до сервера такого типу:
то на сервері RMI може існувати метод
, і цей метод, доступний для клієнта, повинен бути частиною опублікованого інтерфейсу сервера.
На завершення зауважимо, що наш сервер наразі обслуговує лише одного клієнта: у поточній реалізації він не може обслуговувати декількох клієнтів. Дійсно, якщо клієнт підключається до бази даних B1, сервер створює об’єкт Connection DB=DB1. Якщо другий клієнт запитує підключення до бази B2, сервер фіксує це, створюючи Connection DB=DB2, тим самим розриваючи з’єднання першого клієнта з базою B1.
9.4. Вправи
9.4.1. Вправа 1
Розширте попередній сервер SQL так, щоб він міг обслуговувати декількох клієнтів.
9.4.2. Вправа 2
Напишіть Java-аплет для електронної комерції, представлений у вправах розділу JDBC, так, щоб він працював із сервером RMI із попередньої вправи.