Labs SD >

Invocação de procedimentos remotos com Java RMI

Objetivos

No laboratório:

Documentação

Exercício a resolver até ao fim da aula

No laboratório anterior transformamos uma implementação do Jogo do Galo (Tic Tac Toe) numa aplicação distribuída utilizando o gRPC. Analogamente, neste exercício iremos utilizar o Java RMI para estudar outra forma de transformar uma implementação do Jogo do Galo numa aplicação distribuída.

Partindo de uma versão Jogo do Galo feita para um cenário local, semelhante à versão utilizada no laboratório anterior, pretende-se desenvolver uma variante do jogo onde a parte computacionalmente mais pesada é realizada por um servidor remoto. O servidor guarda as variáveis do jogo. O cliente faz a interface com o utilizador.
Tic Tac Toe

Sugestão: nas alíneas seguintes, corra os módulos cliente e servidor numa máquina não partilhada com outros grupos para evitar conflitos de portos. Utilize uma máquina pessoal ou do laboratório e não o sigma.

  1. Descarregue e descomprima o código fonte da aplicação Jogo do Galo/Tic Tac Toe ZIP adaptada a este exercício.
    1. Veja como o programa está estruturado em três módulos: server, client e interface.
      Cada módulo tem um POM próprio.
    2. Importe os projectos no Eclipse, recorrendo a File > Import > Maven > Existing Maven Projects
    3. No ponto de partida, o código está todo centralizado no módulo server, estando o client vazio.
      O módulo interface será usado num próximo passo.
    4. Estude os principais ficheiros com a implementação do jogo (Game.java e TTT.java do módulo server).
      1. Esta implementação é muito semelhante à utilizada como base no laboratório anterior.
        No entanto, alguns métodos já são sincronizados.
      2. Porque é que esta sincronização é necessária?
    5. Compile e experimente o jogo na sua versão centralizada (executando mvn compile exec:java).

  2. Pretende-se que a classe TTT.java, que implementa o jogo, passe a ser invocável remotamente.
    Dessa forma, permitir-se-á que haja um cliente remoto que interage com os jogadores invocando os métodos do servidor via Java RMI (Remote Method Invocation).
    Naturalmente, o cliente poderá correr numa máquina diferente da máquina que serve o jogo.
    1. Comece por desenhar os métodos remotos do servidor numa interface chamada TTTService.
      A interface deve expor todas as funções remotas que o cliente precisa de invocar, ou seja, play, checkWinner e currentBoard.

      Para ser uma interface remota, precisa de herdar de java.rmi.Remote e cada um dos seus métodos deve lançar uma java.rmi.RemoteException.
      Consulte o exemplo de interface remota apresentado no livro para referência.

      1. Coloque a nova interface no módulo interface (ficheiro TTTService.java).
      2. Depois de codificar a interface, compile o código e instale o módulo no seu repositório local:
        cd interface
        mvn install
      3. Adicione a dependência no pom.xml do servidor e do cliente, pois esta interface terá de ser partilhada por ambos:
            ...
                <dependency>
                    <groupId>example</groupId>
                    <artifactId>ttt-rmi-interface</artifactId>
                    <version>1.0.0-SNAPSHOT</version>
                </dependency>
            ...
        
      4. Depois de editar os pom.xml, refresque as dependências do Maven no projeto Eclipse:
        right-click > Maven > Update Project... > Force Update of Snapshots/Releases > OK

    2. No servidor, transforme a classe TTT para que passe a implementar a interface remota TTTService criada anteriormente.
      1. Para que as instâncias desta classe possam ser objetos remotos, modifique a definição da classe TTT de forma a herdar de java.rmi.server.UnicastRemoteObject e acrescente um construtor que lance excepção RemoteException.
      2. Esta classe deverá implementar a lógica dos métodos que o cliente irá chamar posteriormente, tal como definidos na interface TTTService.
        Consulte o exemplo da classe shapeListServant apresentado no livro para referência.

    3. No servidor, crie uma nova classe TTTServer que implementa a função main e onde correrá o servidor.
      Na função main deverá:
      1. Instanciar um objeto remoto do tipo TTT.
      2. Lançar um rmiregistry (serviço de nomes do RMI) e registar o objeto remoto nesse rmiregistry utilizando a classe java.rmi.registry.LocateRegistry.
      3. Atualize a mainclass definida no pom.xml para refletir a classe que possui o método main.
      Consulte o exemplo da classe ShapeListServer apresentado no livro para referência.

    4. Deverá agora proceder à modificação do cliente de forma a chamar remotamente os métodos definidos anteriormente na interface TTTService.
      1. Mova a classe ttt.Game do servidor para o cliente. Implemente um cliente remoto que, com base nos comandos recebidos pela consola local, invoca métodos do jogo remoto.
        Assuma que ambos os jogadores de cada jogo usam o mesmo cliente.
        Consulte o exemplo do cliente apresentado no livro para referência.

      2. Não se esqueça de, na chamada ao método Naming.lookup, definir correctamente o URL que localiza o objeto, na forma: //host:port/name, em que host e port definem a máquina e o porto onde corre o rmiregistry onde foi registado o objeto remoto e name é o nome que foi atribuído ao objeto pelo servidor quando chamou rebind.
      3. Adicione o tratamento adequado às exceções lançadas quando acontece algo inesperado numa invocação remota.
        As exceções de comunicação e tratamento de dados que podem ocorrer são todas subclasses de java.rmi.RemoteException. Exemplos:
        • java.rmi.UnknownHostException
        • java.rmi.ConnectException
        • java.rmi.UnmarshalException
        • java.rmi.MarshalException

  3. Experimente lançar o servidor e depois um cliente para jogar.

  4. Responda às seguintes questões:
    1. Quando se usa gRPC é gerado código para converter os dados de e para um formato de rede. O que acontece quando se usa RMI?
    2. Das classes e interfaces Java que utilizou, quais são as que pertencem apenas ao cliente, apenas ao servidor e a ambos?

O resto do enunciado será entregue na aula. O objectivo será estender a solução resultante do enunciado acima com mais procedimentos ou modificar alguns dos seus procedimentos actuais.

 

Entrega da solução

Fénix, Avaliação, Projetos, mini Exercício 2 - Java RMI

A solução completa deverá ser submetida no Fénix antes do fim da sua aula de laboratório.
Trabalhos submetidos depois da hora de fim da aula não serão considerados.

Ter atenção ao seguinte:

Como seria um projecto Java RMI mais elaborado?

O projecto desenvolvido ao longo das alíneas apresentadas faz algumas simplificações importantes que normalmente não se observam num projecto real de Java RMI. Entre elas:

  1. Existe apenas uma instância de objeto remoto.
    Normalmente pode existir um número variado de interfaces e classes remotas, assim como de suas instâncias.
  2. Há um processo que aloja o objeto remoto e outro processo que obtém referência remota para essa objeto, numa clara distinção entre servidor e cliente.
    Na prática, um processo pode simultaneamente ser servidor de alguns objetos remotos e ter outras referências remotas (para objetos remotos noutros processos), sobre as quais invoca métodos (agindo também como cliente).
  3. No projecto acima nunca ocorre nenhuma situação de carregamento dinâmico de classes.
    Essa situação poderia, por exemplo, acontecer se um método remoto recebesse ou retornasse um objeto por valor. Nesse caso seria necessário definir alguns aspectos de segurança da JVM (em particular, um Security Manager e uma Security Policy).
  4. O RMIRegistry é lançado internamente pelo servidor na mesma JVM.
    (LocateRegistry.createRegistry()). O RMI Registry é normalmente um serviço autónomo que corre numa máquina virtual Java (JVM) separada do processo que instancia e solitica o registo de um objeto remoto. Neste caso, é necessário que os ficheiros com as interfaces remotas dos objetos a registar no Registry estejam disponíveis num URL definido no parâmetro "codebase" da JVM do processo servidor. Poderá descarregar e analisar um exemplo que usa RMI Registry como serviço autónomo ZIP.

© Docentes de Sistemas Distribuídos, Dep. Eng. Informática, Técnico Lisboa