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