9. JAVA RMI
9.1. Introdução
Vimos como criar aplicativos de rede usando as ferramentas de comunicação chamadas sockets. Em um aplicativo cliente/servidor construído com base nessas ferramentas, a conexão entre o cliente e o servidor é o protocolo de comunicação que eles adotaram para se comunicarem. As duas aplicações podem ser escritas em linguagens diferentes: Java, por exemplo, para o cliente, Perl para o servidor ou qualquer outra combinação. Trata-se, de fato, de duas aplicações distintas conectadas por um protocolo de comunicação conhecido por ambas. Além disso, o acesso à rede por meio de sockets não é transparente para uma aplicação Java: ela deve utilizar a classe Socket, criada especificamente para gerenciar essas ferramentas de comunicação que são as sockets.
JAVA e RMI (Remote Method Invocation) permitem criar aplicativos de rede com as seguintes características:
- As aplicações cliente/servidor são aplicações Java em ambas as extremidades da comunicação
- O cliente pode utilizar objetos localizados no servidor como se fossem locais
- A camada de rede torna-se transparente: os aplicativos não precisam se preocupar com a forma como as informações são transportadas de um ponto a outro.
O último ponto é um fator de portabilidade: se a camada de rede de um aplicativo RMI vier a mudar, o próprio aplicativo não precisaria ser reescrito. São as classes RMI da linguagem Java que deverão ser adaptadas à nova camada de rede.
O princípio de uma comunicação RMI é o seguinte:
- Uma aplicação Java clássica é escrita em uma máquina A. Ela desempenhará o papel de servidor. Para isso, alguns de seus objetos serão “publicados” na máquina A, na qual a aplicação é executada, e passarão a ser serviços.
- Uma aplicação Java clássica é desenvolvida na máquina B. Ela desempenhará o papel de cliente. Ela terá acesso aos objetos/serviços publicados na máquina A, ou seja, por meio de uma referência remota, poderá manipulá-los como se fossem locais. Para isso, precisará conhecer a estrutura do objeto remoto ao qual deseja acessar (métodos e propriedades).
9.2. Vamos aprender com um exemplo
A teoria subjacente à interface RMI não é simples. Para entender melhor, vamos acompanhar passo a passo a criação de um aplicativo cliente/servidor utilizando o pacote RMI do Java. Vamos usar um exemplo encontrado em muitos livros sobre RMI: o cliente invoca um único método de um objeto remoto, que então retorna uma sequência de caracteres. Apresentamos aqui uma pequena variação: o servidor repete o que o cliente lhe envia. Já apresentamos neste livro uma aplicação semelhante, que se baseia em sockets.
9.2.1. A aplicação do servidor
9.2.1.1. Etapa 1: a interface do objeto/servidor
Um objeto remoto é uma instância de classe que deve implementar a interface Remote definida no pacote java.rmi. Os métodos do objeto que estarão acessíveis remotamente são aqueles declarados em uma interface derivada da interface Remote:
import java.rmi.*;
// a interface remota
public interface interEcho extends Remote{
public String echo(String msg) throws java.rmi.RemoteException;
}
Aqui, declara-se, portanto, uma interface interEcho que declara um método echo como acessível remotamente. Esse método pode gerar uma exceção da classe RemoteException, classe que agrupa todos os erros relacionados à rede.
9.2.1.2. Etapa 2: criação do objeto servidor
Na etapa seguinte, define-se a classe que implementa a interface remota anterior. Essa classe deve ser derivada da classe UnicastRemoteObject, que possui métodos que permitem a invocação remota de métodos.
import java.rmi.*;
import java.rmi.server.*;
import java.net.*;
// classe que implementa o eco remoto
public class srvEcho extends UnicastRemoteObject implements interEcho{
// construtor
public srvEcho() throws RemoteException{
super();
}// fim do construtor
// método que realiza o eco
public String echo(String msg) throws RemoteException{
return "[" + msg + "]";
}// fim do eco
}// fim da classe
Na classe anterior, encontramos:
- o método que exibe o texto
- um construtor que não faz nada além de chamar o construtor da classe pai. Ele está lá para declarar que pode gerar uma exceção do tipo RemoteException.
Vamos criar uma instância dessa classe com um método main. Para que um objeto/serviço seja acessível externamente, ele deve ser criado e registrado no diretório de objetos acessíveis externamente. Um cliente que deseja acessar um objeto remoto procede, de fato, da seguinte maneira:
- ele se conecta ao serviço de diretório da máquina na qual está o objeto desejado. Esse serviço de diretório opera em uma porta que o cliente deve conhecer (1099 por padrão). O cliente solicita ao diretório uma referência de um objeto/serviço, fornecendo o nome do mesmo. Se esse nome corresponder a um objeto/serviço do diretório, este retorna ao cliente uma referência por meio da qual o cliente poderá se comunicar com o objeto/serviço remoto.
- A partir desse momento, o cliente pode utilizar esse objeto remoto como se ele fosse local
Voltando ao nosso servidor, precisamos criar um objeto do tipo srvEcho e registrá-lo no diretório de objetos acessíveis externamente. Esse registro é feito com o método da classe rebind da classe Naming:
com
nome: o nome que será associado ao objeto remoto
obj: o objeto remoto
Nossa classe srvEcho fica, portanto, da seguinte forma:
import java.rmi.*;
import java.rmi.server.*;
import java.net.*;
// classe que implementa o eco remoto
public class srvEcho extends UnicastRemoteObject implements interEcho{
// construtor
public srvEcho() throws RemoteException{
super();
}// fim do construtor
// método que realiza o eco
public String echo(String msg) throws RemoteException{
return "[" + msg + "]";
}// fim do eco
// criação do serviço
public static void main (String arg[]){
try{
srvEcho serveurEcho=new srvEcho();
Naming.rebind("srvEcho",serveurEcho);
System.out.println("Serveur d’écho prêt");
} catch (Exception e){
System.err.println(" Erreur " + e + " lors du lancement du serveur d’écho ");
}
}// main
}// fim da classe
Ao ler o programa anterior, temos a impressão de que ele vai parar logo após criar e registrar o serviço de eco. Mas não é o caso. Como a classe srvEcho é derivada da classe UnicastRemoteObject, o objeto criado é executado indefinidamente: ele escuta as solicitações dos clientes em uma porta anônima, ou seja, escolhida pelo sistema de acordo com as circunstâncias. A criação do serviço é assíncrona: no exemplo, o método main cria o serviço e continua sua execução: ele exibirá “Servidor de eco pronto”.
9.2.1.3. Etapa 3: compilação do aplicativo servidor
Nesta fase, já podemos compilar nosso servidor. Compilamos o arquivo interEcho.java da interface interEcho, bem como o arquivo srvEcho.java da classe srvEcho. Obtemos os arquivos .class correspondentes: interEcho.class e srvEcho.class.
9.2.1.4. Etapa 4: criação do cliente
Escrevemos um cliente ao qual passamos como parâmetro o URL do servidor de eco e que
- lê uma linha digitada no teclado
- a envia para o servidor de eco
- exibe a resposta enviada por ele
- retorna ao passo 1 e encerra quando a linha digitada for “fim”.
Isso resulta no seguinte cliente:
import java.rmi.*;
import java.io.*;
public class cltEcho {
public static void main(String arg[]){
// sintaxe: cltEcho URLService
// verificação de argumentos
if(arg.length!=1){
System.err.println("Syntaxe : pg url_service_rmi");
System.exit(1);
}
// diálogo cliente-servidor
String urlService=arg[0];
BufferedReader in=null;
String msg=null;
String reponse=null;
interEcho serveur=null;
try{
// abertura do fluxo do teclado
in=new BufferedReader(new InputStreamReader(System.in));
// localização do serviço
serveur=(interEcho) Naming.lookup(urlService);
// ciclo de leitura das mensagens a serem enviadas ao servidor de eco
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
while(! msg.equals("fin")){
// envio da mensagem ao servidor e recebimento da resposta
reponse=serveur.echo(msg);
// acompanhamento
System.out.println("Réponse serveur : " + reponse);
// próxima mensagem
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
}// while
// fim
System.exit(0);
// gestão de erros
} catch (Exception e){
System.err.println("Erreur : " + e);
System.exit(2);
}// try
}// main
}// classe
Não há nada de muito específico nesse cliente, exceto a instrução que solicita uma referência do servidor:
Lembramos que nosso serviço de eco foi registrado no diretório de serviços da máquina em que está instalado com a instrução:
Naming.rebind("srvEcho",serveurEcho);
Portanto, o cliente também utiliza um método da classe Naming para obter uma referência do servidor que deseja utilizar. O método lookup utilizado aceita como parâmetro a URL do serviço solicitado. Esta tem o formato de uma URL clássica:
com
rmi: opcional — protocolo RMI
machine: nome ou endereço IP da máquina na qual o servidor de eco está em operação — opcional, por padrão localhost.
port: porta de escuta do serviço de diretório dessa máquina — opcional, por padrão 1099
nom_service: nome sob o qual o serviço solicitado foi registrado (srvEcho no nosso exemplo)
O que é recuperado é uma instância da interface remota interEcho. Supondo que o cliente e o servidor não estejam na mesma máquina, ao compilar o cliente cltEcho.java, é necessário ter, no mesmo diretório, o arquivo interEcho.class, resultado da compilação da interface remota interEcho; caso contrário, ocorrerá um erro de compilação nas linhas que fazem referência a essa interface.
9.2.1.5. Etapa 5: geração dos arquivos .class necessários para a aplicação cliente-servidor
Para entender melhor o que pertence ao lado do servidor e o que pertence ao lado do cliente, colocaremos o servidor no diretório echo\serveur e o cliente no diretório echo\client.
O diretório do servidor contém os seguintes arquivos-fonte:
E:\data\java\RMI\echo\serveur>dir *.java
INTERE~1 JAV 158 09/03/99 15:06 interEcho.java
SRVECH~1 JAV 759 09/03/99 15:07 srvEcho.java
Após a compilação desses dois arquivos-fonte, obtêm-se os seguintes arquivos .class:
E:\data\java\RMI\echo\serveur>dir *.class
SRVECH~1 CLA 1 129 09/03/99 15:58 srvEcho.class
INTERE~1 CLA 256 09/03/99 15:58 interEcho.class
No diretório do cliente, encontramos o seguinte arquivo-fonte:
bem como o arquivo interEcho.class, que foi gerado durante a compilação do servidor:
Após a compilação do arquivo-fonte, obtêm-se os seguintes arquivos .class:
E:\data\java\RMI\echo\client>dir *.class
CLTECH~1 CLA 1 506 09/03/99 16:08 cltEcho.class
INTERE~1 CLA 256 09/03/99 15:59 interEcho.class
Se tentarmos executar o cliente cltEcho, receberemos o seguinte erro:
E:\data\java\RMI\echo\client>j:\jdk12\bin\java cltEcho rmi://localhost/srvEcho
Erreur : java.rmi.UnmarshalException: error unmarshalling return; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub
Ao tentar executar o servidor srvEcho, é exibido o seguinte erro:
E:\data\java\RMI\echo\serveur>j:\jdk12\bin\java srvEcho
Erreur java.rmi.StubNotFoundException: Stub class not found: srvEcho_Stub; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub lors du lancement du serveur d’écho
Em ambos os casos, a máquina virtual Java indica que não encontrou a classe srvEcho_stub. De fato, nunca ouvimos falar dessa classe. No cliente, a localização do servidor foi feita com a seguinte instrução:
Aqui, urlservice é a string rmi://localhost/srvEcho com
Rmi: protocolo RMI
Localhost: máquina na qual o servidor opera — neste caso, a mesma máquina em que o cliente está. A sintaxe normalmente é máquina:porta. Na ausência da porta, a porta 1099 será usada por padrão. Escutando nessa porta, está o serviço de diretório do servidor.
srvEcho: é o nome do serviço específico solicitado
Durante a compilação, nenhum erro foi relatado. Era necessário apenas que o arquivo interEcho.class da interface remota estivesse disponível.
Na execução, a máquina virtual exige a presença de um arquivo srvEcho_stub.class se o serviço solicitado for o srvEcho; de modo geral, um arquivo X_stub.class para um serviço X. Esse arquivo é necessário apenas na execução, não na compilação do cliente. O mesmo se aplica ao servidor. O que é, então, esse arquivo?
No servidor, encontra-se a classe srvEcho.class, que é nosso objeto/serviço remoto. O cliente, mesmo que não precise dessa classe, precisa, no entanto, de uma espécie de imagem dela para poder se comunicar com ela. Na verdade, o cliente não envia suas solicitações diretamente ao objeto remoto: ele as envia à sua imagem local srvEcho_stub.class, localizada na mesma máquina que ele. Essa imagem local srvEcho_stub.class se comunica com uma imagem da mesma natureza (srvEcho_stub.class), localizada, desta vez, no servidor. Essa imagem é criada a partir do arquivo .class do servidor com uma ferramenta Java chamada rmic. No Windows, o comando:
produzirá, a partir do arquivo srvEcho.class, dois outros arquivos .class:
E:\data\java\RMI\echo\serveur>dir *.class
SRVECH~2 CLA 3 264 09/03/99 16:57 srvEcho_Stub.class
SRVECH~3 CLA 1 736 09/03/99 16:57 srvEcho_Skel.class
Lá está, de fato, o arquivo srvEcho_stub.class, necessário para a execução tanto no cliente quanto no servidor. Há também um arquivo srvEcho_Skel.class, cuja função, por enquanto, desconhecemos. Fazemos uma cópia do arquivo srvEcho_stub.class no diretório do cliente e do servidor e excluímos o arquivo srvEcho_Skel.class. Assim, temos os seguintes arquivos:
no lado do servidor:
E:\data\java\RMI\echo\serveur>dir *.class
SRVECH~1 CLA 1 129 09/03/99 15:58 srvEcho.class
INTERE~1 CLA 256 09/03/99 15:58 interEcho.class
SRVECH~1 CLA 3 264 09/03/99 16:01 srvEcho_Stub.class
no lado do cliente:
E:\data\java\RMI\echo\client>dir *.class
CLTECH~1 CLA 1 506 09/03/99 16:08 cltEcho.class
INTERE~1 CLA 256 09/03/99 15:59 interEcho.class
SRVECH~1 CLA 3 264 09/03/99 16:01 srvEcho_Stub.class
9.2.1.6. Etapa 6: Execução do aplicativo cliente-servidor de eco
Estamos prontos para executar nosso aplicativo cliente-servidor. Inicialmente, o cliente e o servidor funcionarão na mesma máquina. Primeiro, precisamos iniciar nosso aplicativo servidor. Lembramos que ele:
- cria o serviço
- o registra no diretório de serviços da máquina na qual o servidor de eco está em operação
Este último ponto requer a presença de um serviço de diretório. Ele é iniciado pelo comando:
rmiregistry é o serviço de diretório. Aqui, ele é iniciado em segundo plano em uma janela do DOS do Windows pelo comando start. Com o diretório ativo, podemos criar o serviço de eco e registrá-lo no diretório de serviços. Mais uma vez, ele é iniciado em segundo plano pelo comando start:
O servidor de eco é executado em uma nova janela DOS e exibe, conforme solicitado:
Agora só nos resta executar e testar nosso cliente:
E:\data\java\RMI\echo\client>j:\jdk12\bin\java cltEcho rmi://localhost/srvEcho
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin
9.2.1.7. O cliente e o servidor em duas máquinas diferentes
No exemplo anterior, o cliente e o servidor estavam na mesma máquina. Agora, vamos colocá-los em máquinas diferentes:
- o servidor em uma máquina Windows
- o cliente em uma máquina Linux
O servidor é iniciado, como anteriormente, na máquina Windows. Na máquina Linux, transferimos os arquivos .class do cliente:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir
total 9
drwxr-xr-x 2 serge admin 1024 Mar 10 10:02 .
drwxr-xr-x 4 serge admin 1024 Mar 10 10:01 ..
-rw-r--r-- 1 serge admin 1506 Mar 10 10:02 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
-rw-r--r-- 1 serge admin 3264 Mar 10 10:02 srvEcho_Stub.class
O cliente é iniciado:
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message : msg1
Erreur : java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:
java.rmi.UnmarshalException: error unmarshalling call header; nested exception is:
java.rmi.UnmarshalException: skeleton class not found but required for client version
Portanto, temos um erro: a máquina virtual Java aparentemente solicita o arquivo srvEcho_skel.class, que havia sido gerado pelo utilitário rmic, mas que até o momento não havia sido utilizado. Nós o recriamos e também o transferimos para a máquina Linux:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir
total 11
drwxr-xr-x 2 serge admin 1024 Mar 10 10:17 .
drwxr-xr-x 4 serge admin 1024 Mar 10 10:01 ..
-rw-r--r-- 1 serge admin 1506 Mar 10 10:02 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
-rw-r--r-- 1 serge admin 1736 Mar 10 10:17 srvEcho_Skel.class
-rw-r--r-- 1 serge admin 3264 Mar 10 10:02 srvEcho_Stub.class
Aparece o mesmo erro de antes... Então, refletimos e relemos a documentação sobre o RMI. Acabamos concluindo que talvez seja o próprio servidor que precise do tal arquivo srvEcho_Skel.class. Então, reiniciamos, na máquina Windows, o servidor com os dois arquivos srvEcho_Stub.class e srvEcho_Skel.class presentes, além do :
E:\data\java\RMI\echo\serveur>dir *.class
SRVECH~1 CLA 1 129 09/03/99 15:58 srvEcho.class
INTERE~1 CLA 256 09/03/99 15:58 interEcho.class
SRVECH~2 CLA 3 264 10/03/99 9:05 srvEcho_Stub.class
SRVECH~3 CLA 1 736 10/03/99 9:05 srvEcho_Skel.class
E:\data\java\RMI\echo\serveur>start j:\jdk12\bin\java srvEcho
Em seguida, na máquina Linux, testamos novamente o cliente e, desta vez, funcionou:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1506 Mar 10 10:02 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
-rw-r--r-- 1 serge admin 3264 Mar 10 10:02 srvEcho_Stub.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin
Conclui-se, portanto, que, no lado do servidor, os dois arquivos srvEcho_Stub.class e srvEcho_Skel.class devem estar presentes. No lado do cliente, até agora, apenas o arquivo srvEcho_Stub.class foi necessário. Ele se mostrou indispensável quando o cliente e o servidor estavam na mesma máquina Windows. No Linux, vamos removê-lo para ver o que acontece...
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1506 Mar 10 10:02 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
*** Security Exception: No security manager, stub class loader disabled ***
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
at sun.rmi.server.RMIClassLoader.getClassLoader(RMIClassLoader.java:84)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:88)
at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at java.io.ObjectInputStream.inputObject(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:105)
at java.rmi.Naming.lookup(Naming.java:60)
at cltEcho.main(cltEcho.java:28)
Erreur : java.rmi.UnexpectedException: Unexpected exception; nested exception is:
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
Temos um erro interessante que parece indicar que a máquina virtual Java tentou carregar a famosa classe stub, mas não conseguiu devido à ausência de um “security manager”. Lembramos de ter visto algo sobre esse assunto na documentação. Voltamos a examinar a documentação... e descobrimos que o servidor deve criar e instalar um gerenciador de segurança (security manager) que garanta aos clientes que solicitam o carregamento de classes que estas sejam seguras. Na ausência desse gerenciador de segurança, o carregamento dessas classes é impossível. Isso parece fazer sentido: nosso cliente Linux solicitou ao servidor a classe srvEcho_stub.class de que precisava, e o servidor recusou, informando que nenhum gerenciador de segurança havia sido instalado. Portanto, modificamos o código da função main do servidor da seguinte maneira:
// criação do serviço
public static void main (String arg[]){
// instalação de um gerenciador de segurança
System.setSecurityManager(new RMISecurityManager());
// inicialização e registro do serviço
try{
srvEcho serveurEcho=new srvEcho();
Naming.rebind("srvEcho",serveurEcho);
System.out.println("Serveur d’écho prêt");
} catch (Exception e){
System.err.println(" Erreur " + e + " lors du lancement du serveur d’écho ");
}
}// principal
Compilamos e geramos os arquivos srvEcho_stub.class e srvEcho_Skel.class com a ferramenta rmic. Iniciamos o serviço de diretório (rmiregistry) e, em seguida, o servidor, e recebemos um erro que não aparecia antes!
Erreur java.security.AccessControlException: access denied (java.net.SocketPermission 127.0.0.1:1099 connect,resolve) lors du lancement du serveur d’écho
O gerenciador de segurança parece ter sido eficiente demais. Relemos a documentação... Percebemos que, quando um gerenciador de segurança está ativo, é preciso especificar os direitos ao iniciar um programa. Isso é feito com a seguinte opção:
start j:\jdk12\bin\java -Djava.security.policy=mypolicy srvEcho
onde
java.security.policy é uma palavra-chave
mypolicy é um arquivo de texto que define os direitos do programa. Neste caso, é o seguinte:
O programa possui todos os direitos aqui.
Vamos tentar novamente. Acesse o diretório do servidor e execute, sucessivamente:
- inicialização do serviço de diretório: start j:\jdk12\bin\rmiregistry
- inicialização do servidor: start j:\jdk12\bin\java -Djava.security.policy=mypolicy srvEcho
E, desta vez, o servidor de eco (o cliente ainda não) é iniciado corretamente. Agora você pode fazer o seguinte teste:
- desligue o servidor de eco e, em seguida, o serviço de diretório
- reinicie o serviço de diretório estando em um diretório diferente daquele do servidor
- volte ao diretório do servidor inicie o servidor de eco — você receberá o seguinte erro:
Erreur java.rmi.ServerException: RemoteException occurred in server thread; nes
ted exception is:
java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub lors du lancement du serveur d’écho
Conclui-se que o diretório a partir do qual o serviço de diretório é iniciado é importante. Neste caso, o Java não encontrou a classe srvEcho_stub.class porque o serviço de diretório não foi iniciado a partir do diretório do servidor. Ao iniciar o servidor, é possível especificar em qual diretório se encontram as classes necessárias ao servidor:
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/serveur/
srvEcho
O comando deve estar em uma única linha. A palavra-chave java.rmi.server.codebase serve para indicar o caminho do diretório que contém as classes necessárias ao servidor. Aqui, esse URL especifica o protocolo file, que é o protocolo de acesso aos arquivos locais, e o diretório que contém os arquivos .class do servidor. Portanto, se procedermos da seguinte forma:
- parar o serviço de diretório
- reiniciar o serviço de diretório a partir de um diretório diferente do do servidor
- no diretório do servidor, inicie-o com o comando (uma única linha):
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/serveur/
srvEcho
O servidor foi iniciado corretamente. Podemos, então, passar para o cliente. Vamos testá-lo:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1506 Mar 10 14:28 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
*** Security Exception: No security manager, stub class loader disabled ***
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
at sun.rmi.server.RMIClassLoader.getClassLoader(RMIClassLoader.java:84)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:88)
at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at java.io.ObjectInputStream.inputObject(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:105)
at java.rmi.Naming.lookup(Naming.java:60)
at cltEcho.main(cltEcho.java:31)
Erreur : java.rmi.UnexpectedException: Unexpected exception; nested exception is:
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
Recebemos o mesmo erro indicando a ausência de um gerenciador de segurança. Pensamos que talvez tenhamos cometido um erro e que seja o cliente quem deva criar seu próprio gerenciador de segurança. Mantemos o servidor com seu gerenciador de segurança, mas criamos um também para o cliente. A função main do cliente cltEcho.java passa então a ser:
public static void main(String arg[]){
// sintaxe: cltEcho porta da máquina
// máquina: máquina na qual o servidor de eco opera
// porta: porta em que o diretório de serviços opera na máquina do serviço de eco
// verificação dos argumentos
if(arg.length!=1){
System.err.println("Syntaxe : pg url_service_rmi");
System.exit(1);
}
// instalação de um gerenciador de segurança
System.setSecurityManager(new RMISecurityManager());
// comunicação cliente-servidor
String urlService=arg[0];
BufferedReader in=null;
String msg=null;
String reponse=null;
interEcho serveur=null;
try{
....
} catch (Exception e){
....
}// tentar
}// main
Em seguida, procede-se da seguinte forma:
- recompila-se o cltEcho.java
- transferimos os arquivos .class para a máquina Linux
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1506 Mar 10 14:28 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
- inicia-se o cliente
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
java.io.FileNotFoundException: /e:/data/java/rmi/echo/serveur/srvEcho_Stub.class
at java.io.FileInputStream.<init>(FileInputStream.java)
at sun.net.www.protocol.file.FileURLConnection.connect(FileURLConnection.java:150)
at sun.net.www.protocol.file.FileURLConnection.getInputStream(FileURLConnection.java:170)
at sun.applet.AppletClassLoader.loadClass(AppletClassLoader.java:119)
at sun.applet.AppletClassLoader.findClass(AppletClassLoader.java:496)
at sun.applet.AppletClassLoader.loadClass(AppletClassLoader.java:199)
at sun.rmi.server.RMIClassLoader.loadClass(RMIClassLoader.java:159)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:97)
at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at java.io.ObjectInputStream.inputObject(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:105)
at java.rmi.Naming.lookup(Naming.java:60)
at cltEcho.main(cltEcho.java:31)
File not found when looking for: srvEcho_Stub
Erreur : java.rmi.UnmarshalException: Return value class not found; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub
Apesar das aparências, estamos avançando: o erro não é mais o mesmo. Observamos que o cliente conseguiu solicitar ao servidor a classe srvEcho_Stub.class, mas que este não a encontrou. Portanto, o cliente deve ter um gerenciador de segurança para poder solicitar classes ao servidor.
Se analisarmos o erro anterior, vemos que o arquivo srvEcho_Stub.class foi procurado no diretório e:/data/java/rmi/echo/serveur/ e não foi encontrado. No entanto, ele está exatamente lá. Se analisarmos mais detalhadamente a lista de métodos envolvidos no erro, encontramos este: sun.net.www.protocol.file.FileURLConnection.getInputStream. O cliente parece ter aberto um fluxo com um objeto do tipo FileURLConnection. Concluímos que tudo isso está relacionado à forma como iniciamos nosso servidor:
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/serveur/
srvEcho
A mensagem de erro parece se referir ao valor da palavra-chave java.rmi.server.codebase. Ao consultar a documentação novamente, percebe-se que o valor dessa palavra-chave, nos exemplos fornecidos, é sempre: http://.., c.a.d. O protocolo utilizado é o HTTP. Não fica claro como o cliente solicita e obtém suas classes do servidor. Talvez ele as solicite com o URL da palavra-chave java.rmi.server.codebase, URL especificada ao iniciar o servidor. Decidimos, portanto, iniciar o servidor com o seguinte novo comando:
start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/echo/ srvEcho
O protocolo agora é http. É necessário mover os arquivos .class para um local acessível ao servidor http da máquina na qual as classes serão armazenadas. No nosso exemplo, o servidor está rodando em uma máquina Windows com um servidor HTTP PWS da Microsoft. A raiz desse servidor é d:\Inetpub\wwwroot. Portanto, procedemos da seguinte maneira:
- criamos o diretório d:\Inetpub\wwwroot\rmi\echo
- colocamos nele os arquivos .class do servidor, bem como o arquivo mypolicy
- iniciamos o servidor Web, caso ainda não tenha sido feito
- reinicia-se o serviço de diretório (rmiregistry)
- reinicie o servidor com o comando
start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/echo/ srvEcho
- na máquina Linux, inicie o cliente:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1622 Mar 10 14:37 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin
Ufa! Funcionou. O cliente conseguiu recuperar o tal arquivo srvEcho_Stub.class.
Tudo isso nos deu algumas ideias, e ficamos nos perguntando se o cliente instalado na máquina Windows do servidor também funcionaria sem o arquivo srvEcho_Stub.class. Acessamos o diretório do cliente, excluímos o arquivo srvEcho_Stub.class, caso ele esteja lá, e iniciamos o cliente da mesma forma que no Linux:
E:\data\java\RMI\echo\client>dir *.class
CLTECH~1 CLA 1 622 10/03/99 14:12 cltEcho.class
INTERE~1 CLA 256 09/03/99 15:59 interEcho.class
E:\data\java\RMI\echo\client>j:\jdk12\bin\java -Djava.security.policy=mypolicy cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message : nouveau message
Réponse serveur : [nouveau message]
Message : fin
9.2.1.8. Résumé
No lado do servidor Windows:
- o servidor possui um gerenciador de segurança
- ele foi iniciado com as seguintes opções: start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/echo/ srvEcho
No lado do cliente (Linux ou Windows)
- o cliente possui um gerenciador de segurança
- no Linux, ele foi iniciado por java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
- No Windows, ele foi iniciado por j:\jdk12\bin\java -Djava.security.policy=mypolicy cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
9.2.1.9. Servidor de eco no Linux, clientes no Windows e no Linux
Agora, vamos executar o servidor em uma máquina Linux e testar clientes Linux e Windows. O procedimento a ser seguido é o seguinte:
- transferimos os arquivos .class do servidor para a máquina Linux
shiva[serge]:/home/admin/serge/WWW/rmi/echo/serveur#
$ dir
total 11
drwxr-xr-x 2 serge admin 1024 Mar 10 16:15 .
drwxr-xr-x 3 serge admin 1024 Mar 10 16:09 ..
-rw-r--r-- 1 serge admin 256 Mar 10 16:09 interEcho.class
-rw-r--r-- 1 serge admin 1245 Mar 10 16:09 srvEcho.class
-rw-r--r-- 1 serge admin 1736 Mar 10 16:09 srvEcho_Skel.class
-rw-r--r-- 1 serge admin 3264 Mar 10 16:09 srvEcho_Stub.class
- Como a classe srvEcho_Stub.class será solicitada pelos clientes, o diretório escolhido para as classes do servidor é um diretório acessível ao servidor HTTP da máquina Linux. Nesse caso, o URL desse diretório é http://shiva.istia.univ-angers.fr/~serge/rmi/echo/serveur
- o serviço de diretório é iniciado em segundo plano: /usr/local/bin/jdk/rmiregistry &
- o servidor é iniciado em segundo plano: /usr/local/bin/jdk/bin/java
-Djava.rmi.server.codebase=http://shiva.istia.univ-angers.fr/~serge/rmi/echo/serveur/
srvEcho &
É possível testar os clientes. Primeiro, o cliente do Windows.
- Vamos para o diretório do cliente na máquina Windows
- iniciamos o cliente com o comando:
E:\data\java\RMI\echo\client>j:\jdk12\bin\java -Djava.security.policy=mypolicy cltEcho rmi://shiva.istia.univ-angers.fr/srvEcho
Message : msg1
Réponse serveur : [msg1]
Message : fin
Testamos o cliente Linux:
shiva[serge]:/home/admin/serge/java/rmi/echo/client#
$ java cltEcho srvEcho
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin
Observe que, para o cliente Linux que roda na mesma máquina que o servidor de eco, não foi necessário especificar a máquina no URL do serviço solicitado.
9.3. Segundo exemplo: Servidor SQL em máquina Windows
9.3.1. O problema
Vimos no capítulo JDBC como gerenciar bancos de dados relacionais. Nos exemplos apresentados, os aplicativos e o banco de dados utilizado estavam na mesma máquina Windows. Propomos aqui criar um servidor RMI em uma máquina Windows, que permitiria que clientes remotos acessassem os bancos de dados públicos ODBC do computador no qual o servidor está instalado.

O cliente RMI poderia realizar três operações:
- conectar-se ao banco de dados de sua escolha
- enviar consultas SQL
- encerrar a conexão
O servidor executa as consultas SQL do cliente e envia os resultados a ele. Essa é sua função principal e, por isso, vamos chamá-lo de servidor SQL.
Aplicamos as diferentes etapas vistas anteriormente com o servidor de eco.
9.3.2. Etapa 1: a interface remota
A interface remota é aquela que lista os métodos do servidor RMI que estarão acessíveis aos clientes RMI. Utilizaremos a seguinte interface:
import java.rmi.*;
// a interface remota
public interface interSQL extends Remote{
public String connect(String pilote, String url, String id, String mdp)
throws java.rmi.RemoteException;
public String[] executeSQL(String requete, String separateur)
throws java.rmi.RemoteException;
public String close()
throws java.rmi.RemoteException;
}
A função dos diferentes métodos é a seguinte:
Connect: o cliente se conecta a um banco de dados remoto, cujo pilote ele fornece, o url, o JDBC, bem como sua identidade id e sua senha mdp para acessar esse banco de dados. O servidor retorna uma sequência de caracteres indicando o resultado da conexão:
executeSQL: o cliente solicita a execução de uma consulta SQL no banco de dados ao qual está conectado. Ele indica o caractere que deve separar os campos nos resultados que lhe são retornados. O servidor retorna uma matriz de cadeias de caracteres:
para uma consulta de atualização do banco de dados, sendo n o número de linhas atualizadas
se a consulta gerou um erro
se a consulta não gerou nenhum resultado
se a consulta gerou resultados. As linhas assim retornadas pelo servidor são as linhas de resultados da consulta.
close: o cliente encerra sua conexão com o banco de dados remoto. O servidor retorna uma string indicando o resultado desse encerramento:
9.3.3. Etapa 2: Código do servidor
Segue-se o código-fonte Java do servidor SQL. Para compreendê-lo, é necessário ter assimilado o gerenciamento de bancos de dados JDBC, bem como a construção de servidores RMI. Os comentários do programa devem facilitar sua compreensão.
// pacotes importados
import java.rmi.*;
import java.rmi.server.*;
import java.sql.*;
import java.util.*;
// classe srvSQL
public class srvSQL extends UnicastRemoteObject implements interSQL{
// dados globais da classe
private Connection DB;
// ------------- construtor
public srvSQL() throws RemoteException{
super();
}
// --------------- conexão
public String connect(String pilote, String url, String id,
String mdp) throws RemoteException{
// conexão à base de dados url por meio do driver
// identificação com ID e senha
String resultat=null; // resultado do método
try{
// carregamento do driver
Class.forName(pilote);
// solicitação de conexão
DB=DriverManager.getConnection(url,id,mdp);
// ok
resultat="200 Connexion réussie";
} catch (Exception e){
// erro
resultat="500 Echec de la connexion (" + e + ")";
}
// fim
return resultat;
}
// ------------- executeSQL
public String[] executeSQL(String requete, String separateur)
throws RemoteException{
// executa uma consulta SQL no banco de dados DB
// e armazena os resultados em uma matriz de strings
// dados necessários para a execução da consulta
Statement S=null;
ResultSet RS=null;
String[] lignes=null;
Vector resultats=new Vector();
String ligne=null;
try{
// criação do contêiner da consulta
S=DB.createStatement();
// execução da consulta
if (! S.execute(requete)){
// consulta de atualização
// é retornado o número de linhas atualizadas
lignes=new String[1];
lignes[0]="100 "+S.getUpdateCount();
return lignes;
}
// era uma consulta
// recuperando os resultados
RS=S.getResultSet();
// número de campos do Resultset
int nbChamps=RS.getMetaData().getColumnCount();
// eles são processados
while(RS.next()){
// criação da linha de resultados
ligne="101 ";
for (int i=1;i<nbChamps;i++)
ligne+=RS.getString(i)+separateur;
ligne+=RS.getString(nbChamps);
// adição ao vetor de resultados
resultats.addElement(ligne);
}// while
// fim da análise dos resultados
// liberamos os recursos
RS.close();
S.close();
// retornando os resultados
int nbLignes=resultats.size();
if (nbLignes==0){
lignes=new String[1];
lignes[0]="501 Pas de résultats";
} else {
lignes=new String[resultats.size()];
for(int i=0;i<lignes.length;i++)
lignes[i]=(String) resultats.elementAt(i);
}//if
return lignes;
} catch (Exception e){
// erro
lignes=new String[1];
lignes[0]="500 " + e;
return lignes;
}// try-catch
}// executeSQL
// --------------- fechar
public String close() throws RemoteException {
// fecha a conexão com o banco de dados
String resultat=null;
try{
DB.close();
resultat="200 Base fermée";
} catch (Exception e){
resultat="500 Erreur à la fermeture de la base ("+e+")";
}
// retorna o resultado
return resultat;
}
// ----------- main
public static void main (String[] args){
// gerenciador de segurança
System.setSecurityManager(new RMISecurityManager());
// inicialização do serviço
srvSQL serveurSQL=null;
try{
// criação
serveurSQL=new srvSQL();
// registro
Naming.rebind("srvSQL",serveurSQL);
// acompanhamento
System.out.println("Serveur SQL prêt");
} catch (Exception e){
// erro
System.err.println("Erreur lors du lancement du serveur SQL ("+ e +")");
}// try-catch
}// main
}// classe
9.3.4. Código do cliente RMI
O cliente do servidor RMI é chamado com os seguintes parâmetros:
urlserviceAnnuaire: URL RMI do serviço de diretório que registrou o servidor SQL
driver: driver que o servidor SQL deve utilizar para gerenciar o banco de dados
urlBase: URL JDBC do banco de dados a ser gerenciado
id: identidade do cliente ou null se não houver identidade
mdp: senha do cliente ou nulo se não houver senha
separador: caractere que o servidor SQL deve usar para separar os campos das linhas de resultados de uma consulta
Aqui está um exemplo de parâmetros possíveis:
onde:
nome RMI do servidor SQL
o driver padrão para bancos de dados com interface ODBC
para utilizar um banco de dados de itens declarado na lista de bancos de dados públicos ODBC do computador Windows
sem identificação
sem senha
os campos dos resultados serão separados por uma vírgula
Depois de iniciado com os parâmetros anteriores, o cliente segue as etapas a seguir:
- ele se conecta ao servidor RMI srvSQL, ou seja, um servidor RMI na mesma máquina que o cliente
- solicita a conexão com o banco de dados de artigos
- solicita que o usuário digite uma consulta SQL no teclado
- ele a envia para o servidor SQL
- exibe na tela os resultados retornados pelo servidor
- ele solicita novamente que o usuário digite uma consulta SQL no teclado. Ele será encerrado quando a consulta terminar.
Segue abaixo o código Java do cliente. Os comentários devem ser suficientes para sua compreensão.
import java.rmi.*;
import java.io.*;
public class cltSQL {
// dados globais da classe
private static String syntaxe =
"syntaxe : cltSQL urlServiceAnnuaire pilote urlBase id mdp separateur";
private static BufferedReader in=null;
private static interSQL serveurSQL=null;
public static void main(String arg[]){
// sintaxe: cltSQL urlServiceAnnuaire separador driver URL ID senha
// urlServiceAnnuaire: URL do diretório de serviços RMI a ser contatado
// driver: driver a ser utilizado para o banco de dados a ser explorado
// urlBase: URL JDBC do banco de dados a ser utilizado
// id: identificação do usuário
// mdp: sua senha
// separador: string que separa os campos nos resultados de uma consulta
// verificação do número de argumentos
if(arg.length!=6)
erreur(syntaxe,1);
// inicialização dos parâmetros de conexão com o banco de dados
String urlService=arg[0];
String pilote=arg[1];
String urlBase=arg[2];
String id, mdp, separateur;
if(arg[3].equals("null")) id=""; else id=arg[3];
if(arg[4].equals("null")) mdp=""; else mdp=arg[4];
if(arg[5].equals("null")) separateur=" "; else separateur=arg[5];
// instalação de um gerenciador de segurança
System.setSecurityManager(new RMISecurityManager());
// comunicação cliente-servidor
String requete=null;
String reponse=null;
String[] lignes=null;
String codeErreur=null;
try{
// abertura do fluxo do teclado
in=new BufferedReader(new InputStreamReader(System.in));
// acompanhamento
System.out.println("--> Connexion au serveur RMI en cours...");
// localização do serviço
serveurSQL=(interSQL) Naming.lookup(urlService);
// acompanhamento
System.out.println("--> Connexion à la base de données en cours");
// solicitação de conexão inicial ao banco de dados
reponse=serveurSQL.connect(pilote,urlBase,id,mdp);
// acompanhamento
System.out.println("<-- "+reponse);
// análise da resposta
codeErreur=reponse.substring(0,3);
if(codeErreur.equals("500"))
erreur("Abandon sur erreur de connexion à la base",3);
// ciclo de leitura das solicitações a serem enviadas ao servidor SQL
System.out.print("--> Requête : ");
requete=in.readLine().toLowerCase().trim();
while(! requete.equals("fin")){
// envio da solicitação ao servidor e recebimento da resposta
lignes=serveurSQL.executeSQL(requete,separateur);
// acompanhamento
afficheLignes(lignes);
// próxima solicitação
System.out.print("--> Requête : ");
requete=in.readLine().toLowerCase().trim();
}// enquanto
// acompanhamento
System.out.println("--> Fermeture de la connexion à la base de données distante");
// encerrando a conexão
reponse=serveurSQL.close();
// acompanhamento
System.out.println("<-- " + reponse);
// fim
System.exit(0);
// gestão de erros
} catch (Exception e){
erreur("Abandon sur erreur : " + e,2);
}// tentar
}// main
// ----------- AfficheLignes
private static void afficheLignes(String[] lignes){
for (int i=0;i<lignes.length;i++)
System.out.println("<-- " + lignes[i]);
}// afficheLignes
// ------------ erro
private static void erreur(String msg, int exitCode){
// exibição da mensagem de erro
System.err.println(msg);
// possível liberação de recursos
try{
in.close();
serveurSQL.close();
} catch(Exception e){}
// saindo
System.exit(exitCode);
}// erro
}// classe
9.3.5. Etapa 3: criação dos arquivos .class
- o servidor é compilado
E:\data\java\RMI\sql\serveur>j:\jdk12\bin\javac interSQL.java
E:\data\java\RMI\sql\serveur>j:\jdk12\bin\javac srvSQL.java
E:\data\java\RMI\sql\serveur>dir *.class
INTERS~1 CLA 451 12/03/99 17:54 interSQL.class
SRVSQL~1 CLA 3 238 12/03/99 17:54 srvSQL.class
- os arquivos Stub e Skel são criados
E:\data\java\RMI\sql\serveur>j:\jdk12\bin\rmic srvSQL
E:\data\java\RMI\sql\serveur>dir *.class
INTERS~1 CLA 451 12/03/99 17:54 interSQL.class
SRVSQL~1 CLA 3 238 12/03/99 17:54 srvSQL.class
SRVSQL~2 CLA 4 491 12/03/99 17:56 srvSQL_Stub.class
SRVSQL~3 CLA 2 414 12/03/99 17:56 srvSQL_Skel.class
- Os arquivos interSQL.class, srvSQL_Stub.class e srvSQL_Skel.class são transferidos para o diretório do cliente
E:\data\java\RMI\sql\client>dir
CLTSQL~1 JAV 3 486 11/03/99 11:39 cltSQL.java
INTERS~1 CLA 451 11/03/99 10:55 interSQL.class
SRVSQL~1 CLA 4 491 11/03/99 13:19 srvSQL_Stub.class
SRVSQL~2 CLA 2 414 11/03/99 13:19 srvSQL_Skel.class
- compilamos o cliente
E:\data\java\RMI\sql\client>j:\jdk12\bin\javac cltSQL.java
E:\data\java\RMI\sql\client>dir *.class
INTERS~1 CLA 451 11/03/99 10:55 interSQL.class
CLTSQL~1 CLA 2 839 12/03/99 18:00 cltSQL.class
SRVSQL~1 CLA 4 491 11/03/99 13:19 srvSQL_Stub.class
SRVSQL~2 CLA 2 414 11/03/99 13:19 srvSQL_Skel.class
9.3.6. Etapa 4: Testes com servidor e cliente na mesma máquina Windows
- O serviço de diretório é iniciado em um diretório diferente daquele do servidor e do cliente
- Coloca-se o seguinte arquivo mypolicy nos diretórios do cliente e do servidor
- inicia-se o servidor
E:\data\java\RMI\sql\serveur>start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/sql/serveur/ srvSQL
- iniciamos o cliente
E:\data\java\RMI\sql\client>j:\jdk12\bin\java -Djava.security.policy=mypolicy cltSQL srvSQL
sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Connexion au serveur RMI en cours...
--> Connexion à la base de données en cours
<-- 200 Connexion réussie
--> Consulta: select nome, stock_actu from artigos order by stock_actu desc
<-- 101 vélo,31
<-- 101 essai3,13
<-- 101 skis nautiques,13
<-- 101 canoé,13
<-- 101 panthère,11
<-- 101 léopard,11
<-- 101 cachalot,10
<-- 101 fusil,10
<-- 101 arc,10
--> Consulta: update articles set stock_actu=stock_actu-1 where stock_actu<=11
<-- 100 5
--> Consulta: select nome, stock_actu from artigos order by stock_actu asc
<-- 101 cachalot,9
<-- 101 fusil,9
<-- 101 arc,9
<-- 101 panthère,10
<-- 101 léopard,10
<-- 101 essai3,13
<-- 101 skis nautiques,13
<-- 101 canoé,13
<-- 101 vélo,31
--> Requête : fin
--> Fermeture de la connexion à la base de données distante
<-- 200 Base fermée
9.3.7. Etapa 5: Testes com servidor em máquina Windows e cliente em máquina Linux
- Se necessário, desligue o servidor e o serviço de diretório
- transferimos os arquivos .class do cliente para um computador Linux
shiva[serge]:/home/admin/serge/java/rmi/sql/client#
$ dir *.class
-rw-r--r-- 1 serge admin 2839 Mar 11 14:37 cltSQL.class
-rw-r--r-- 1 serge admin 451 Mar 11 14:37 interSQL.class
- Os arquivos do servidor são colocados em um diretório acessível ao servidor HTTP da máquina Windows
D:\Inetpub\wwwroot\rmi\sql>dir
INTERS~1 CLA 451 11/03/99 10:55 interSQL.class
SRVSQL~1 CLA 3 238 11/03/99 13:19 srvSQL.class
SRVSQL~2 CLA 4 491 11/03/99 13:19 srvSQL_Stub.class
SRVSQL~3 CLA 2 414 11/03/99 13:19 srvSQL_Skel.class
MYPOLICY 81 08/06/98 15:01 mypolicy
- Estamos relançando o serviço de lista telefônica
- reiniciamos o servidor com parâmetros diferentes dos utilizados no teste anterior
D:\Inetpub\wwwroot\rmi\sql>start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/sql/ srvSQL
- Iniciamos o cliente na máquina Linux
/usr/local/bin/jdk/bin/java cltSQL rmi://tahe.istia.univ-angers.fr/srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Consulta: select nome,stock_actu,stock_mini from artigos order by nome
<-- 101 arc,9,8
<-- 101 cachalot,9,6
<-- 101 canoé,13,7
<-- 101 essai3,13,9
<-- 101 fusil,9,8
<-- 101 léopard,10,7
<-- 101 panthère,10,7
<-- 101 skis nautiques,13,8
<-- 101 vélo,31,8
--> Consulta: update articles set stock_actu=stock_mini where stock_mini<=7
<-- 100 4
--> Consulta: selecionar nome, stock_actu, stock_mini dos artigos, ordenados por nome
<-- 101 arc,9,8
<-- 101 cachalot,6,6
<-- 101 canoé,7,7
<-- 101 essai3,13,9
<-- 101 fusil,9,8
<-- 101 léopard,7,7
<-- 101 panthère,7,7
<-- 101 skis nautiques,13,8
<-- 101 vélo,31,8
--> Requête : fin
--> Fermeture de la connexion à la base de données distante
<-- 200 Base fermée
9.3.8. Conclusão
Temos aqui um aplicativo interessante, pois permite o acesso a um banco de dados a partir de qualquer estação da rede. Teríamos muito bem podido escrevê-lo da maneira tradicional, usando sockets — o que, aliás, é solicitado em um exercício do capítulo sobre bancos de dados. Se escrevêssemos esse aplicativo da maneira tradicional:
- teríamos um cliente e um servidor que poderiam ser escritos em linguagens diferentes
- o cliente e o servidor se comunicariam por meio da troca de linhas de texto e teriam um diálogo que poderia ser mais ou menos assim:
onde os dois primeiros parâmetros especificam onde encontrar o servidor, e os quatro seguintes indicam os parâmetros de conexão com o banco de dados a ser utilizado
O servidor poderia responder com algo do tipo:
para solicitar ao servidor que execute uma consulta SQL no banco de dados conectado ao cliente. O separador é o caractere que separa os campos nas linhas da resposta.
O servidor poderia responder algo como
para uma consulta de atualização do banco de dados, sendo n o número de linhas atualizadas
se a solicitação tiver gerado um erro
se a consulta não gerou nenhum resultado
se a consulta tiver gerado resultados. As linhas assim retornadas pelo servidor são as linhas de resultados da consulta.
para encerrar a conexão com o banco de dados remoto. O servidor pode retornar uma string indicando o resultado desse encerramento:
Vemos aqui que, se for possível construir um aplicativo tradicional com um protocolo do tipo anterior, podemos deduzir uma possível estrutura do servidor RMI. Onde, no protocolo, temos uma frase do cliente para o servidor do tipo:
poderemos ter, no servidor RMI, um método
e esse método, acessível ao cliente, deverá, portanto, fazer parte da interface publicada do servidor.
Para encerrar, observemos que nosso servidor atualmente gerencia apenas um cliente: ele não pode gerenciar vários, da forma como está escrito no momento. De fato, se um cliente se conectar a um banco de dados B1, um objeto Connection DB=DB1 é criado pelo servidor. Se um segundo cliente solicitar uma conexão com um banco de dados B2, o servidor registra isso como Connection DB=DB2, interrompendo assim a conexão do primeiro cliente com a base B1.
9.4. Exercícios
9.4.1. Exercício 1
Amplie o servidor SQL anterior para que ele gerencie vários clientes.
9.4.2. Exercício 2
Escreva o applet Java de comércio eletrônico apresentado nos exercícios do capítulo JDBC para que ele funcione com o servidor RMI do exercício anterior.