Comment puis-je m'assurer que RMI utilise uniquement un ensemble spécifique de ports ?

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

  •  09-06-2019
  •  | 
  •  

Question

Dans notre application, nous utilisons RMI pour la communication client-serveur de manières très différentes :

  1. Pousser les données du serveur vers le client pour les afficher.
  2. Envoi des informations de contrôle du client au serveur.
  3. Les rappels de ces messages de contrôle codent les chemins qui remontent du serveur au client (remarque de l'encadré : il s'agit d'un effet secondaire de certains codes existants et n'est pas notre intention à long terme).

Ce que nous aimerions faire, c'est nous assurer que tout notre code lié au RMI utilisera uniquement un inventaire de ports spécifié et connu.Cela inclut le port de registre (généralement 1099), le port du serveur et tous les ports résultant des rappels.

Voici ce que nous savons déjà :

  1. LocateRegistry.getRegistry(1099) ou Locate.createRegistry(1099) garantira que le registre écoute sur 1099.
  2. L’utilisation de la méthode statique constructeur/exportObject UnicastRemoteObject avec un argument de port spécifiera le port du serveur.

Ces points sont également abordés dans ce Message du forum Soleil.

Ce que nous ne savons pas, c'est :comment pouvons-nous nous assurer que les connexions client au serveur résultant des rappels se connecteront uniquement sur un port spécifié plutôt que par défaut sur un port anonyme ?

MODIFIER:Ajout d'une réponse assez longue résumant mes découvertes et comment nous avons résolu le problème.Espérons que cela aidera toute autre personne confrontée à des problèmes similaires.

DEUXIÈME ÉDITION :Il s'avère que dans mon application, il semble y avoir une condition de concurrence critique dans ma création et ma modification d'usines de sockets.Je voulais permettre à l'utilisateur de remplacer mes paramètres par défaut dans un script Beanshell.Malheureusement, il semble que mon script soit exécuté bien après la création du premier socket par l'usine.En conséquence, j'obtiens un mélange de ports provenant de l'ensemble des valeurs par défaut et des paramètres utilisateur.Des travaux supplémentaires seront nécessaires, ce qui dépasse le cadre de cette question, mais j'ai pensé que je le signalerais comme un point d'intérêt pour d'autres qui pourraient avoir à naviguer dans ces eaux à un moment donné....

Était-ce utile?

La solution

Vous pouvez le faire avec une RMI Socket Factory personnalisée.

Les usines de sockets créent les sockets que RMI doit utiliser à la fois du côté client et du côté serveur. Ainsi, si vous écrivez les vôtres, vous avez un contrôle total sur les ports utilisés.Les usines client sont créées sur le serveur, sérialisées puis envoyées au client, ce qui est plutôt soigné.

Voici un guide chez Sun vous expliquant comment procéder.

Autres conseils

Vous n'avez pas besoin d'usines de sockets pour cela, ni même de plusieurs ports.Si vous démarrez le registre à partir de la JVM de votre serveur, vous pouvez utiliser le port 1099 pour tout, et c'est effectivement ce qui se passera par défaut.Si vous ne démarrez pas du tout le registre, comme dans un objet de rappel client, vous pouvez fournir le port 1099 lors de son exportation.

La partie de votre question concernant « les connexions client au serveur résultant des rappels » n'a pas de sens.Elles ne sont pas différentes des connexions client d'origine au serveur et elles utiliseront le(s) même(s) port(s) du serveur.

Résumé de la réponse longue ci-dessous :pour résoudre le problème que j'avais (restreindre les ports du serveur et de rappel à chaque extrémité de la connexion RMI), j'avais besoin de créer deux paires de fabriques de sockets client et serveur.

Une réponse plus longue s'ensuit :

Notre solution au problème de rappel comportait essentiellement trois parties.Le premier était l'encapsulation d'objet qui nécessitait la possibilité de spécifier qu'il était utilisé pour une connexion client-serveur plutôt que pour une connexion client-serveur.étant utilisé pour un rappel serveur-client.Utiliser une extension de UnicastRemoteObject nous a donné la possibilité de spécifier les usines de sockets client et serveur que nous souhaitions utiliser.Cependant, le meilleur endroit pour verrouiller les usines de sockets est dans le constructeur de l'objet distant.

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

Ainsi, le premier argument spécifie la partie sur laquelle l'objet attend des requêtes, tandis que le deuxième et le troisième spécifient les fabriques de sockets qui seront utilisées à chaque extrémité de la connexion pilotant cet objet distant.

Puisque nous voulions restreindre les ports utilisés par la connexion, nous devions étendre les usines de sockets RMI et verrouiller les ports.Voici quelques croquis de nos usines de serveurs et de clients :

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

Notez que la fabrique de sockets de serveur ci-dessus garantit que seul le port que vous avez spécifié précédemment sera utilisé par cette fabrique.La fabrique de sockets client doit être associée à la fabrique de sockets appropriée (sinon vous ne vous connecterez jamais).

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

Ainsi, la seule chose qui reste pour forcer votre connexion bidirectionnelle à rester sur le même ensemble de ports est une certaine logique pour reconnaître que vous rappelez côté client.Dans cette situation, assurez-vous simplement que votre méthode d'usine pour l'objet distant appelle le constructeur RemoteObjectWrapper en haut avec le paramètre de rappel défini sur true.

J'ai rencontré divers problèmes pour implémenter une architecture serveur/client RMI, avec des rappels clients.Mon scénario est que le serveur et le client sont derrière le pare-feu/NAT.En fin de compte, j’ai obtenu une implémentation entièrement fonctionnelle.Voici les principales choses que j'ai faites :

Côté serveur, IP locale :192.168.1.10.IP publique (Internet) 80.80.80.10

Sur le PC pare-feu/routeur/serveur local, ouvrez le port 6620.Sur le PC pare-feu/routeur/serveur local, ouvrez le port 1099.Sur le routeur / NAT Redirection des connexions entrantes sur le port 6620 vers 192.168.1.10:6620 sur le routeur / NAT Redirection Connexions entrantes sur le port 1099 vers 192.168.1.10:1099

Dans le programme actuel :

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

Côté client, IP locale :10.0.1.123 IP publique (Internet) 70.70.70.20

Sur le PC pare-feu/routeur/serveur local, ouvrez le port 1999.Sur le routeur/NAT, redirigez les connexions entrantes sur le port 1999 vers 10.0.1.123:1999

Dans le programme actuel :

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

J'espère que cela t'aides.Iraklis

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top