Programação usando RMI
Introdução
- Em linhas gerais, essa é uma aplicação cliente-servidor na qual o lado
cliente submete uma tarefa a ser executada pelo objeto remoto localizado no
lado servidor. Tal objeto, por sua vez, executa a tarefa e devolve o resultado
da execução para o cliente que o invocou.
- Dessa forma, seguiremos os seguintes passos na construção e execução dessa
aplicação:
- 1. Criação do Objeto Remoto:
- Definição da Interface Remota
- Implementação da Interface Remota
- 2. Criação do Programa Cliente
- 3. Compilação e Execução da Aplicação
Criação do Objeto Remoto
Definição da Interface Remota
- A interface chama-se compute.Compute:
- Obrigatoriamente deve extender a interface java.rmi.Remote.
- Define um método chamado executeTask(Task t):
- Executa a tarefa informada como parâmetro e devolve um java.lang.Object
como resposta.
package compute;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Compute extends Remote {
Object executeTask(Task t) throws RemoteException;
}
- Vamos definir agora uma segunda interface (não remota) chamada
compute.Task.
- Essa interface define o modelo de uma tarefa que pode ser executada por
nosso objeto remoto
- Qualquer classe que represente uma tarefa tem que implementar o método
execute definido por essa interface
package compute;
import java.io.Serializable;
public interface Task extends Serializable {
Object execute();
}
Implementação da Interface Remota
- A classe de implementação chama-se engine.ComputeEngine:
- Esta classe extende java.rmi.server.UnicastRemoteObject para
poder aceitar comunicação ponto-a-ponto.
- O método executeTask(Task t) simplesmente chama o método
execute da tarefa passada como argumento
- O método main faz o registro cria uma instância dessa classe e a
registra no servidor de nomes do RMI (conforme veremos mais adiante).
- Observe que a primeira coisa que o método main faz é a instalação
de um SecurityManager (gerenciador de segurança). O objetivo desse gerenciador
é proteger o sistema local, permitindo ou negando acesso aos recursos do
sistema.
- Todo programa que usa RMI deve deve efetuar a instalação de um gerenciador
de segurança, pois, do contrário, não será possível fazer o download
automático de classes referentes a objetos passados como parâmetro ou
retornados nas chamadas de métodos.
package engine;
import java.rmi.*;
import java.rmi.server.*;
import compute.*;
public class ComputeEngine extends UnicastRemoteObject
implements Compute
{
public ComputeEngine() throws RemoteException {
super();
}
public Object executeTask(Task t) {
return t.execute();
}
public static void main(String[] args) {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
String name = "//localhost/Compute";
try {
Compute engine = new ComputeEngine();
Naming.rebind(name, engine);
System.out.println("ComputeEngine bound");
} catch (Exception e) {
System.err.println("ComputeEngine exception: " +
e.getMessage());
e.printStackTrace();
}
}
}
Criação do Programa Cliente
Criando uma Tarefa para o Cálculo do PI
- Antes de mostrarmos a criação do programa cliente, vamos criar uma classe
chamada client.PI:
- Essa classe representa uma tarefa (implementa a interface
compute.Task.
- Ela possui toda a lógica para o cálculo do valor de PI, com base na
quantidade casas decimais informada como argumento.
package client;
import compute.*;
import java.math.*;
public class Pi implements Task {
/** constants used in pi computation */
private static final BigDecimal ZERO =
BigDecimal.valueOf(0);
private static final BigDecimal ONE =
BigDecimal.valueOf(1);
private static final BigDecimal FOUR =
BigDecimal.valueOf(4);
/** rounding mode to use during pi computation */
private static final int roundingMode =
BigDecimal.ROUND_HALF_EVEN;
/** digits of precision after the decimal point */
private int digits;
/**
* Construct a task to calculate pi to the specified
* precision.
*/
public Pi(int digits) {
this.digits = digits;
}
/**
* Calculate pi.
*/
public Object execute() {
return computePi(digits);
}
/**
* Compute the value of pi to the specified number of
* digits after the decimal point. The value is
* computed using Machin's formula:
*
* pi/4 = 4*arctan(1/5) - arctan(1/239)
*
* and a power series expansion of arctan(x) to
* sufficient precision.
*/
public static BigDecimal computePi(int digits) {
int scale = digits + 5;
BigDecimal arctan1_5 = arctan(5, scale);
BigDecimal arctan1_239 = arctan(239, scale);
BigDecimal pi = arctan1_5.multiply(FOUR).subtract(
arctan1_239).multiply(FOUR);
return pi.setScale(digits,
BigDecimal.ROUND_HALF_UP);
}
/**
* Compute the value, in radians, of the arctangent of
* the inverse of the supplied integer to the speficied
* number of digits after the decimal point. The value
* is computed using the power series expansion for the
* arc tangent:
*
* arctan(x) = x - (x^3)/3 + (x^5)/5 - (x^7)/7 +
* (x^9)/9 ...
*/
public static BigDecimal arctan(int inverseX,
int scale)
{
BigDecimal result, numer, term;
BigDecimal invX = BigDecimal.valueOf(inverseX);
BigDecimal invX2 =
BigDecimal.valueOf(inverseX * inverseX);
numer = ONE.divide(invX, scale, roundingMode);
result = numer;
int i = 1;
do {
numer =
numer.divide(invX2, scale, roundingMode);
int denom = 2 * i + 1;
term =
numer.divide(BigDecimal.valueOf(denom),
scale, roundingMode);
if ((i % 2) != 0) {
result = result.subtract(term);
} else {
result = result.add(term);
}
i++;
} while (term.compareTo(ZERO) != 0);
return result;
}
}
Criando o Cliente
- Nosso programa cliente chama-se client.ComputePI:
- Observe que a primeira coisa que o método main faz é instalar um
SecurityManager, também com o intuito de proteger os recursos da máquina
local.
- Em seguida, é obtida uma referência ao objeto remoto registrado
no servidor de nomes do RMI.
- Por fim, é solicitada ao objeto remoto a execução da tarefa passada como
argumento e o resultado dessa execução é então impresso na saída padrão.
package client;
import java.rmi.*;
import java.math.*;
import compute.*;
public class ComputePi {
public static void main(String args[]) {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
try {
String name = "//" + args[0] + "/Compute";
Compute comp = (Compute) Naming.lookup(name);
Pi task = new Pi(Integer.parseInt(args[1]));
BigDecimal pi = (BigDecimal) (comp.executeTask(task));
System.out.println(pi);
} catch (Exception e) {
System.err.println("ComputePi exception: " +
e.getMessage());
e.printStackTrace();
}
}
}
Compilando a Aplicação
Compilando as Classes da Aplicação
- Primeiramente, crie na sua máquina uma pasta chamada lab_rmi:
cd /home/usuario
mkdir lab_rmi
cd lab_rmi
- Crie uma pasta chamada src dentro da pasta lab_rmi e salve
todo o código-fonte das classes mostradas aqui.
- Observe que as classes estão dentro de pacotes. Então elas devem estar
contidas em pastas que correspondam ao nome dos respectivos pacotes.
- Crie uma pasta chamada classes dentro da pasta lab_rmi. Nela
ficará o código compilado.
cd /home/usuario/lab_rmi
mkdir src
mkdir classes
- Compile o seu código-fonte, redirecionando a saída para a pasta
classes:
cd /home/usuario/lab_rmi/src
javac -d ../classes client/*.java engine/*.java compute/*.java
Distribuindo a Aplicação
- Agora, vamos criar pastas separadas para o lado cliente e o lado servidor
da nossa aplicação:
cd /home/usuario/lab_rmi
mkdir cliente
mkdir servidor
cp -r classes/client classes/compute cliente
cp -r classes/engine classes/compute servidor
- Observe que a pasta compute, a qual contém as interfaces da
aplicação, deve estar tanto do lado cliente quanto do lado servidor.
- A pasta engine, a qual contém as classes referentes ao stub, deve
estar somente do lado servidor, tendo em vista que faremos uso do recurso de
download automático de classes provido pelo RMI.
Gerando os Stubs RMI
- Um Stub RMI é um objeto por meio do qual um cliente interage com o objeto
remoto registrado no servidor.
- Para gerar um stub do nosso objeto remoto, é necessário executar o comando
rmic, conforme mostrado a seguir:
cd /home/usuario/lab_rmi/servidor
rmic engine.ComputeEngine
Executando a Aplicação
Definindo as Políticas de Segurança
- Para definir as políticas de segurança da nossa aplicação, vamos criar um
arquivo de políticas chamado de java.policy. Este arquivo será passado
como argumento durante a execução do servidor e do cliente.
- Para o cliente vamos definir o arquivo abaixo. Este arquivo deverá ser
gravado na pasta /home/usuario/lab_rmi/cliente.
grant {
permission java.net.SocketPermission "*:1024-65535",
"connect,accept";
permission java.io.FilePermission
"/home/usuario/lab_rmi/servidor/-", "read";
};
- Para o servidor vamos definir o arquivo a seguir. Este arquivo deverá ser
gravado na pasta /home/usuario/lab_rmi/servidor.
grant {
permission java.net.SocketPermission "*:1024-65535",
"connect,accept";
permission java.io.FilePermission
"/home/usuario/lab_rmi/cliente/-", "read";
};
Executando o Servidor
- Antes de registrarmos o objeto remoto, é necessário executar o serviço de
registro do RMI:
- OBSERVAÇÃO IMPORTANTE:
- Na máquina onde vai ser executado o rmiregistry não deve ter uma
variável de ambiente CLASSPATH definida a qual contenha algum caminho que
leve aonde estão as classes do cliente e do servidor;
- O rmiregistry também não deve ser executado a partir de uma pasta que
contenha os arquivos .class da aplicação, nesse caso, as pastas
/home/usuario/lab_rmi/cliente ou
/home/usuario/lab_rmi/servidor
- Sendo assim, vamos executar o rmiregistry a partir de uma outra pasta, por
exemplo, /tmp.
cd /tmp
rmiregistry &
- Agora, podemos registrar o objeto remoto:
cd /home/usuario/lab_rmi/servidor
java -Djava.rmi.server.codebase=file:/home/usuario/lab_rmi/servidor/
-Djava.security.policy=java.policy
engine.ComputeEngine
Executando o Cliente
- Para a execução do cliente, abra um outro terminal e digite:
cd /home/usuario/lab_rmi/cliente
java -Djava.rmi.server.codebase=file:/home/usuario/lab_rmi/cliente/
-Djava.security.policy=java.policy
client.ComputePi localhost 10
- Após a execução do cliente, será impresso na tela o seguinte resultado
(considerando 10 casas decimais após a vírgula):
3.1415926536
Índice