Факультет «Информатика исистемы управления»
Методические указания к лабораторной работе
по курсу «Распределенныесистемы обработки информации»
«Удаленный вызовметодов(RMI)»
Москва 2004 г.
Цель работы
Ознакомиться с RMI. Написать клиент RMI и сервер RMI.
Что такое RMI?
Java RMI (Remote Method Invocation – удаленный вызовметодов) представляет собой тип удаленного вызова процедур, независимый отсети, облегченный и полностью переносимый, так как написан на языке Java.
Основные шаги работы с RMI:
1. Определите(или найдите) удаленный интерфейс, согласованный с сервером.
2. Напишитекод сервера.
3. Запуститепрограмму rmic (Java RMI stub compiler – компилятор заглушек RMI) для генерациисвязующего кода.
4. Напишитекод клиента.
5. Убедитесь,что на сервере запущен RMI реестр (программа rmiregistry).
6. Запуститесервер.
7. Запуститеодного или нескольких клиентов.
Процедуры RMI определяют с помощьюизвестного механизма Java – интерфейсов. Удаленные нтерфейсы долдны бытьподклассами java.rmi. Remote, при этом и клиент и сервер должны находиться в одном пакете Java. Все параметры удаленныхметодов должны относиться или к примитивным типам (int, double и т.п.), либореализовывать интерфейс java.io. Serializable.
Введение в распределенныевычисления с использованием RMI
Технология Remote Method Invocation (RMI), впервые представленнаяв JDK 1.1, продвинула сетевоепрограммирование на более высокий уровень. Хотя RMI относительно проста виспользовании, она является необыкновенно мощной технологией и раскрывает передобычным Java‑программистом полностью новую парадигму – мирраспределенных объектных вычислений.
Этот курс представляетсобой углубленное введение в эту универсальную технологию. RMI получила значительноеразвитие в JDK 1.1 и во многом была улучшена в Java 2 SDK. При необходимости,различия между этими двумя версиями будут отмечены.
Цели
Главной цельюразработчиков RMI было предоставление возможности программистам разрабатыватьраспределенные Java‑программы, используя такие же синтаксис и семантику, как ипри разработке обычных нераспределенных программ. Для этого они должны былипреобразовать модель работы классов и объектов в одной виртуальной машине Java™ (JVM) в новую модель работыклассов и объектов в распределенной (несколько JVM) вычислительной среде.
Сравнение распределенныхи нераспределенных Java‑программ
Разработчики RMI стремились сделатьиспользование распределенных Java‑объектов таким же, как и использованиелокальных объектов. В следующей таблице перечислены некоторые важные отличия.
Не беспокойтесь о том,что не все отличия вам понятны. Все прояснится после рассмотрения архитектуры RMI. Вы можете использоватьэту таблицу в качестве ссылки во время изучения RMI.Локальный объект Удаленный объект Определение объекта Локальный объект определяется при помощи класса Java. Экспортируемое поведение удаленного объекта определяется при помощи интерфейса, который должен быть расширен из интерфейса Remote. Реализация объекта Локальный объект реализуется своим классом Java. Поведение удаленного объекта определяется классом Java, который реализует удаленный интерфейс. Создание объекта Новый экземпляр локального объекта создается оператором new. Новый экземпляр удаленного объекта создается на компьютере хоста оператором new. Клиент не может непосредственно создать новый удаленный объект (если не использует технологию Java 2 Remote Object Activation). Доступ к объекту Доступ к локальному объекту осуществляется непосредственно через переменную-ссылку на объект. Доступ к удаленному объекту осуществляется через переменную-ссылку на объект, указывающую на реализацию замещающей заглушки удаленного интерфейса. Ссылки В одной JVM, ссылка на объект указывает непосредственно на объект в динамической памяти. «Удаленная ссылка» представляет собой указатель на замещающий объект («заглушку») в локальной динамической памяти. Заглушка содержит информацию, которая дает возможность соединиться с удаленным объектом, содержащим реализацию методов. Активные ссылки В одной JVM, объект считается «живым», если существует хотя бы одна ссылка на него. В распределенной среде удаленная JVM может разрушиться, и сетевое соединение может быть потеряно. Считается, что удаленный объект имеет активную удаленную ссылку на него, если к нему производился доступ в течение определенного периода времени (срока аренды). Если все удаленные ссылки были удалены явно, или если у всех удаленных ссылок закончился срок аренды, тогда удаленный объект становится доступен для удаленной сборки мусора. Финализация Если объект реализует метод finalize(), он вызывается перед тем, как объект утилизируется сборщиком мусора. Если удаленный объект реализует интерфейс Unreferenced, при удалении всех удаленных ссылок вызывается метод unreferenced этого интерфейса. Сборка мусора При удалении всех локальных ссылок на объект, он становится кандидатом на удаление сборщиком мусора. Удаленный сборщик мусора работает совместно с локальным. Если нет удаленных ссылок и удалены все локальные ссылки на объект, он становится кандидатом для сборщика мусора в обычном значении этого понятия. Исключительные ситуации Исключительные ситуации являются либо исключительными ситуациями времени исполнения, либо классом Exceptions. Компилятор Java заставляет программу обрабатывать все Exceptions. RMI заставляет программу иметь дело с любыми возможными объектами RemoteException, которые могут генерироваться. Это сделано для гарантии устойчивости распределенных приложений.
Архитектура Java RMI
Целью разработкиархитектуры RMI было создание распределенной объектной модели Java, которая свободноинтегрируется в язык программирования Java и локальную объектную модель. Разработчики RMI достигли этой цели; быласоздана система, которая переносит безопасность и устойчивость архитектуры Java в мир распределенныхвычислений.
Интерфейсы: основа RMI
Архитектура RMI основана на одном важномпринципе: определение поведения и реализация этого поведения считаются разнымипонятиями. RMI дает возможность разделить и выполнить на разных JVM код, определяющийповедение, и код, реализующий поведение.
Это прекрасносоответствует требованиям распределенных систем, в которых клиенты знают обопределениях служб, а серверы предоставляют эти службы.
Конкретно в RMI определение удаленнойслужбы кодируется при помощи интерфейса Java. Реализация удаленнойслужбы кодируется в классе. Таким образом, ключ к пониманию RMI – помнить, что интерфейсыопределяют поведение, а классы определяют реализацию.
Следующий рисунокиллюстрирует это разделение:
Помните, что интерфейсы Java не содержат исполняемогокода. RMI поддерживает два класса, реализующих один и тот же интерфейс.Первый класс является реализацией поведения и исполняется на сервере. Второйкласс работает как промежуточный интерфейс для удаленной службы и исполняетсяна клиентской машине. Это показано на следующей диаграмме. Клиентская программавызывает методы прокси-объекта, RMI передает запрос на удаленную JVM и направляет его вреализацию объекта. Любые возвращаемые из реализации значения передаются назадв прокси-объект и затем в клиентскую программу.
Уровни архитектуры RMI
Рассмотреввысокоуровневую архитектуру RMI, взглянем на ее реализацию. Реализация RMI, по существу, состоит изтрех абстрактных уровней. Первый – это уровень заглушки и скелета,расположенный непосредственно перед разработчиком. Этот уровень перехватываетвызовы методов, произведенные клиентом при помощи переменной-ссылки наинтерфейс, и переадресует их в удаленную службу RMI.
Следующий уровень – уровеньудаленной ссылки. Этот уровень понимает, как интерпретировать и управлятьссылками на удаленные объекты служб. В JDK 1.1 этот уровеньсоединяет клиентов с удаленными объектами служб, которые исполняются насервере. Это соединение является связью типа один к одному (однонаправленноесоединение). В Java 2 SDK этот уровень был расширен поддержкой активации пассивныхудаленных объектов при помощи технологии RemoteObjectActivation.
Транспортный уровеньоснован на соединениях ТСР/IР между сетевыми машинами. Он обеспечивает основныевозможности соединения и некоторые стратегии защиты от несанкционированногодоступа.
При использованииуровневой архитектуры каждый из уровней может быть изменен или заменен без воздействияна остальную систему. Например, транспортный уровень может быть замененпротоколом UDP/IP без изменения остальных уровней.
Уровень заглушки искелета
Уровень заглушки искелета RMI расположен непосредственно перед разработчиком Java. На этом уровне RMI использует прокси-модельпроектирования, которая описана в книге Gamma, Helm, Johnson и Vlissides «Design Patterns». В прокси-модели объектодного контекста представляется другим (прокси-объектом) в отдельном контексте.Прокси-объект знает, как направлять вызовы методов между этими объектами. Наследующей диаграмме классов показана прокси-модель.
В прокси-модели,используемой в RMI, роль прокси выполняет класс заглушки, а роль RealSub j ect выполняет класс,реализующий удаленную службу.
Скелет являетсявспомогательным классом, который создается для использования RMI. Скелет понимает, каквзаимодействовать с заглушкой при RMI‑соединении. Скелет поддерживает общение сзаглушкой; он читает параметры для вызова метода из соединения, производитвызов объекта, реализующего удаленную службу, принимает возвращаемое значение изаписывает его обратно в заглушку.
В реализации RMI Java 2 SDK новый протокол связисделал классы скелетов не нужными. RMI использует отражение для установления соединенияс объектом удаленной службы. Вы должны использовать классы и объекты скелетовтолько в JDK 1.1 и совместимых с ним реализациях систем.
Уровень удаленных ссылок
Уровни удаленных ссылокопределяют и поддерживают семантику вызовов соединения RMI. Этот уровепредоставляет объект RemoteRef, который обеспечивает соединение с объектами,реализующими удаленные службы.
Объекты заглушкииспользуют метод invoke() в объекте RemoteRef для направления вызова метода. Объект RemoteRef понимает семантикувызова удаленных служб.
Реализация RMI в JDK 1.1 обеспечивает толькоодин способ соединения клиентов с реализациями удаленных служб:однонаправленное соединение типа точка-точка. Перед тем, как клиент сможетиспользовать удаленную службу, экземпляр объекта, реализующего ее, должен бытьсоздан на сервере и экспортирован в систему RMI. (Если это основнаяслужба, она также должна быть поименована и зарегистрирована в реестре RMI).
Реализация RMI в Java 2 SDK добавляет новуюсемантику для соединения клиент-сервер. В этой версии RMI поддерживает способные кактивизации удаленные объекты. Когда производится вызов метода прокси длятакого объекта, RMI определяет, находится ли объект, реализующий удаленную службу, впассивном состоянии. Если да, то RMI создаст экземпляр объекта и восстановит его состояниеиз дискового файла. Как только объект активизируется в памяти, он начинаетвести себя так же, как и объект, реализующий удаленную службу JDK 1.1.
Доступны и другие типысемантики соединений. Например, в случае широковещательного соединения, одинпрокси-объект может передать запрос метода нескольким реализациям одновременнои принять первый ответ (это уменьшает время отклика и, возможно, повышаетдоступность объекта). В будущем Sun возможно добавит дополнительные типы семантики вRMI.
Транспортный уровень
Транспортный уровеньосуществляет соединение между различными JVM. Все соединенияпредставляют собой основанные на потоках сетевые соединения, использующиеTCP/IP.
Даже если две JVM работают на одном и томже физическом компьютере, они соединяются через стек сетевых протоколов TCP/IP.(Вот почему вы должны иметь действующую конфигурацию TCP/IP на вашем
компьютере для выполненияупражнений этого курса). На следующей диаграмме показаны TCP/IP соединениямежду разными JVM. Транспортный уровень RMI был разработан для осуществления соединениямежду клиентами и сервером даже с учетом сетевых помех.
Хотя транспортный уровеньпредпочитает использовать несколько ТСР/IР соединений, некоторыесетевые конфигурации разрешают только одно TCP/IP‑соединение между клиентоми сервером (некоторые броузеры ограничивают апплеты одним сетевым соединением сих сервером).
В этом случае,транспортный уровень распределяет несколько виртуальных соединений внутриодного TCP/IP‑соединения.
Именование удаленныхобъектов
При рассмотренииархитектуры RMI постоянно откладывался один вопрос: «Как клиент находит удаленнуюслужбу RMI?» Сейчас вы получите ответ на этот вопрос. Клиенты находятудаленные службы, используя службу имен или каталогов. Это может показатьсяхождением по кругу. Как клиент может найти службу, используя службу? И этодействительно так. Служба имен или каталогов исполняется на хорошо известномхосте и имеет известный номер порта. (Хорошо известный означает, что всев организации знают об этом).
RMI может использовать многоразличных служб каталогов, включая Java Naming and Directory Interface (JNDI). RMI и сама включает в себяпростую службу, называемую реестром RMI, rmiregistry. Реестр RMI работает на каждоймашине, содержащей объекты удаленных служб и принимающей запросы наобслуживание, по умолчанию используя порт 1099.
На хосте программасервера создает удаленную службу, предварительно создавая локальный объект,реализующий эту службу. Затем она экспортирует этот объект в RMI. Как только объектэкспортирован, RMI создает службу прослушивания, ожидающую соединения с клиентом изапроса службы. После экспорта, сервер регистрирует объект в реестре RMI, используя общедоступноеимя.
На стороне клиента креестру RMI доступ обеспечивается через статический класс Naming. Он предоставляет метод lookup(), который клиентиспользует для запросов к реестру. Метод lookup() принимает URL, указывающий на имяхоста и имя требуемой службы. Метод возвращает удаленную ссылку наобслуживающий объект. URL принимает следующий вид:
rmi://
[:]/
где host_name – это имя, распознаваемоев локальной сети (LAN), или DNS‑имя в сети Internet. Необходимо только указать name_service_port, если служба именисполняется на порте, отличном от принимаемого по умолчанию 1099.
Использование RMI
Сейчас наступило времясоздать рабочую RMI‑систему и получить практический опыт. Вы создадите простуюудаленную службу, реализующую калькулятор, и попробуете использовать ее изклиентской программы.
Рабочая RMI‑система состоит изнескольких частей.
• Определениеинтерфейсов для удаленных служб
• Реализацияудаленных служб
• Файлызаглушки и скелета
• Сервер,предоставляющий удаленные службы
• Службаимен RMI, дающая возможность клиентам найти удаленные службы
• Поставщикфайла классов (HTTP или FTP‑сервер)
• Клиентскаяпрограмма, которая нуждается в удаленных службах
Для упрощения задачи выбудете использовать один и тот же каталог для кода как клиента, так и сервера.При запуске клиента и сервера из одного и того же каталога вам не придетсянастраивать HTTP или FTP серверы для доступа к файлам классов. (Использование серверов HTTP и FTP в качестве поставщиковфайлов классов детально рассматривается в разделе «Распространение иустановка программного обеспечения RMI»)
Если предположить, что RMI‑система ужеспроектирована, для ее создания необходимо выполнить следующие шаги:
1. Написатьи откомпилировать Java‑код для интерфейсов
2. Написатьи откомпилировать Java‑код для классов реализации
3. Создатьфайлы классов заглушки и скелета из классов реализации
4. НаписатьJava‑код программыхоста для удаленного обслуживания
5. РазработатьJava‑код для клиентскойпрограммы RMI
6. Установитьи запустить RMI‑систему
1. Интерфейсы
Первым шагом являетсянаписание и компилирование Java‑кода для интерфейсов служб.
Когда высоздаете удаленный интерфейс, вы должны следовать следующим правилам:
1. Удаленныйинтерфейс должен быть публичным – public (он не может иметь «доступ науровне пакета», так же он не может быть «дружественным»). В противном случаеклиенты будут получать ошибку при попытке загрузки объекта, реализующегоудаленный интерфейс.
2. Удаленныйинтерфейс должен расширять интерфейс java.rmi. Remote.
3. Каждыйметод удаленного интерфейса должен объявлять java.rmi. RemoteException всвоем предложении throws в добавок к любым исключениям, специфичным дляприложения.
4. Удаленныйобъект, передаваемый как аргумент или возвращаемое значение (либо напрямую,либо как к части локального объекта), должен быть объявлен как удаленный интерфейс,а не реализация класса.
Интерфейс Calculator определяет все удаленныевозможности, предлагаемые службой:
public interfaceCalculator
extendsjava.rmi. Remote {public long add (long a, long b)
throws java.rmi. RemoteException;
public long sub(long a, long b)
throws java.rmi. RemoteException;
public long mul(long a, long b)
throws java.rmi. RemoteException;
public long div(long a, long b)
throws java.rmi. RemoteException;
Скопируйте этот файл вваш каталог и откомпилируйте его при помощи компилятора Java:
>javac Calculator.java
2. Реализация
Теперь вы пишетереализацию удаленной службы. Ниже приведен класс CalculatorImpl:
public classCalculatorImpl extends
java.rmi.server.UnicastRemoteObj ect implements Calculator {
// Реализации должныиметь
// явныйконструктор для
// того, чтобыобъявить
// исключительную ситуациюRemoteException
publicCalculatorImpl()
throwsjava.rmi. RemoteException {
super ();
public long add(long a, long b)
throwsjava.rmi. RemoteException {return a + b;
public long sub(long a, long b)
throwsjava.rmi. RemoteException {return a – b;
public long mul(long a, long b)
throwsjava.rmi. RemoteException {return a * b;
public long div(long a, long b)
throwsjava.rmi. RemoteException {
return a / b;
}
И опять, скопируйте этоткод в ваш каталог и откомпилируйте его.
Класс реализациииспользует UnicastRemoteObject для присоединения ксистеме RMI. В данном примере класс реализации непосредственно расширяет UnicastRemoteOb j ect. Это не являетсяобязательным требованием. Класс, не расширяющий UnicastRemoteObj ect, может использовать свойметод exportOb j ect () для присоединения к RMI.
Если класс расширяет UnicastRemoteObj ect, он должен обеспечитьконструктор, объявляющий, что он может сгенерировать объект RemoteException. Если этот конструкторвызывает метод super (), он активизирует код в UnicastRemoteObj ect, который выполняет RMI‑соединение иинициализацию удаленного объекта.
3. Заглушки и скелеты
Дальше вы используетекомпилятор RMI, rmic, для генерации файлов заглушки и скелета. Компилятор запускаетсяс указанием файла класса, реализующего удаленную службу.
>rmic CalculatorImpl
Попробуйте выполнить этов вашем каталоге. После запуска rmic вы должны найти файл Calculator_Stub. class.
4. Хост-сервер
Удаленные службы RMI должны быть помещены впроцесс сервера. Класс CalculatorServer является очень простым сервером,предоставляющим простые элементы для размещения.
importjava.rmi. Naming;
public classCalculatorServer {
publicCalculatorServer () {try {
Calculator c =new CalculatorImpl();
Naming.rebind (»
rmi://localhost:1099/
CalculatorService»,c);} catch (Exception e) {
System.out.println(«Trouble:» + e);
public staticvoid main (String args[]) {new CalculatorServer();
В этом примере вы видитевызов статического метода Naming.rebind(). Однако этот вызовтребует, чтобы регистрация была запущена отдельным процессом на вашемкомпьютере. Имя сервера регистрации – это rmiregistry, и под 32‑битнойWindows вы пишете:
start rmiregistry
для запуска в фоновомрежиме.
Как и многие другиесетевые программы, rmiregistry обращается по IP адресу машины, накоторой она установлена, но она также слушает порт. Если вы вызовите rmiregistryкак показано выше, без аргументов, будет использован порт по умолчанию 1099.Если вы хотите использовать другой порт, вы добавляете аргумент в команднуюстроку, указывающий порт. Следующий пример устанавливает порт 2005, так что rmiregistryпод управлением 32-битной Windows должна запускаться так:
start rmiregistry 2005
Информация о порте такжедолжна передаваться в команде bind(), наряду с IP адресом машины, гдерасполагается регистрация. Но это может выявить проблему, если вы хотитепроверять RMI программы локально. В выпуске JDK 1.1.1, есть целая связкапроблем:
1)localhost неработает с RMI. Поэтому для экспериментов с RMI на одной машине вы должныиспользовать имя машины. Чтобы найти имя вашей машины под управлением 32‑битнойWindows, перейдите в панель управления и выберите «Network». Выберите закладку «Identification»,и посмотрите имя вашего компьютера. Регистр в имени игнорируется. (Примеримени: «peppy»)
2) RMI не работает, покаваш компьютер имеет активные TCP/IP соединения, даже если все ваши компонентыпросто общаются друг с другом на локальной машине. Это значит, что вы должнысоединятся с вашим провайдером Internet до того, как попробуете запуститьпрограмму или будете огорчены неким сообщением об ошибке.
Если учесть все это,команда bind() принимает вид:
Naming.bind (« //peppy:2005/CalculatorService»,с);
Если вы используете портпо умолчанию 1099, вам не нужно указывать порт, так что вы можете простосказать:
Naming.bind (« //peppy/CalculatorService»,с);
Вы можете выполнитьлокальную проверку, оставив в покое IP адрес, а использовать толькоидентификатор:
Naming.bind («CalculatorService», с);
Имя сервиса здесьпроизвольно. В данном случае CalculatorService выбрано просто как имя класса, но выможете назвать так, как захотите. Важно, чтобы это было уникальное имярегистрации, чтобы клиент знал, когда будет искать, что производит удаленныеобъекты. Если имя уже зарегистрировано, вы получите AlreadyBoundException.Чтобы предотвратить это, вы всегда можете использовать rebind() вместо bind(),так как rebind() либо добавляет новый элемент, либо заменяет ужесуществующий.
Даже после завершенияработы main(), ваш объект будет оставаться созданным изарегистрированным, ожидая, что придет клиент и выполнит запрос. Пока rmiregistryостается запущенным, и вы не вызовите Naming.unbind() на вашей машине,объект будет оставаться там. По этой причине, когда вы разрабатываете ваш код,вам необходимо выгружать rmiregistry и перезапускать его, когдаскомпилируете новую версию вашего удаленного объекта.
Вам не обязательнозапускать rmiregistry как внешний процесс. Если вы знаете, что тольковаше приложение использует регистрацию, вы можете загрузить ее внутри вашейпрограммы с помощью строки:
LocateRegistry.createRegistry(2005);
Как и раньше, 2005 – этономер порта, который мы использовали в этом примере. Это эквивалентно запуску rmiregistry2005 из командной строки, но часто этот способ является более подходящимпри разработке RMI кода, так как это снжает число необходимых действий призапуске и остановке регистрации После того, как вы выполните этот код, выможете вызвать bind(), используя Naming, как и ранее.
5. Клиент
Исходный код клиентаследующий:
importjava.rmi. Naming;•
importjava.rmi. RemoteException;
importjava.net. MalformedURLException;
importjava.rmi. NotBoundException;
public classCalculatorClient {
public staticvoid main (String[] args) {try {
Calculator c =(Calculator)
Naming.lookup(«rmi://remotehost
/CalculatorService»);System.out.println (c.sub (4, 3)); System.out.println (c.add (4, 5));System.out.println (c.mul (3, 6)); System.out.println (c.div (9, 3));
}
catch(MalformedURLException murle) {
System.out.println();
System.out.println(
«MalformedURLException»);
System.out.println(murle);
}
catch(RemoteException re) {
System.out.println();
System.out.println(
«RemoteException»);
System.out.println(re);}catch (NotBoundException nbe) {
System.out.println();
System.out.println(
«NotBoundException»);System.out.println(nbe);
} catch (
java.lang. ArithmeticException
ae) {
System.out.println();System.out.println (
«java.lang. ArithmeticException»);System.out.println(ae);
6. Запуск RMI‑системы
Теперь вы готовы кзапуску системы! Вы должны запустить три консоли, одну для сервера, одну дляклиента и одну для реестра RMI.
Начните с реестра. Выдолжны находиться в каталоге, в котором находятся написанные вами классы.Напишите следующее:
Rmiregistry
Если не сработает, то втекущем каталоге наберите полный путь к файлу rmiregistry.exe, Он находится в каталогеJAVA_HOME/bin/
Если все пройдет хорошо,реестр начнет работать, и вы можете перейти к следующей консоли.
Во второй консолизапустите сервер, содержащий CalculatorService, и наберите следующее:
>java CalculatorServer
Программа запустится,загрузит реализацию в память и будет ожидать соединения клиента.
В последней консолизапустите клиентскую программу.
>java CalculatorClient
Если все пройдет хорошо,вы увидите следующую информацию:
1
9
18
3
Вот и все: вы создалиработающую систему RMI. Даже если вы запустили три консоли на одном и том же компьютере,RMI использует стекпротоколов TCP/IP вашей сети для взаимодействия между тремя отдельными JVM. Это вполне законченная RMI‑система.
Литература
1. Серия «Библиотека профессионала» К. ХорстманнГ. Корнелл «Java 2. том 2 «Тонкости программирования»» «Издательство Вильямс»2002 г.
2.Методичка с сайта JGURU.ru
3. Sun Java 2 SE API