¿Cómo me aseguro de que RMI utilice sólo un conjunto específico de puertos?

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

  •  09-06-2019
  •  | 
  •  

Pregunta

En nuestra aplicación, utilizamos RMI para la comunicación cliente-servidor de formas muy diferentes:

  1. Enviar datos desde el servidor al cliente para que se muestren.
  2. Envío de información de control del cliente al servidor.
  3. Las devoluciones de llamada de esos mensajes de control codifican rutas que van desde el servidor al cliente (nota en la barra lateral: este es un efecto secundario de algún código heredado y no es nuestra intención a largo plazo).

Lo que nos gustaría hacer es asegurarnos de que todo nuestro código relacionado con RMI utilice únicamente un inventario de puertos específico conocido.Esto incluye el puerto de registro (normalmente se espera que sea 1099), el puerto del servidor y cualquier puerto resultante de las devoluciones de llamada.

Esto es lo que ya sabemos:

  1. LocateRegistry.getRegistry(1099) o Locate.createRegistry(1099) garantizarán que el registro esté escuchando en 1099.
  2. El uso del método estático UnicastRemoteObject constructor/exportObject con un argumento de puerto especificará el puerto del servidor.

Estos puntos también se tratan en este Publicación en el foro de Sun.

Lo que no sabemos es:¿Cómo nos aseguramos de que las conexiones del cliente al servidor resultantes de las devoluciones de llamada solo se conecten en un puerto específico en lugar de hacerlo de forma predeterminada en un puerto anónimo?

EDITAR:Agregué una respuesta bastante larga que resume mis hallazgos y cómo resolvimos el problema.Con suerte, esto ayudará a cualquiera que tenga problemas similares.

SEGUNDA EDICIÓN:Resulta que en mi aplicación, parece haber una condición de carrera en mi creación y modificación de fábricas de enchufes.Quería permitir que el usuario anule mi configuración predeterminada en un script Beanshell.Lamentablemente, parece que mi script se está ejecutando significativamente después de que la fábrica crea el primer socket.Como resultado, obtengo una combinación de puertos del conjunto de valores predeterminados y de la configuración del usuario.Se requerirá más trabajo que está fuera del alcance de esta pregunta, pero pensé en señalarlo como un punto de interés para otros que podrían tener que pisar estas aguas en algún momento...

¿Fue útil?

Solución

Puede hacer esto con una RMI Socket Factory personalizada.

Las fábricas de sockets crean los sockets para que RMI los use tanto en el cliente como en el servidor, por lo que si escribe los suyos propios tendrá control total sobre los puertos utilizados.Las fábricas de clientes se crean en el servidor, se serializan y luego se envían al cliente, lo cual es bastante bueno.

Aquí hay una guía en Sun que le indica cómo hacerlo.

Otros consejos

No necesita fábricas de sockets para esto, ni siquiera múltiples puertos.Si está iniciando el Registro desde la JVM de su servidor, puede usar el puerto 1099 para todo y, de hecho, eso es lo que sucederá de forma predeterminada.Si no está iniciando el registro en absoluto, como en el caso de un objeto de devolución de llamada de cliente, puede proporcionar el puerto 1099 al exportarlo.

La parte de su pregunta sobre "las conexiones del cliente al servidor resultantes de las devoluciones de llamada" no tiene sentido.No son diferentes de las conexiones originales del cliente al servidor y utilizarán los mismos puertos del servidor.

Resumen de la respuesta larga a continuación:Para resolver el problema que tenía (restringir el servidor y los puertos de devolución de llamada en cada extremo de la conexión RMI), necesitaba crear dos pares de fábricas de sockets de cliente y servidor.

Sigue una respuesta más larga:

Nuestra solución al problema de la devolución de llamadas constaba esencialmente de tres partes.El primero fue el ajuste de objetos, que necesitaba la capacidad de especificar que se estaba utilizando para una conexión de cliente a servidor vs.siendo utilizado para una devolución de llamada de servidor a cliente.Usando una extensión de UnicastRemoteObject nos dio la posibilidad de especificar las fábricas de sockets de cliente y servidor que queríamos utilizar.Sin embargo, el mejor lugar para bloquear las fábricas de sockets es el constructor del objeto remoto.

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));
}
// ....
}

Entonces, el primer argumento especifica la parte en la que el objeto espera solicitudes, mientras que el segundo y el tercero especifican las fábricas de sockets que se utilizarán en cada extremo de la conexión que controla este objeto remoto.

Como queríamos restringir los puertos utilizados por la conexión, necesitábamos ampliar las fábricas de sockets RMI y bloquear los puertos.Aquí hay algunos bocetos de nuestras fábricas de servidores y clientes:

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);
    }
}
// ....
}

Tenga en cuenta que la fábrica de sockets del servidor anterior garantiza que esta fábrica solo utilizará el puerto que usted especificó previamente.La fábrica de sockets del cliente debe estar emparejada con la fábrica de sockets adecuada (o nunca se conectará).

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);
    }
}
// ....
}

Entonces, lo único que queda para forzar que su conexión bidireccional permanezca en el mismo conjunto de puertos es algo de lógica para reconocer que está llamando nuevamente al lado del cliente.En esa situación, solo asegúrese de que su método de fábrica para el objeto remoto llame al constructor RemoteObjectWrapper en la parte superior con el parámetro de devolución de llamada establecido en verdadero.

He tenido varios problemas al implementar una arquitectura de servidor/cliente RMI con devoluciones de llamada de cliente.Mi escenario es que tanto el Servidor como el Cliente están detrás del Firewall/NAT.Al final obtuve una implementación completamente funcional.Estas son las cosas principales que hice:

Lado del servidor, IP local:192.168.1.10.IP pública (Internet) 80.80.80.10

En la PC Firewall/Router/Servidor local, abra el puerto 6620.En la PC Firewall/Router/Servidor local, abra el puerto 1099.En las conexiones entrantes de redirigir enrutador/NAT en el puerto 6620 a 192.168.1.10:6620 en las conexiones entrantes de redirigir enrutador/NAT en el puerto 1099 a 192.168.1.10:1099

En el programa real:

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);

Lado del cliente, IP local:10.0.1.123 IP pública (Internet) 70.70.70.20

En la PC Firewall/Router/Servidor local abra el puerto 1999.En el enrutador/NAT redirige las conexiones entrantes en el puerto 1999 a 10.0.1.123:1999

En el programa real:

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

Espero que esto ayude.Iraklis

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top