Пользователь
0,0
рейтинг
8 ноября 2009 в 15:39

Разработка → RMI (Remote Method Invocation)

Java*
Случилось так, что поставленная задача требовала применения удалённого вызова методов. Порывшись на Хабре, ничего не нашел по данному вопросу (хотелось что-нибудь почитать в качестве первого знакомства, перед чтением документации). Изучив спецификации на 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
Андрей @taviscaron
карма
17,0
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (14)

  • 0
    У rmiregistry есть неприятная особенность, регистрировать объекты можно только с того же компьютера, с которого она запущена. При попытке зарегистрировать объект из другого компьютера выдается невнятная ошибка.
    • 0
      Тут два варианта, либо стаб система не нашла, либо codebase не прописан.
      • +2
        Либо (если порыться в итернете), то в одном из форумов по java написано, что из-за политики безопасности так делать нельзя.
        • 0
          Ну это уже зависит от настроек SM, нет?
    • +3
      При попытке зарегистрировать стаб на удалённом rmiregistry, на стороне rmiregistry возбуждается исключение AccessException. К нам оно приходит в виде RemoteException:

      RemoteException — if remote communication with the registry failed; if exception is a ServerException containing an AccessException, then the registry denies the caller access to perform this operation (if originating from a non-local host, for example) (из api ref для Registry.bind)

      Тоже самое будет при попытке unbind и rebind

      Вообще из соображений безопасности, это весьма логично…
      • 0
        Логично… С другой стороны, почему нельзя было сделать возможность разрешенного диапазона адресов для регистрации. (а если сделано, то не очевидно...)
        Если делать систему из N вычислительных единиц, которые обмениваются данными друг с другом, причем время от времени могут менять свое местоположение (такая вот распределенная система =) ), то каждый запрос будет выглядеть примерно так:

        Есть главный компьютер «Главный», который знает актуальное положение всех единиц
        Тогда запрос будет проходить так:
        Единица1 запрашивает у rmiregistry Главного "ссылку" на Главного (актуальную, но это не обязательно)
        Единица1 запрашивает у Главного положение Единицы2
        Единица1 запрашивает у rmiregistry Единица2 "ссылку" на Единица2
        Единица1 запрашивает у Единица2 метод


        Вместо вполне логичного:
        Единица1 запрашивает у rmiregistry Главного "ссылку" на Единица2
        Единица1 запрашивает у Единица2 метод
        Без использования дополнительно-созданного аналога DNS

        Ведь запись в rmiregistry(bind) представляет собой всего:
        1) «Место» где находится объект
        2) Тип объекта (.class фаил)

        Получается вполне логично было бы сделать возможность регистрацию объектов с других компьютеров.

        PS А вообще, по части удаленных вызовов с PyRo (правда это Python) по красоте сравниться не может ни одна из существующих RPC (Ice, Corbo и другие). Если брать microsoft, то можно со счета сбиться какую по счету RPC они предлагают.

        PPS ссылка на минимальный пример использования PyRo
  • 0
    «Рассмотрим на примере распределённых вычислений.» — в данном случае «распределённых» слишком громко сказано.
    • +1
      Согласен. Я это заметил в статье (:
  • +1
    >все параметры передаются по значению (т.е. копии объектов, а не ссылки на них, как это обычно происходит)
    Вы тут неправы. В RMI есть возможность переадавать как по значению (Serializable), так и по ссылке (Remote).
    • +1
      Спасибо. Моё упущение. Исправил.
  • 0
    Как всегда радует статья в стиле «я вчера почитал, а теперь расскажу вам...»
    как вы надоели, пионеры
    • 0
      Вот почему-то гуру редко пишут
      • 0
        Да, к сожалению в России это так. Подавляющее большинство статей пишут «не наши». Но и у нас есть достойные люди, например бывший суровый челябинский программист
        • 0
          согласен, читаю его с большим удовольствием :)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.