JavaRush /Java блог /Java Developer /RMI: практика использования
Автор
Milan Vucic
Репетитор по программированию в Codementor.io

RMI: практика использования

Статья из группы Java Developer
Привет! Сегодня мы рассмотрим довольно интересную тему — RMI. Это расшифровывается как Remote Method Invocation — удаленный вызов методов. RMI: практика использования - 1При помощи RMI ты можешь научить две программы общаться между собой, даже если они находятся на разных компьютерах. Звучит круто? :) А ведь это не так уж и сложно сделать! В сегодняшней лекции мы разберемся, из каких частей состоит RMI-взаимодействие и как его настроить. Первое, что нам понадобится — это клиент и сервер. Можешь особо не углубляться в компьютерную терминологию. В случае с RMI это просто две программы. Одна из них будет содержать какой-то объект, а вторая — вызывать методы этого объекта. Вызвать в одной программе методы объекта, который находится в другой программе — такого мы еще не делали! Самое время попробовать! :) Чтобы не утонуть в дебрях, пусть наша программа будет простой. Вообще, на серверах обычно происходят какие-то вычисления, которые запрашивает клиент. Так будет и у нас. В роли сервера у нас будет простая программа-калькулятор. У нее будет всего один метод — multiply(). Он будет умножать два числа, которые ему отправила программа-клиент, и возвращать результат. Прежде всего нам понадобится интерфейс:

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Calculator extends Remote {

   int multiply(int x, int y) throws RemoteException;
}
Зачем нам интерфейс? Дело в том, что работа RMI основана на создании прокси, которые ты изучал в одной из прошлых лекций. А работа с прокси, как ты, наверное, помнишь, ведется именно на уровне интерфейсов, а не классов. К нашему интерфейсу есть 2 важных требования!
  1. Он должен наследовать интерфейс-маркер Remote.
  2. Все его методы должны выбрасывать RemoteException (это не делается в IDE автоматически, надо написать руками!).
Теперь нам надо создать класс-сервер, который будет реализовывать наш интерфейс Calculator. RMI: практика использования - 2Тут тоже все довольно просто:

import java.rmi.RemoteException;

public class RemoteCalculationServer implements Calculator {

   @Override
   public int multiply(int x, int y) throws RemoteException {
       return x*y;
   }

}
Здесь даже комментировать особо нечего :) Теперь нам нужно написать программу-сервер, которая будет настраивать и запускать наш серверный класс-калькулятор. Она будет выглядеть вот так:

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

public class ServerMain {

   public static final String UNIQUE_BINDING_NAME = "server.calculator";

   public static void main(String[] args) throws RemoteException, AlreadyBoundException, InterruptedException {

       final RemoteCalculationServer server = new RemoteCalculationServer();

       final Registry registry = LocateRegistry.createRegistry(2732);

       Remote stub = UnicastRemoteObject.exportObject(server, 0);
       registry.bind(UNIQUE_BINDING_NAME, stub);

       Thread.sleep(Integer.MAX_VALUE);

   }
}
Давай разбираться :) В первой строке мы создаем какую-то строковую переменную:

public static final String UNIQUE_BINDING_NAME = "server.calculator";
Эта строка — уникальное имя удаленного объекта. По этому имени программа-клиент сможет найти наш сервер: ты увидишь это позже. Далее мы создаем наш объект-калькулятор:

final RemoteCalculationServer server = new RemoteCalculationServer();
Тут все понятно. Далее уже поинтереснее:

final Registry registry = LocateRegistry.createRegistry(2732);
Эта штука под названием Registry — реестр удаленных объектов. «Удаленных» не в том смысле, что мы их удалили с компа, а в том, что к объектам из этого регистра возможен удаленный доступ из других программ :) В метод LocateRegistry.createRegistry() мы передали число 2732. Это номер порта. Если не знаешь, что такое порт — можно почитать вот тут, но сейчас тебе достаточно запомнить, что это уникальный номер, по которому другие программы смогут найти наш реестр объектов (это ты тоже увидишь ниже). Едем дальше. Посмотрим, что у нас происходит в следующей строке:

Remote stub = UnicastRemoteObject.exportObject(server, 0);
В этой строке мы создаем заглушку. Заглушка (stub) инкапсулирует внутри себя весь процесс удаленного вызова. Можно сказать, что это самый важный элемент RMI. Что же она делает?
  1. Принимает всю информацию об удаленном вызове какого-то метода.
  2. Если у метода есть параметры, заглушка десериализует их. Обрати внимание на этот пункт! Параметры, которые ты передаешь методам для удаленного вызова, должны быть сериализуемыми (ведь они будут передаваться по сети). У нас такой проблемы нет — мы передаем просто числа. Но если ты будешь передавать объекты, не забудь об этом!
  3. После этого она вызывает нужный метод.
Мы передаем в метод UnicastRemoteObject.exportObject() наш объект-калькулятор server. Таким образом мы делаем возможным удаленный вызов его методов. Нам осталось сделать только одно:

registry.bind(UNIQUE_BINDING_NAME, stub);
Мы «регистрируем» нашу заглушку в реестре удаленных объектов под тем именем, которое придумали в самом начале. Теперь клиент сможет ее найти! Возможно, ты обратил внимание, что в конце мы усыпили главный поток программы:

Thread.sleep(Integer.MAX_VALUE);
Нам просто нужно, чтобы сервер работал долгое время. Мы ведь будем запускать в IDEa сразу два метода main(): сначала серверный (в классе ServerMain, который мы уже написали), а потом — клиентский (в классе ClientMain, который мы напишем ниже). Важно, чтобы программа-сервер не вырубилась, пока мы будем запускать клиент, поэтому мы ее просто усыпили на долгое время. Работать она все равно будет :) Теперь мы можем запустить метод main() нашего сервера. Пусть он работает и ждет, когда программа-клиент вызовет какой-нибудь метод :) Теперь давай напишем программу-клиент! Она будет отправлять нашему серверу числа для умножения.

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class ClientMain {

   public static final String UNIQUE_BINDING_NAME = "server.calculator";

   public static void main(String[] args) throws RemoteException, NotBoundException {

       final Registry registry = LocateRegistry.getRegistry(2732);

       Calculator calculator = (Calculator) registry.lookup(UNIQUE_BINDING_NAME);

       int multiplyResult = calculator.multiply(20, 30);

       System.out.println(multiplyResult);
   }
}
Она выглядит несложно. Что же здесь происходит? Во-первых, клиент должен быть в курсе уникального имени объекта, методы которого он будет вызывать удаленно. Поэтому в программе-клиенте мы тоже создали переменную public static final String UNIQUE_BINDING_NAME = "server.calculator"; Далее, в методе main() мы получаем доступ к регистру удаленных объектов. Для этого нам нужно вызвать метод LocateRegistry.getRegistry() и передать туда номер порта, на котором создавали наш регистр в программе ServerMain — порт 2732 (этот номер был выбран для примера, ты можешь попробовать использовать другой):

final Registry registry = LocateRegistry.getRegistry(2732);
Теперь нам осталось только получить из регистра нужный объект! Это легко, ведь мы знаем его уникальное имя!

Calculator calculator = (Calculator) registry.lookup(UNIQUE_BINDING_NAME);
Обрати внимание на приведение типов. Мы приводим полученный объект к интерфейсу Calculator, а не к конкретному классу RemoteCalculationServer. Как мы и говорили в начале лекции, работа RMI основана на использовании прокси, поэтому удаленный вызов доступен только для методов интерфейсов, а не классов. В конце мы удаленно вызываем метод multiply() у нашего объекта и выводим результат в консоль.

int multiplyResult = calculator.multiply(20, 30);
System.out.println(multiplyResult);
Метод main() в классе ServerMain мы уже давно запустили, самое время запустить метод main() в программе-клиенте ClientMain! Вывод в консоль: 600 Вот и все! Наша программа (даже две!) успешно выполнила свою функцию :) Если у тебя есть время и желание, можешь немного разнообразить ее. Например, сделать так, чтобы калькулятор поддерживал все четыре стандартные операции, а в качестве параметров передавались не числа, а объект CalculationInstance(int x, int y). В качестве дополнительного материала можешь посмотреть статьи и примеры — вот здесь: Увидимся на следующих занятиях! :)
Комментарии (28)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Raf Java Master Уровень 31
7 ноября 2023
Полностью скопировал все что тут указано ... Exception in thread "main" java.rmi.ConnectException:
Tuti Fruti Уровень 37
3 октября 2023
Вторая ссылка введкт на казино только у меня?
Andrei Po Уровень 41
16 июня 2023
если кто-то сталкивался с подобным, Idea выдаёт: Exception in thread "main" java.rmi.ConnectException: Connection refused to host: 192.168.128.156; nested exception is: java.net.ConnectException: Connection timed out: connect и ещё 115 линий Exception как удалось это преодолеть? (если было подобное)
27 февраля 2023
А как это добро запустить на разных ПК? На одном все четко работает, но на разных нет.
Umbrella Уровень 49
17 февраля 2023
Открыл вторую ссылку на доп материал перенесло на сайт с рейтингом онлайн казино😅
Серега Батенин Уровень 34
16 января 2023
Как и у многих наверное вопрос. В клиентском коде мне же надо создавать объект интерфейса, методы, которого я хочу использовать. Но если у меня такого нет в программе, то мне идея же будет ругаться мол не знаю я таких, чего ты хочешь от меня? Собственно мне и у клиента надо описывать этот интерфейс или как?
1 июля 2022
Idea пишет, что exportObject устаревший
PaiMei in J# Уровень 35
19 октября 2021
А зачем мы на стороне сервера погружаем поток в sleep? Он же в любом случае при запуске не будет завершаться, а будет висеть в ожидании подключения со стороны клиента. P.s. Буду признателен, если кто - нибудь сможет объяснить почему сервер ведет себя подобным образом, т.е. я при попытке расставить маячки вывода в консоль в методе main у сервера (простые sout, например System.out.println("1") и т.д.) вывод в консоль показывает, что поток, по сути, доходит до конца метода main, но все же не завершается, а как-будто подвисает в бесконечном цикле.
Михаил Ершов Уровень 41
28 июня 2021
Когда клиент запрашивает метод через сервер, то клиент получает метод и делает это на своем компе или же расчеты делаются на серваке и как бы ответ приходит к клиенту?
Е К Уровень 41
9 июня 2021
Задался задачкой остановить сервер из клиента. Для этого на стороне сервера сделал ещё один метод serverStop с таким телом:

int i = 0;
        while (!Thread.currentThread().isInterrupted()) {
            Thread.currentThread().interrupt();
            System.out.println(i++);
            System.out.println(Thread.currentThread().isInterrupted());
        }
Вызываю его клиентом. Он отрабатывает НО - как ни пытался, не останавливается сервак... Есть мысли комрады?