Описание и пример RMI технологии

Технология RMI (Java Remote Method Invocation) позволяет java-приложению, запущенному на одной виртуальной машине, вызвать методы объекта, работающего на другой виртуальной машине JVM (Java Virtual Machine).

RMI основана на более ранней технологии удаленного вызова процедур Remote Procedure Call (RPC), разработанной в 80-х годах и используемой для процедурного программирования. RPC позволяет процедуре одного приложения вызывать функцию на другом компьютере, как будто эта функция является частью программы. Таким образом, RPC выполняет всю работу по организации сетевых взаимодействий и маршалинга данных (пакетирования параметров функций и возврата значений для передачи их по сети). Но механизм RPC, поддерживающий ограниченный набор простых типов данных, не может использовать объекты Java при обмене информацией. Вторым важным недостатком RPC является необходимость использования специального языка определения интерфейса (IDL) для описания функций, допускающих удаленный вызов. С целью устранения этих недостатков и была разработана технология RMI.

RMI содержит набор объектов (классов) для организации удаленного взаимодействия java-приложений. RMI системы часто включают два отдельных приложения : сервер и клиент. Серверное приложение, как правило, создает удаленные объекты (remote objects), делает доступные ссылки на эти объекты и находится в ожидании вызова методов этих объектов. Клиентское приложение получает у сервера ссылку на удаленные объекты, после чего вызывает его методы. Технология RMI, обеспечивающая механизм взаимодействия клиента и сервера передачей между ними соответствующей информацией, реализована в виде java.rmi пакета, содержащего целый ряд вложенных подпакетов; один из наиболее важных подпакетов java.rmi.server реализует функции сервера RMI.

RMI обеспечивает маршалинг данных по сети и позволяет java приложениям передавать объекты с помощью механизма сериализации объектов. В состав J2SE включены инструментальные средства сетевых взаимодействий из определенных интерфейсов программы; это означает, что RMI не требует от программиста знания языка IDL. Кроме того, никакого нейтрального к языку IDL интерфейса не требуется, так как RMI поддерживает только Java; достаточно собственных интерфейсов Java.

Описание удаленного RMI объекта

Серверный RMI объект должен наследовать (extends) свойства класса java.rmi.server.UnicastRemoteObject, который представляет базовые функциональные возможности, необходимые удаленным объектам для обслуживания удаленных запросов. Конструкторы и методы класса UnicastRemoteObject возбуждают исключение RemoteException.

Конструктор класса UnicastRemoteObject обеспечивает экспорт объекта, чтобы он был доступным для приема удаленных вызовов. Экспорт позволяет удаленному RMI объекту ожидать соединений с клиентами для осуществления взаимодействия типа "точка-точка" с использованием стандартных соединений через сокеты. Предполагается, что клиенты RMI должны осуществлять соединение с использованием порта 1099 для поиска удаленного объекта в реестре RMI сервера. Перегруженный конструктор класса UnicastRemoteObject позволяет определить свой номер порта для экспорта удаленного объекта. Ссылка установления связи с удаленным RMI объектом обычно имеет следующий вид :

rmi://host:port/object,

где
host представляет собой имя компьютера, в котором определен реестр сервера rmiregistry для удаленных объектов с зарегистрированным удаленным RMI объектом,
port представляет собой номер порта, на котором работает серверное приложение. По умолчанию для реестра RMI объектов используется порт 1099, который можно в этом случае в ссылке не указывать;
object – это имя удаленного RMI объекта, зарегистрированного в реестре сервера.

Для связывания удаленного RMI объекта с реестром сервера используется один из методов bind или rebind. Метод rebind регистрирует объект в реестре с предварительной проверкой; если объект был ранее зарегистрирован под этим именем, то метод заменит его новым объектом. Это может потребоваться при регистриции новой версии существующего удаленного объекта.

Пример использования RMI

Рассмотрим использование механизма вызова методов удаленного объекта RMI на примере работы с электронной картой. Пример включает два приложения : серверное и клиентское. Серверное приложение ведет учет электронных карт и остатка средств на карте; деньги можно на карту положить и снять. Клиентское приложение регистрирует на сервере карты, добавляет и снимает денежные средства.

Серверное приложение включает три модуля :

• Card – электронная карта;
• BillingService – интерфейс сервиса объекта RMI;
• BillingServiceImpl – объект RMI.

Клиентское приложение также включает три модуля. Пересылаемый по сети объект Card и интерфейс описания RMI объекта BillingService в клиентском приложении должны совпадать с серверными. Третий модуль BillingClient использует интерфейс описания RMI объекта и Card для взаимодействия с объектом RMI, стартованном на сервере.

Листинг карточки, Card

В представленном ниже листинге класса Card.java не отображены методы get/set. Класс Card реализует интерфейс Serializable, который обеспечивает упаковку объекта в байт-коды в одном приложении и преобразования байт-кодов в объект Card в другом приложении.

import java.io.Serializable;

public class Card implements Serializable
{
    private static final long serialVersionUID = 1L;

    private  String  name  ;
    private  String  number;
    private  double  money ;

    public Card(String name, String number, double money)
    {
        super();
        this.name   = name;
        this.number = number;
        this.money  = money;
    }
    @Override
    public boolean equals(Object card)
    {
        Card crd = (Card)card;
        return this.getNumber().equals(crd.getNumber());
    }
}

Листинг RMI сервиса, BillingService

Интерфейс сервиса BillingService наследует свойства java.rmi.Remote и включает методы работы с картой Card. Во всех методах присутствует объект Card, передаваемый по сети между двумя java-приложениями.

import java.rmi.*;

public interface BillingService extends Remote
{
    // Регистрация новой карты
    public void addNewCard(Card card) throws RemoteException;

    // Добавление денежных средств на карту
    public void addMoney(Card card, double money) 
                                    throws RemoteException;
    // Снятие денежных средств с карты
    public void subMoney(Card card, double money) 
                                    throws RemoteException;
    // Получение баланса карты
    public double getCardBalance(Card card) 
                                      throws RemoteException;
}

Листинг RMI объекта, BillingServiceImpl

Класс BillingServiceImpl представляет собой удаленный объект, реализующий интерфейс BillingService. Чтобы сервер воспринимал его как RMI объект он наследует свойства класса UnicastRemoteObject. Приложение клиента взаимодействует с RMI-объектом типа BillingServiceImpl, вызывая методы addNewCard, addMoney, subMoney, getCardBalance, определенные в интерфейсе BillingService. Объект BillingServiceImpl хранит сведения о картах в коллекции cards.

При старте объекта в методе main определяется системное свойство 'java.rmi.server.hostname' как IP адрес локального компьютера (127.0.0.1), формируется и регистрируется в реестре RMI объект.

import java.util.List;
import java.util.ArrayList;

import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;

import com.rmi.shared.BillingService;
import com.rmi.shared.Card;

public class BillingServiceImpl extends UnicastRemoteObject 
                                implements BillingService
{
    private static final long serialVersionUID = 1L;

    private  List<Card> cards;

    // инициализация сервера
    public BillingServiceImpl() throws RemoteException
    {
        super();
        cards = new ArrayList<Card>();
    }

    @Override
    public void addNewCard(Card card) throws RemoteException 
    {
        cards.add(card);
        System.out.println("register card : " + card.getNumber());
    }

    @Override
    public void addMoney(Card card, double money) 
                                    throws RemoteException {
        for (Card crd : cards) {
            if (crd.equals(card)) {
                crd.setMoney(crd.getMoney() + money);
                System.out.println("add money : "
                                 + "card " + card.getNumber()
                                 + ", summa = " + money);
                break;
            }
        }
    }

    @Override
    public void subMoney(Card card, double money) 
                                    throws RemoteException {
        for (Card crd : cards) {
            if (crd.equals(card)) {
                crd.setMoney(crd.getMoney() - money);
                System.out.println("sub money : "
                                 + "card : " + card.getNumber()
                                 + ", summa = " + money);
                break;
            }
        }
    }

    @Override
    public double getCardBalance(Card card) 
                                      throws RemoteException {
        double balance = 0;
        for (Card crd : cards) {
            if (crd.equals(card)) {
                balance = crd.getMoney();
                System.out.println("balance : "
                         + "card : " + card.getNumber()
                         + ", summa = " + balance);
                break;
            }
        }
        return balance;
    }

    /**
     * Старт удаленного RMI объекта BillingService
     * @param args аргументы
     * @throws Exception
     */
    public static void main (String[] args) throws Exception 
    {
        String localhost    = "127.0.0.1";
        String RMI_HOSTNAME = "java.rmi.server.hostname";
        try {   
            System.setProperty(RMI_HOSTNAME, localhost);
            // Создание удаленного RMI объекта
            BillingService service = new BillingServiceImpl();

            // Определение имени удаленного RMI объекта
            String serviceName = "BillingService";

            System.out.println("Initializing " + serviceName);

            /*
             * Регистрация удаленного RMI объекта BillingService
             * в реестре rmiregistry
            */
            Registry registry = LocateRegistry.createRegistry(1099);
            registry.rebind(serviceName, service);
            System.out.println("Start " + serviceName);
        } catch (RemoteException e) {
            System.err.println("RemoteException : "+e.getMessage());
            System.exit(1);
        } catch (Exception e) {
            System.err.println("Exception : " + e.getMessage());
            System.exit(2);
        }
    }
}

Старт серверного приложения

Для старта сервера используем командный файл run.rmi-server.bat со следующим кодом :

set CLASSPATH=C:\rmi-server\bin
java com.rmi.server.BillingServiceImpl

PAUSE

После старта серверного приложения в консоль выведится следующая информация :


C:\rmi-server>set CLASSPATH=C:\rmi-server\bin

C:\rmi-server>java com.rmi.server.BillingServiceImpl
Initializing BillingService
Start BillingService
 

Сервер переходит в режим ожидания ...

Описание клиентского приложения

Как было отмечено выше, клиентское приложение включает 3 файла, два из которых, Card и BillingService, полностью совпадают с серверными. Третий модуль BillingClient для взаимодействия с объектом RMI использует интерфейс BillingService и электронную карту Card.

Разумнее было бы построить проекты таким образом, чтобы общие java-модули (Card и BillingService) были описаны только один раз в одном из приложений. Это можно было бы сделать, например, с использованием maven. Но в этом случае усложниться процесс описания проектов и, к тому же, (кто знает) будет ли у Вас в будущем при разработке собственного проекта доступ к исходным кодам серверного приложения? Возможно, что в Вашем распоряжении будут только интерфейсы взаимодействия и структуры объектов.

Листинг клиентского класса BillingClient

Класс BillingClient включает методы :

• createCard– создание объекта электронной карты;
• registerCards– регистрации карт;
• addMoney– добавления денежных средств на карты;
• getBalance– чтение остатков средств на картах.

Все вызовы методов интерфейса BillingService обрамлены конструкцией try ... catch с перехватыванием исключений типа RemoteException.

В конструкторе класса сначала устанавливается соединение с RMI объектом, после чего последовательно вызываются методы регистрации карт, добавления денежных средств на карты и получения остатков средств на картах. Чтобы найти серверный RMI объект на локальном сервере, устанавливается системное свойство RMI_HOSTNAME (127.0.0.1) и определяется ссылка на объект (SERVICE_PATH) с использованием службы имен и каталогов JNDI (Java Naming and Directory Interface). Поскольку сервер при регистрации RMI объекта использовал порт 1099, то в ссылке порт не указывается.

import java.net.MalformedURLException;
import java.rmi.*;

import com.rmi.shared.BillingService;
import com.rmi.shared.Card;

public class BillingClient 
{
    String localhost    = "127.0.0.1";
    String RMI_HOSTNAME = "java.rmi.server.hostname";
    String SERVICE_PATH = "rmi://localhost/BillingService";

    String[][] CARDS    = {{"Ivanov", "1213-456-7890"}, 
                           {"Petrov", "987-654-3210"}};
    double[]   MONEYS   =  {135790.0, 24680.0};

    public BillingClient()
    {
        try {
            System.setProperty(RMI_HOSTNAME, localhost);
            // URL удаленного объекта
            String objectName = SERVICE_PATH;
			
            BillingService bs;
            bs = (BillingService) Naming.lookup(objectName);
            
			System.out.println("\nRegister cards ...");
            registerCards(bs);
            
			System.out.println("Add moneys ...");
            addMoney(bs);
            
			System.out.println("Get balance ...\n");
            getBalance(bs);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (NotBoundException e) {
            System.err.println("NotBoundException : " + 
                                           e.getMessage());
        }
    }

    private Card createCard (final int idx)
    {
        return new Card(CARDS[idx][0], CARDS[idx][1], 0);
    }

    private void registerCards(BillingService bs)
    {
        for (int i = 0; i < CARDS.length; i++) {
            Card card = createCard (i);
            try {
                bs.addNewCard(card);
            } catch (RemoteException e) {
                System.err.println("RemoteException : " + 
                                         e.getMessage());
            }
        }
    }
    private void addMoney(BillingService bs)
    {
        for (int i = 0; i < CARDS.length; i++) {
            Card card = createCard (i);
            try {
                bs.addMoney(card, MONEYS[i]);
            } catch (RemoteException e) {
                System.err.println("RemoteException : " + 
                                         e.getMessage());
            }
        }
    }
    private void getBalance(BillingService bs)
    {
        for (int i = 0; i < CARDS.length; i++) {
            Card card = createCard (i);
            try {
                System.out.println("card : " + 
                                      card.getNumber() + 
                                   ", balance = " + 
                                      bs.getCardBalance(card));
            } catch (RemoteException e) {
                System.err.println("RemoteException : " + 
                                         e.getMessage());
            }
        }
    }
    public static void main(String[] args)
    {
        new BillingClient();
        System.exit(0);
    }
}

Сообщения клиентского приложения

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


C:\rmi-client>set CLASSPATH=C:\rmi-client\bin

C:\rmi-client>java com.rmi.client.BillingClient

Register cards ...
Add moneys ...
Get balance ...

card : 1213-456-7890, balance = 135790.0
card : 987-654-3210, balance = 24680.0
 

Сообщения серверного приложения

Серверное приложение выводит в консоль сообщения при вызове методов :


C:\rmi-server>set CLASSPATH=C:\rmi-server\bin

C:\rmi-server>java com.rmi.server.BillingServiceImpl
Initializing BillingService
Start BillingService

register card : 1213-456-7890
register card : 987-654-3210

add money : card 1213-456-7890, summa = 135790.0
add money : card 987-654-3210, summa = 24680.0

balance : card : 1213-456-7890, summa = 135790.0
balance : card : 987-654-3210, summa = 24680.0
 

Скачать пример

Исходный коды рассмотренного примера в виде двух проектов Eclipse можно скачать здесь (15.7 Kб). Проекты включают командные bat-файлы для проверки функционирования примера. Для этого достаточно только разместить проекты не диске C:\ (Windows) и стартовать командный файл сначала сервера, потом клиента.

  Рейтинг@Mail.ru