JAVA RMI RECIFE-PE 2011 2 Alberto de Lima Medeiros Paulo Marcio Fernandes Xavier Tulio José Ferreira dos Santos JAVA RMI Trabalho apresentado ao professor Flavio Gonçalves da Rocha, da disciplina de Sistemas distribuídos, da turma 2008.1 do turno da Noite, do curso: Ciência da Computação. RECIFE-PE 2010 SUMÁRIO 3 1 INTRODUÇÃO.......................................................... 4 2 OBJETIVO.............................................................. 4 3 4 5 6 7 5 Arquitetura Java RMI..................................................... Interfaces: O coração do RMI............................................. Arquitetura de Camadas do RMI......................... Usando o RMI..................................................... Interfaces....................................................... Implementação.............................................. 4 4 5 7 8 6 4 Introdução Introdução á Computação Distribuída 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. Onde o desenvolvedor ou equipe de desenvolvimento terá a necessidade de criar um paradigma de distribuição dos métodos e uma boa abordagem dos mesmos. Objetivo O principal objetivo para os criadores do RMI, é que programadores desenvolvam seus sistemas do mesmo modo que desenvolviam antes porem sendo possível utilizá-los como sistemas distribuídos. Para que isso ocorresse ele tiveram que mapear cuidadosamente as classes e objetos do Java em JVM(Java Virtual Machine), afim que os mesmos sejam implementados em sistemas distribuídos como múltiplas JVMS. Os criadores tiveram o trabalho de simular que essas classes e objetos trabalhassem separadamente mas de que pra os processos fossem localmente, ou seja uma classe e seus métodos estariam em outra maquina diferente da que estou porem isso seria indiferente para meu processo pois para o mesmo tudo estaria loca. 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 5 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. 1º 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. 2º 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). 3º 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. Neste exemplo mostro melhor como Funciona. 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). 6 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: view plainprint? rmi://<host_name>[:port_number]/<service_name> rmi://<host_name>[:port_number]/<service_name> Cliente. Qualquer programa JAVA pode ser um potencial cliente, contanto que ele possua acesso as classes e objetos mínimos necessários e obtenha a referencia do objeto registrado. Stub. Este é o responsável por qualquer comunicação com o objeto distuibuido através do skeleton. O stub transforma as chamadas locais do cliente em chamadas no objeto remoto, e também é responsável por traduzir a chamada remota para o formato esperado pela chamada local. Sendo assim o mesmo se assemelha ao Maximo a programação usual. 7 Skeleton. O skeleton tem o papel semelhante a o stub, porem no lado do servidor. O mesmo é responsável por receber as requisições do cliente e traduzi-las para o servidor de forma análoga e devolve-la para o stub posteriormente. Camadas de referência. Responsável pela criação e gerenciamento de referencias aos objetos remotos. Ele basicamente converte as solicitações RMI para a camada de transporte que esta sendo utilizada. Camadas de transporte. Prove a comunicação em rede entre as jvms, usa os sokets default (tcp) ou outro tipo pode ser utilizado. Usando o RMI Criando 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 Mostrarei como 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 1º - Escrever e compilar o código Java das implementações das classes 2º - 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. 3º - Escrever e compilar o código Java das implementações das classes 4º - 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. 8 Segue ilustração dos paços acima. 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. view plainprint? 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: view plainprint? 1. javac Mensageiro.java 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. view plainprint? 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 Salve este arquivo (MensageiroImpl.java) no seu diretório e compile, com a seguinte linha de comando: view plainprint? 1. javac MensageiroImpl.java 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. view plainprint? 1. rmic MensageiroImpl 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. view plainprint? 1. import java.rmi.Naming; 10 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. view plainprint? 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. 15. 16. catch( MalformedURLException e ) { System.out.println(); System.out.println( "MalformedURLException: " + e.toString() ); 11 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. } 31. } } catch( RemoteException e ) { System.out.println(); System.out.println( "RemoteException: " + e.toString() ); } catch( NotBoundException e ) { System.out.println(); System.out.println( "NotBoundException: " + e.toString() ); } catch( Exception e ) { System.out.println(); System.out.println( "Exception: " + e.toString() ); } Salve este arquivo (MensageiroClient.java) no seu diretório e compile, com a seguinte linha de comando: view plainprint? 1. javac MensageiroClient.java 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: view plainprint? 1. rmiregistry 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: view plainprint? 1. java MensageiroServer 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: view plainprint? 12 1. java MensageiroClient 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): view plainprint? 1. Hellow World! Hellow World! No console 3 (cliente): view plainprint? 1. This is not a Hello World! message This is not a Hello World! Message Referências Site WikiPédia - PT.wikipedia.org Livro Use a cabeça Java 2ª edição