Вітання! Сьогодні ми розглянемо досить цікаву тему – RMI . Це розшифровується як Remote Method Invocation – віддалений виклик методів. За допомогою RMI ти можеш навчити дві програми спілкуватися між собою навіть якщо вони знаходяться на різних комп'ютерах. Звучить круто? :) А це не так вже й складно зробити! У сьогоднішній лекції ми розберемося, із яких частин складається RMI-взаємодія та як його налаштувати. Перше, що нам знадобиться – це клієнт та сервер. Можеш особливо не заглиблюватись у комп'ютерну термінологію. У випадку RMI це просто дві програми. Одна з них міститиме якийсь об'єкт, а друга викликатиме методи цього об'єкта. Викликати в одній програмі методи об'єкта, що знаходиться в іншій програмі, – такого ми ще не робабо! Саме час спробувати! :) Щоб не потонути в нетрях, нехай наша програма буде простою. Взагалі, на серверах зазвичай відбуваються якісь обчислення, які запитує клієнт. Так буде й у нас. У ролі сервера у нас буде проста програма-калькулятор. У неї буде лише один метод —
multiply()
. Він множитиме два числа, які йому відправила програма-клієнт, і повертатиме результат. Насамперед нам знадобиться інтерфейс:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Calculator extends Remote {
int multiply(int x, int y) throws RemoteException;
}
Для чого нам інтерфейс? Справа в тому, що робота RMI заснована на створенні проксі, які ти вивчав в одній із минулих лекцій . А робота з проксі, як ти, мабуть, пам'ятаєш, ведеться саме на рівні інтерфейсів, а не класів. До нашого інтерфейсу є дві важливі вимоги!
- Він повинен успадковувати інтерфейс-маркер Remote.
- Всі його методи повинні викидати RemoteException (це не робиться в IDE автоматично, треба написати руками!).
Calculator
. Тут також усе досить просто:
import java.rmi.RemoteException;
public class RemoteCalculationServer implements Calculator {
@Override
public int multiply(int x, int y) throws RemoteException {
return x*y;
}
}
Тут навіть коментувати особливо нічого :) Тепер нам потрібно написати програму-сервер, яка налаштовуватиме і запускатиме наш серверний клас-калькулятор. Вона виглядатиме ось так:
import java.rmi.AlreadyBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class ServerMain {
public static final String UNIQUE_BINDING_NAME = "server.calculator";
public static void main(String[] args) throws RemoteException, AlreadyBoundException, InterruptedException {
final RemoteCalculationServer server = new RemoteCalculationServer();
final Registry registry = LocateRegistry.createRegistry(2732);
Remote stub = UnicastRemoteObject.exportObject(server, 0);
registry.bind(UNIQUE_BINDING_NAME, stub);
Thread.sleep(Integer.MAX_VALUE);
}
}
Давай розбиратися :) У першому рядку ми створюємо якусь рядкову змінну:
public static final String UNIQUE_BINDING_NAME = "server.calculator";
Цей рядок є унікальним ім'ям віддаленого об'єкта . За цим ім'ям програма-клієнт зможе знайти наш сервер: ти побачиш це пізніше. Далі ми створюємо наш об'єкт-калькулятор:
final RemoteCalculationServer server = new RemoteCalculationServer();
Тут усе зрозуміло. Далі вже цікавіше:
final Registry registry = LocateRegistry.createRegistry(2732);
Ця штука під назвою Registry - реєстр віддалених об'єктів . «Видалених» не в тому сенсі, що ми їх видалабо з комп'ютера, а в тому, що до об'єктів з цього регістру можливий віддалений доступ з інших програм :) У спосіб ми LocateRegistry.createRegistry()
передали число 2732. Це номер порту. Якщо не знаєш, що таке порт — можна почитати ось тут , але зараз тобі достатньо запам'ятати, що це унікальний номер, за яким інші програми зможуть знайти наш реєстр об'єктів (це теж побачиш нижче). Їдемо далі. Подивимося, що у нас відбувається у наступному рядку:
Remote stub = UnicastRemoteObject.exportObject(server, 0);
У цьому рядку ми створюємо заглушку . Заглушка (stub) інкапсулює в собі весь процес віддаленого виклику. Можна сміливо сказати, що це найважливіший елемент RMI. Що вона робить?
- Приймає всю інформацію про віддалений виклик якогось методу.
- Якщо метод має параметри, заглушка десеріалізує їх. Зверніть увагу на цей пункт! Параметри, які ти передаєш методам для віддаленого дзвінка, повинні бути серіалізованими (адже вони передаватимуться по мережі). У нас такої проблеми немає – ми передаємо просто цифри. Але якщо ти будеш передавати об'єкти, не забудь про це!
- Після цього вона викликає необхідний спосіб.
UnicastRemoteObject.exportObject()
наш об'єкт-калькулятор server. Таким чином ми уможливлюємо віддалений виклик його методів. Нам залишилося зробити лише одне:
registry.bind(UNIQUE_BINDING_NAME, stub);
Ми реєструємо нашу заглушку в реєстрі віддалених об'єктів під тим ім'ям, яке придумали на самому початку. Тепер клієнт зможе її знайти! Можливо, ти звернув увагу, що наприкінці ми приспали головний потік програми:
Thread.sleep(Integer.MAX_VALUE);
Нам просто потрібно, щоб сервер працював довгий час. Адже ми будемо запускати в IDEa відразу два методи main()
: спочатку серверний (у класі ServerMain
, який ми вже написали), а потім - клієнтський (у класі ClientMain
, який ми напишемо нижче). Важливо, щоб програма-сервер не вирубалася, поки ми запускатимемо клієнт, тому ми її просто приспали на довгий час. Працювати вона все одно :) Тепер ми можемо запустити метод main()
нашого сервера. Нехай він працює і чекає, коли програма-клієнт викличе якийсь метод:) Тепер давай напишемо програму-клієнт! Вона надсилатиме нашому серверу числа для множення.
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class ClientMain {
public static final String UNIQUE_BINDING_NAME = "server.calculator";
public static void main(String[] args) throws RemoteException, NotBoundException {
final Registry registry = LocateRegistry.getRegistry(2732);
Calculator calculator = (Calculator) registry.lookup(UNIQUE_BINDING_NAME);
int multiplyResult = calculator.multiply(20, 30);
System.out.println(multiplyResult);
}
}
Вона має нескладний вигляд. Що тут відбувається? По-перше, клієнт має бути в курсі унікального імені об'єкта, методи якого він викликатиме віддалено. Тому в програмі-клієнті ми теж створабо змінну public static final String UNIQUE_BINDING_NAME = "server.calculator";
Далі, у методі main()
ми отримуємо доступ до регістру віддалених об'єктів. Для цього нам потрібно викликати метод LocateRegistry.getRegistry()
і передати туди номер порту, на якому створювали наш регістр у програмі ServerMain - порт 2732 (цей номер був обраний для прикладу, ти можеш спробувати використати інший):
final Registry registry = LocateRegistry.getRegistry(2732);
Тепер нам залишилося лише отримати з регістру потрібний об'єкт! Це легко, адже ми знаємо його унікальне ім'я!
Calculator calculator = (Calculator) registry.lookup(UNIQUE_BINDING_NAME);
Зверніть увагу на приведення типів. Ми наводимо отриманий об'єкт до інтерфейсу Calculator
, а чи не до конкретного класу RemoteCalculationServer
. Як ми говорабо на початку лекції, робота RMI заснована на використанні проксі, тому віддалений виклик доступний лише для методів інтерфейсів, а не класів. Наприкінці ми віддалено викликаємо метод multiply()
у нашого об'єкта та виводимо результат у консоль.
int multiplyResult = calculator.multiply(20, 30);
System.out.println(multiplyResult);
Метод main()
у класі ServerMain
ми вже давно запустабо, саме час запустити метод main()
у програмі-клієнті ClientMain
! Висновок у консоль: 600 Ось і все! Наша програма (навіть дві!) успішно виконала свою функцію:) Якщо у тебе є час і бажання, можеш трохи урізноманітнити її. Наприклад, зробити так, щоб калькулятор підтримував всі чотири стандартні операції, а як параметри передавалися не числа, а об'єкт CalculationInstance(int x, int y)
. Як додатковий матеріал можеш подивитися статті та приклади — ось тут:
Побачимося на наступних заняттях! :)