Domanda

Nella nostra applicazione utilizziamo l'RMI per la comunicazione client-server in modi molto diversi:

  1. Push dei dati dal server al client per essere visualizzati.
  2. Invio di informazioni di controllo dal client al server.
  3. Callback da quei percorsi di codice dei messaggi di controllo che risalgono dal server al client (nota nella barra laterale: questo è un effetto collaterale di alcuni codici legacy e non è il nostro intento a lungo termine).

Ciò che vorremmo fare è garantire che tutto il nostro codice relativo a RMI utilizzi solo un inventario di porte noto e specificato.Ciò include la porta del registro (normalmente prevista essere 1099), la porta del server e qualsiasi porta risultante dai callback.

Ecco quello che già sappiamo:

  1. LocateRegistry.getRegistry(1099) o Locate.createRegistry(1099) garantiranno che il registro sia in ascolto su 1099.
  2. Utilizzando il metodo statico del costruttore/exportObject UnicastRemoteObject con un argomento port verrà specificata la porta del server.

Questi punti sono trattati anche in questo Post del forum Sun.

Ciò che non sappiamo è:come possiamo garantire che le connessioni client al server risultanti dalle richiamate si connetteranno solo su una porta specificata anziché su una porta anonima per impostazione predefinita?

MODIFICARE:Aggiunta una risposta piuttosto lunga che riassume le mie scoperte e come abbiamo risolto il problema.Si spera che questo possa aiutare chiunque altro con problemi simili.

SECONDA MODIFICA:Si scopre che nella mia applicazione sembra esserci una condizione di competizione nella creazione e modifica delle fabbriche di socket.Avrei voluto consentire all'utente di sovrascrivere le mie impostazioni predefinite in uno script Beanshell.Purtroppo, sembra che il mio script venga eseguito in modo significativo dopo che il primo socket è stato creato dalla fabbrica.Di conseguenza, ottengo un mix di porte dall'insieme di impostazioni predefinite e dalle impostazioni dell'utente.Sarà necessario ulteriore lavoro che esula dall'ambito di questa domanda, ma ho pensato di segnalarlo come punto di interesse per altri che potrebbero dover calcare queste acque ad un certo punto...

È stato utile?

Soluzione

Puoi farlo con una RMI Socket Factory personalizzata.

Le fabbriche di socket creano i socket per RMI da utilizzare sia sul client che sul server, quindi se scrivi il tuo hai il pieno controllo sulle porte utilizzate.Le fabbriche client vengono create sul server, serializzate e quindi inviate al client, il che è abbastanza accurato.

Ecco una guida di Sun che ti spiega come farlo.

Altri suggerimenti

Non sono necessarie fabbriche di socket per questo, né più porte.Se stai avviando il registro dalla JVM del tuo server puoi utilizzare la porta 1099 per tutto, e in effetti questo è ciò che accadrà per impostazione predefinita.Se non stai avviando affatto il registro, come in un oggetto callback client, puoi fornire la porta 1099 durante l'esportazione.

La parte della tua domanda sulle "connessioni client al server risultanti dai callback" non ha senso.Non sono diversi dalle connessioni client originali al server e utilizzeranno le stesse porte del server.

Riepilogo della risposta lunga di seguito:per risolvere il problema che avevo (limitare le porte del server e di callback alle due estremità della connessione RMI), avevo bisogno di creare due coppie di socket factory client e server.

Ne consegue una risposta più lunga:

La nostra soluzione al problema della richiamata era composta essenzialmente da tre parti.Il primo era il confezionamento dell'oggetto che richiedeva la possibilità di specificare che veniva utilizzato per una connessione da client a server rispetto a una connessione client-server.utilizzato per una richiamata da server a client.Utilizzando un'estensione di UnicastRemoteObject ci ha dato la possibilità di specificare i socket factory client e server che volevamo utilizzare.Tuttavia, il posto migliore per bloccare le socket factory è nel costruttore dell'oggetto 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));
}
// ....
}

Quindi, il primo argomento specifica la parte su cui l'oggetto attende le richieste, mentre il secondo e il terzo specificano i socket factory che verranno utilizzati alle due estremità della connessione che guida questo oggetto remoto.

Poiché volevamo limitare le porte utilizzate dalla connessione, avevamo bisogno di estendere i socket factory RMI e bloccare le porte.Ecco alcuni schizzi delle nostre fabbriche di server e client:

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

Tieni presente che il socket factory del server riportato sopra garantisce che solo la porta specificata in precedenza verrà utilizzata da questo factory.Il socket factory del client deve essere accoppiato con il socket factory appropriato (altrimenti non ti connetterai mai).

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

Quindi, l'unica cosa rimasta per forzare la tua connessione bidirezionale a rimanere sullo stesso set di porte è una logica per riconoscere che stai richiamando sul lato client.In quella situazione, assicurati solo che il tuo metodo factory per l'oggetto remoto chiami il costruttore RemoteObjectWrapper in alto con il parametro callback impostato su true.

Ho riscontrato vari problemi nell'implementazione di un'architettura RMI Server/Client, con Client Callbacks.Il mio scenario è che sia il server che il client siano dietro Firewall/NAT.Alla fine ho ottenuto un'implementazione completamente funzionante.Ecco le cose principali che ho fatto:

Lato server, IP locale:192.168.1.10.IP pubblico (Internet) 80.80.80.10

Sul PC firewall/router/server locale aprire la porta 6620.Sul PC firewall/router/server locale aprire la porta 1099.Sul router/NAT reindirizza le connessioni in arrivo sulla porta da 6620 a 192.168.1.10:6620 sul router/NAT reindirizza le connessioni in arrivo sulla porta da 1099 a 192.168.1.10:1099

Nel programma attuale:

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

Lato client, IP locale:10.0.1.123 IP pubblico (Internet) 70.70.70.20

Sul PC firewall/router/server locale aprire la porta 1999.Sul router/NAT reindirizza le connessioni in entrata sulla porta 1999 a 10.0.1.123:1999

Nel programma attuale:

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

Spero che questo ti aiuti.Iraklis

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top