Skip to content

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:

  1. As aplicações cliente/servidor são aplicações Java em ambas as extremidades da comunicação
  2. O cliente pode utilizar objetos localizados no servidor como se fossem locais
  3. 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:

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

  1. o método que exibe o texto
  2. 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:

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

Naming.rebind(String nom, Remote obj)

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

  1. lê uma linha digitada no teclado
  2. a envia para o servidor de eco
  3. exibe a resposta enviada por ele
  4. 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:

            serveur=(interEcho) Naming.lookup(urlService);

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:

    rmi://máquina:porta/nom_service

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:

E:\data\java\RMI\echo\client>dir *.java

CLTECH~1 JAV         1 427  09/03/99  16:08 cltEcho.java

bem como o arquivo interEcho.class, que foi gerado durante a compilação do servidor:

E:\data\java\RMI\echo\client>dir *.class

INTERE~1 CLA           256  09/03/99  15:59 interEcho.class

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:

            serveur=(interEcho) Naming.lookup(urlService);                

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:

E:\data\java\RMI\echo\serveur>j:\jdk12\bin\rmic srvEcho

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:

start j:\jdk12\bin\rmiregistry

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:

E:\data\java\RMI\echo\serveur>start j:\jdk12\bin\java srvEcho

O servidor de eco é executado em uma nova janela DOS e exibe, conforme solicitado:

Serveur d’écho prêt

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:

grant {
    // Permitir tudo por enquanto
    permission java.security.AllPermission;
};

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.

Image

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:

    200 - Connexion réussie
    500 - Echec de la connexion

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:

    100 n

para uma consulta de atualização do banco de dados, sendo n o número de linhas atualizadas

    500 msg d’erreur

se a consulta gerou um erro

    501 Pas de résultats

se a consulta não gerou nenhum resultado

    101 ligne1
    101 ligne2
    101 ...

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:

    200 Base fermée
    500 Erreur lors de la fermeture de la base (msg d’erreur)

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 pilote urlBase id mdp separateur

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:

    srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,

onde:

urlserviceAnnuaire    srvSQL

nome RMI do servidor SQL

pilote     sun.jdbc.odbc.JdbcOdbcDriver

o driver padrão para bancos de dados com interface ODBC

urlBase    jdbc:odbc:articles

para utilizar um banco de dados de itens declarado na lista de bancos de dados públicos ODBC do computador Windows

id    null

sem identificação

mdp    null

sem senha

separateur    , 

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
connect(‘’sun.jdbc.odbc.JdbcOdbcDriver’’, ‘‘jdbc:odbc:articles’’, ’’’’, ’’’’)
  • solicita que o usuário digite uma consulta SQL no teclado
  • ele a envia para o servidor SQL
executeSQL(requete, ’’,’’);
  • 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
F:\>start j:\jdk12\bin\rmiregistry
  • Coloca-se o seguinte arquivo mypolicy nos diretórios do cliente e do servidor
grant {
    // Permitir tudo por enquanto
    permission java.security.AllPermission;
};
  • 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
F:\>start j:\jdk12\bin\rmiregistry
  • 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:
client : connect machine port pilote urlBase id mdp

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:

200 - Connexion réussie
500 - Echec de la connexion
client : executeSQL requete, separateur

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

    100 n

para uma consulta de atualização do banco de dados, sendo n o número de linhas atualizadas

    500 msg d’erreur

se a solicitação tiver gerado um erro

    501 Pas de résultats

se a consulta não gerou nenhum resultado

    101 ligne1
    101 ligne2
    101 ...

se a consulta tiver gerado resultados. As linhas assim retornadas pelo servidor são as linhas de resultados da consulta.

client : close

para encerrar a conexão com o banco de dados remoto. O servidor pode retornar uma string indicando o resultado desse encerramento:

    200 Base fermée
    500 Erreur lors de la fermeture de la base (msg d’erreur)

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:

    commande param1 param2 ... paramq

poderemos ter, no servidor RMI, um método

    String commande(param1,param2,..., paramq)

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.