Como posso garantir que o RMI use apenas um conjunto específico de portas?

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

  •  09-06-2019
  •  | 
  •  

Pergunta

Em nossa aplicação, estamos usando RMI para comunicação cliente-servidor de maneiras muito diferentes:

  1. Enviando dados do servidor para o cliente para serem exibidos.
  2. Envio de informações de controle do cliente para o servidor.
  3. Retornos de chamada dessas mensagens de controle codificam caminhos que retornam do servidor ao cliente (nota da barra lateral - este é um efeito colateral de algum código legado e não é nossa intenção de longo prazo).

O que gostaríamos de fazer é garantir que todo o nosso código relacionado ao RMI usará apenas um inventário de portas especificado e conhecido.Isso inclui a porta de registro (normalmente esperada como 1099), a porta do servidor e quaisquer portas resultantes dos retornos de chamada.

Aqui está o que já sabemos:

  1. LocateRegistry.getRegistry(1099) ou Locate.createRegistry(1099) garantirá que o registro esteja escutando em 1099.
  2. Usar o método estático construtor UnicastRemoteObject/exportObject com um argumento port especificará a porta do servidor.

Esses pontos também são abordados neste Postagem no fórum Sun.

O que não sabemos é:como podemos garantir que as conexões do cliente de volta ao servidor resultantes dos retornos de chamada se conectarão apenas em uma porta especificada, em vez de usar como padrão uma porta anônima?

EDITAR:Adicionada uma resposta longa resumindo minhas descobertas e como resolvemos o problema.Esperançosamente, isso ajudará qualquer pessoa com problemas semelhantes.

SEGUNDA EDIÇÃO:Acontece que em meu aplicativo parece haver uma condição de corrida na criação e modificação de fábricas de soquetes.Eu queria permitir que o usuário substituísse minhas configurações padrão em um script Beanshell.Infelizmente, parece que meu script está sendo executado significativamente depois que o primeiro soquete é criado pela fábrica.Como resultado, estou obtendo uma combinação de portas do conjunto de padrões e das configurações do usuário.Será necessário mais trabalho que esteja fora do escopo desta questão, mas pensei em apontá-lo como um ponto de interesse para outros que possam ter que trilhar essas águas em algum momento....

Foi útil?

Solução

Você pode fazer isso com um RMI Socket Factory personalizado.

As fábricas de soquetes criam os soquetes para o RMI usar tanto no cliente quanto no servidor, portanto, se você escrever o seu próprio, terá controle total sobre as portas usadas.As fábricas do cliente são criadas no servidor, serializadas e depois enviadas para o cliente, o que é muito legal.

Aqui está um guia da Sun dizendo como fazer isso.

Outras dicas

Você não precisa de fábricas de soquetes para isso, nem mesmo de várias portas.Se você estiver iniciando o Registro a partir da JVM do seu servidor, poderá usar a porta 1099 para tudo e, de fato, é isso que acontecerá por padrão.Se você não estiver iniciando o registro, como em um objeto de retorno de chamada do cliente, poderá fornecer a porta 1099 ao exportá-lo.

A parte da sua pergunta sobre 'as conexões do cliente de volta ao servidor resultantes de retornos de chamada' não faz sentido.Elas não são diferentes das conexões originais do cliente com o servidor e usarão as mesmas portas do servidor.

Resumo da longa resposta abaixo:para resolver o problema que tive (restringindo portas de servidor e de retorno de chamada em cada extremidade da conexão RMI), precisei criar dois pares de fábricas de soquete de cliente e servidor.

Segue-se uma resposta mais longa:

Nossa solução para o problema do retorno de chamada tinha essencialmente três partes.O primeiro foi o empacotamento do objeto, que precisava da capacidade de especificar que estava sendo usado para uma conexão cliente-servidor vs.sendo usado para um retorno de chamada de servidor para cliente.Usando uma extensão de UnicastRemoteObject nos deu a capacidade de especificar os soquetes de fábrica do cliente e do servidor que queríamos usar.Entretanto, o melhor lugar para bloquear as fábricas de soquetes é no construtor do 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));
}
// ....
}

Portanto, o primeiro argumento especifica a parte na qual o objeto está esperando solicitações, enquanto o segundo e o terceiro especificam as fábricas de soquetes que serão usadas em cada extremidade da conexão que conduz esse objeto remoto.

Como queríamos restringir as portas usadas pela conexão, precisávamos estender as fábricas de soquetes RMI e bloquear as portas.Aqui estão alguns esboços de nossas fábricas de servidores e 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);
    }
}
// ....
}

Observe que o server socket factory acima garante que apenas a porta que você especificou anteriormente será usada por este factory.A fábrica de soquetes do cliente deve ser emparelhada com a fábrica de soquetes apropriada (ou você 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);
    }
}
// ....
}

Portanto, a única coisa que resta para forçar sua conexão bidirecional a permanecer no mesmo conjunto de portas é alguma lógica para reconhecer que você está ligando de volta para o lado do cliente.Nessa situação, apenas certifique-se de que seu método de fábrica para o objeto remoto chame o construtor RemoteObjectWrapper na parte superior com o parâmetro de retorno de chamada definido como verdadeiro.

Tenho tido vários problemas na implementação de uma arquitetura RMI Servidor/Cliente, com Client Callbacks.Meu cenário é que tanto o servidor quanto o cliente estão atrás de Firewall/NAT.No final, consegui uma implementação totalmente funcional.Aqui estão as principais coisas que fiz:

Lado do servidor, IP local:192.168.1.10.IP público (Internet) 80.80.80.10

No PC Firewall/Roteador/Servidor Local, abra a porta 6620.No PC Firewall/Roteador/Servidor Local, abra a porta 1099.No roteador/NAT redirecionar as conexões de entrada na porta 6620 a 192.168.1.10:6620 No roteador/nat redirecionar conexões de entrada na porta 1099 a 192.168.1.10:1099

No 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 do cliente, IP local:10.0.1.123 IP público (Internet) 70.70.70.20

No PC Firewall/Roteador/Servidor Local abra a porta 1999.No roteador/NAT redirecione as conexões de entrada na porta 1999 para 10.0.1.123:1999

No 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 isto ajude.Iraklis

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top