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

StackOverflow https://stackoverflow.com/questions/56687

  •  09-06-2019
  •  | 
  •  

Вопрос

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

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

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

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

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

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

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

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

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

Это было полезно?

Решение

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

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

Вот руководство Sun, рассказывающее вам, как это сделать.

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

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

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

Краткое изложение длинного ответа ниже:чтобы решить проблему, которая у меня возникла (ограничение портов сервера и обратного вызова на обоих концах соединения 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.

У меня возникали различные проблемы с реализацией архитектуры 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 ");

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top