1. 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 objectos
distribuídos.
Este guia prático vai lhe introduzir à esta tecnologia versátil, que melhorou muito desde
sua primeira versão.
O principal objectivo 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 objectos trabalham em uma única Java Virtual
Machine (JVM) para um novo modelo de como as classes e objectos trabalhariam num
ambiente distribuído de computação (múltiplas JVMs). Os arquitectos do RMI tentaram
fazer com que o uso dos objectos distribuídos em Java fosse similar ao uso de objectos
Java locais.
Esta secção introduz a arquitectura RMI da perspectiva dos objectos Java remotos
distribuídos, e explora as diferenças de comportamento com objectos locais. A
arquitectura RMI define como os objectos se comportam, como e quando excepções
podem ocorrer, como a memória é gerida e como os parâmetros são passados e
retornados de métodos remotos.
A arquitectura RMI estende a segurança e robustez da arquitectura Java para o mundo
da computação distribuída.
A arquitectura 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
Página 1 de 11
2. 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 actua como um Proxy para o serviço remoto. Veja o seguinte diagrama:
O programa cliente faz chamadas de métodos pelo objecto Proxy, o RMI envia a
requisição para a JVM remota e redirecciona para a implementação. Qualquer valor
retornado pela implementação é devolvido ao Proxy e então ao programa cliente.
Com o entendimento da arquitectura 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 abstracçã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
gerir referências feitas dos clientes para os objectos 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 arquitectura de camadas, cada uma das camadas poderia ser facilmente
Página 2 de 11
3. melhorada ou substituída sem afectar o resto do sistema. Por exemplo, a camada de
transporte poderia ser substituída por uma camada que implemente conexões UDP/IP,
sem afectar as camadas superiores.
Como um cliente acha o serviço remoto RMI?
Os clientes acham os serviços remotos usando o serviço de nomeação ou directório
(naming or directory). Isso parece um pouco redundante, mas o serviço de nomeação
ou directório roda como um endereço bem formado (host:port).
O RMI pode usar diferentes tipos de serviços de directó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 objecto que implemente aquele serviço. Em seguida ele exporta aquele
objecto para o RMI. Quando o objecto é exportado o RMI cria um serviço que aguarda
as conexões do cliente. O servidor registra o objecto no RMI Registry, com um nome
público.
No lado do cliente o RMI Registry é acedido 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 objecto do serviço. A URL é formada
como seguinte:
rmi://<host_name>[:port_number]/<service_name>
Página 3 de 11
4. 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
objecto 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
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
directó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 directório para salvar todos os seus arquivos de projecto.
Você pode fazer o download do código fonte usado nesse tutorial.
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.
Página 4 de 11
5. 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 excepção
RemoteException.
Salve este arquivo (Mensageiro.java) no seu directório e compile, com a seguinte linha
de comando:
javac Mensageiro.java
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.
01 import java.rmi.RemoteException;
02 import java.rmi.server.UnicastRemoteObject;
03
04 public class MensageiroImpl extends UnicastRemoteObject implements Mensageiro {
05
06 public MensageiroImpl() throws RemoteException {
07 super();
08 }
09
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 directório e compile, com a seguinte
linha de comando:
Página 5 de 11
6. javac MensageiroImpl.java
Observe que a classe se utiliza (estende) da classe UnicastRemoteObject para ligar com
o sistema RMI. Neste exemplo a classe estende a classe UnicastRemoteObject
directamente. Isto não é realmente necessário, mas essa discussã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 activa o código em UnicastRemoteObject, que
executa a ligação RMI e a iniciação do objecto remoto.
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.
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.
01 import java.rmi.Naming;
02
03 public class MensageiroServer {
04
05 public MensageiroServer() {
06 try {
07 Mensageiro m = new MensageiroImpl();
08 Naming.rebind("rmi://localhost:1099/MensageiroService", m);
Página 6 de 11
7. 09 }
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
O código fonte para o cliente é o seguinte. Salve o arquivo como:
MensageiroClient.java.
01 import java.rmi.Naming;
02 import java.rmi.RemoteException;
03 import java.rmi.NotBoundException;
04 import java.net.MalformedURLException;
05
06 public class MensageiroClient {
07
08 public static void main( String args[] ) {
09 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 }
Página 7 de 11
8. Salve este arquivo (MensageiroClient.java) no seu diretório e compile, com a seguinte
linha de comando:
javac MensageiroClient.java
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:
rmiregistry
Isso irá iniciar o RMI Registry e rodá-lo.
No segundo console vamos executar o programa servidor. Você deve estar no mesmo
directório em que estão gravados seus arquivos para rodar o aplicativo. Execute o
seguinte comando:
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 directório em
que estão gravados seus arquivos para rodar o aplicativo. Execute o comando:
java MensageiroClient
Página 8 de 11
9. 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):
Hellow World!
No console 3 (cliente):
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.
Exercício:
1. Defina a Interface Remota
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.math.BigInteger;
public interface Operator extends Remote {
BigInteger add(BigInteger b1, BigInteger b2) throws RemoteException;
BigInteger subtract(BigInteger b1, BigInteger b2) throws RemoteException;
BigInteger divide(BigInteger b1, BigInteger b2) throws RemoteException;
BigInteger multiply(BigInteger b1, BigInteger b2) throws RemoteException;
}
2. Compile a Interface
Javac Operator.java
3. Implementer a Interface Remota
import java.rmi.RemoteException;
import java.math.BigInteger;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.Naming;
public class OperatorServer extends UnicastRemoteObject implements Operator
{
public OperatorServer() throws RemoteException { }
public BigInteger add(BigInteger b1, BigInteger b2) throws RemoteException {
return b1.add(b2);
}
Página 9 de 11
10. public BigInteger subtract(BigInteger b1, BigInteger b2) throws
RemoteException {
return b1.subtract(b2);
}
public BigInteger divide(BigInteger b1, BigInteger b2) throws RemoteException
{
return b1.divide(b2);
}
public BigInteger multiply(BigInteger b1, BigInteger b2) throws
RemoteException {
return b1.multiply(b2);
}
public static void main(String []args) {
String name = "//localhost/OperatorServer";
try {
OperatorServer os = new OperatorServer();
Naming.rebind(name,os);
System.out.println(name + " bound");
} catch (Exception e) {
System.err.println("OperatorServer exception: " + e.getMessage());
e.printStackTrace();
}
}
}
4. Compile a classe servidor
javac OperatorServer.java
5. Crie o Stub e o Skeleton
rmic OperatorServer
6. Verifique se as seguintes classes estão criadas
OperatorServer.class
OperatorServer.java
OperatorServer_Skel.class
OperatorServer_Stub.class
7. Crie a classe cliente
import java.rmi.Naming;
import java.math.BigInteger;
public class OperatorClient {
public static void main(String args[]) {
String name = "//localhost/OperatorServer";
try {
Operator o = (Operator)Naming.lookup (name);
BigInteger b1 = new BigInteger("1234567");
BigInteger b2 = new BigInteger("1");
BigInteger b3 = o.add(b1, b2);
System.out.println("b3: " + b3);
} catch( Exception e) {
System.out.println(e); e.printStackTrace();
}
Página 10 de 11
11. }
}
8. Compile a classe cliente
Javac OperatorClient.java
9. Abra um consola do MS DOS
rmiregistry
10. Abra a 2ª consola do MS DOS – execução da aplicação servidor
java OperatorServer
Vais mostrar: //localhost/OperatorServerbound
11. Abra a 3ª consola do MS DOS – execução da aplicação cliente
java OperatorClient
Vais mostrar: 1234568
Página 11 de 11