Как я могу гарантировать, что RMI использует только определенный набор портов?

В нашем приложении мы используем RMI для взаимодействия клиент-сервер по-разному:

  1. Передача данных с сервера на клиент для отображения.
  2. Отправка управляющей информации от клиента на сервер.
  3. Обратные вызовы из этих управляющих сообщений кодируют пути, которые возвращаются от сервера к клиенту (примечание в боковой панели - это побочный эффект некоторого устаревшего кода и не является нашим долгосрочным намерением).

Что мы хотели бы сделать, так это убедиться, что весь наш код, связанный с RMI, будет использовать только известный указанный перечень портов. Это включает порт реестра (обычно это 1099), порт сервера и все порты, полученные в результате обратных вызовов.

Вот что мы уже знаем:

  1. LocateRegistry.getRegistry (1099) или Locate.createRegistry (1099) гарантирует, что реестр прослушивает 1099.
  2. Использование статического метода UnicastRemoteObject constructor / exportObject с аргументом порта указывает порт сервера.

Эти моменты также рассматриваются в этом Сообщение форума Sun.

Чего мы не знаем: как мы можем гарантировать, что клиентские подключения к серверу в результате обратных вызовов будут подключаться только к указанному порту, а не по умолчанию к анонимному порту?

Обновлено: добавлен длинный ответ, в котором резюмируются мои выводы и то, как мы решили проблему. Надеюсь, это поможет кому-нибудь еще с подобными проблемами.

ВТОРОЕ РЕДАКТИРОВАНИЕ: оказывается, что в моем приложении, похоже, существует состояние гонки при создании и модификации фабрик сокетов. Я хотел разрешить пользователю переопределить мои настройки по умолчанию в сценарии Beanshell. К сожалению, похоже, что мой скрипт значительно запускается после того, как фабрика создала первый сокет. В результате я получаю сочетание портов из набора значений по умолчанию и пользовательских настроек. Потребуется дополнительная работа, выходящая за рамки этого вопроса, но я подумал, что укажу на это как на точку, представляющую интерес для других, которым, возможно, придется в какой-то момент вступить в эти воды ...

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
9
0
16 579
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Вы можете сделать это с помощью настраиваемой фабрики сокетов RMI.

Фабрики сокетов создают сокеты для RMI для использования как на стороне клиента, так и на стороне сервера, поэтому, если вы напишете свой собственный, вы получите полный контроль над используемыми портами. Фабрики клиентов создаются на сервере, сериализуются и затем отправляются клиенту, что довольно удобно.

Вот руководство Sun, которое расскажет, как это сделать.

Да, но тогда вам понадобится класс, доступный как на клиенте, так и на сервере. Я знаю, что это выполнимо, но это довольно раздражает. Есть ли способ избежать этого?

Dave Cheney 17.11.2008 02:36

Если я правильно помню - прошло много лет с тех пор, как я это сделал - вам не нужен класс на клиенте. Интерфейс SocketFactory находится на клиенте, и это все, что вам нужно, класс сериализуется и отправляется с сервера, что делает его таким аккуратным.

Dave Webb 12.12.2008 12:05

Ссылка не работает, но вот хороший пример: netcluesoft.com/rmi-through-a-firewall.html

Jacob Nordfalk 28.11.2013 17:23

Это похоже на Oracle эквивалент старой ссылки: docs.oracle.com/javase/7/docs/technotes/guides/rmi/…

Ogre Psalm33 30.10.2014 15:09

Вы можете сделать это с помощью RMIServerSocketFactory, но для этого понадобится молоток, чтобы расколоть гайку. Номер порта можно указать при экспорте.

user207421 28.10.2016 02:46

Краткое изложение длинного ответа ниже: чтобы решить возникшую у меня проблему (ограничение портов сервера и обратного вызова на обоих концах соединения RMI), мне нужно было создать две пары фабрик клиентских и серверных сокетов.

Получается более длинный ответ:

Наше решение проблемы обратного вызова состояло из трех частей. Первым была обертка объекта, в которой требовалась возможность указать, что она используется для соединения клиента с сервером, а не используется для обратного вызова от сервера к клиенту. Использование расширения UnicastRemoteObject дало нам возможность указать фабрики клиентских и серверных сокетов, которые мы хотели использовать. Однако лучшее место для блокировки фабрик сокетов - это конструктор удаленного объекта.

public class RemoteObjectWrapped extends UnicastRemoteObject {
// ....
private RemoteObjectWrapped(final boolean callback) throws RemoteException {
  super((callback ? RemoteConnectionParameters.getCallbackPort() : RemoteConnectionParameters.getServerSidePort()),
        (callback ? CALLBACK_CLIENT_SOCKET_FACTORY : CLIENT_SOCKET_FACTORY),
        (callback ? CALLBACK_SERVER_SOCKET_FACTORY : SERVER_SOCKET_FACTORY));
}
// ....
}

Итак, первый аргумент указывает часть, в которой объект ожидает запросов, тогда как второй и третий указывают фабрики сокетов, которые будут использоваться на любом конце соединения, управляющего этим удаленным объектом.

Поскольку мы хотели ограничить порты, используемые соединением, нам нужно было расширить фабрики сокетов RMI и заблокировать порты. Вот несколько набросков наших серверных и клиентских фабрик:

public class SpecifiedServerSocketFactory implements RMIServerSocketFactory {
/** Always use this port when specified. */
private int serverPort;
/**
 * @param ignoredPort This port is ignored.  
 * @return a {@link ServerSocket} if we managed to create one on the correct port.
 * @throws java.io.IOException
 */
@Override
public ServerSocket createServerSocket(final int ignoredPort) throws IOException {
    try {
        final ServerSocket serverSocket = new ServerSocket(this.serverPort);
        return serverSocket;
    } catch (IOException ioe) {
        throw new IOException("Failed to open server socket on port " + serverPort, ioe);
    }
}
// ....
}

Обратите внимание, что указанная выше фабрика серверных сокетов гарантирует, что только тот порт, который вы указали ранее, будет когда-либо использоваться этой фабрикой. Фабрика клиентских сокетов должна быть связана с соответствующей фабрикой сокетов (иначе вы никогда не подключитесь).

public class SpecifiedClientSocketFactory implements RMIClientSocketFactory, Serializable {
/** Serialization hint */
public static final long serialVersionUID = 1L;
/** This is the remote port to which we will always connect. */
private int remotePort;
/** Storing the host just for reference. */
private String remoteHost = "HOST NOT YET SET";
// ....
/**
 * @param host The host to which we are trying to connect
 * @param ignoredPort This port is ignored.  
 * @return A new Socket if we managed to create one to the host.
 * @throws java.io.IOException
 */
@Override
public Socket createSocket(final String host, final int ignoredPort) throws IOException {
    try {
        final Socket socket = new Socket(host, remotePort);
        this.remoteHost = host;
        return socket;
    } catch (IOException ioe) {
        throw new IOException("Failed to open a socket back to host " + host + " on port " + remotePort, ioe);
    }
}
// ....
}

Итак, единственное, что осталось, чтобы ваше двустороннее соединение оставалось на одном и том же наборе портов, - это некоторая логика, позволяющая распознать, что вы звоните обратно на клиентскую сторону. В этой ситуации просто убедитесь, что ваш фабричный метод для удаленного объекта вызывает конструктор RemoteObjectWrapper вверху с параметром обратного вызова, установленным в значение true.

Вам не нужна фабрика серверных сокетов. Просто укажите порт при экспорте объекта.

user207421 21.05.2016 00:59

У меня были различные проблемы с реализацией архитектуры RMI Server / Client с обратными вызовами клиентов. Мой сценарий таков, что и сервер, и клиент находятся за брандмауэром / NAT. В итоге я получил полностью рабочую реализацию. Вот основные вещи, которые я сделал:

Сторона сервера, локальный IP-адрес: 192.168.1.10. Публичный (Интернет) IP 80.80.80.10

На брандмауэре / маршрутизаторе / локальном сервере откройте порт 6620. На брандмауэре / маршрутизаторе / локальном сервере откройте порт 1099. На маршрутизаторе / NAT перенаправить входящие соединения на порт 6620 на 192.168.1.10:6620 На маршрутизаторе / NAT перенаправить входящие соединения с порта 1099 на 192.168.1.10:1099

В актуальной программе:

System.getProperties().put("java.rmi.server.hostname", IP 80.80.80.10);
MyService rmiserver = new MyService();
MyService stub = (MyService) UnicastRemoteObject.exportObject(rmiserver, 6620);
LocateRegistry.createRegistry(1099);
Registry registry = LocateRegistry.getRegistry();
registry.rebind("FAManagerService", stub);

Клиентская сторона, локальный IP-адрес: 10.0.1.123 Общедоступный (Интернет) IP-адрес 70.70.70.20

На ПК межсетевого экрана / маршрутизатора / локального сервера откройте порт 1999. На маршрутизаторе / NAT перенаправить входящие соединения на порт 1999 на 10.0.1.123:1999

В актуальной программе:

System.getProperties().put("java.rmi.server.hostname", 70.70.70.20);
UnicastRemoteObject.exportObject(this, 1999);
MyService server = (MyService) Naming.lookup("rmi://" + serverIP + "/MyService ");

Надеюсь это поможет. Ираклис

Вы могли бы использовать порт 1099 для всего. Вы должны удалить другой ответ. Код здесь не компилируется.

user207421 21.05.2016 00:59

@EJP Вы уверены, что 1099 можно использовать для поиска в реестре обе RMI и фактических вызовов удаленной службы RMI? Спасибо.

sv. 22.02.2017 01:18

@sv. Да, потому что tRegistry этого ответа работает в той же JVM, что и все удаленные объекты: не иначе.

user207421 02.01.2020 05:35

Для этого не нужны фабрики сокетов или даже несколько портов. Если вы запускаете реестр со своей серверной JVM, вы можете использовать порт 1099 для всего, и это действительно то, что будет происходить по умолчанию. Если вы вообще не запускаете реестр, как в объекте обратного вызова клиента, вы можете указать порт 1099 при его экспорте.

Часть вашего вопроса о «клиентских подключениях обратно к серверу в результате обратных вызовов» не имеет смысла. Они ничем не отличаются от исходных клиентских подключений к серверу, и они будут использовать один и тот же порт (-ы) сервера.

Другие вопросы по теме