unidad una sistemas remote protocolo principales pasos para objeto metodo method implicados distribuidos distribuida desarrollar con comunicacion cliente características caracteristicas beneficios arquitectura aplicación java rmi

java - una - ¿Cómo me aseguro de que RMI use solo un conjunto específico de puertos?



rmi sistemas distribuidos (4)

En nuestra aplicación, estamos utilizando RMI para la comunicación cliente-servidor de maneras muy diferentes:

  1. Empujando los datos del servidor al cliente para que se muestren.
  2. Envío de información de control del cliente al servidor.
  3. Devolución de llamadas desde esos mensajes de control rutas de código que se conectan desde el servidor al cliente (nota de 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 garantizar que todo nuestro código relacionado con RMI use solo un inventario de puertos específico conocido. Esto incluye el puerto de registro (comúnmente se espera que sea 1099), el puerto del servidor y todos los puertos resultantes de las devoluciones de llamada.

Esto es lo que ya sabemos:

  1. LocateRegistry.getRegistry (1099) o Locate.createRegistry (1099) se asegurarán de 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 esta publicación del foro de Sun.

Lo que no sabemos es: ¿cómo nos aseguramos de que las conexiones de los clientes con el servidor como resultado de las devoluciones de llamadas solo se conecten en un puerto específico en lugar de establecerse de manera predeterminada en un puerto anónimo?

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

SEGUNDA EDICIÓN: Resulta que en mi solicitud, parece haber una condición de carrera en mi creación y modificación de fábricas de socket. Quería permitir que el usuario anulara mi configuración predeterminada en un script de Beanshell. Lamentablemente, parece que mi script se está ejecutando de manera significativa 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 la configuración del usuario. Se necesitará más trabajo que esté fuera del alcance de esta pregunta, pero pensé que lo señalaría como un punto de interés para otros que podrían tener que pisar estas aguas en algún momento ...


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

Se produce una respuesta más larga:

Nuestra solución al problema de devolución de llamada tenía esencialmente tres partes. La primera era la envoltura de objetos que necesitaba la capacidad de especificar que se estaba utilizando para una conexión de cliente a servidor, en comparación con la utilización de un servidor para la devolución de llamada del cliente. El uso de una extensión de UnicastRemoteObject nos dio la capacidad de especificar las fábricas de socket de cliente y servidor que queríamos usar. Sin embargo, el mejor lugar para bloquear las fábricas de socket es en 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)); } // .... }

Por lo tanto, el primer argumento especifica la parte en la que el objeto está esperando solicitudes, mientras que el segundo y el tercero especifican las fábricas de socket que se usarán en cada extremo de la conexión que maneja este objeto remoto.

Como queríamos restringir los puertos utilizados por la conexión, necesitábamos extender las fábricas de conectores 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 socket del servidor anterior garantiza que solo el puerto que haya especificado anteriormente será utilizado por esta fábrica. La fábrica de sockets de cliente debe emparejarse 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); } } // .... }

Por lo tanto, lo único que queda para forzar su conexión bidireccional para permanecer en el mismo conjunto de puertos es una lógica para reconocer que está llamando 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 arriba con el parámetro de devolución de llamada establecido en verdadero.


Puede hacerlo con una Factory RMI Socket personalizada.

Las fábricas de sockets crean los sockets para que RMI los use tanto en el extremo del cliente como en el del servidor, por lo que si escribes el tuyo, tienes control total sobre los puertos utilizados. Las fábricas del cliente se crean en el servidor, se serializan y luego se envían al cliente, que es bastante ordenado.

Aquí hay una guía de Sun que te dice cómo hacerlo.


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

La parte de su pregunta sobre ''las conexiones del cliente de regreso al servidor como resultado de las devoluciones de llamada'' no tiene sentido. No son diferentes de las conexiones del cliente original con el servidor, y usarán el mismo puerto (s) de servidor.


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

Lado del servidor, IP local: 192.168.1.10. Público (Internet) IP 80.80.80.10

En el Firewall / Router / Local Server PC, abra el puerto 6620. En el Firewall / Router / Local Server PC, abra el puerto 1099. En el Router / NAT, redirija las conexiones entrantes en el puerto 6620 a 192.168.1.10:6620 En el Router / NAT redireccione el entrante conexiones en el puerto 1099 a 192.168.1.10:1099

En el programa actual:

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 Público (Internet) IP 70.70.70.20

En el puerto abierto Firewall / Router / Local Server PC 1999. En el enrutador / NAT, redireccione las conexiones entrantes en el puerto 1999 a 10.0.1.123:1999

En el programa actual:

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