Рабочий каталог расположен в
Технология, о которой мы говорили в предыдущем разделе, предполагает существенный объем программирования для реализации взаимодействия компонентов. Следующая рассматриваемая технология - - позволяет существенно упростить разработку распределенных систем.
дает возможность выполнять объекты Java на различных компьютерах или в отдельных процессах путем взаимодействия их друг с другом посредством удаленных вызовов методов. Технология основана на более ранней подобной технологии удаленного вызова процедур ( RPC ) для RPC позволяет процедуре вызывать функцию на другом компьютере столь же легко, как если бы эта функция была частью программы, выполняющейся на том же компьютере. RPC выполняет всю работу по организации сетевых взаимодействий и маршалинга данных (т.е. пакетирования параметров функций и возврата значений для передачи их через сеть).
Но RPC не подходит для передачи и возврата объектов Java, потому что она поддерживает ограниченный набор простых типов данных. Есть и другой недостаток у RPC - программисту необходимо знать специальный язык определения интерфейса ( IDL ) для описания функций, которые допускают удаленный вызов. Для устранения этих недостатков и была разработана технология .
представляет собой реализацию RPC на Java для распределенных коммуникационных взаимодействий "Java-объект - Java -объект". Объект Java регистрируется для удаленного доступа, что дает возможность клиентам получать удаленную ссылку на этот объект - она позволяет использовать этот объект дистанционно. Синтаксис вызова метода идентичен синтаксису вызова методов других объектов в той же программе. обслуживает маршалинг данных через сеть и дает возможность программам на Java передавать законченные объекты Java с помощью механизма Java. В составе имеются инструментальные средства создания требуемого кода для сетевых взаимодействий из определенных интерфейсов программы, это означает, что не требует от программиста знания языка IDL.
Кроме того, никакого нейтрального к языку IDL интерфейса не требуется, так как поддерживает только Java ; достаточно собственных интерфейсов Java.
В последующих разделах будет рассмотрен пример, использующий . При этом предлагается несколько вариантов решения поставленной задачи.
В примере выполняются четыре основных действия:
Решать предложенную задачу мы будем поэтапно - постепенно наращивая сложность наших программ, при этом иллюстрируя те или иные особенности применяемых технологий.
Для начала рассмотрим совсем простую задачу: создадим систему, функционально состоящую из двух компонентов - сервера (процессингового центра) и клиента (касса). Будем предполагать, что сервер в системе один (именно он обладает всей информацией о зарегистрированных картах и их балансах), а касс много и на них проходят операции регистрации новых карт, а также операции изменения баланса карт (соответственно - оплаты и занесения наличных).
На данном этапе мы никак не будем учитывать то, что связь между нашими объектами неустойчивая, - более того, мы будем считать ее устойчивой. Кроме того, в целях сокращения размеров примеров не будут рассматриваться вопросы долговременного хранения обрабатываемой информации - при перезапуске наша система будет "забывать" все занесенные в нее карты и их балансы.
Определение удаленного интерфейса
При проектировании сервера нам необходимо определить выполняемые им функции и, соответственно, методы, которые будет вызывать клиент. Поскольку операциями нашей системы являются регистрация новой карты, занесение денег на карту, оплата картой покупки и просмотр баланса карты, будем считать, что наш сервер будет обладать четырьмя перечисленными методами - именно их клиент (касса) и будет вызывать. Поскольку единственным, что отличает одну карту от другой, является ее номер (код) - он и будет служить идентификатором, карты и поэтому будет присутствовать во всех методах сервера в качестве параметра. Поскольку этот код, вообще говоря, алфавитно-цифровой, будем использовать тип данных String для его хранения.
Удаленные методы, посредством которых клиент взаимодействует с удаленным объектом, используя , должны быть определены в удаленном интерфейсе. Соответственно, первый этап при создании распределенного приложения с помощью состоит в определении удаленного интерфейса, который описывает эти удаленные методы. Чтобы создать удаленный интерфейс, необходимо определить интерфейс, который будет расширять интерфейс java.rmi.Remote.Интерфейс Remote представляет собой тегирующий интерфейс - он не объявляет каких-либо методов, поэтому не обременен реализацией класса.
Распределенное -приложение должно экспортировать объект класса, который реализует интерфейс Remote,чтобы сделать этот удаленный объект доступным для приема удаленных вызовов метода из любой виртуальной машины Java, которая имеет соединение с компьютером, где выполняется удаленный объект.
Интерфейс BillingService (пример 5.1), который расширяет интерфейс Remote (строка 9), представляет собой интерфейс для нашего удаленного объекта (сервера). В строках 10-17 объявляются методы для работы с пластиковыми картами. Удаленный объект должен реализовать все объявленные в удаленном интерфейсе методы.
1 // BillingService.java
2 // Интерфейс BillingService объявляет методы для работы
3 // с пластиковыми картами
4 package com.asw.rmi.ex1;
5
6 // Набор базовых пакетов Java
7 import java.rmi.*;
8
9 public interface BillingService extends Remote {
10 // определение новой карты
11 public void addNewCard(String personName, String card) throws RemoteException;
12 // добавить денежные средства на карту
13 public void addMoney(String card, double money) throws RemoteException;
14 // снять денежные средства с карты
15 public void subMoney(String card, double money) throws RemoteException;
16 // получение баланса карты
17 public double getCardBalance(String card) throws RemoteException;
18 }
Когда узлы взаимодействуют между собой по сети, есть вероятность возникновения проблем при таких взаимодействиях. Например, компьютер сервера может выйти из строя или может отказать какой-либо сетевой ресурс. Поэтому для контроля подобных проблем взаимодействия в сети каждый метод в интерфейсе Remote должен содержать throws для указания, что метод может возбуждать контролируемые исключения RemoteException.
использует механизм Java для передачи параметров методу и возврата значений через сеть. В связи с этим все параметры метода и возвращаемые значения должны иметь описатель Serializable или один из
Реализация удаленного интерфейса
Следующим этапом является определение реализации удаленного объекта. В нашем случае класс реализации удаленного объекта имеет то же имя, что и удаленный интерфейс, но заканчивается на Impl.
Класс UnicastRemoteObject (пакет java. ) представляет базовые функциональные возможности, которые необходимы удаленным объектам для обслуживания удаленных запросов.
Конструкторы и методы класса UnicastRemoteObject возбуждают контролируемое исключение RemoteException,поэтому подклассы класса UnicastRemoteObject должны определять конструкторы, которые также возбуждают исключение RemoteException.
Конструктор класса UnicastRemoteObject экспортирует объект, чтобы сделать его доступным для приема удаленных вызовов. Экспорт объекта дает возможность удаленному объекту ожидать соединений с клиентами на анонимном порте (т.е. порте, выбираемом компьютером, на котором выполняется удаленный объект). Это дает возможность объекту осуществлять однонаправленное взаимодействие (взаимодействие "точка-точка" между двумя объектами посредством вызовов методов) с использованием стандартных соединений через сокеты. Классам удаленных объектов не нужно расширять этот класс, если эти классы применяют статический метод exportObject класса UnicastRemoteObject для экспорта удаленных объектов.
Предполагается, что клиенты должны осуществлять соединение на порте 1099 при попытке найти удаленный объект в реестре . Перегруженный конструктор для класса UnicastRemoteObject дает возможность задавать дополнительную информацию, такую как номер порта для экспорта удаленного объекта. Для этого необходимо определить URL, который клиент может использовать для получения удаленной ссылки на объект. Эта ссылка применяется для вызова методов удаленного объекта. URL обычно имеет форму
rmi://хост:порт/ИмяУдаленногоОбъекта,
где хост представляет собой имя компьютера, который выполняет сервер реестра ( rmiregistry ) для удаленных объектов (он также является компьютером, на котором выполняется удаленный объект), порт представляет собой номер порта, на котором выполняется сервер реестра на хост-компьютере, а ИмяУдаленногоОбъекта - имя, которое клиент будет предоставлять при попытках обнаружить удаленный объект в реестре. Утилита rmi-registry обслуживает реестр удаленных объектов и является составной частью . Номер порта реестра по умолчанию - 1099.
Для связывания удаленного объекта с реестром используются методы bind или rebind.Метод rebind гарантирует, что если объект уже был зарегистрирован под заданным именем, новый удаленный объект заменит ранее зарегистрированный объект. Это может быть важно, если регистрируется новая версия существующего удаленного объекта.
Класс BillingServiceImpl (пример 5.2) представляет собой удаленный объект, который реализует удаленный интерфейс BillingService.Клиент взаимодействует с объектом класса BillingServiceImpl,вызывая методы addNewCard, addMoney, subMoney, getCardBalance интерфейса BillingService для обработки информации по пластиковым картам. Класс BillingServiceImpl хранит сведения о картах в card ), где номер карты является ключом таблицы.
1 // BillingServicelmpl.java
2 // BillingServiceImpl реализует удаленный интерфейс BillingService
для
3 // предоставления удаленного объекта BillingService
4 package com.asw.rmi.ex1;
5
6 // Набор базовых пакетов Java
7 import java.rmi.*;
8 import java.util.*;
9 import java.rmi.server.*;
10
11 public class BillingServiceImpl extends UnicastRemoteObject
12 implements BillingService {
13
14 private Hashtable hash; // хэш-таблица для хранения карт
15 // инициализация сервера
16 public BillingServiceImpl() throws RemoteException{
17 super();
18 hash = new Hashtable();
19 }
20
21 // реализация метода addNewCard интерфейса BillingService
22 public void addNewCard(String personName, String card)
23 throws RemoteException {
24
25 hash.put(card, new Double(0.0));
26 }
27
28 // реализация метода addMoney интерфейса BillingService
29 public void addMoney(String card, double money) throws RemoteException {
30 Double d = (Double)hash.get(card);
31
32 if (d!=null) hash.put(card,new Double(d.doubleValue()+money));
33 else throw new NotExistsCardOperation();
34 }
35
36 // реализация метода subMoney интерфейса BillingService
37 public void subMoney(String card, double money) throws RemoteException {
38 Double d = (Double)hash.get(card);
39
40 if (d!=null) hash.put(card,new Double(d.doubleValue()-money));
41 else throw new NotExistsCardOperation();
42 }
43
44 // реализация метода getCardBalance интерфейса BillingService
45 public double getCardBalance(String card) throws RemoteException {
46 Double d = (Double)hash.get(card);
47 if (d!=null) return d.doubleValue();
48 else throw new NotExistsCardOperation();
49 };
50
51 // запуск удаленного объекта BillingService
52 public static void main (String[] args) throws Exception {
53 System.out.println("Initializing BillingService...");
54
55 // создание удаленного объекта
56 BillingService service = new BillingServiceImpl();
57
58 //задание имени удаленного объекта
59 String serviceName = "rmi://localhost/BillingService";
60 // регистрация удаленного объекта BillingService в реестре rmiregistry
61 Naming.rebind(serviceName, service);
62 }
63
64 }
Класс BillingServiceImpl реализует методы addNewCard (строки 22-26), addMoney (строки 29-34), subMoney (строки 37-42), getCardBalance (строки 45-49) интерфейса BillingService,чтобы отвечать на удаленные запросы.
Метод main (строки 52-62) создает удаленный объект BillingServiceImpl.Когда конструктор выполняется, он экспортирует удаленный объект, чтобы прослушивать удаленные запросы. В строке 59 определяется URL, который клиент может применить для получения удаленной ссылки на объект для вызова методов удаленного объекта.
В этой программе URL удаленного объекта имеет вид rmi://localhost/BillingService. Из этого следует, что реестр выполняется на машине localhost (т.е. на локальном компьютере), а для обнаружения клиентом сервиса нужно использовать имя BillingService.Имя localhost является синонимом IP -адреса 127.0.0.1 ( ).
В строке 61 вызывается статический метод rebind класса Naming (пакет java.rmi) для связывания удаленного объекта service класса BillingServiceImpl в реестре с URL rmi://localhost/BillingService.
Класс NotExistsCardOperation (пример 5.3) расширяет класс RemoteException.
1 // NotExistsCardOperation.java
2 package com.asw.rmi.ex1;
3
4 // Набор базовых пакетов Java
5 import java.rmi.RemoteException;
6
7 public class NotExistsCardOperation extends RemoteException {
8
9 }
Далее мы определяем клиентское приложение, которое будет обрабатывать запросы к пластиковым картам и пересылать их серверу. Для работы клиентского приложения необходимо знать URL вызываемого им удаленного объекта. Статический метод lookup класса Naming применяется для получения объектной ссылки на удаленный объект с заданным URL. Метод lookup осуществляет соединение с реестром и возвращает удаленную ссылку на удаленный объект.
Клиент может использовать эту удаленную ссылку, если она обращается к локальному объекту, выполняющемуся на той же виртуальной машине. Эта удаленная ссылка обращается к объекту-заглушке на клиенте. Заглушки дают возможность клиентам вызывать методы удаленного объекта. Объекты-заглушки принимают удаленные вызовы метода и передают эти вызовы , который выполняет сетевые соединения, позволяющие клиентам взаимодействовать с удаленным объектом. Уровень отвечает за сетевые соединения с удаленными объектами, поэтому обращения к удаленным объектам являются прозрачными для пользователя. обслуживает соединение с удаленным объектом, передачу параметров и возврат значений.
(рис 5.1) Архитектура RMIКласс BillingClient (пример 5.4) является клиентским приложением, которое вызывает удаленные методы addNewCard, addMoney и getCardBa-lance интерфейса BillingService для работы с пластиковыми картами посетителей через . В нашем примере мы не реализуем реальную работу касс столовых с пластиковыми картами.
Для иллюстрации определения и работы клиентского приложения нам достаточно произвести несколько операций с несколькими картами, используя удаленные методы удаленного объекта. В данном случае клиент в цикле заносит денежные средства на три пластиковые карты и в конце печатает результирующий баланс по этим пластиковым картам. При первом проходе цикла в случае отсутствия карт с заданными номерами клиент их создает.
1 // BillingClient.java
2 // BillingClient использует удаленный объект BillingService для
3 // работы с информацией на пластиковых картах
4 package com.asw.rmi.ex1;
5
6 // Набор базовых пакетов Java
7 import java.rmi.*;
8
9 public class BillingClient {
10 // выполнение BillingClient
11 public static void main(String[] args) throws Exception]
12 // создание строки, содержащей URL удаленного объекта
13 String objectName = "rmi://"+args[0]+"/BillingService";
14 System.out.println("Starting...\n");
15 // соединение с реестром RMI и получение удаленной ссылки
16 // на удаленный объект
17 BillingService bs = (BillingService)Naming.lookup(objectName);
18 System.out.println("done");
19
20 // начисление денежных средств на пластиковые карты
21 for (int i = 0; i < 10000; i++) {
22 try {
23 bs.addMoney("1", 1);
24 } catch (RemoteException e) {
25 bs.addNewCard("Piter", "1");
26 }
27
28 try {
29 bs.addMoney("2", 1);
30 } catch (RemoteException e) {
31 bs.addNewCard("Stefan", "2");
32 }
33
34 try {
35 bs.addMoney("3", 1);
36 } catch (RemoteException e) {
37 bs.addNewCard("Nataly", "3");
38 }
39 }
40 // печать текущего баланса обработанных карт
41 System.out.println("1:"+bs.getCardBalance("1"));
42 System.out.println("2:"+bs.getCardBalance("2"));
43 System.out.println("3:"+bs.getCardBalance("3"));
44 }
45 }
Метод main (строки 11-44) принимает в качестве параметра имя компьютера, на котором выполняется удаленный объект BillingService.В строке 13 создается строка objectName,которая содержит URL для нашего удаленного объекта. В строке 17 вызывается метод lookup класса Naming для получения удаленной ссылки на удаленный объект BillingService с заданным URL. В строках 21-38 производится добавление денежных средств на карты, а в строках 41-43 - печать текущего баланса этих карт.
Компиляция и выполнение сервера и клиента
Подготовив отдельные фрагменты, мы можем сформировать и выполнить наше распределенное приложение, но для этого потребуется несколько действий. Для начала необходимо компилировать исходные классы. Далее, нужно компилировать класс удаленного объекта ( ...Impl ), используя компилятор rmic (утилита ) для формирования класса-заглушки (о котором говорилось в предыдущем разделе). Этот класс должен быть доступен для клиента (либо локально, либо путем загрузки по сети),
чтобы дать возможность устанавливать Java 1.1 rmic формирует два класса - класс-заглушку и класс-каркас ( ). В Java 2 класс-каркас больше не требуется. Параметр командной строки -v1.2 указывает, что rmic следует создать только класс-заглушку.
Для нашего примера командная строка для компиляции класса удаленного объекта будет выглядеть следующим образом:
rmic -v1.2 com.asw.rmi.ex1.BillingServiceImpl
которая сгенерирует файл BillingServiceImpl_Stub.class.
Следующий этап - запуск реестра , который зарегистрирует удаленный объект. Командная строка
rmiregistry
запускает реестр на локальной машине. В окне командной строки в ответ на эту команду никакого текста отображаться не будет. Типичная ошибка заключается в том, что если не запустить реестр прежде чем попытаться привязать удаленный объект к реестру, будет сгенерировано исключение java.rmi.ConnectException,которое указывает, что программа не может соединиться с реестром.
Чтобы удаленный объект мог принимать удаленные вызовы методов, необходимо связать объект с именем в реестре . Для этого нужно запустить серверное приложение из командной строки. В нашем случае командная строка выглядит так:
java com.asw.rmi.ex1.BillingServiceImpl
В результате отображается сообщение об инициализации BillingService.
Теперь клиентское приложение может соединиться с удаленным объектом, выполняющимся на локальной машине localhost. Команда
java com.asw.rmi.ex1.BillingClient
соединит BillingClient с объектом BillingServiceImpl.
Если серверное приложение выполняется не на клиенте, можно указать IP -адрес или доменное имя компьютера-сервера в качестве параметра командной строки при выполнении клиента. Например, чтобы осуществить доступ к компьютеру с IP -адресом 192.168.1.1, введем команду
java com.asw.rmi.ex1.BillingClient 192.168.1.1
Несколько слов о синхронизации
Теперь, после того как нами реализовано первое приложение с использованием , настало время немного поговорить об одной из особенностей, связанной с применением этой технологии.
Дело в том, что при вызове клиентом метода удаленного объекта исполняющей частью системы на стороне сервера формируется (или выбирается из числа уже созданных) поток, в котором и происходит вызов. Таким образом, становится возможным одновременное выполнение методов сервера несколькими клиентами.
Непонимание этого факта может привести к ошибкам при программировании серверных объектов, которые затем будет очень сложно локализовать и устранить. Для иллюстрации рассмотрим один из возможных сценариев выполнения нашего приложения, в случае если два клиента одновременно вызывают метод addMoney для одной и той же карты (на приведенной схеме (таблица 5.1) события разворачиваются во времени - время течет сверху вниз):
| t | Клиент 1 | Клиент 2 |
|---|---|---|
| t1 | Вызов метода сервера addMoney с номером карты 1 и суммой 10 |
Вызов метода сервера addMoney с номером карты 1 и суммой 12 |
| t2 | Double d =
(Double)hash.get(card); |
Double d = (Double)hash.get(card); |
| t3 | if (d!=null) hash.put(card,new Double(d.doubleValue()+ |
|
| t4 | if (d!=null) hash.put(card,new Double(d.doubleValue()+ |
|
| t5 |
Итак, в момент t1 оба клиента вызывают метод addMoney для карты с номером 1 и с разными суммами начисления (мы предполагаем, что карта с таким номером существует). Далее по каким-то причинам выполнение потоков, в которых происходит работа методов addMoney для первого и второго клиентов, происходит с разными скоростями. В момент времени t2 первый клиент получает из хэша значение баланса карты - второй делает то же самое.
В момент времени t3 первый клиент изменяет значение баланса, прибавляя к нему свое начисление, и возвращает баланс в хэш. Ту же самую операцию, но только в момент времени t4 - позднее, чем первый - делает второй клиент. В результате приведенного сценария в хэше окажется значение баланса, измененное вторым клиентов, а изменения, которые сделал первый клиент, просто потеряются - т.е. созданное нами приложение отработает неправильно.
Эффект, в результате которого результат работы приложения зависит от скорости выполнения потоков (фактически - от последовательности действий), в литературе получил название "гонка потоков". Об этой и других проблемах, связанных с разработкой "параллельных" приложений, речь пойдет ниже, в соответствующем разделе. Пока же, для того чтобы обеспечить работоспособность нашего приложения, попробуем добавить ключевое слово synchronized в описание метода addMoney,получив следующее объявление (пример 5.5).
public synchronized void addMoney(String card, double money) throws RemoteException {
Double d = (Double)hash.get(card);
if (d!=null) hash.put(card,new Double(d.doubleValue()+money));
else throw new NotExistsCardOperation();
}
Таким образом, будет гарантировано то, что в указанный метод одномоментно сможет войти только один поток - приведенный сценарий станет
Может показаться, что, введя синхронизацию на уровне методов, мы решили проблему, однако это не так. Чтобы убедиться в этом, достаточно рассмотреть одновременное выполнение двух разных методов - addMoney и subMoney - с теми же самыми предположениями о скоростях выполнения. Несложно догадаться, что в приведенном примере блокировка должна выполняться не на уровне методов, а на уровне ресурсов - в нашем случае
public void addMoney(String card, double money) throws RemoteException{
synchronized (hash) {
Double d = (Double) hash.get(card);
if (d != null)
hash.put(card, new Double(d.doubleValue() + money));
else throw new NotExistsCardOperation();
}
}
В предыдущем примере мы создали могут создаваться приложения, обмен в которых осуществляется посредством передачи
Определение удаленного интерфейса
В отличие от предыдущего варианта интерфейс BillingService (пример 5.7) имеет не четыре, а три метода. Метод addNewCard (строка 11) заводит новую карту с указанными параметрами. Метод processOperations (строка 13) производит изменение баланса карт с указанными параметрами. В предыдущей реализации изменение баланса карты производилось двумя методами, один из которых увеличивал текущий баланс карты, другой - уменьшал, а величина изменения в любом случае была положительной. В данном случае величина изменения баланса может быть любой - как положительной, так и отрицательной; в первом случае это означает поступление денежных средств на баланс карты, во втором - списание денежных средств. Метод getCard (строка 15) по номеру карты возвращает экземпляр класса Card.
1 // BillingService.java
2 // Интерфейс BillingService объявляет методы для работы
3 // с пластиковыми картами
4 package com.asw.rmi.ex2;
5
6 // Набор базовых пакетов Java
7 import java.rmi.*;
8
9 public interface BillingService extends Remote {
10 // определение новой карты
11 public void addNewCard(Card card) throws RemoteException;
12 // изменение баланса карты
13 public void processOperations(CardOperation[] operations) throws RemoteException;
14 // получение баланса карты
15 public Card getCard(String card) throws RemoteException;
16 }
Как уже говорилось ранее, использует механизм Java для передачи параметров методу и возврата значений через сеть. Поэтому все классы, используемые в качестве параметров метода и возвращаемых значений, должны иметь описатель Serializable.
Тип Card (пример 5.8) несет информацию об имени посетителя ( person ), дате получения карты (createDate),номере карты (cardNumber) и балансе (balance).
1 // Card.java
2 // описание типа переменных Card
3 package com.asw.rmi.ex2;
4
5 // Набор базовых пакетов Java
6 import java.io.Serializable;
7 import java.util.*;
8
9 public class Card implements Serializable{
10 public Card(String person, Date createDate, String cardNumber, double balance)]
11 this.person = person;
12 this.createDate = createDate;
13 this.cardNumber = cardNumber;
14 this.balance = balance;
15 }
16 public String person;
17 public Date createDate;
18 public String cardNumber;
19 double balance;
20 public String toString(){
21 return "Card: cardNumber="+cardNumber+"\tBalance="+balance+
22 +"\tPerson="+person+"\tCreateDate="+createDate+"";
23 }
24 }
Тип CardOperation (пример 5.9) представляет собой одну операцию изменения баланса карты и несет информацию о номере карты (card),величине изменения баланса карты ( amount ) и дате операции (operationDate).
1 // CardOperation.java
2 // описание типа переменных CardOperation
3 package com.asw.rmi.ex2;
4
5 // Набор базовых пакетов Java
6 import java.util.*;
7 import java.io.*;
8
9 public class CardOperation implements Serializable {
10 public CardOperation(String card,double amount,Date operationDate){
11 this.card = card;
12 this.amount = amount;
13 this.operationDate = operationDate;
14 }
15 public String card;
16 public double amount;
17 public Date operationDate;
18 }
Реализация удаленного интерфейса
Класс BillingServiceImpl (пример 5.10) является удаленным объектом, который реализует удаленный интерфейс BillingService.Класс BillingServiceImpl реализует методы addNewCard (строки 22-25), processOperations (строки 28-36), getCard (строки 39-42) интерфейса BillingService,чтобы отвечать на удаленные запросы. Класс BillingServiceImpl хранит сведения о картах в
1 // BillingServicelmpl.java
2 // BillingServiceImpl реализует удаленный интерфейс BillingService
3 // для предоставления удаленного объекта BillingService
4 package com.asw.rmi.ex2;
5
6 // Набор базовых пакетов Java
7 import java.rmi.*;
8 import java.util.*;
9 import java.rmi.server.*;
10
11 public class BillingServiceImpl extends UnicastRemoteObject
12 implements BillingService {
13
14 private Hashtable hash; // хэш-таблица для хранения карт
15 // инициализация сервера
16 public BillingServiceImpl() throws RemoteException]
17 super();
18 hash = new Hashtable();
19 }
20
21 // реализация метода addNewCard интерфейса BillingService
22 public void addNewCard(Card card) throws RemoteException {
23
24 hash.put(card.cardNumber, card);
25 }
26
27 // реализация метода processOperations интерфейса BillingService
28 public void processOperations(CardOperation[] operations)
29 throws RemoteException {
30 for (int i=0;i<operations.length;i++){
31 Card c = (Card)hash.get(operations[i].card);
32 if (c==null) throw new NotExistsCardOperation();
33 c.balance+=operations[i].amount;
34 hash.put(operations[i].card,c);
35 }
36 }
37
38 // реализация метода getCard интерфейса BillingService
39 public Card getCard(String card) throws RemoteException{
40 Card c = (Card)hash.get(card);
41 return c;
42 };
43
44 // запуск удаленного объекта BillingService
45 public static void main (String[] args) throws Exception {
46 System.out.println("Initializing BillingService...");
47
48 // создание удаленного объекта
49 BillingService service = new BillingServiceImpl();
50
51 //задание имени удаленного объекта
52 String serviceName = "rmi://localhost/BillingService";
53 // регистрация удаленного объекта BillingService в реестре rmiregistry
54 Naming.rebind(serviceName, service);
55 }
56
57 }
Метод main (строки 45-55) создает удаленный объект BillingServiceImpl.В строке 52 определяется URL, который клиент может применить для получения удаленной ссылки на объект для вызова методов удаленного объекта.
В этой программе URL удаленного объекта имеет вид rmi://localhost/BillingService, т.е. реестр выполняется на машине localhost (т.е. на локальном компьютере), а для обнаружения клиентом сервиса должно использоваться имя BillingService.Имя localhost является синонимом IP -адреса 127.0.0.1.
В строке 54 вызывается статический метод rebind класса Naming (пакет java.rmi) для связывания удаленного объекта service класса BillingServiceImpl в реестре с URL rmi://localhost/BillingService.
Класс NotExistsCardOperation (пример 5.11) расширяет класс RemoteException.
1 // NotExistsCardOperation.java
2 package com.asw.rmi.ex2;
3
4 // Набор базовых пакетов Java
5 import java.rmi.RemoteException;
6
7 public class NotExistsCardOperation extends RemoteException {
8
9 }
Далее мы определяем клиентское приложение, которое будет обрабатывать запросы к пластиковым картам.
Класс BillingClient (пример 5.12) является клиентским приложением, которое вызывает удаленные методы addNewCard, addMoney и getCardBalance интерфейса BillingService для работы с пластиковыми картами посетителей через . Для иллюстрации работы клиентского приложения нам достаточно произвести несколько операций с несколькими картами, используя удаленные методы удаленного объекта. В данном случае, так же как и в первом примере, мы в цикле заносим денежные средства на три пластиковые карты, предварительно их создав, и в конце печатаем результирующий баланс по этим картам.
1 // BillingClient.java
2 // BillingClient использует удаленный объект BillingService для
3 // работы с информацией на пластиковых картах
4 package com.asw.rmi.ex2;
5
6 // Набор базовых пакетов Java
7 import java.rmi.*;
8 import java.util.Date;
9
10 public class BillingClient {
11 // выполнение BillingClient
12 public static void main(String[] args) throws Exception]
13 // создание строки, содержащей URL удаленного объекта
14 String objectName = "rmi://"+args[0]+"/BillingService";
15 System.out.println("Starting...\n");
16 // соединение с реестром RMI и получение удаленной ссылки
17 // на удаленный объект
18 BillingService bs = (BillingService)Naming.lookup(objectName);
19 System.out.println("done");
20
21 // проверка на наличие карт с указанными номерами
22 // в случае отсутствия карты с указанными параметрами
23 // добавляем новую карту
24 Card c;
25 c = bs.getCard("1");
26 if (c==null) {
27 c = new Card("Piter",new Date(),"1",0.0);
28 bs.addNewCard(c);
29 }
30
31 c = bs.getCard("2");
32 if (c==null) {
33 c = new Card("Stefan",new Date(),"2",0.0);
34 bs.addNewCard(c);
35 }
36
37 c = bs.getCard("3");
38 if (c==null) {
39 c = new Card("Nataly",new Date(),"3",0.0);
40 bs.addNewCard(c);
41 }
42
43 // определение массива операций по картам
44 System.err.println("begin...\n");
45 int cnt = 30000;
46 CardOperation[] co = new CardOperation[cnt];
47 for (int i = 0; i < i++;) {
48 switch (i%3){
49 case 0: co[i] = new CardOperation("1",1,new Date());break;
50 case 1: co[i] = new CardOperation("2",1,new Date());break;
51 case 2: co[i] = new CardOperation("3",1,new Date());break;
52 }
53 }
54 // проведение указанных в массиве операций
55 bs.processOperations(co);
56
57 // печать текущего баланса обработанных карт
58 System.out.println(bs.getCard("1"));
59 System.out.println(bs.getCard("2"));
60 System.out.println(bs.getCard("3"));
61 }
62 }
Метод main (строки 12-61) принимает в качестве параметра имя компьютера, на котором выполняется удаленный объект BillingService.В строке 14 создается строка objectName,которая содержит URL для нашего удаленного объекта. В строке 18 вызывается метод lookup класса Naming для получения удаленной ссылки на удаленный объект BillingService с заданным URL.
В строках 24-41 производится проверка наличия карт с номерами №1, №2, №3, в случае их отсутствия производится добавление новых карт с соответствующими параметрами. В строках 44-53 определяется массив операций по указанным картам. В цикле в массив заносятся операции по добавлению денежных средств на указанные карты и в указанном количестве. В строке 55 метод processOperations производит изменение балансов согласно полученному в качестве параметра массиву операций по картам. В строках 58-60 печатаем текущий баланс карт с номерами №1, №2, №3.
Компиляция и выполнение сервера и клиента
Компиляция и запуск аналогичны для первого примера. Для начала необходимо компилировать классы. Далее нужно компилировать класс удаленного объекта (...Impl).
Для нашего примера командная строка для компиляции класса удаленного объекта будет выглядеть следующим образом:
rmic -v1.2 com.asw.rmi.ex2.BillingServiceImpl
и она сгенерирует файл BillingServiceImplStub.class.
Следующий этап - запуск реестра , который зарегистрирует удаленный объект. Командная строка
rmiregistry
запускает реестр на локальной машине.
Для необходимо запустить серверное приложение из командной строки. В нашем случае командная строка выглядит следующим образом:
java com.asw.rmi.ex2.BillingServiceImpl
В результате отображается сообщение об инициализации BillingService.
Теперь клиентское приложение может соединиться с удаленным объектом, выполняющемся на локальной машине localhost.Команда
java com.asw.rmi.ex2.BillingClient localhost
соединит BillingClient с объектом BillingServiceImpl.
Несмотря на то, что два рассмотренных примера делают практически одно и то же, они все же достаточно сильно различаются. О первом отличии уже говорилось, однако нелишним будет еще раз акцентировать внимание на том, что во втором примере осуществлена передача пользовательских типов от клиента к серверу. И для того, чтобы это реализовать, нам не потребовалось предпринимать никаких действий, кроме объявления наших классов как сериализуемые. Абсолютно всю техническую работу сделала за нас .
Второе отличие заключается в скорости работы этих двух приложений - второе приложение работает гораздо быстрее. Связано это с тем, что вызов удаленного метода связан с довольно большими накладными расходами, заметную часть которых составляет время работы системной "обвязки" - чем меньше вызовов, тем этот вклад меньше. Свой вклад, безусловно, вносит и передача данных по сети - передача большого количества мелких пакетов опять же связана с большими накладными расходами, чем передача одного объемного.
Рабочий каталог расположен в
Технология, о которой мы говорили в предыдущем разделе, предполагает существенный объем программирования для реализации взаимодействия компонентов. Следующая рассматриваемая технология - - позволяет существенно упростить разработку распределенных систем.
дает возможность выполнять объекты Java на различных компьютерах или в отдельных процессах путем взаимодействия их друг с другом посредством удаленных вызовов методов. Технология основана на более ранней подобной технологии удаленного вызова процедур ( RPC ) для RPC позволяет процедуре вызывать функцию на другом компьютере столь же легко, как если бы эта функция была частью программы, выполняющейся на том же компьютере. RPC выполняет всю работу по организации сетевых взаимодействий и маршалинга данных (т.е. пакетирования параметров функций и возврата значений для передачи их через сеть).
Но RPC не подходит для передачи и возврата объектов Java, потому что она поддерживает ограниченный набор простых типов данных. Есть и другой недостаток у RPC - программисту необходимо знать специальный язык определения интерфейса ( IDL ) для описания функций, которые допускают удаленный вызов. Для устранения этих недостатков и была разработана технология .
представляет собой реализацию RPC на Java для распределенных коммуникационных взаимодействий "Java-объект - Java -объект". Объект Java регистрируется для удаленного доступа, что дает возможность клиентам получать удаленную ссылку на этот объект - она позволяет использовать этот объект дистанционно. Синтаксис вызова метода идентичен синтаксису вызова методов других объектов в той же программе. обслуживает маршалинг данных через сеть и дает возможность программам на Java передавать законченные объекты Java с помощью механизма Java. В составе имеются инструментальные средства создания требуемого кода для сетевых взаимодействий из определенных интерфейсов программы, это означает, что не требует от программиста знания языка IDL.
Кроме того, никакого нейтрального к языку IDL интерфейса не требуется, так как поддерживает только Java ; достаточно собственных интерфейсов Java.
В последующих разделах будет рассмотрен пример, использующий . При этом предлагается несколько вариантов решения поставленной задачи.
В примере выполняются четыре основных действия:
Решать предложенную задачу мы будем поэтапно - постепенно наращивая сложность наших программ, при этом иллюстрируя те или иные особенности применяемых технологий.
Для начала рассмотрим совсем простую задачу: создадим систему, функционально состоящую из двух компонентов - сервера (процессингового центра) и клиента (касса). Будем предполагать, что сервер в системе один (именно он обладает всей информацией о зарегистрированных картах и их балансах), а касс много и на них проходят операции регистрации новых карт, а также операции изменения баланса карт (соответственно - оплаты и занесения наличных).
На данном этапе мы никак не будем учитывать то, что связь между нашими объектами неустойчивая, - более того, мы будем считать ее устойчивой. Кроме того, в целях сокращения размеров примеров не будут рассматриваться вопросы долговременного хранения обрабатываемой информации - при перезапуске наша система будет "забывать" все занесенные в нее карты и их балансы.
Определение удаленного интерфейса
При проектировании сервера нам необходимо определить выполняемые им функции и, соответственно, методы, которые будет вызывать клиент. Поскольку операциями нашей системы являются регистрация новой карты, занесение денег на карту, оплата картой покупки и просмотр баланса карты, будем считать, что наш сервер будет обладать четырьмя перечисленными методами - именно их клиент (касса) и будет вызывать. Поскольку единственным, что отличает одну карту от другой, является ее номер (код) - он и будет служить идентификатором, карты и поэтому будет присутствовать во всех методах сервера в качестве параметра. Поскольку этот код, вообще говоря, алфавитно-цифровой, будем использовать тип данных String для его хранения.
Удаленные методы, посредством которых клиент взаимодействует с удаленным объектом, используя , должны быть определены в удаленном интерфейсе. Соответственно, первый этап при создании распределенного приложения с помощью состоит в определении удаленного интерфейса, который описывает эти удаленные методы. Чтобы создать удаленный интерфейс, необходимо определить интерфейс, который будет расширять интерфейс java.rmi.Remote.Интерфейс Remote представляет собой тегирующий интерфейс - он не объявляет каких-либо методов, поэтому не обременен реализацией класса.
Распределенное -приложение должно экспортировать объект класса, который реализует интерфейс Remote,чтобы сделать этот удаленный объект доступным для приема удаленных вызовов метода из любой виртуальной машины Java, которая имеет соединение с компьютером, где выполняется удаленный объект.
Интерфейс BillingService (пример 5.1), который расширяет интерфейс Remote (строка 9), представляет собой интерфейс для нашего удаленного объекта (сервера). В строках 10-17 объявляются методы для работы с пластиковыми картами. Удаленный объект должен реализовать все объявленные в удаленном интерфейсе методы.
1 // BillingService.java
2 // Интерфейс BillingService объявляет методы для работы
3 // с пластиковыми картами
4 package com.asw.rmi.ex1;
5
6 // Набор базовых пакетов Java
7 import java.rmi.*;
8
9 public interface BillingService extends Remote {
10 // определение новой карты
11 public void addNewCard(String personName, String card) throws RemoteException;
12 // добавить денежные средства на карту
13 public void addMoney(String card, double money) throws RemoteException;
14 // снять денежные средства с карты
15 public void subMoney(String card, double money) throws RemoteException;
16 // получение баланса карты
17 public double getCardBalance(String card) throws RemoteException;
18 }
Когда узлы взаимодействуют между собой по сети, есть вероятность возникновения проблем при таких взаимодействиях. Например, компьютер сервера может выйти из строя или может отказать какой-либо сетевой ресурс. Поэтому для контроля подобных проблем взаимодействия в сети каждый метод в интерфейсе Remote должен содержать throws для указания, что метод может возбуждать контролируемые исключения RemoteException.
использует механизм Java для передачи параметров методу и возврата значений через сеть. В связи с этим все параметры метода и возвращаемые значения должны иметь описатель Serializable или один из
Реализация удаленного интерфейса
Следующим этапом является определение реализации удаленного объекта. В нашем случае класс реализации удаленного объекта имеет то же имя, что и удаленный интерфейс, но заканчивается на Impl.
Класс UnicastRemoteObject (пакет java. ) представляет базовые функциональные возможности, которые необходимы удаленным объектам для обслуживания удаленных запросов.
Конструкторы и методы класса UnicastRemoteObject возбуждают контролируемое исключение RemoteException,поэтому подклассы класса UnicastRemoteObject должны определять конструкторы, которые также возбуждают исключение RemoteException.
Конструктор класса UnicastRemoteObject экспортирует объект, чтобы сделать его доступным для приема удаленных вызовов. Экспорт объекта дает возможность удаленному объекту ожидать соединений с клиентами на анонимном порте (т.е. порте, выбираемом компьютером, на котором выполняется удаленный объект). Это дает возможность объекту осуществлять однонаправленное взаимодействие (взаимодействие "точка-точка" между двумя объектами посредством вызовов методов) с использованием стандартных соединений через сокеты. Классам удаленных объектов не нужно расширять этот класс, если эти классы применяют статический метод exportObject класса UnicastRemoteObject для экспорта удаленных объектов.
Предполагается, что клиенты должны осуществлять соединение на порте 1099 при попытке найти удаленный объект в реестре . Перегруженный конструктор для класса UnicastRemoteObject дает возможность задавать дополнительную информацию, такую как номер порта для экспорта удаленного объекта. Для этого необходимо определить URL, который клиент может использовать для получения удаленной ссылки на объект. Эта ссылка применяется для вызова методов удаленного объекта. URL обычно имеет форму
rmi://хост:порт/ИмяУдаленногоОбъекта,
где хост представляет собой имя компьютера, который выполняет сервер реестра ( rmiregistry ) для удаленных объектов (он также является компьютером, на котором выполняется удаленный объект), порт представляет собой номер порта, на котором выполняется сервер реестра на хост-компьютере, а ИмяУдаленногоОбъекта - имя, которое клиент будет предоставлять при попытках обнаружить удаленный объект в реестре. Утилита rmi-registry обслуживает реестр удаленных объектов и является составной частью . Номер порта реестра по умолчанию - 1099.
Для связывания удаленного объекта с реестром используются методы bind или rebind.Метод rebind гарантирует, что если объект уже был зарегистрирован под заданным именем, новый удаленный объект заменит ранее зарегистрированный объект. Это может быть важно, если регистрируется новая версия существующего удаленного объекта.
Класс BillingServiceImpl (пример 5.2) представляет собой удаленный объект, который реализует удаленный интерфейс BillingService.Клиент взаимодействует с объектом класса BillingServiceImpl,вызывая методы addNewCard, addMoney, subMoney, getCardBalance интерфейса BillingService для обработки информации по пластиковым картам. Класс BillingServiceImpl хранит сведения о картах в card ), где номер карты является ключом таблицы.
1 // BillingServicelmpl.java
2 // BillingServiceImpl реализует удаленный интерфейс BillingService
для
3 // предоставления удаленного объекта BillingService
4 package com.asw.rmi.ex1;
5
6 // Набор базовых пакетов Java
7 import java.rmi.*;
8 import java.util.*;
9 import java.rmi.server.*;
10
11 public class BillingServiceImpl extends UnicastRemoteObject
12 implements BillingService {
13
14 private Hashtable hash; // хэш-таблица для хранения карт
15 // инициализация сервера
16 public BillingServiceImpl() throws RemoteException{
17 super();
18 hash = new Hashtable();
19 }
20
21 // реализация метода addNewCard интерфейса BillingService
22 public void addNewCard(String personName, String card)
23 throws RemoteException {
24
25 hash.put(card, new Double(0.0));
26 }
27
28 // реализация метода addMoney интерфейса BillingService
29 public void addMoney(String card, double money) throws RemoteException {
30 Double d = (Double)hash.get(card);
31
32 if (d!=null) hash.put(card,new Double(d.doubleValue()+money));
33 else throw new NotExistsCardOperation();
34 }
35
36 // реализация метода subMoney интерфейса BillingService
37 public void subMoney(String card, double money) throws RemoteException {
38 Double d = (Double)hash.get(card);
39
40 if (d!=null) hash.put(card,new Double(d.doubleValue()-money));
41 else throw new NotExistsCardOperation();
42 }
43
44 // реализация метода getCardBalance интерфейса BillingService
45 public double getCardBalance(String card) throws RemoteException {
46 Double d = (Double)hash.get(card);
47 if (d!=null) return d.doubleValue();
48 else throw new NotExistsCardOperation();
49 };
50
51 // запуск удаленного объекта BillingService
52 public static void main (String[] args) throws Exception {
53 System.out.println("Initializing BillingService...");
54
55 // создание удаленного объекта
56 BillingService service = new BillingServiceImpl();
57
58 //задание имени удаленного объекта
59 String serviceName = "rmi://localhost/BillingService";
60 // регистрация удаленного объекта BillingService в реестре rmiregistry
61 Naming.rebind(serviceName, service);
62 }
63
64 }
Класс BillingServiceImpl реализует методы addNewCard (строки 22-26), addMoney (строки 29-34), subMoney (строки 37-42), getCardBalance (строки 45-49) интерфейса BillingService,чтобы отвечать на удаленные запросы.
Метод main (строки 52-62) создает удаленный объект BillingServiceImpl.Когда конструктор выполняется, он экспортирует удаленный объект, чтобы прослушивать удаленные запросы. В строке 59 определяется URL, который клиент может применить для получения удаленной ссылки на объект для вызова методов удаленного объекта.
В этой программе URL удаленного объекта имеет вид rmi://localhost/BillingService. Из этого следует, что реестр выполняется на машине localhost (т.е. на локальном компьютере), а для обнаружения клиентом сервиса нужно использовать имя BillingService.Имя localhost является синонимом IP -адреса 127.0.0.1 ( ).
В строке 61 вызывается статический метод rebind класса Naming (пакет java.rmi) для связывания удаленного объекта service класса BillingServiceImpl в реестре с URL rmi://localhost/BillingService.
Класс NotExistsCardOperation (пример 5.3) расширяет класс RemoteException.
1 // NotExistsCardOperation.java
2 package com.asw.rmi.ex1;
3
4 // Набор базовых пакетов Java
5 import java.rmi.RemoteException;
6
7 public class NotExistsCardOperation extends RemoteException {
8
9 }
Далее мы определяем клиентское приложение, которое будет обрабатывать запросы к пластиковым картам и пересылать их серверу. Для работы клиентского приложения необходимо знать URL вызываемого им удаленного объекта. Статический метод lookup класса Naming применяется для получения объектной ссылки на удаленный объект с заданным URL. Метод lookup осуществляет соединение с реестром и возвращает удаленную ссылку на удаленный объект.
Клиент может использовать эту удаленную ссылку, если она обращается к локальному объекту, выполняющемуся на той же виртуальной машине. Эта удаленная ссылка обращается к объекту-заглушке на клиенте. Заглушки дают возможность клиентам вызывать методы удаленного объекта. Объекты-заглушки принимают удаленные вызовы метода и передают эти вызовы , который выполняет сетевые соединения, позволяющие клиентам взаимодействовать с удаленным объектом. Уровень отвечает за сетевые соединения с удаленными объектами, поэтому обращения к удаленным объектам являются прозрачными для пользователя. обслуживает соединение с удаленным объектом, передачу параметров и возврат значений.
(рис 5.1) Архитектура RMIКласс BillingClient (пример 5.4) является клиентским приложением, которое вызывает удаленные методы addNewCard, addMoney и getCardBa-lance интерфейса BillingService для работы с пластиковыми картами посетителей через . В нашем примере мы не реализуем реальную работу касс столовых с пластиковыми картами.
Для иллюстрации определения и работы клиентского приложения нам достаточно произвести несколько операций с несколькими картами, используя удаленные методы удаленного объекта. В данном случае клиент в цикле заносит денежные средства на три пластиковые карты и в конце печатает результирующий баланс по этим пластиковым картам. При первом проходе цикла в случае отсутствия карт с заданными номерами клиент их создает.
1 // BillingClient.java
2 // BillingClient использует удаленный объект BillingService для
3 // работы с информацией на пластиковых картах
4 package com.asw.rmi.ex1;
5
6 // Набор базовых пакетов Java
7 import java.rmi.*;
8
9 public class BillingClient {
10 // выполнение BillingClient
11 public static void main(String[] args) throws Exception]
12 // создание строки, содержащей URL удаленного объекта
13 String objectName = "rmi://"+args[0]+"/BillingService";
14 System.out.println("Starting...\n");
15 // соединение с реестром RMI и получение удаленной ссылки
16 // на удаленный объект
17 BillingService bs = (BillingService)Naming.lookup(objectName);
18 System.out.println("done");
19
20 // начисление денежных средств на пластиковые карты
21 for (int i = 0; i < 10000; i++) {
22 try {
23 bs.addMoney("1", 1);
24 } catch (RemoteException e) {
25 bs.addNewCard("Piter", "1");
26 }
27
28 try {
29 bs.addMoney("2", 1);
30 } catch (RemoteException e) {
31 bs.addNewCard("Stefan", "2");
32 }
33
34 try {
35 bs.addMoney("3", 1);
36 } catch (RemoteException e) {
37 bs.addNewCard("Nataly", "3");
38 }
39 }
40 // печать текущего баланса обработанных карт
41 System.out.println("1:"+bs.getCardBalance("1"));
42 System.out.println("2:"+bs.getCardBalance("2"));
43 System.out.println("3:"+bs.getCardBalance("3"));
44 }
45 }
Метод main (строки 11-44) принимает в качестве параметра имя компьютера, на котором выполняется удаленный объект BillingService.В строке 13 создается строка objectName,которая содержит URL для нашего удаленного объекта. В строке 17 вызывается метод lookup класса Naming для получения удаленной ссылки на удаленный объект BillingService с заданным URL. В строках 21-38 производится добавление денежных средств на карты, а в строках 41-43 - печать текущего баланса этих карт.
Компиляция и выполнение сервера и клиента
Подготовив отдельные фрагменты, мы можем сформировать и выполнить наше распределенное приложение, но для этого потребуется несколько действий. Для начала необходимо компилировать исходные классы. Далее, нужно компилировать класс удаленного объекта ( ...Impl ), используя компилятор rmic (утилита ) для формирования класса-заглушки (о котором говорилось в предыдущем разделе). Этот класс должен быть доступен для клиента (либо локально, либо путем загрузки по сети),
чтобы дать возможность устанавливать Java 1.1 rmic формирует два класса - класс-заглушку и класс-каркас ( ). В Java 2 класс-каркас больше не требуется. Параметр командной строки -v1.2 указывает, что rmic следует создать только класс-заглушку.
Для нашего примера командная строка для компиляции класса удаленного объекта будет выглядеть следующим образом:
rmic -v1.2 com.asw.rmi.ex1.BillingServiceImpl
которая сгенерирует файл BillingServiceImpl_Stub.class.
Следующий этап - запуск реестра , который зарегистрирует удаленный объект. Командная строка
rmiregistry
запускает реестр на локальной машине. В окне командной строки в ответ на эту команду никакого текста отображаться не будет. Типичная ошибка заключается в том, что если не запустить реестр прежде чем попытаться привязать удаленный объект к реестру, будет сгенерировано исключение java.rmi.ConnectException,которое указывает, что программа не может соединиться с реестром.
Чтобы удаленный объект мог принимать удаленные вызовы методов, необходимо связать объект с именем в реестре . Для этого нужно запустить серверное приложение из командной строки. В нашем случае командная строка выглядит так:
java com.asw.rmi.ex1.BillingServiceImpl
В результате отображается сообщение об инициализации BillingService.
Теперь клиентское приложение может соединиться с удаленным объектом, выполняющимся на локальной машине localhost. Команда
java com.asw.rmi.ex1.BillingClient
соединит BillingClient с объектом BillingServiceImpl.
Если серверное приложение выполняется не на клиенте, можно указать IP -адрес или доменное имя компьютера-сервера в качестве параметра командной строки при выполнении клиента. Например, чтобы осуществить доступ к компьютеру с IP -адресом 192.168.1.1, введем команду
java com.asw.rmi.ex1.BillingClient 192.168.1.1
Несколько слов о синхронизации
Теперь, после того как нами реализовано первое приложение с использованием , настало время немного поговорить об одной из особенностей, связанной с применением этой технологии.
Дело в том, что при вызове клиентом метода удаленного объекта исполняющей частью системы на стороне сервера формируется (или выбирается из числа уже созданных) поток, в котором и происходит вызов. Таким образом, становится возможным одновременное выполнение методов сервера несколькими клиентами.
Непонимание этого факта может привести к ошибкам при программировании серверных объектов, которые затем будет очень сложно локализовать и устранить. Для иллюстрации рассмотрим один из возможных сценариев выполнения нашего приложения, в случае если два клиента одновременно вызывают метод addMoney для одной и той же карты (на приведенной схеме (таблица 5.1) события разворачиваются во времени - время течет сверху вниз):
| t | Клиент 1 | Клиент 2 |
|---|---|---|
| t1 | Вызов метода сервера addMoney с номером карты 1 и суммой 10 |
Вызов метода сервера addMoney с номером карты 1 и суммой 12 |
| t2 | Double d =
(Double)hash.get(card); |
Double d = (Double)hash.get(card); |
| t3 | if (d!=null) hash.put(card,new Double(d.doubleValue()+ |
|
| t4 | if (d!=null) hash.put(card,new Double(d.doubleValue()+ |
|
| t5 |
Итак, в момент t1 оба клиента вызывают метод addMoney для карты с номером 1 и с разными суммами начисления (мы предполагаем, что карта с таким номером существует). Далее по каким-то причинам выполнение потоков, в которых происходит работа методов addMoney для первого и второго клиентов, происходит с разными скоростями. В момент времени t2 первый клиент получает из хэша значение баланса карты - второй делает то же самое.
В момент времени t3 первый клиент изменяет значение баланса, прибавляя к нему свое начисление, и возвращает баланс в хэш. Ту же самую операцию, но только в момент времени t4 - позднее, чем первый - делает второй клиент. В результате приведенного сценария в хэше окажется значение баланса, измененное вторым клиентов, а изменения, которые сделал первый клиент, просто потеряются - т.е. созданное нами приложение отработает неправильно.
Эффект, в результате которого результат работы приложения зависит от скорости выполнения потоков (фактически - от последовательности действий), в литературе получил название "гонка потоков". Об этой и других проблемах, связанных с разработкой "параллельных" приложений, речь пойдет ниже, в соответствующем разделе. Пока же, для того чтобы обеспечить работоспособность нашего приложения, попробуем добавить ключевое слово synchronized в описание метода addMoney,получив следующее объявление (пример 5.5).
public synchronized void addMoney(String card, double money) throws RemoteException {
Double d = (Double)hash.get(card);
if (d!=null) hash.put(card,new Double(d.doubleValue()+money));
else throw new NotExistsCardOperation();
}
Таким образом, будет гарантировано то, что в указанный метод одномоментно сможет войти только один поток - приведенный сценарий станет
Может показаться, что, введя синхронизацию на уровне методов, мы решили проблему, однако это не так. Чтобы убедиться в этом, достаточно рассмотреть одновременное выполнение двух разных методов - addMoney и subMoney - с теми же самыми предположениями о скоростях выполнения. Несложно догадаться, что в приведенном примере блокировка должна выполняться не на уровне методов, а на уровне ресурсов - в нашем случае
public void addMoney(String card, double money) throws RemoteException{
synchronized (hash) {
Double d = (Double) hash.get(card);
if (d != null)
hash.put(card, new Double(d.doubleValue() + money));
else throw new NotExistsCardOperation();
}
}
В предыдущем примере мы создали могут создаваться приложения, обмен в которых осуществляется посредством передачи
Определение удаленного интерфейса
В отличие от предыдущего варианта интерфейс BillingService (пример 5.7) имеет не четыре, а три метода. Метод addNewCard (строка 11) заводит новую карту с указанными параметрами. Метод processOperations (строка 13) производит изменение баланса карт с указанными параметрами. В предыдущей реализации изменение баланса карты производилось двумя методами, один из которых увеличивал текущий баланс карты, другой - уменьшал, а величина изменения в любом случае была положительной. В данном случае величина изменения баланса может быть любой - как положительной, так и отрицательной; в первом случае это означает поступление денежных средств на баланс карты, во втором - списание денежных средств. Метод getCard (строка 15) по номеру карты возвращает экземпляр класса Card.
1 // BillingService.java
2 // Интерфейс BillingService объявляет методы для работы
3 // с пластиковыми картами
4 package com.asw.rmi.ex2;
5
6 // Набор базовых пакетов Java
7 import java.rmi.*;
8
9 public interface BillingService extends Remote {
10 // определение новой карты
11 public void addNewCard(Card card) throws RemoteException;
12 // изменение баланса карты
13 public void processOperations(CardOperation[] operations) throws RemoteException;
14 // получение баланса карты
15 public Card getCard(String card) throws RemoteException;
16 }
Как уже говорилось ранее, использует механизм Java для передачи параметров методу и возврата значений через сеть. Поэтому все классы, используемые в качестве параметров метода и возвращаемых значений, должны иметь описатель Serializable.
Тип Card (пример 5.8) несет информацию об имени посетителя ( person ), дате получения карты (createDate),номере карты (cardNumber) и балансе (balance).
1 // Card.java
2 // описание типа переменных Card
3 package com.asw.rmi.ex2;
4
5 // Набор базовых пакетов Java
6 import java.io.Serializable;
7 import java.util.*;
8
9 public class Card implements Serializable{
10 public Card(String person, Date createDate, String cardNumber, double balance)]
11 this.person = person;
12 this.createDate = createDate;
13 this.cardNumber = cardNumber;
14 this.balance = balance;
15 }
16 public String person;
17 public Date createDate;
18 public String cardNumber;
19 double balance;
20 public String toString(){
21 return "Card: cardNumber="+cardNumber+"\tBalance="+balance+
22 +"\tPerson="+person+"\tCreateDate="+createDate+"";
23 }
24 }
Тип CardOperation (пример 5.9) представляет собой одну операцию изменения баланса карты и несет информацию о номере карты (card),величине изменения баланса карты ( amount ) и дате операции (operationDate).
1 // CardOperation.java
2 // описание типа переменных CardOperation
3 package com.asw.rmi.ex2;
4
5 // Набор базовых пакетов Java
6 import java.util.*;
7 import java.io.*;
8
9 public class CardOperation implements Serializable {
10 public CardOperation(String card,double amount,Date operationDate){
11 this.card = card;
12 this.amount = amount;
13 this.operationDate = operationDate;
14 }
15 public String card;
16 public double amount;
17 public Date operationDate;
18 }
Реализация удаленного интерфейса
Класс BillingServiceImpl (пример 5.10) является удаленным объектом, который реализует удаленный интерфейс BillingService.Класс BillingServiceImpl реализует методы addNewCard (строки 22-25), processOperations (строки 28-36), getCard (строки 39-42) интерфейса BillingService,чтобы отвечать на удаленные запросы. Класс BillingServiceImpl хранит сведения о картах в
1 // BillingServicelmpl.java
2 // BillingServiceImpl реализует удаленный интерфейс BillingService
3 // для предоставления удаленного объекта BillingService
4 package com.asw.rmi.ex2;
5
6 // Набор базовых пакетов Java
7 import java.rmi.*;
8 import java.util.*;
9 import java.rmi.server.*;
10
11 public class BillingServiceImpl extends UnicastRemoteObject
12 implements BillingService {
13
14 private Hashtable hash; // хэш-таблица для хранения карт
15 // инициализация сервера
16 public BillingServiceImpl() throws RemoteException]
17 super();
18 hash = new Hashtable();
19 }
20
21 // реализация метода addNewCard интерфейса BillingService
22 public void addNewCard(Card card) throws RemoteException {
23
24 hash.put(card.cardNumber, card);
25 }
26
27 // реализация метода processOperations интерфейса BillingService
28 public void processOperations(CardOperation[] operations)
29 throws RemoteException {
30 for (int i=0;i<operations.length;i++){
31 Card c = (Card)hash.get(operations[i].card);
32 if (c==null) throw new NotExistsCardOperation();
33 c.balance+=operations[i].amount;
34 hash.put(operations[i].card,c);
35 }
36 }
37
38 // реализация метода getCard интерфейса BillingService
39 public Card getCard(String card) throws RemoteException{
40 Card c = (Card)hash.get(card);
41 return c;
42 };
43
44 // запуск удаленного объекта BillingService
45 public static void main (String[] args) throws Exception {
46 System.out.println("Initializing BillingService...");
47
48 // создание удаленного объекта
49 BillingService service = new BillingServiceImpl();
50
51 //задание имени удаленного объекта
52 String serviceName = "rmi://localhost/BillingService";
53 // регистрация удаленного объекта BillingService в реестре rmiregistry
54 Naming.rebind(serviceName, service);
55 }
56
57 }
Метод main (строки 45-55) создает удаленный объект BillingServiceImpl.В строке 52 определяется URL, который клиент может применить для получения удаленной ссылки на объект для вызова методов удаленного объекта.
В этой программе URL удаленного объекта имеет вид rmi://localhost/BillingService, т.е. реестр выполняется на машине localhost (т.е. на локальном компьютере), а для обнаружения клиентом сервиса должно использоваться имя BillingService.Имя localhost является синонимом IP -адреса 127.0.0.1.
В строке 54 вызывается статический метод rebind класса Naming (пакет java.rmi) для связывания удаленного объекта service класса BillingServiceImpl в реестре с URL rmi://localhost/BillingService.
Класс NotExistsCardOperation (пример 5.11) расширяет класс RemoteException.
1 // NotExistsCardOperation.java
2 package com.asw.rmi.ex2;
3
4 // Набор базовых пакетов Java
5 import java.rmi.RemoteException;
6
7 public class NotExistsCardOperation extends RemoteException {
8
9 }
Далее мы определяем клиентское приложение, которое будет обрабатывать запросы к пластиковым картам.
Класс BillingClient (пример 5.12) является клиентским приложением, которое вызывает удаленные методы addNewCard, addMoney и getCardBalance интерфейса BillingService для работы с пластиковыми картами посетителей через . Для иллюстрации работы клиентского приложения нам достаточно произвести несколько операций с несколькими картами, используя удаленные методы удаленного объекта. В данном случае, так же как и в первом примере, мы в цикле заносим денежные средства на три пластиковые карты, предварительно их создав, и в конце печатаем результирующий баланс по этим картам.
1 // BillingClient.java
2 // BillingClient использует удаленный объект BillingService для
3 // работы с информацией на пластиковых картах
4 package com.asw.rmi.ex2;
5
6 // Набор базовых пакетов Java
7 import java.rmi.*;
8 import java.util.Date;
9
10 public class BillingClient {
11 // выполнение BillingClient
12 public static void main(String[] args) throws Exception]
13 // создание строки, содержащей URL удаленного объекта
14 String objectName = "rmi://"+args[0]+"/BillingService";
15 System.out.println("Starting...\n");
16 // соединение с реестром RMI и получение удаленной ссылки
17 // на удаленный объект
18 BillingService bs = (BillingService)Naming.lookup(objectName);
19 System.out.println("done");
20
21 // проверка на наличие карт с указанными номерами
22 // в случае отсутствия карты с указанными параметрами
23 // добавляем новую карту
24 Card c;
25 c = bs.getCard("1");
26 if (c==null) {
27 c = new Card("Piter",new Date(),"1",0.0);
28 bs.addNewCard(c);
29 }
30
31 c = bs.getCard("2");
32 if (c==null) {
33 c = new Card("Stefan",new Date(),"2",0.0);
34 bs.addNewCard(c);
35 }
36
37 c = bs.getCard("3");
38 if (c==null) {
39 c = new Card("Nataly",new Date(),"3",0.0);
40 bs.addNewCard(c);
41 }
42
43 // определение массива операций по картам
44 System.err.println("begin...\n");
45 int cnt = 30000;
46 CardOperation[] co = new CardOperation[cnt];
47 for (int i = 0; i < i++;) {
48 switch (i%3){
49 case 0: co[i] = new CardOperation("1",1,new Date());break;
50 case 1: co[i] = new CardOperation("2",1,new Date());break;
51 case 2: co[i] = new CardOperation("3",1,new Date());break;
52 }
53 }
54 // проведение указанных в массиве операций
55 bs.processOperations(co);
56
57 // печать текущего баланса обработанных карт
58 System.out.println(bs.getCard("1"));
59 System.out.println(bs.getCard("2"));
60 System.out.println(bs.getCard("3"));
61 }
62 }
Метод main (строки 12-61) принимает в качестве параметра имя компьютера, на котором выполняется удаленный объект BillingService.В строке 14 создается строка objectName,которая содержит URL для нашего удаленного объекта. В строке 18 вызывается метод lookup класса Naming для получения удаленной ссылки на удаленный объект BillingService с заданным URL.
В строках 24-41 производится проверка наличия карт с номерами №1, №2, №3, в случае их отсутствия производится добавление новых карт с соответствующими параметрами. В строках 44-53 определяется массив операций по указанным картам. В цикле в массив заносятся операции по добавлению денежных средств на указанные карты и в указанном количестве. В строке 55 метод processOperations производит изменение балансов согласно полученному в качестве параметра массиву операций по картам. В строках 58-60 печатаем текущий баланс карт с номерами №1, №2, №3.
Компиляция и выполнение сервера и клиента
Компиляция и запуск аналогичны для первого примера. Для начала необходимо компилировать классы. Далее нужно компилировать класс удаленного объекта (...Impl).
Для нашего примера командная строка для компиляции класса удаленного объекта будет выглядеть следующим образом:
rmic -v1.2 com.asw.rmi.ex2.BillingServiceImpl
и она сгенерирует файл BillingServiceImplStub.class.
Следующий этап - запуск реестра , который зарегистрирует удаленный объект. Командная строка
rmiregistry
запускает реестр на локальной машине.
Для необходимо запустить серверное приложение из командной строки. В нашем случае командная строка выглядит следующим образом:
java com.asw.rmi.ex2.BillingServiceImpl
В результате отображается сообщение об инициализации BillingService.
Теперь клиентское приложение может соединиться с удаленным объектом, выполняющемся на локальной машине localhost.Команда
java com.asw.rmi.ex2.BillingClient localhost
соединит BillingClient с объектом BillingServiceImpl.
Несмотря на то, что два рассмотренных примера делают практически одно и то же, они все же достаточно сильно различаются. О первом отличии уже говорилось, однако нелишним будет еще раз акцентировать внимание на том, что во втором примере осуществлена передача пользовательских типов от клиента к серверу. И для того, чтобы это реализовать, нам не потребовалось предпринимать никаких действий, кроме объявления наших классов как сериализуемые. Абсолютно всю техническую работу сделала за нас .
Второе отличие заключается в скорости работы этих двух приложений - второе приложение работает гораздо быстрее. Связано это с тем, что вызов удаленного метода связан с довольно большими накладными расходами, заметную часть которых составляет время работы системной "обвязки" - чем меньше вызовов, тем этот вклад меньше. Свой вклад, безусловно, вносит и передача данных по сети - передача большого количества мелких пакетов опять же связана с большими накладными расходами, чем передача одного объемного.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.