Главная » Java » Java, rmi — Remote Method Invocation

0

 

  Возможность удаленной загрузки и выполнения кода на сторонних системах решительным образом изменяет подходы к программированию сетевых вычислений. Технология удаленного вызова методов (Remote Method Invocation — RMI), поддерживаемая платформой Java, предлагает способы создания объектов, методы которых могут быть вызваны из среды других виртуальных машин, — в том числе и работающих на других хост-компьютерах. Поскольку технология RMI спроектирована с целью поддержки коммуникаций между виртуальными машинами Java, она пользуется всеми преимуществами архитектуры платформы Java и выглядит совершенно естественной для Java-программистов. Технология реализована в виде пакета Java.rmi, содержащего целый ряд вложенных пакетов — в частности, один из наиболее важных, Java.rmi.server, реализующий функции сервера RMI.

   Чтобы воспользоваться средствами RMI, первым делом следует спроектировать один или несколько интерфейсов удаленного вызова (remote interfaces), методы которых поддерживают возможность обращения из среды других виртуальных машин. Интерфейс удаленного вызова расширяет стандартный интерфейс Remote, и его методы, в дополнение к другим исключениям, способны выбрасывать исключения типа RemoteException. Ниже приведен пример простого интерфейса, метод которого принимает в качестве параметра объект задания, подлежащего выполнению, и возвращает результат в виде объекта Object:

 

import Java. rmi. *;

 

public interface ComputeServer extends Remote  {

Object compute(Task task)  throws  RemoteException;

}

Интерфейс Task носит самый общий характер — он позволяет объекту ComputeServer выполнять любые требуемые вычисления:

 

public interface Task extends Java.io.Serializable {

Object  run();

}

 

Task как таковой не является интерфейсом удаленного вызова. Каждому объекту ComputeServer, работающему на некотором хост-компьютере, может быть передан запрос на локальное выполнение задания и возврат полученных результатов. (Приложение, вызывающее метод удаленного объекта, по традиции называют клиентом,  а приложение,  выполняющее код этого метода, – сервером. Приложение, выполняющее функцию клиента одного сервера, в то же время может выступать в роли сервера для других клиентов.) Интерфейс Task расширяет интерфейс Serializable, поскольку все типы, передаваемые серверу или получаемые от него, должны поддерживать механизм сериализации (serialization) (см. раздел 15.7).

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

   Если же метод вызывается удаленным образом, возможные повреждения сетевой среды создают новый источник неопределенности — если сбой возник после передачи клиентского запроса, клиент не в состоянии узнать, был ли получен запрос сервером. Ошибка может возникнуть и до того момента, когда запрос достигнет сервера, и после передачи запроса на сервер, но перед получением результата вычислений. Клиент не обладает достаточными средствами, чтобы выяснить, какая из двух ситуаций имела место в действительности. Если, например, в запросе к серверу содержалось требование осуществить расходную операцию с банковским счетом, вы, разумеется, не хотели бы повторять такой запрос — он, вполне вероятно, может быть выполнен дважды. Интерфейсы удаленного вызова должны проектироваться таким образом, чтобы позволить клиентам осуществлять операции по восстановлению корректного состояния собственных объектов после подобных частных отказов (partial failures). Методы интерфейсов удаленного вызова могут допускать вполне безопасное повторное обращение; интерфейс способен содержать и специальные средства восстановления состояний объектов после сбоев — например, поддерживать механизм транзакций (transactions), который гарантирует, что выполнение метода, не допускающего повторных обращений, может быть прервано в любой момент без каких-либо серьезных осложнений. Клиенты сервера, в свою очередь, также должны заботиться о преодолении последствий ошибочных ситуаций, и помочь им в этом призван объект исключения типа RemoteException.

     Ниже приведена простая реализация интерфейса ComputeServer:

 

import Java.rmi.*;

import Java.rmi.server.*;

 

public class ComputeServerimpl

extends UnicastRemoteObject

  implements  ComputeServer

 {

public ComputeServerimpl()  throws  RemoteException  {  }

 

public Object  compute(Task task)   {

   return  task.run();

}

public  static void main(String[]   args)

 throws  Java.io.IOException

{

// используется строгий менеджер безопасности,

// предлагаемый в RMI по умолчанию

system.setSecurityManager(new RMlSecurityManager());

Computeserver server = new computeServerlmpl();

Naming.rebind("ComputeServer",   server);

System.out.printinC"готов к выполнению заданий");

  }

}

Показанный код довольно прямолинеен. После прихода запроса, содержащего объект Task клиентского задания, в теле метода compute вызывается метод run объекта Task, возвращающий по завершении работы объект типа Object. Каждый запрос обычно обрабатывается посредством отдельного потока вычислений, поэтому сервер в такой редакции, которая приведена выше, может одновременно выполнять множество заданий, присланных различными клиентами. Класс ComputeServerlmpl расширяет класс unicastRemoteObject, поддерживающий средства удаленного вызова методов на единственном сервере (single-server RMI). Класс UnicastRemoteObject, как и все другие типы системы RMI, предназначенные для использования на стороне сервера, определен в составе пакета Java, rmi. server. В объявлении конструктора класса ComputeServerlmpl упомянута возможность выбрасывания им исключения типа RemoteException, поскольку оно может быть сгенерировано неявно вызываемым конструктором класса UnicastRemoteObject при попытке регистрации объекта в системе RMI.

  Теперь наступает черед более простых и привлекательных вещей. Клиент вправе обратиться к серверу с требованием выполнения любых вычислений. Предположим, например, что необходимо получить значение числа л с точностью до определенного количества десятичных знаков. Чтобы заставить сервер решить эту задачу, необходимо осуществить следующее:

 

import Java.math.BigDecimal;

 

public class  Pi   implements Task {

 private int decimals;

 

/** вычисление числа Pi   с заданной точностью */

public Pi(int decimals)   {

this.decimals = decimals;

}

public Object  run()   {

BigDecimal   res = computePi();

 return  res;

}

BigDecimal   computePi()   {

//………

  }

}

Класс Pi реализует интерфейс Task, содержащий метод run, который возвращает объект типа java.math.BigDecimal, содержащий скомое значение. Откомпилированный байт-код класса Pi можно расположить в таком месте, откуда его будет легко загрузить, указав адрес URL, — точно так же, как обычно поступают с аплетами. URL, который указывает на код, подлежащий загрузке, задается объекту клиента в виде свойства.

   При вызове метода compute сервера с передачей объекта Pi сервер испытывает потребность в получении кода класса Pi. Он анализирует URL, неявно переданный при вызове, загружает класс, вызывает метод run объекта Pi в безопасном контексте и возвращает результат.

   Пример класса ComputeServer подчеркивает особенности однородной модели вычислений, лежащей в основе виртуальной машины Java и предполагающей, что любой загружаемый код выполняется безопасным образом и обозначает одну и ту же последовательность действий при работе на всех платформах.

   Базовая инфраструктура, которую мы только что вкратце рассмотрели, может найти применение во многих вычислительных средах для решения самых разнообразных задач. Так, например, нетрудно привести пример вычислительного окружения, состоящего из множества компьютеров, которым рассылаются отдельные кадры анимационных изображений, подлежащих тонированию (rendering), либо фрагменты аудиоданных, нуждающихся в дополнительной обработке. В других ситуациях — скажем, тогда, когда объект Task не может быть воспринят как заслуживающий доверия, — модель, вероятно, придется до некоторой степени усложнить. Впрочем, все, что может потребоваться в конечном итоге, — это виртуальные машины Java на обеих сторонах сетевого соединения.

   Ссылки на объекты UnicastRemoteObject предназначены для использования сервисами удаленного вызова, которые инициируются пользователем или системой. Если необходимо построить удаленный объект, загружаемый автоматически по первому требованию, достаточно воспользоваться средствами класса Java, rmi .activation.Activatable.

   Поскольку процедуры удаленного вызова по определению нельзя считать безопасными, система RMI требует применения менеджера безопасности (security manager). Класс RMISecurityManager представляет весьма консервативный менеджер безопасности, принятый по умолчанию, который, будучи способным предотвратить любое несанкционированное воздействие на систему, отвечает самым строгим критериям. Впрочем, вы вправе предложить и иной вариант менеджера безопасности.

   Ваш собственный класс сервера может оказаться неспособным к расширению одного из классов серверов RMI, поскольку ему приходится наследовать какой-либо другой класс. Класс аплета, например, обязан расширять класс Applet, поэтому он не в состоянии одновременно служить производным классом от UnicastRemoteObject. Чтобы обойти это ограничение уровня языка, можно создать класс сервера, который явно не наследует тип UnicastRemoteObject, но пользуется вызовом его статического метода в своем конструкторе:

 

public class SnazzyApplet extends Applet

 implements SnazzyRemote

{

public SnazzyApplet()  throws  RemoteException {

     UnicastRemoteObject.exportobject(this);

}

//   …   Реализация  методов  типов  SnazzyRemote  и Applet

}

 

    Классы загружаются от клиента на сервер (либо с сервера к клиенту) только по мере необходимости. Зачастую об одном и том же классе заранее "осведомлены" обе стороны: так, например, класс BigDecimal служит частью ядра платформы — он известен "в лицо" и клиентам, и серверам — и поэтому в его загрузке потребности нет. Классы, созданные прикладной программой, интерпретируются так же — если класс установлен и в среде клиента, и в окружении сервера, по сети он передаваться не будет.

  Аргументы и возвращаемые значения методов, вызываемых удаленным образом, обрабатываются несколько иначе, нежели в случае использования методов обычных локальных объектов. Система RMI передает эти значения, пользуясь механизмом сериализации (см. раздел 15.7). Когда удаленный объект передается в виде аргумента или возвращаемого значения, адресат получает ссылку на тот же объект, который был передан. Точно так же действуют и локальные ссылки в локальных методах — ссылка указывает на один и тот же объект как в коде-инициаторе метода, так и в теле метода. Значения простых типов пересылаются единым образом и в локальном коде, и при удаленном вызове — адресат всегда получает копию значения.

   Ссылки на локальные объекты при удаленном вызове должны передаваться иначе. Локальные объекты проектируются так, что они не в состоянии справляться с частными отказами, о которых мы говорили выше, и поэтому передавать по сети удаленные ссылки на локальные объекты непосредственно не представляется возможным. Вместо этого для передачи локальных объектов используется механизм глубокого копирования, выполняемый средствами сериализации. Все ссылки на удаленные объекты, содержащиеся внутри объекта либо в любой части графа объектов (object graph), которые тот представляет, преобразуются и пересылаются по правилам, рассмотренным в разделе 15.7. Все элементы графа объектов сериализуются системой-отправителем данных и десериализуются системой-получателем. Изменения, внесенные в содержимое объекта после его сериализации или десериализации, не будут видимы системой-корреспондентом, поскольку обе системы обладают собственными копиями объекта.

   Реестр RMI (RMI registry) поддерживает простую систему именования, предназначенную для обеспечения возможности хранения ссылок на удаленные объекты с целью отслеживания процессов загрузки клиентов. Систему нельзя назвать полной и развитой, но со своими задачами она вполне справляется. Доступ к реестру может быть получен средствами класса Naming.

   Объекты сервера находятся в компетенции распределенного сборщика мусора (distributed garbage collector). Если ссылок на удаленный объект не существует, тот может быть утилизирован. Принципы работы процесса распределенной сборки мусора напоминают те, которыми руководствуется локальный сборщик мусора, однако никаких гарантий относительно того, успешно ли завершились операции утилизации удаленных объектов, дать нельзя. Если клиент прервал контакт с сервером на продолжительное время, предполагается, что он просто отключился, не послав серверу соответствующего уведомления (например, ввиду системного сбоя). Возможна и такая ситуация, когда из-за долговременного повреждения сетевого соединения клиент обнаруживает, что объект сервера в период его бездействия был утилизирован. Чтобы предотвратить подобное развитие событий, могут быть предприняты самые разнообразные действия — объект сервера,   пытающийся  избежать  удаления  сборщиком  мусора,   вправе,   например,

 

обеспечить хранение удаленной ссылки на себя (таковая поможет поддерживать его жизнь). Ссылку нетрудно удалить, если код сервера примет решение о том, что продолжать дальнейшую "борьбу" за существование более нецелесообразно.

   Технология RMI позволяет создавать код и клиента, и сервера исключительно средствами языка программирования Java. Эта особенность весьма привлекательна для тех, кто близко знаком с Java. Разумеется, вы вольны реализовать любые методы сервера и с помощью native-кода. Методы, объявленные как native, позволяют использовать RMI как мост к существующему программному обеспечению, написанному на других языках программирования.

   Вложенный пакет Java, rmi .activation включает средства поддержки активизируемых серверов (activatable servers), которые" автоматически приступают к работе при получении соответствующего задания. В единую группу активизации (activation group) может быть включено несколько серверов, призванных действовать в контексте одной и той же виртуальной машины Java.

 

Источник: Арнолд, Кен, Гослинг, Джеймс, Холмс, Дэвид. Язык программирования Java. 3-е изд .. : Пер. с англ. – М. : Издательский дом «Вильяме», 2001. – 624 с. : ил. – Парал. тит. англ.

По теме:

  • Комментарии