Java RMI: настройка реестра, реализация сервера и клиента

Java RMI (Remote Method Invocation) — это протокол и набор API, который позволяет объектам Java вызывать методы объектов, запущенных на другом компьютере. RMI предоставляет интерфейс для экспорта объекта на стороне сервера и получения «заглушки» (stub) на стороне клиента, чтобы вызывать удалённые методы так, будто они локальные.
Что такое RMI и зачем он нужен
Коротко: RMI упрощает распределённую обработку в Java-приложениях, скрывая детали сетевого взаимодействия за интерфейсами и автоматически генерируемыми заглушками. Термин: “stub” — локальный прокси, который пересылает вызовы на удалённый объект.
Преимущества RMI: простая модель программирования (вызов методов), интеграция с Java-типами и исключениями, автоматическое маршаллирование объектов, возможность регистрировать сервисы в централизованном реестре (rmiregistry).
Ограничения: зависит от JVM, чувствителен к класс-путям и безопасности; в современных системах часто заменяется межъязыковыми протоколами (gRPC, REST).
Объявление интерфейса сервера
Интерфейс сервера объявляет контракт между клиентом и сервером. Он должен расширять java.rmi.Remote, а удалённые методы должны объявлять throws RemoteException.
Пример интерфейса Greeting:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Greeting extends Remote {
String greet(String name) throws RemoteException;
}Пояснение: Greeting — имя интерфейса; метод greet принимает имя и возвращает строку-приветствие. RemoteException нужен, чтобы обработать сетевые/транспортные ошибки.
Реализация серверного объекта
Серверный объект реализует интерфейс и предоставляет логику метода greet.
public class GreetingObject implements Greeting {
private String fmtString = "Hello, %s";
public String greet(String name) {
return String.format(this.fmtString, name);
}
}Замечание: реализация не должна быть зависима от контекста клиента. Все сериализуемые аргументы и возвращаемые значения должны реализовывать Serializable, если это не примитивные типы.
Основной метод сервера — регистрация в реестре
Сервер должен экспортировать объект в RMI runtime, получить stub и зарегистрировать его в RMI Registry под именем сервиса.
Ключевые шаги (пошагово):
- Создаём экземпляр серверного объекта:
Greeting greeting = new GreetingObject();- Экспортируем объект, получаем stub (UnicastRemoteObject создаёт транспорт для удалённых вызовов):
Greeting stub = (Greeting) UnicastRemoteObject.exportObject(greeting, 0);- Получаем ссылку на локальный реестр и связываем имя с заглушкой:
String name = "Greeting";
Registry registry = LocateRegistry.getRegistry(port);
registry.rebind(name, stub);Полный пример класса Main с методом main():
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class Main {
static public void main(String[] args) throws Exception {
if (args.length == 0) {
System.err.println("usage: java Main port#");
System.exit(1);
}
int index = 0;
int port = Integer.parseInt(args[index++]);
String name = "Greeting";
Greeting greeting = new GreetingObject();
Greeting stub = (Greeting) UnicastRemoteObject.exportObject(greeting, 0);
Registry registry = LocateRegistry.getRegistry(port);
registry.rebind(name, stub);
System.out.println("Greeting bound to \"" + name + "\"");
}
}Объяснение: exportObject с портом 0 означает, что JVM выберет свободный порт; rebind заменит предыдущую привязку с тем же именем.
Сборка сервера (командами CLI)
Примеры команд для Linux (простая ручная сборка без Maven/Gradle):
rm -rf target
mkdir target
javac -d target src/server/*.javaСоздаём исполняемый JAR с классами сервера:
jar cvf target/rmi-server.jar -C target serverА также JAR только с интерфейсами, нужен клиенту для компиляции/запуска:
jar cvf target/rmi-lib.jar -C target server/Greeting.classСовет: храните interfaces (общие классы) отдельно, чтобы и сервер, и клиент использовали одинаковую версию.
Реализация клиента
Клиент получает ссылку на реестр, запрашивает сервис по имени и вызывает метод через stub.
Ключевые шаги:
- Получить ссылку на реестр (указать хост и порт):
Registry registry = LocateRegistry.getRegistry(host, port);- Выполнить lookup сервиса:
Greeting greeting = (Greeting) registry.lookup(name);- Вызвать метод и вывести результат:
System.out.println(name + " reported: " + greeting.greet(myName));Полный клиентский код:
package client;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import server.Greeting;
public class Client {
static public void main(String[] args) throws Exception {
if (args.length != 3) {
System.err.println("usage: java Client host port myName");
System.exit(1);
}
int index = 0;
String host = args[index++];
int port = Integer.parseInt(args[index++]);
String myName = args[index++];
String name = "Greeting";
Registry registry = LocateRegistry.getRegistry(host, port);
Greeting greeting = (Greeting) registry.lookup(name);
System.out.println(name + " reported: " + greeting.greet(myName));
}
}RMI Registry — запуск и типичные ошибки
Если реестр не запущен, при попытке registry.rebind или lookup вы получите Connection refused. Реестр — это отдельный процесс rmiregistry, который поставляется с JDK и обычно находится в каталоге bin вашей Java-инсталляции.
Пример запуска (стандартный путь указан для JDK 1.8):
/usr/lib/jvm/jdk1.8.0_71/bin/rmiregistryПо умолчанию слушает порт 1099. Чтобы сменить порт, передайте его как аргумент:
/usr/lib/jvm/jdk1.8.0_71/bin/rmiregistry 1100Проверка слушающих сокетов (пример):
netstat -an -t tcp -p | grep LISTEN
# ...
# tcp6 0 0 :::1100 :::* LISTEN 23450/rmiregistryЕсли при запуске сервера вы видите java.rmi.UnmarshalException с причиной ClassNotFoundException для server.Greeting, это значит, что rmiregistry не видит классы/интерфейсы. Необходимо указать CLASSPATH при запуске rmiregistry или запускать rmiregistry из директории с классами.
Пример запуска rmiregistry с класс-путом к библиотеке интерфейсов:
CLASSPATH=../../junk/target/rmi-lib.jar /usr/lib/jvm/jdk1.8.0_71/bin/rmiregistry 1100После этого повторный запуск сервера должен показать:
Greeting bound to "Greeting"Запуск клиента — пример
При наличии rmi-client.jar и rmi-lib.jar:
java -cp target/rmi-client.jar:target/rmi-lib.jar client.Client localhost 1100 Peter
# prints
# Greeting reported: Hello, PeterОтладка и распространённые ошибки — чеклист и runbook
Важные ошибки и шаги диагностики:
Connection refused
- Проверьте, запущен ли rmiregistry на указанном хосте/порту.
- Проверьте фаервол/iptables/SG (security group) — откройте порт.
- Убедитесь, что приложение слушает нужный интерфейс (0.0.0.0 vs localhost).
ClassNotFoundException при unmarshalling
- Убедитесь, что rmiregistry и сервер имеют доступ к классам интерфейса (CLASSPATH).
- В продакшне используйте одинаковые версии общих JAR.
java.rmi.ConnectException: Connection refused to host:
- Проверьте правильность hostname/интерфейса в stub (SecurityManager и hostname могут влиять).
- Если сервер находится за NAT, настройте правильный java.rmi.server.hostname.
SecurityException/AccessControlException
- Проверьте политику безопасности (policy file) при включённом SecurityManager.
Инцидентный runbook (кратко):
- Проверить, что rmiregistry запущен и слушает нужный порт (netstat/lsof).
- Проверить CLASSPATH rmiregistry — содержит ли jar с интерфейсами.
- Проверить версии JAR у сервера и клиента.
- Проверить сетевые ограничения (фаерволы, NAT, SELinux).
- При необходимости включить логирование RMI (java.rmi.server log) и собрать стеки исключений.
Диаграмма принятия решения (mermaid)
flowchart TD
A[Клиент не может подключиться] --> B{Есть ли rmiregistry на хосте:порту?}
B -- Нет --> C[Запустить rmiregistry с CLASSPATH интерфейсов]
B -- Да --> D{Получает ли сервер ошибку при bind?}
D -- Да --> E[Проверить ClassNotFound и CLASSPATH сервера]
D -- Нет --> F{Клиент получает ClassNotFound при lookup?}
F -- Да --> G[Проверить клиентский CLASSPATH 'rmi-lib.jar']
F -- Нет --> H[Проверить сетевые правила/hostname и java.rmi.server.hostname]Безопасность и лучшие практики
- Не открывайте rmiregistry напрямую в интернет. Размещайте его в приватной сети или введите VPN/SSH-туннель.
- Используйте SecurityManager и файлы политики для ограничения доступа к удалённым классам.
- Рассмотрите использование SSL/TLS-сокет-фабрик (custom socket factories) для шифрования трафика RMI.
- Контролируйте доступ к кодовой базе (codebase), чтобы предотвращать загрузку подозрительного кода.
Альтернативы и когда RMI не подходит
Когда RMI стоит рассмотреть с осторожностью:
- Проекты, где участвуют разные языки программирования — RMI ограничен Java.
- Когда необходима явная поддержка версионности и контрактов — gRPC/Protobuf, REST/HTTP часто более подходящие.
- В распределённых системах с высокой пропускной способностью и сложной маршрутизацией сообщений — используйте очереди сообщений (Kafka, RabbitMQ) или RPC на основе HTTP/2.
Альтернативы:
- gRPC — мульти-язычный, бинарный, эффективный.
- REST/JSON — простота, хорошо для публичных HTTP API.
- Apache Thrift — мульти-язычный RPC.
- Message brokers — асинхронная коммуникация.
Роль‑ориентированные чеклисты
Developer:
- Убедиться, что интерфейсы сериализуемы и совместимы по версиям.
- Добавить модульные тесты для удалённых вызовов (интеграционные тесты с тестовым rmiregistry).
DevOps / SRE:
- Автоматизировать запуск rmiregistry как системного сервиса с правильным CLASSPATH.
- Мониторинг порта rmiregistry, логов и ошибок unmarshalling.
Security:
- Настроить policy-файлы, ограничивающие разрешения.
- Проверять кодовую базу и зону загрузки классов (codebase).
Критерии приёмки
- Сервер корректно регистрирует сервис: лог «Greeting bound to “Greeting”».
- Клиент получает корректное приветствие при вызове greet.
- Наличие тестов, покрывающих сценарии успешного вызова и отказа (connection/refuse, classnotfound).
- Документация по деплою реестра и переменным окружения (CLASSPATH).
Короткий словарь
- RMI — Remote Method Invocation, механизм удалённых вызовов в Java.
- rmiregistry — отдельный процесс, предоставляющий центральный реестр имён сервисов.
- stub — локальная заглушка, проксирующая вызовы на удалённый объект.
- codebase — URL/путь, откуда могут загружаться классы при unmarshalling.
Краткое резюме
Java RMI даёт удобную модель удалённых вызовов в рамках Java-экосистемы: объявите интерфейс (extends Remote), реализуйте сервер, экспортируйте объект, зарегистрируйте его в rmiregistry и вызывайте методы клиентом через lookup. На практике важны правильный CLASSPATH для rmiregistry, настройки безопасности и учет сетевых ограничений.
Важно: при современных требованиях к мульти-язычности, скалированию и безопасности рассмотрите альтернативы (gRPC, REST) в дополнение к RMI.
Если вы используете RMI в проекте — поделитесь опытом: какие были сложности и как вы их решали?
Похожие материалы
Скриншоты в Chrome через Инструменты разработчика
Как отключить Apple CarPlay быстро
DirectStorage на Windows 10 — требования и подготовка
Как сбросить пароль BIOS
Outlook для Mac — теперь бесплатно и как скачать