Гид по технологиям

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

6 min read Java Обновлено 06 Dec 2025
Java RMI: настройка реестра, сервер и клиент
Java RMI: настройка реестра, сервер и клиент

Реестр Java RMI и клиент-серверное взаимодействие

Java RMI (Remote Method Invocation) — это протокол и набор API, который позволяет объектам Java вызывать методы объектов, запущенных на другом компьютере. RMI предоставляет интерфейс для экспорта объекта на стороне сервера и получения «заглушки» (stub) на стороне клиента, чтобы вызывать удалённые методы так, будто они локальные.

Схема работы Java RMI Registry: реестр, сервер и клиент

Что такое 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 (кратко):

  1. Проверить, что rmiregistry запущен и слушает нужный порт (netstat/lsof).
  2. Проверить CLASSPATH rmiregistry — содержит ли jar с интерфейсами.
  3. Проверить версии JAR у сервера и клиента.
  4. Проверить сетевые ограничения (фаерволы, NAT, SELinux).
  5. При необходимости включить логирование 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 в проекте — поделитесь опытом: какие были сложности и как вы их решали?

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Скриншоты в Chrome через Инструменты разработчика
Инструменты разработчика

Скриншоты в Chrome через Инструменты разработчика

Как отключить Apple CarPlay быстро
Автомобили

Как отключить Apple CarPlay быстро

DirectStorage на Windows 10 — требования и подготовка
Гейминг

DirectStorage на Windows 10 — требования и подготовка

Как сбросить пароль BIOS
Безопасность

Как сбросить пароль BIOS

Outlook для Mac — теперь бесплатно и как скачать
Software

Outlook для Mac — теперь бесплатно и как скачать

Как заставить Google Clock читать новости утром
Android.

Как заставить Google Clock читать новости утром