9. JAVA RMI
9.1. Introdução
Vimos como criar aplicações de rede utilizando as ferramentas de comunicação denominadas sockets. Numa aplicação cliente/servidor construída com base nessas ferramentas, a ligação entre o cliente e o servidor é o protocolo de comunicação que adotaram para comunicar. Ambas as aplicações podem ser escritas em linguagens diferentes: Java, por exemplo, para o cliente, Perl para o servidor ou qualquer outra combinação. Temos, de facto, duas aplicações distintas ligadas por um protocolo de comunicação conhecido por ambas. Além disso, o acesso à rede através de sockets não é transparente para uma aplicação Java: esta tem de utilizar a classe Socket, criada especificamente para gerir estas ferramentas de comunicação que são as sockets.
JAVA RMI (Remote Method Invocation) permite criar aplicações 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: as aplicações não têm de se preocupar com a forma como a informação é transportada de um ponto para outro.
Este último ponto é um fator de portabilidade: se a camada de rede de uma aplicação RMI vier a mudar, a própria aplicação não terá de ser reescrita. Serão as classes RMI da linguagem Java que terão de ser adaptadas à nova camada de rede.
O princípio de uma comunicação RMI é o seguinte:
- Uma aplicação Java clássica é escrita numa máquina A. Esta irá desempenhar o papel de servidor. Para tal, alguns dos seus objetos serão «publicados» na máquina A, na qual a aplicação é executada, tornando-se assim serviços.
- Uma aplicação Java clássica é escrita numa máquina B. Esta irá desempenhar o papel de cliente. Terá acesso aos objetos/serviços publicados na máquina A, ou seja, através de uma referência remota, poderá manipulá-los como se fossem locais. Para tal, precisará de conhecer a estrutura do objeto remoto ao qual pretende aceder (métodos e propriedades).
9.2. Vamos aprender com um exemplo
A teoria subjacente à interface RMI não é simples. Para compreender melhor, vamos acompanhar passo a passo a criação de uma aplicação cliente/servidor utilizando o pacote RMI do Java. Tomamos como exemplo uma aplicação que se encontra em muitos livros sobre RMI: o cliente invoca um único método de um objeto remoto, que lhe devolve então uma cadeia de caracteres. Apresentamos aqui uma ligeira variação: o servidor repete o que o cliente lhe envia. Já apresentámos neste livro uma aplicação semelhante, que se baseava em sockets.
9.2.1. A aplicação do servidor
9.2.1.1. Passo 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 numa 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. Este método pode gerar uma exceção da classe RemoteException, classe que agrupa todos os erros relacionados com a rede.
9.2.1.2. Passo 2: definição do objeto servidor
Na etapa seguinte, define-se a classe que implementa a interface remota anterior. Esta classe deve derivar da classe UnicastRemoteObject, que dispõe dos métodos que permitem a invocação de métodos à distância.
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 implementa 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 devolve o valor
- um construtor que não faz nada além de chamar o construtor da classe pai. Está presente para declarar que pode gerar uma exceção do tipo RemoteException.
Vamos criar uma instância desta classe com um método main. Para que um objeto/serviço seja acessível a partir do exterior, tem de ser criado e registado no diretório de objetos acessíveis a partir do exterior. Um cliente que pretenda aceder a um objeto remoto procede, de facto, da seguinte forma:
- dirige-se ao serviço de diretório da máquina na qual se encontra o objeto que pretende. Este serviço de diretório opera numa porta que o cliente deve conhecer (1099 por predefinição). O cliente solicita ao diretório uma referência a um objeto/serviço, indicando o seu nome. Se esse nome corresponder a um objeto/serviço do diretório, este devolve ao cliente uma referência através da qual o cliente poderá comunicar com o objeto/serviço remoto.
- A partir desse momento, o cliente pode utilizar esse objeto remoto como se fosse local
Voltando ao nosso servidor, temos de criar um objeto do tipo srvEcho e registá-lo no diretório de objetos acessíveis a partir do exterior. Este registo é efetuado com o método da classe rebind da classe Naming:
com
nome: o nome que será associado ao objeto remoto
obj: o objeto remoto
A nossa classe srvEcho passa, assim, a ter o seguinte aspeto:
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 implementa 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, fica-se com a impressão de que ele vai parar imediatamente após criar e registar o serviço de eco. Não é esse o caso. Como a classe srvEcho deriva da classe UnicastRemoteObject, o objeto criado executa-se indefinidamente: fica à escuta de pedidos dos clientes numa 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 a sua execução: irá, de facto, apresentar «Servidor de eco pronto».
9.2.1.3. Etapa 3: compilação da aplicação de servidor
Nesta fase, já podemos compilar o nosso servidor. Compilamos o ficheiro interEcho.java da interface interEcho, bem como o ficheiro srvEcho.java da classe srvEcho. Obtenemos os ficheiros .class correspondentes: interEcho.class e srvEcho.class.
9.2.1.4. Passo 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 que este envia
- volta ao passo 1 e termina quando a linha digitada for «fim».
O resultado é o seguinte cliente:
import java.rmi.*;
import java.io.*;
public class cltEcho {
public static void main(String arg[]){
// sintaxe: cltEcho URLService
// verificação dos 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 enviar para o servidor de eco
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
while(! msg.equals("fin")){
// envio da mensagem para o servidor e receção da resposta
reponse=serveur.echo(msg);
// acompanhamento
System.out.println("Réponse serveur : " + reponse);
// mensagem seguinte
System.out.print("Message : ");
msg=in.readLine().toLowerCase().trim();
}// while
// terminado
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 especial neste cliente, a não ser a instrução que solicita uma referência do servidor:
Recorde-se que o nosso serviço de eco foi registado no diretório de serviços da máquina onde se encontra com a instrução:
Naming.rebind("srvEcho",serveurEcho);
O cliente também utiliza, portanto, um método da classe Naming para obter uma referência do servidor que pretende utilizar. O método lookup utilizado aceita como parâmetro o URL do serviço solicitado. Este tem o formato de um URL clássico:
com
rmi: opcional — protocolo RMI
machine: nome ou endereço IP da máquina na qual o servidor de eco opera — opcional, por predefinição localhost.
port: porta de escuta do serviço de diretório desta máquina — opcional, por predefinição 1099
nom_service: nome sob o qual o serviço solicitado foi registado (srvEcho no nosso exemplo)
O que é recuperado é uma instância da interface remota interEcho. Supondo que o cliente e o servidor não se encontrem na mesma máquina, ao compilar o cliente cltEcho.java, é necessário dispor, no mesmo diretório, do ficheiro 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. Passo 5: geração dos ficheiros .class necessários para a aplicação cliente-servidor
Para compreender bem o que pertence ao lado do servidor e o que pertence ao lado do cliente, colocaremos o servidor num diretório echo\serveur e o cliente num diretório echo\client.
O diretório do servidor contém os seguintes ficheiros-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 destes dois ficheiros fonte, obtêm-se os seguintes ficheiros .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, encontra-se o seguinte ficheiro fonte:
bem como o ficheiro interEcho.class, que foi gerado durante a compilação do servidor:
Após a compilação do ficheiro fonte, obtêm-se os seguintes ficheiros .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, obtemos 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
Se tentarmos executar o servidor srvEcho, obtemos 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 facto, nunca ouvimos falar desta classe. No cliente, a localização do servidor foi efetuada com a seguinte instrução:
Aqui, urlservice é a cadeia rmi://localhost/srvEcho com
Rmi: protocolo RMI
Localhost: máquina onde o servidor está a funcionar — neste caso, a mesma máquina em que o cliente está a funcionar. A sintaxe é normalmente máquina:porta. Na ausência da porta, será utilizada a porta 1099 por predefinição. A escutar nesta porta encontra-se o serviço de diretório do servidor.
srvEcho: é o nome do serviço específico solicitado
Durante a compilação, não foi sinalizado qualquer erro. Bastava apenas que o ficheiro interEcho.class da interface remota estivesse disponível.
Durante a execução, a máquina virtual exige a presença de um ficheiro srvEcho_stub.class se o serviço solicitado for o serviço srvEcho; de um modo geral, um ficheiro X_stub.class para um serviço X. Este ficheiro só é necessário durante a execução, não durante a compilação do cliente. O mesmo se aplica ao servidor. O que é, então, este ficheiro?
No servidor, encontra-se a classe srvEcho.class, que é o nosso objeto/serviço remoto. O cliente, mesmo que não necessite desta classe, precisa, no entanto, de uma espécie de imagem da mesma para poder comunicar com ela. Na verdade, o cliente não envia as suas solicitações diretamente para o objeto remoto: envia-as para a sua imagem local srvEcho_stub.class, localizada na mesma máquina que ele. Esta imagem local srvEcho_stub.class comunica com uma imagem da mesma natureza (srvEcho_stub.class), localizada, desta vez, no servidor. Esta imagem é criada a partir do ficheiro .class do servidor com uma ferramenta Java chamada rmic. No Windows, o comando:
irá produzir, a partir do ficheiro srvEcho.class, dois outros ficheiros .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
Aqui está, de facto, o ficheiro srvEcho_stub.class de que o cliente e o servidor necessitam para a execução. Existe também um ficheiro srvEcho_Skel.class cuja função, por enquanto, desconhecemos. Fazemos uma cópia do ficheiro srvEcho_stub.class no diretório do cliente e do servidor e eliminamos o ficheiro srvEcho_Skel.class. Ficamos, assim, com os seguintes ficheiros:
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
do 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. Passo 6: Execução da aplicação cliente-servidor de eco
Estamos prontos para executar a nossa aplicação cliente-servidor. Numa primeira fase, o cliente e o servidor funcionarão na mesma máquina. Em primeiro lugar, é necessário iniciar a nossa aplicação servidor. Recorde-se que esta:
- cria o serviço
- o regista no diretório de serviços da máquina em que o servidor de eco está a funcionar
Este último ponto requer a existência de um serviço de diretório. Este é iniciado através do comando:
rmiregistry é o serviço de diretório. Aqui, é iniciado em segundo plano numa janela do DOS do Windows através do comando start. Com o diretório ativo, podemos criar o serviço de eco e registá-lo no diretório de serviços. Mais uma vez, este é iniciado em segundo plano através do comando start:
O servidor de eco é executado numa nova janela DOS e apresenta, tal como solicitado:
Resta-nos apenas iniciar e testar o 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, colocamo-los em máquinas diferentes:
- o servidor numa máquina Windows
- o cliente numa máquina Linux
O servidor é iniciado, tal como anteriormente, na máquina Windows. Na máquina Linux, transferimos os ficheiros .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 parece estar a solicitar o ficheiro srvEcho_skel.class, que tinha sido gerado pelo utilitário rmic, mas que até agora não tinha sido utilizado. Recriamo-lo e transferimo-lo também 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 por concluir que talvez seja o próprio servidor que precise do famoso ficheiro srvEcho_Skel.class. Reiniciamos então, na máquina Windows, o servidor com os dois ficheiros srvEcho_Stub.class e srvEcho_Skel.class presentes :
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
depois, na máquina Linux, testamos novamente o cliente e, desta vez, funciona:
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, do lado do servidor, os dois ficheiros srvEcho_Stub.class e srvEcho_Skel.class devem estar presentes. Do lado do cliente, até agora só foi necessário o ficheiro srvEcho_Stub.class. Este revelou-se indispensável quando o cliente e o servidor se encontravam na mesma máquina Windows. No Linux, vamos removê-lo para ver...
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 aqui 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-nos de ter visto algo sobre este tema na documentação. Voltamos a consultar a documentação... e descobrimos que o servidor deve criar e instalar um gestor de segurança (security manager) que garanta aos clientes que solicitam o carregamento de classes que estas são seguras. Na ausência desse gestor de segurança, o carregamento das classes é impossível. Parece fazer sentido: o nosso cliente Linux solicitou ao servidor a classe srvEcho_stub.class de que necessita e este recusou, indicando que não tinha sido instalado nenhum gestor de segurança. Por isso, alteramos o código da função main do servidor da seguinte forma:
// criação do serviço
public static void main (String arg[]){
// instalação de um gestor de segurança
System.setSecurityManager(new RMISecurityManager());
// inicialização e registo 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 ficheiros 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 surge um erro que antes não tínhamos!
Erreur java.security.AccessControlException: access denied (java.net.SocketPermission 127.0.0.1:1099 connect,resolve) lors du lancement du serveur d’écho
O gestor de segurança parece ter sido demasiado eficaz. Voltamos a consultar a documentação... Percebemos que, quando um gestor de segurança está ativo, é necessário especificar os direitos do programa no momento do seu arranque. Isto faz-se com a seguinte opção:
start j:\jdk12\bin\java -Djava.security.policy=mypolicy srvEcho
onde
java.security.policy é uma palavra-chave
mypolicy é um ficheiro de texto que define os direitos do programa. Neste caso, é o seguinte:
O programa tem aqui todos os direitos.
Vamos recomeçar. Dirijamo-nos ao diretório do servidor e executamos, sucessivamente:
- iniciar o 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) inicia corretamente. Agora pode fazer a seguinte experiência:
- encerrar o servidor de eco e, em seguida, o serviço de diretório
- reinicie o serviço de diretório estando num diretório diferente do do servidor
- volte ao diretório do servidor inicie o servidor de eco — obtém 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 que 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 está numa única linha. A palavra-chave java.rmi.server.codebase serve para indicar o URL do diretório que contém as classes necessárias ao servidor. Neste caso, este URL especifica o protocolo «file», que é o protocolo de acesso aos ficheiros locais, e o diretório que contém os ficheiros .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 é então iniciado corretamente. Podemos, portanto, passar para o cliente. Testamo-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
Recebe-se o mesmo erro a indicar a ausência de um gestor de segurança. Pensa-se que talvez tenha havido um erro e que seja o cliente que deve criar o seu próprio gestor de segurança. Deixa-se o servidor com o seu gestor de segurança, mas cria-se também um 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 onde o servidor de eco está a funcionar
// 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 gestor 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 ficheiros .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 a avançar: o erro já não é o mesmo. Verifica-se que o cliente conseguiu solicitar ao servidor a classe srvEcho_Stub.class, mas que este não a encontrou. Portanto, o cliente deve dispor de um gestor de segurança para poder solicitar classes ao servidor.
Se analisarmos o erro anterior, vemos que o ficheiro srvEcho_Stub.class foi procurado no diretório e:/data/java/rmi/echo/serveur/ e não foi encontrado. No entanto, é precisamente aí que se encontra. 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 isto está relacionado com a forma como iniciámos o 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 referir-se ao valor da palavra-chave java.rmi.server.codebase. Ao consultar novamente a documentação, verifica-se que o valor desta palavra-chave, nos exemplos apresentados, é sempre: http://.., c.a.d. O protocolo utilizado é o HTTP. Não fica claro como é que o cliente solicita e obtém as suas classes junto do servidor. Talvez as solicite utilizando 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 ficheiros .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á a funcionar numa máquina Windows com um servidor HTTP PWS da Microsoft. A raiz deste servidor é d:\Inetpub\wwwroot. Assim, procedemos da seguinte forma:
- criamos o diretório d:\Inetpub\wwwroot\rmi\echo
- colocamos nele os ficheiros .class do servidor, bem como o ficheiro mypolicy
- iniciamos o servidor Web, caso ainda não o tenhamos feito
- reinicia-se o serviço de diretório (rmiregistry)
- reinicia-se 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 famoso ficheiro srvEcho_Stub.class.
Tudo isto deu-nos algumas ideias, e perguntamo-nos se o cliente que se encontra na máquina Windows do servidor também funcionaria sem o ficheiro srvEcho_Stub.class. Passamos para o diretório do cliente, eliminamos o ficheiro srvEcho_Stub.class, caso lá se encontre, 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 gestor de segurança
- 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 dispõe de um gestor de segurança
- no Linux, foi iniciado por java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
- No Windows, 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
Passamos agora o servidor para uma máquina Linux e testamos clientes Linux e Windows. O procedimento a seguir é o seguinte:
- transferimos os ficheiros .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
- uma vez que 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. Neste caso, a classe URL deste 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 Windows.
- Vamos para o diretório do cliente no computador 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
De notar que, para o cliente Linux que funciona 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 numa máquina Windows
9.3.1. O problema
No capítulo JDBC, vimos como gerir bases de dados relacionais. Nos exemplos apresentados, as aplicações e a base de dados utilizada encontravam-se na mesma máquina Windows. Propomos aqui criar um servidor RMI numa máquina Windows, que permita aos clientes remotos aceder às bases de dados públicas ODBC do computador onde o servidor se encontra.

O cliente RMI poderia realizar três operações:
- ligar-se à base de dados da sua escolha
- enviar pedidos SQL
- encerrar a ligação
O servidor executa as consultas SQL do cliente e envia-lhe os resultados. Esta é a sua função essencial e é por isso que o designaremos como servidor SQL.
Aplicamos os diferentes passos vistos anteriormente com o servidor de eco.
9.3.2. Etapa 1: a interface remota
A interface remota é a interface 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 liga-se a uma base de dados remota, indicando o pilote, o url, o JDBC, bem como a sua identidade id e a sua palavra-passe mdp para aceder a essa base de dados. O servidor devolve-lhe uma cadeia de caracteres que indica o resultado da ligação:
executeSQL: o cliente solicita a execução de uma consulta SQL na base de dados à qual está ligado. Indica o carácter que deve separar os campos nos resultados que lhe são devolvidos. O servidor devolve um conjunto de cadeias de caracteres:
para uma consulta de atualização da base de dados, sendo n o número de linhas atualizadas
se a consulta tiver gerado um erro
se a consulta não tiver gerado nenhum resultado
se a consulta tiver gerado resultados. As linhas assim devolvidas pelo servidor são as linhas de resultados da consulta.
close: o cliente encerra a sua ligação à base de dados remota. O servidor devolve uma cadeia de caracteres que indica o resultado deste encerramento:
9.3.3. Etapa 2: Código do servidor
Segue-se o código-fonte Java do servidor SQL. Para o compreender, é necessário ter assimilado a gestão de bases de dados JDBC, bem como a construção de servidores RMI. Os comentários do programa deverão facilitar a 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();
}
// --------------- ligação
public String connect(String pilote, String url, String id,
String mdp) throws RemoteException{
// ligação à base de dados url através do controlador
// identificação com o ID e a palavra-passe
String resultat=null; // resultado do método
try{
// carregamento do controlador
Class.forName(pilote);
// pedido de ligaçã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 na base de dados DB
// e coloca os resultados numa tabela de cadeias
// 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 contentor da consulta
S=DB.createStatement();
// execução da consulta
if (! S.execute(requete)){
// consulta de atualização
// é devolvido o número de linhas atualizadas
lignes=new String[1];
lignes[0]="100 "+S.getUpdateCount();
return lignes;
}
// era uma consulta
// recuperam-se os resultados
RS=S.getResultSet();
// número de campos do Resultset
int nbChamps=RS.getMetaData().getColumnCount();
// processa-se os resultados
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();
// retornam-se 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 {
// encerra a ligação à base 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+")";
}
// retorno do resultado
return resultat;
}
// ----------- main
public static void main (String[] args){
// gestor de segurança
System.setSecurityManager(new RMISecurityManager());
// inicialização do serviço
srvSQL serveurSQL=null;
try{
// criação
serveurSQL=new srvSQL();
// registo
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 registou o servidor SQL
driver: driver que o servidor SQL deve utilizar para gerir a base de dados
urlBase: URL JDBC da base de dados a gerir
id: identidade do cliente ou null se não houver identidade
mdp: palavra-passe do cliente ou nulo se não houver palavra-passe
separador: caractere que o servidor SQL deve utilizar para separar os campos das linhas de resultados de uma consulta
Eis um exemplo de parâmetros possíveis:
onde:
nome RMI do servidor SQL
o controlador habitual das bases de dados com interface ODBC
para utilizar uma base de dados de artigos declarada na lista de bases de dados públicas ODBC do computador Windows
sem identificação
sem palavra-passe
os campos dos resultados serão separados por uma vírgula
Depois de iniciado com os parâmetros anteriores, o cliente segue os seguintes passos:
- liga-se ao servidor RMI srvSQL, ou seja, um servidor RMI na mesma máquina que o cliente
- solicita a ligação à base de dados de artigos
- pede ao utilizador que introduza uma consulta SQL através do teclado
- envia-a para o servidor SQL
- exibe no ecrã os resultados devolvidos pelo servidor
- solicita novamente ao utilizador que introduza uma consulta SQL através do teclado. O programa terminará quando a consulta terminar.
Segue-se o código Java do cliente. Os comentários devem ser suficientes para a 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, palavra-passe
// urlServiceAnnuaire: URL do diretório de serviços RMI a contactar
// driver: driver a utilizar para a base de dados a explorar
// urlBase: URL JDBC da base de dados a utilizar
// id: identificação do utilizador
// mdp: a sua palavra-passe
// separador: cadeia de caracteres 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 ligação à base 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 gestor 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");
// pedido de ligação inicial à base 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 enviar ao servidor SQL
System.out.print("--> Requête : ");
requete=in.readLine().toLowerCase().trim();
while(! requete.equals("fin")){
// envio da solicitação ao servidor e receção 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");
// encerrar a ligação
reponse=serveurSQL.close();
// seguimento
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);
// eventual libertação de recursos
try{
in.close();
serveurSQL.close();
} catch(Exception e){}
// sair
System.exit(exitCode);
}// erro
}// classe
9.3.5. Etapa 3: criação dos ficheiros .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 ficheiros «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 ficheiros 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. Passo 4: Testes com servidor e cliente na mesma máquina Windows
- O serviço de diretório é iniciado num diretório diferente do do servidor e do cliente
- Coloca-se o seguinte ficheiro «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
- inicia-se 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 artigos 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. Passo 5: Testes com o servidor numa máquina Windows e o cliente numa máquina Linux
- Se necessário, desligue o servidor e o serviço de diretório
- transferimos os ficheiros .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 ficheiros do servidor são colocados num 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
- Reativamos 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
- inicia-se 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 uma aplicação interessante, na medida em que permite o acesso a uma base de dados a partir de qualquer computador da rede. Teríamos muito bem podido escrevê-la de forma tradicional com sockets, o que, aliás, é solicitado num exercício do capítulo sobre bases de dados. Se escrevêssemos esta aplicação de forma tradicional:
- teríamos um cliente e um servidor que poderiam ser escritos em linguagens diferentes
- o cliente e o servidor comunicariam através da troca de linhas de texto e teriam um diálogo que poderia ser do tipo:
onde os dois primeiros parâmetros especificam onde encontrar o servidor, e os quatro seguintes indicam os parâmetros de ligação à base de dados a utilizar
O servidor poderia responder com algo do género:
para solicitar ao servidor que execute uma consulta SQL na base de dados ligada ao cliente. «separador» é o carácter que separa os campos nas linhas da resposta.
O servidor poderá responder com algo do género
para uma consulta de atualização da base de dados, sendo n o número de linhas atualizadas
se a solicitação tiver gerado um erro
se a consulta não tiver gerado nenhum resultado
se a consulta tiver gerado resultados. As linhas assim devolvidas pelo servidor são as linhas de resultados da consulta.
para encerrar a ligação com a base de dados remota. O servidor poderá devolver uma cadeia de caracteres indicando o resultado deste encerramento:
Vemos aqui que, se for possível construir uma aplicação tradicional com um protocolo do tipo anterior, podemos deduzir uma possível estrutura do servidor RMI. Nos casos em que, 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 concluir, note-se que o nosso servidor, atualmente, só gere um cliente: não consegue gerir vários, tal como está escrito neste momento. Com efeito, se um cliente se ligar a uma base de dados B1, o servidor cria um objeto Connection DB=DB1. Se um segundo cliente solicitar uma ligação a uma base de dados B2, o servidor regista-o criando Connection DB=DB2, interrompendo assim a ligação do primeiro cliente à base de dados B1.
9.4. Exercícios
9.4.1. Exercício 1
Amplie o servidor SQL anterior para que possa gerir 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 funcione com o servidor RMI do exercício anterior.