Pull to refresh

RMI (Remote Method Invocation)

Reading time 5 min
Views 84K
Случилось так, что поставленная задача требовала применения удалённого вызова методов. Порывшись на Хабре, ничего не нашел по данному вопросу (хотелось что-нибудь почитать в качестве первого знакомства, перед чтением документации). Изучив спецификации на java.sun.com спешу поделиться с вами своей первой статьей. :)

«Что такое RMI?»


Remote method Invocation — механизм, который позволяет вызывать метод удалённого объекта. Согласно ему, все операции по подготовке и передаче данных инкапсулируются в вызываемом методе клиентского объекта-заглушки (stub). Сам же вызов метода ничем не отличается от вызова метода обычного локального объекта, за небольшим исключением:
  • все параметры передаются по значению (т.е. копии объектов, а не ссылки на них, как это обычно происходит) — исправил ниже. Спасибо KonstantinSolomatov
  • локальные объекты передаются по значению (копии)
  • при передаче удалённого (Remote) объекта, если он экспортирован, передаётся stub этого объекта
  • передаваемые объекты должны быть Serializable
  • кроме всех прочих исключительных ситуаций, при вызове удалённого метода может возбуждаться исключение RemoteException (ошибки маршализации/демаршализации, передачи данных и другие возможные ошибки протокола)
Так же нужно отметить, что при вызове метода мы работаем с удалённым интерфейсом, а не с удалённым классом.

«Зачем это нужно?»


Задача RMI — организация клиент-серверного взаимодействия. Это значит, что вам не придётся беспокоится о передаче и предварительной обработке данных (протокол и т.д.). Удобно? Да. Но не во всех случаях. Если в вашей клиент-серверной среде подразумевается работа программ, написанных не только на java, от RMI толку мало (хотя при большом желании можно попытаться «выкрутиться» при помощи JNI).

«Давайте уже что-нибудь напишем!»


Давайте. Рассмотрим на примере распределённых вычислений. Задача у нас такая: мы будем искать простые числа самым простым способом, перебором. Распределённо же будем проверять числа подбором делителей от 2 до sqrt(n), где n — число, которое проверяем. («Распределённые вычисления» — громкое название для такого примера. Но ведь вычисляем? Да! Распределённо? Распределённо!)

Решать задачу будем так: есть сервер, который будет «скармливать» числа на проверку «зарегистрировавшимся» клиентам, посему взаимодействовать мы будем в обоих направлениях (клиент->сервер — регистрация, сервер->клиент — число на проверку), для этого опишем 2 интерфейса:
  1. public interface ClientRegister extends Remote {
  2.   public void register (PrimeChecker checker) throws RemoteException;
  3. }
  4.  
  5. public interface PrimeChecker extends Remote {
  6.   public boolean check (BigDecimal number) throws RemoteException;
  7. }
* This source code was highlighted with Source Code Highlighter.

Интерфейс ClientRegister ипользуется клиентом для регистрации себя на сервере в роли PrimeChecker`a. Сервер использует PrimeChecker для передачи клиенту числа на проверку.

Как вы уже заметили, удалённый интерфейс должен расширять, прямо или косвенно, интерфейс Remote. Так же среди прочих исключений определим RemoteException (о нём мы говорили выше).

Приступим к реализации сервера (полный код):
  1. public class PrimeNumbersSearchServer implements ClientRegister {
  2.  
  3.   ...
  4.  
  5.   public static void main(String[] args) {
  6.     PrimeNumbersSearchServer server = new PrimeNumbersSearchServer();
  7.  
  8.     try {
  9.       ClientRegister stub = (ClientRegister)UnicastRemoteObject.exportObject(server, 0);
  10.  
  11.       Registry registry = LocateRegistry.createRegistry(12345);
  12.       registry.bind("ClientRegister", stub);
  13.  
  14.       server.startSearch();
  15.     } catch (Exception e) {
  16.       System.out.println ("Error occured: " + e.getMessage());
  17.       System.exit (1);
  18.     }
  19.   }
  20. }
* This source code was highlighted with Source Code Highlighter.

Разберём инициализацию:
  1. ClientRegister stub = (ClientRegister)UnicastRemoteObject.exportObject(server, 0);
* This source code was highlighted with Source Code Highlighter.

Экспортируем удалённый объект и получаем stub, посредством которого клиент будет вызывать методы нашего объекта. Второй параметр exportObject — порт, который будет использоваться для соеденения с удалённым объектом, 0 — выбор любого свободного порта. stub нужно передать клиенту. Тут возможны совершенно разные варианты. Можно даже передать stub клиенту на дискете 3.5'' :) Мы воспользуемся RMI-регистратором. Его можно как создать внутри нашей vm, так и использовать «внешний», представляемый утилитой rmiregistry. Я использовал первый вариант:
  1. Registry registry = LocateRegistry.createRegistry(12345);
  2. registry.bind("ClientRegister", stub);
* This source code was highlighted with Source Code Highlighter.

Создаём регистратор и связываем наш stub с именем ClientRegister. Регистратор будет принимать соеденения на 12345 порту.

Теперь клиент (полный код):
  1. public class PrimeNumbersSearchClient implements PrimeChecker {
  2.  
  3.   ...
  4.  
  5.   public static void main(String[] args) {
  6.     PrimeNumbersSearchClient client = new PrimeNumbersSearchClient();
  7.  
  8.     try {
  9.       Registry registry = LocateRegistry.getRegistry(null, 12345);
  10.       ClientRegister server = (ClientRegister)registry.lookup("ClientRegister");
  11.  
  12.       PrimeChecker stub = (PrimeChecker)UnicastRemoteObject.exportObject(client, 0);
  13.       server.register(stub);
  14.  
  15.     } catch (Exception e) {
  16.       System.out.println ("Error occured: " + e.getMessage());
  17.       System.exit (1);
  18.     }
  19.   }
  20. }
* This source code was highlighted with Source Code Highlighter.

Клиенту нужно получить серверный stub, чтобы зарегистрироваться.
  1. Registry registry = LocateRegistry.getRegistry(null, 12345);
  2. ClientRegister server = (ClientRegister)registry.lookup("ClientRegister");
* This source code was highlighted with Source Code Highlighter.

Находим удалённый регистратор и запрашиваем у него stub связанный с именем «ClientRegister». Первый параметр LocateRegistry.getRegistry(null, 12345) — хост (null — localhost), второй — порт.

Далее экспортируем клиентский удалённый объект и передадим серверу stub (уже клиентский) — зарегистрируемся. Сервер добавит клиента в очередь доступных checker'ов и начнёт передавать ему числа для проверки. После проверки, если она завершилась без ошибок, клиент снова попадает в очередь и т.д.

UPD: перенёс в Java
Tags:
Hubs:
+24
Comments 14
Comments Comments 14

Articles