Você está na página 1de 10

Introdução ao RMI

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 datagrama:

1
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:

1. 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

2
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 {

3
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. }

4
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

5
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.

6
Invocação de Métodos Remotos - RMI
As aplicações do RMI compreendem frequentemente dois programas
separados, um servidor e um cliente. Um programa servidor cria alguns objetos
remotos, faz referência a esses objetos, e aguarda os "clients" invocarem os
métodos desses objetos. Enquanto que um programa cliente obtém uma referência
remota ao objetos criados pelo servidor e invoca os métodos desses objetos.

Entendendo o funcionamento
A seguinte ilustração descreve uma aplicação distribuída RMI que utiliza o
"RMI registry" para obter uma referência a um objeto remoto. O servidor chama o
"registry" para associar (bind) um nome com um objeto remoto. O cliente olha o
objeto remoto por seu nome no "registry" do servidor, e invoca então um método
nele. A ilustração mostra também que o sistema do RMI utiliza um web server
existente para carregar definições da classe, do servidor ao cliente e do cliente ao
servidor, para objetos quando necessário.

Criando aplicações distribuídas usando o RMI


Utilizar RMI para desenvolver uma aplicação distribuída envolve estas etapas
gerais:
1 - Projetando e implementando os componentes de sua aplicação distribuída.
2 - Compilando o código fonte.
3 - Fazendo com que as classes sejam acessíveis via rede.
4 - Iniciando a aplicação.

1 - Projetando e implementando os componentes de sua aplicação distribuída.


Para nossa aplicação modelo iremos criar uma classe servidora que irá
conter um método que será invocado remotamente através da utilização de RMI,
para isso iremos precisar de alguns arquivos:
- Ola.java (interface que será implementada tanto pelo server quando pelo
client)
- OlaImpl.java (implementação do servidor)
- Cliente.java (client que fará uso de métodos do OlaImpl - server)

Como sempre vamos aos códigos fontes, para esse as explicações serão
inseridas na forma de comentários dentro dos próprios arquivos (assim pouparemos
espaço aqui).

7
Ola.java
Interface que será utilizada tanto pelo servidor quanto pelo cliente, é necessária
para fazer a integração de ambos.
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Ola extends Remote {


String showMsg(String msg) throws RemoteException;
}

OlaImpl.java
Classe que será acessada remotamente pelo Client.java
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class OlaImpl extends UnicastRemoteObject implements Ola {

// precisamos definir um construtor pois o default não


// gera RemoteException
public OlaImpl() throws RemoteException {
// opcional, vai ser chamado mesmo que não
// o coloquemos aqui
super();
}

public String showMsg(String msg) {


// note que não tem o throws aqui.
// A exceção é gerada pela super-classe se for o caso

System.out.println("msg: " + msg);


return("msg enviada");
}

public static void main(String args[]) {

try {
// aqui criamos o objeto que sera acessado
// remotamente
OlaImpl obj = new OlaImpl();

// Aqui registramos o objeto obj como "OlaServidor"


// no servidor
Naming.rebind("OlaServidor", obj);

System.out.println("Servidor carregado no registry");


} catch (Exception e) {
System.out.println("OlaImpl erro: " + e.getMessage());
e.printStackTrace();
}
}
}

Client.java
Classe que irá rodar do lado do cliente e utilizará o método "showMsg" da classe
OlaImpl que estará rodando do lado do servidor.

import java.rmi.Naming;

public class Client {

8
public static void main(String[] args) {

/*
* obj é o identificados que iremos utilizar para fazer
* referencia ao objeto remoto que será implementado
*/
Ola obj = null;

String msg = "minha mensagem";


String retorno = null;

try {
/*
* - o lookup carrega o OlaImpl_Stub do CLASSPATH
* - 127.0.0.1 é o IP da máquina onde o server
* está rodando, no nosso caso é a mesma máquina
* - OlaServidor é o nome que utilizamos para fazer
* referencia ao objeto no servidor
*/

// aqui instanciamos o objeto remoto


obj = (Ola)Naming.lookup("//127.0.0.1/OlaServidor");

// agora executamos o metoro showMsg no


// objeto remoto
retorno = obj.showMsg(msg);
System.out.println(retorno);
}
catch (Exception e) {
System.out.println("Client exception: " + e.getMessage());
e.printStackTrace();
}
}
}

2 - Compilando o código fonte.


Agora vamos compilar os códigos fonte, para isso basta digitar:

javac *.java

Além da compilação básica devemos criar uma classe do tipo Stub para a
classe que será acessada remotamente, para criar essa classe devemos rodar o
comando:

rmic OlaImpl

3 - Fazendo com que as classes sejam acessíveis via rede.


Para que as classes possam ser acessadas via rede, precisamos inicializar o
servidor de rmi através do comando:

start rmiregistry (para linux/unix normalmente é somente rmiregistry)

4 - Iniciando a aplicação.
Agora vamos rodar nossa aplicação, vamos abrir dois prompts do DOS, no
primeiro colocamos o server para rodar com o comando:

java OlaImpl

9
Com o servidor rodando vamos para a outra janela do DOS e rodamos o
client com o comando:

java Client

Basicamente o que fizemos foi, criar um objeto chamado "OlaImpl" com um


método chamado "showMsg" do lado do servidor, e do lado do cliente criamos um
objeto chamado "Client" que instancia "OlaImpl" e executa o método "showMsg" via
RMI.

Veja aqui alguns "screen shots" do Server e do Client rodando em minha


máquina:

Server

Client

Essa é uma forma muito utilizada para criar aplicações distribuídas, embora
esse artigo pareça simples, ele demonstra a base para esse tipo de comunicação.

Artigos extraídos de:


http://www.guj.com.br/article.show.logic?id=37#
http://www.plugmasters.com.br/sys/materias/617/1/Invoca%E7%E3o-de-M%E9todos-Remotos---RMI

10

Você também pode gostar