Introdução á Computação Distribuida com RMI

A tecnologia RMI - Remote Method Invocation (Invocação de Métodos Remotos), foi primeiramente introduzida no Java, no JDK versão 1.1, elevando a programação para redes em um patamar mais elevado. Apesar do RMI ser relativamente fácil, ele põe o desenvolvedor Java frente à um novo paradigma, o mundo da computação de objetos distribuídos. Este guia prático vai lhe introduzir à esta tecnologia versátil, que melhorou muito desde sua primeira versão.

Objetivo

O principal objetivo para os criadores (designers) do RMI era permitir os programadores a desenvolverem programas distribuídos em Java com a mesma sintaxe e semântica usada em programas não-distribuídos. Para isso, eles tiveram que mapear cuidadosamente como classes Java e objetos trabalham em uma única Java Virtual Machine (JVM) para um novo modelo de como as classes e objetos trabalhariam num ambiente distribuído de computação (múltiplas JVMs). Os arquitetos do RMI tentaram fazer com que o uso dos objetos distribuídos em Java fosse similar ao uso de objetos Java locais. Esta seção introduz a arquitetura RMI da perspectiva dos objetos Java remotos distribuídos, e explora as diferenças de comportamento com objetos locais. A arquitetura RMI define como os objetos se comportam, como e quando exceções podem ocorrer, como a memória é gerenciada e como os parâmetros são passados e retornados de métodos remotos.

Arquitetura Java RMI

A arquitetura RMI estende a segurança e robustez da arquitetura Java para o mundo da computação distribuída.

Interfaces: O coração do RMI

A arquitetura RMI é baseada em um importante princípio: a definição do comportamento e a implementação do comportamento são conceitos separados. RMI permite que o código que define o comportamento e o código que implementa o comportamento permanecerem separados e rodarem em JVMs separadas. Em RMI, a definição do serviço remoto é codificada usando uma interface Java. A implementação do serviço remoto é codificada em uma classe. Logo, a chave para se entender o RMI é lembrar que as interfaces definem o comportamento e as classes definem a implementação. A classe que implementa o comportamento roda do lado do servidor RMI. A classe que roda no cliente atua como um Proxy para o serviço remoto. Veja o seguinte diagrama: O programa cliente faz chamadas de métodos pelo objeto Proxy, o RMI envia a requisição para a JVM remota e redireciona para a implementação. Qualquer valor retornado pela implementação é devolvido ao Proxy e então ao programa cliente.

Arquitetura de Camadas do RMI

Com o entendimento da arquitetura RMI num alto nível, vamos dar uma breve olhada na sua implementação. A implementação do RMI é essencialmente feita de três camadas de abstração. A camada Stub e Skeleton está abaixo dos olhos do desenvolvedor. Esta camada intercepta as chamadas de métodos feitas pelo cliente para que a variável de referência da interface redirecione essas chamadas para o serviço RMI remoto. A próxima camada é a Remote Reference Layer. Esta camada sabe como interpretar e gerencias referências feitas dos clientes para os objetos do serviço remoto. A conexão do cliente ao servidor é Unicast (uma-para-um). A camada de transporte é baseada nas conexões TCP/IP entre as maquinas em uma rede. Usando essa arquitetura de camadas, cada uma das camadas poderia ser facilmente melhorada ou substituída sem afetar o resto do sistema. Por exemplo, a camada de transporte poderia ser substituída por uma camada que implemente conexões UDP/IP, sem afetar as camadas superiores.

Nomeando Objetos Remotos

Como um cliente acha o serviço remoto RMI? Os clientes acham os serviços remotos usando o serviço de nomeação ou diretório (naming or directory). Isso parece um pouco redundante, mas o serviço de nomeação ou diretório roda como um endereço bem formado (host:port). O RMI pode usar diferentes tipos de serviços de diretório, incluindo o JNDI. O próprio RMI inclue um simples serviço, chamado de RMI Registry. O RMI Registry roda em cada maquina que hospeda o serviço remoto, por definição na porta 1099. Numa máquina host, um programa servidor cria um serviço remoto, primeiramente criando o objeto que implemente aquele serviço. Em seguida ele exporta aquele objeto para o RMI. Quando o objeto é exportado o RMI cria um serviço que aguarda as conexões do cliente. O servidor registra o objeto no RMI Registry, com um nome público. No lado do cliente o RMI Registry é acessado através da classe estática Naming. Ela provém o método lookup( ), que o cliente usa para requisitar o registro. Esse método aceita a URL que especifica o nome do servidor e o nome do serviço desejado. O método retorna uma referência remota para o objeto do serviço. A URL é formada como seguinte:

rmi://<host_name>[:port_number]/<service_name>  

Usando o RMI

Agora vamos trabalhar com um sistema que realmente implementa um sistema com RMI. Vamos criar um aplicativo simples, cliente e servidor, que executa métodos do objeto remoto. Para tanto não necessitamos de duas máquinas distintas ou com IP distintos. O exemplo pode ser rodado na mesma máquina, pois o RMI sabe como trabalhar com isso, mesmo que o host e o cliente sejam na mesma localidade. Um sistema RMI é composto de várias partes:

  • Definição das interfaces para os serviços remotos
  • Implementações dos serviços remotos
  • Arquivos de Stub e Skeletons
  • Um servidor para hospedar os serviços remotos
  • Um serviço de RMI Naming que permite o cliente achar os serviços remotos
  • Um provedor de arquivos de classes (servidor http ou ftp)
  • Um programa cliente que necessita os serviços remotos
    Criando seu aplicativo com RMI
    Agora iremos, de fato, criar um sistema que implemente o RMI, utilizando-se de um programa cliente e um programa servidor. Não utilizaremos um servidor FTP ou HTTP, no entanto utilizaremos os programas na mesma máquina e uma mesma estrutura de diretórios. Os passos a serem seguidos agora são:
  • Escrever e compilar o código Java da interface
  • Escrever e compilar o código Java das implementações das classes
  • Gerar as classes Stub e Skeleton das classes de implementação Crie um diretório para salvar todos os seus arquivos de projeto. Você pode fazer o download do código fonte usado nesse tutorial.
    Interfaces
    O primeiro passo, como dito, será criar a interface e compilá-la. A interface define todas as funcionalidades remotas oferecidas pelo serviço. Nomeio o arquivo como: Mensageiro.java.
    1. import java.rmi.Remote;   
    2. import java.rmi.RemoteException;   
    3.   
    4. public interface Mensageiro extends Remote {   
    5.   
    6.     public void enviarMensagem( String msg ) throws RemoteException;   
    7.     public String lerMensagem() throws RemoteException;   
    8. }  

    Perceba que esta interface estende a classe Remote, e cada assinatura de método declara as funcionalidades do serviço, e que podem gerar uma exceção RemoteException. Salve este arquivo (Mensageiro.java) no seu diretório e compile, com a seguinte linha de comando:
    1. javac Mensageiro.java  

    Implementação
    Agora, você deverá escrever a implementação para o serviço remoto, ou seja, o código a ser executado no ambiente remoto. Nomeia o arquivo como: MensageiroImpl.java.
    1. import java.rmi.RemoteException;   
    2. import java.rmi.server.UnicastRemoteObject;   
    3.   
    4. public class MensageiroImpl extends UnicastRemoteObject implements Mensageiro {   
    5.   
    6.     public MensageiroImpl() throws RemoteException {   
    7.         super();   
    8.     }   
    9.   
    10.     public void enviarMensagem( String msg ) throws RemoteException {   
    11.         System.out.println( msg );   
    12.     }   
    13.   
    14.     public String lerMensagem() throws RemoteException {   
    15.         return "This is not a Hello World! message";   
    16.     }   
    17. }  

    Salve este arquivo (MensageiroImpl.java) no seu diretório e compile, com a seguinte linha de comando:
    1. javac MensageiroImpl.java  

    Observe que a classe se utiliza (estende) da classe UnicastRemoteObject para linkar com o sistema RMI. Neste exemplo a classe estende a classe UnicastRemoteObject diretamente. Isto não é realmente necessário, mas essa discusão fica para uma próxima etapa. Quando uma classe estende a classe UnicastRemoteObject, ele deve prover um construtor que declare que ele pode lançar uma exceção RemoteException, pois quando o método super( ) é chamado, ele ativa o código em UnicastRemoteObject, que executa o link RMI e a iniciação do objeto remoto.
    Stubs e Skeletons
    Gere os arquivos Stubs e Skeletons da classe de implementação que roda no servidor. Para tanto, execute o comando rmic, compilador RMI do JDK.
    1. rmic MensageiroImpl  

    Após a execução deste comando, você deveria ver no seu diretório os arquivos Mensageiro_Stub.class, Mensageiro_Skeleton.class. Servidor O serviço remoto RMI deve ser hospedado em um processo servidor. A classe MensageiroServer é um servidor bem simples, que provê serviços essenciais. Salve o arquivo como: MensageiroServer.java.
    1. import java.rmi.Naming;   
    2.   
    3. public class MensageiroServer {   
    4.   
    5.     public MensageiroServer() {   
    6.         try {   
    7.             Mensageiro m = new MensageiroImpl();   
    8.             Naming.rebind("rmi://localhost:1099/MensageiroService", m);   
    9.         }   
    10.         catch( Exception e ) {   
    11.             System.out.println( "Trouble: " + e );   
    12.         }   
    13.     }   
    14.   
    15.     public static void main(String[] args) {   
    16.         new MensageiroServer();   
    17.     }   
    18. }  

    Salve este arquivo (MensageiroServer.java) no seu diretório e compile, com a seguinte linha de comando: > javac MensageiroServer.java
    Cliente
    O código fonte para o cliente é o seguinte. Salve o arquivo como: MensageiroClient.java.
    1. import java.rmi.Naming;   
    2. import java.rmi.RemoteException;   
    3. import java.rmi.NotBoundException;   
    4. import java.net.MalformedURLException;   
    5.   
    6. public class MensageiroClient {   
    7.   
    8.     public static void main( String args[] ) {   
    9.         try {   
    10.             Mensageiro m = (Mensageiro) Naming.lookup( "rmi://localhost/MensageiroService" );   
    11.             System.out.println( m.lerMensagem() );   
    12.             m.enviarMensagem( "Hello World!" );   
    13.         }   
    14.         catch( MalformedURLException e ) {   
    15.             System.out.println();   
    16.             System.out.println( "MalformedURLException: " + e.toString() );   
    17.         }   
    18.         catch( RemoteException e ) {   
    19.             System.out.println();   
    20.             System.out.println( "RemoteException: " + e.toString() );   
    21.         }   
    22.         catch( NotBoundException e ) {   
    23.             System.out.println();   
    24.             System.out.println( "NotBoundException: " + e.toString() );   
    25.         }   
    26.         catch( Exception e ) {   
    27.             System.out.println();   
    28.             System.out.println( "Exception: " + e.toString() );   
    29.         }   
    30.     }   
    31. }  

    Salve este arquivo (MensageiroClient.java) no seu diretório e compile, com a seguinte linha de comando:
    1. javac MensageiroClient.java  

    Rodando o sistema RMI
    Agora que todos os arquivos do projeto de exemplo foram criados e devidamente compilados, estamos prontos para rodar o sistema! Você precisará abrir três diferentes consoles do MS-DOS no seu Windows, ou outro, caso utilize um diferente sistema operacional. Em um dos consoles vai rodar o programa servidor, no outro o cliente e no terceiro o RMI Registry. Inicie com o RMI Registry. Você deve estar no mesmo diretório em que estão gravados seus arquivos para rodar o aplicativo. Execute a seguinte linha de comando:
    1. rmiregistry  

    Isso irá iniciar o RMI Registry e rodá-lo. No segundo console vamos executar o programa servidor. Você deve estar no mesmo diretório em que estão gravados seus arquivos para rodar o aplicativo. Execute o seguinte comando:
    1. java MensageiroServer  

    Isso irá iniciar, carregar a implementação na memória e esperar pela conexão cliente. No último console, rode o programa cliente. Você deve estar no mesmo diretório em que estão gravados seus arquivos para rodar o aplicativo. Excute o comando:
    1. java MensageiroClient  

    Se tudo correr bem, que é o que esperamos e o que deveria acontecer, a seguinte saída será gerada nos consoles 2 (servidor) e 3 (cliente). No console 2 (servidor):
    1. Hellow World!  

    No console 3 (cliente):
    1. This is not a Hello World! message  

    É isso aí. Você acabou de criar um sistema utilizando a tecnologia RMI. Apesar de você ter rodado os programas na mesma máquina, o RMI usa a pilha de rede TCP/IP para se comunicar entre as três diferentes instâncias da JVM. Espero que tenham gostado e aprendido com esse pequeno exemplo de como se usar o RMI.

  • Fonte:Daniel Destro