RMI: Servidor J2SE e Clientes SuperWaba ou J2ME. (Tutorial aqui!)

Olá Pessoal. Estou iniciando o desenvolvimento no mundo móvel e me deparei com uma situação desafiadora. Como transmitir dados de um cliente móvel para um servidor Java. A única solução que eu tinha achado era através de sockets. Porém, eu consegui achar uma solução que usasse RMI.

Portanto, para quem enfrenta o mesmo problema que eu, vou postar um minitutorial sobre como conseguir tal facenha.
RMI, Invicação de Métodos Remotos, demonstra um cenário aonde é possível acessar métodos de um objeto que não estão localizados na máquina ou dispositivo aonde se está rodando a aplicação. Para quem deseja saber um pouco mais sobre RMI, acesse o tutorial Introdução ao RMI aqui do GUJ.

A solução RMI para J2ME (MIDP 2.0) foi proposta pelo projeto Arcademis, e a solução para o SW foi feita pelo mesmo projeto, por mim, porém apenas recompilando o código alterando as classes de sockets e outras coisinhas… Créditos totais ao pessoal do Arcademis!
Para começar, efetue o download das três APIS que seguem no fim do tutorial:
arcademis_J2SE.jar: API para o servidor J2SE ou EE
arcademis_J2ME.jar: API para o cliente J2ME
arcademis_SW.jar: API para o cliente SuperWaba

Para testes, a seguinte aplicação vai ser desenvolvida: um cliente pede ao servidor os seus dados (nome do SO, versão, etc) e ele retorna uma string contendo esses dados. Vamos começar implementando a interface remota:

/*
 * SystemInformatation.java
 * ------------------------
 * Interface de um objeto Remoto para retornar informações sobre o sistema
 * operacional de um host
 */

package rmi;

import arcademis.*;

public interface SystemInformation extends Remote {

    String getInformation() throws ArcademisException;
}

Em seguida, vamos implementar essa interface remota

/*
 * SimpleSystemInformatation.java
 * ------------------------------
 * Implementação da Interface Remota SystemInformatation para obter
 * informações do sistema operacional de um host
 */

package rmi;

import rme.server.RmeRemoteObject;

public class SimpleSystemInformation extends RmeRemoteObject 
        implements SystemInformation {

    public SimpleSystemInformation() {
    }
    
    public String getInformation() {
        String info = "";
        
        info += "Sistema Operacional: " + System.getProperty("os.name");
        info += "\nArquitetura: " + System.getProperty("os.arch");
        info += "\nVersão SO: " + System.getProperty("os.version");
        info += "\nUsuário: " + System.getProperty("user.name");
        
        return info;
    }
}

Se vc entende um pouco da arquitetura do RMI, sabe que essa comunicação exige que vc tenha compilado os stubs e skeletons da implementação da interface remota, pois eles vão servir de elo de ligação e sincronização. Para esse projeto, vc vai ter que efetuar isso no braço. Não é nada complicado. Adiaremos esse passo.
Agora vamos implementar o servidor. Adicione o jar do servidor ao classpath do seu projeto e mãos a obra!

/*
 * Servidor.java
 */

import rme.RmeConfigurator;
import rme.naming.NameServer;
import rme.naming.RmeNaming;
import rmi.SimpleSystemInformation;

public class Servidor{

    /* No construtor, inicia-se o serviço de registro de Objeto Remoto. TimeOut de 3 seg */
    public Servidor() throws Exception {
        System.out.println("Executando o serviço de registro de objeto remoto...");
        NameServer.main(new String[0]);
        Thread.sleep(3000);
        System.out.println("Serviço OK!");
    }
    
    public void execute() {

        try {            
            RmeConfigurator mc = new RmeConfigurator();
            mc.configure();

            SimpleSystemInformation infoObj = new SimpleSystemInformation();
            System.out.println("Registrando objeto Remoto...");
            RmeNaming.bind("rmeTeste",infoObj);
            System.out.println("Registrado!");
            infoObj.activate();
        }
        catch (Exception e)
        {
            System.err.println("Erro " + e);
            e.printStackTrace();
            System.exit(2);
        }

        System.out.println("Esperando por ação...");
    }
    
    // Gerando os stubs e skeletons
    public static void main(String args[]) {
        String localPath = new java.io.File("").getAbsolutePath() + "/src";
        rme.rmec.RmeC.main(new String[] {"-d", localPath,
            "rmi.SimpleSystemInformation"} );
    }
}

Observe que o servidor possui um método main, mais ele só serve para gerarmos os stubs e skeletons. Coloquei ele aí para facilitar apenas isso. Aquela classe RmeC é a responsável pela geração dos stubs e skeletons. Simples, execute do jeito que está ali, que se o seu projeto tem uma pasta de fontes chamada src, ele vai pro local certo! Só tome cuidado com o último parâmetro: deve ser o nome da classe que possui a implementação da interface remota (sem o .java) e precedido da hierarquia de pacotes. Nesse caso, a classe está dentro do pacote rmi.
Agora faça alguma interfacizinha bem bonitinha e inicie o servidor:

        try {
            new Servidor().execute();
        } catch (Exception ex) {
            System.err.println("Erro ao instanciar o servidor!");
        }

Pronto. Servidor rodando. Agora vamos implementar o cliente. A única diferença da implementação entre J2ME e SW é o jar que vc inclui no seu classpath e os imports. O código é exatamente o mesmo!
Copie pro projeto de app móvel as classes de interface remota e o Stub da implementação remota, no caso teste, os arquivos SystemInformation.java e SimpleSystemInformation_Stub.java. O importante é que elas estejam no mesmo pacote que estavam no servidor, senão dá erro!

Agora, finalmente, a implementação do cliente:

/*
 * RMIClient.java
 * --------------
 * Cliente de uma aplicação cliente-servidor utilizando RMI
 */

import rme.RmeConfigurator;
import rme.naming.RmeNaming;

public class RMIClient {

    /* Método do cliente que chama a execução do método remoto */
    public String getSystemInfo(String host) throws Exception {
        
        SystemInformation info = null;

        try {
            System.out.println("Configurando...");
            RmeConfigurator conf = new RmeConfigurator();
            conf.configure();
            System.out.println("Procurando por objeto remoto...");
            info = (SystemInformation)RmeNaming.lookup("rmeTeste");
        }
        catch( Exception e )
        {
            e.printStackTrace();
            throw new Exception("Erro ao procurar o objeto remoto!\n" + e);
        }

        try {
            // Executando a chamada remota
            return info.getInformation();
        }
        catch( Exception ex ) {
            ex.printStackTrace();
            throw new Exception("Erro ao realizar a chamada remota!\n" + ex);
        }
    }
}

Pronto! Agora faça a chamada do cliente em alguma outra interfacizinha bonita pelo código abaixo:

RMIClient client = new RMIClient();
try {
    System.out.println(client.getSystemInfo("127.0.0.1"));
} catch (Exception e) {
    e.printStackTrace(); 
}

Abaixo, seguem os jars…
Um abraço a todos, algum problema é só postar!
Até a próxima, pessoal!

Cara, muito obrigado pela iniciativa em fazer um tutorial parabéns! Embora ainda não tenha utilidade para mim, um dia pode vir a ter.

Olá, achei o seu tutorial e estou tentando testar. sempre que tento compilar recebo as seguites mensagens de erro: (alguma sugestão?)
SimpleSystemInformation.java:12: cannot find symbol
symbol: class SystemInformation
implements SystemInformation {
^
Servidor.java:28: cannot find symbol
symbol : method bind(java.lang.String,rmi.SimpleSystemInformation)
location: class rme.naming.RmeNaming
RmeNaming.bind(“rmeTeste”,infoObj);
^
RMIClient.java:16: cannot find symbol
symbol : class SystemInformation
location: class RMIClient
SystemInformation info = null;
^
RMIClient.java:23: cannot find symbol
symbol : class SystemInformation
location: class RMIClient
info = (SystemInformation)RmeNaming.lookup(“rmeTeste”);
^

Tá, e qual vantagem que Maria leva em usar RMI e não Socket direto ? :?:

Na verdade usando RMI vc estará usando Socket, pois não existe mágica, já que a conexão ou é TCP (Socket) ou UPD (Datagrama). Quando se usa HTTP no J2ME, no fundo se está usando Socket, sendo o HTTP apenas uma casca, um protocolo, usando o Socket como meio para mensagem chegar até seu destino.

Desculpe, mas não vi ganhos para se partir para algo mais complexo e trabalhoso que provavelmente custará mais, que é usar RMI, pois ele deve adicionar sua própria gordura ao Socket, como o próprio HTTP faz.

Quem transfere dados em J2ME sabe que é fundamental usar bem a banda para não ficar pagando rios de dinheiro para a operadora sem necessidade.

Nesta tarefa, algumas abordagens são usadas:

:arrow:Compressão do conteúdo antes do envio
:arrow:Transmissão de registros de tamanho fixo e não delimitados
:arrow:Não uso de formatos “verbosos” como XML
:arrow:Evitar transmitir informações picadas em vários requisições HTTP; ao invés disto, juntar sempre o máximo possível em 1
:arrow:etc…etc…etc…

Qual a vantagem em se usar RMI ao invés de sockets???
Se não houvesse nenhuma, tenho certeza que não existira RMI em Java.

1° - Esse tutorial eu postei porque eu fiz um projeto para PDAs (utilizando o SW), que pode acessar a rede através de uma conexão banda-larga, tornando a transmissão de dados tão rápida quanto um desktop.
2° - É claro que a conexão via RMI utiliza sockets, já que é a base da transmissão de dados. Isto é obvio para todo mundo!
3° - A chamada de métodos remotos é uma coisa tão útil, que vale a pena o overhead de informações transmitidas. Imagine se você quer inserir um objeto (tabela) no banco de dados em um servidor. Pela chamada de método remoto, você apenas dá um Server.insert(nome, idade, etc) e se der certo ele retorna true, se der errado retorna um exception que você declara por interface remota, e por aí vai. Imagine o trabalho que isto daria para vc controlar apenas pelos sockets crus. byte a byte sendo enviado, tratamento de sincronismo dos dados (pq a conexão TCP garante que a stream envia os dados corretamente, mas não garante sincronia entre as transmissões, já que você deve tratar timeout, e trocentas outras coisas mais (que se você já estudou protocolos de rede, como Stop-And-Wait ARC, vai saber o que estou dizendo.)

Enfim, é inadimitível que uma solução RMI para alguns casos j2mE (QUE não tem foco APENAS para celulares) não sirva para nenhum proposto.

esbruno, verifique se você esqueceu de adicionar os jars, referente ao projeto, e as classes de implementação da interface remota.

[quote=arec_metafora]
Enfim, é inadimitível que uma solução RMI para alguns casos j2mE (QUE não tem foco APENAS para celulares) não sirva para nenhum proposto.[/quote]

Não estou metendo o pau no RMI, apenas acho que pra quem usa J2ME (que não é apenas para celulares, apenas uns 95%), não vai se beneficiar disto, visto que é um overhead alto para a capacidade destes dispositivos e soluções “cruas” como Socket ou HTTP ainda são as mais adequadas.

Desenvolvimento em PDA pra mim se faz com ferramenta de gente, que é em C++, SW, .Net Compact Framework,etc…não J2ME. Difícel encontrar um louco por aí cujo projeto em produção roda em PDA com J2ME.

Por isso mesmo.
Se você leu todo o post, viu que o meu projeto se destinava a SW, tanto é que eu usei um projeto feito em J2ME e fiz modificações para serem usadas no SW.

Com algumas adaptações, consegui que ele rodasse no SW (que não é 100%, mas quase 99% Java (não J2ME), dado que os bytecodes gerados são o mesmo)

Com relação ao uso de J2ME em dispositivos de baixa capacidade, eu trabalho em uma empresa que desenvolve aplicações para dispositivos que suportam WM e Java.
E metade da demanda do J2ME são para bleckberries e Nokia série 60, que suportam conexeão banda-larga sem fio.

Mas é claro que é muito mais útil usar RMI com o SW. Este é o foco do Post

Segui todos os passos escritos no tutorial, apatentemente é como se o java não encontrasse a classe SystemInformation na hora de compilar o código, ai todas as classes que dependem dela não “compilam”.

esbruno, verifique o seguinte…

Na hora de copiar a classe para o projeto cliente (app móvel), faça a mesma estrutura de pacotes que você fez na aplicação server.

Por exemplo, se vc criou um package rmi, e dentro dele criou a classe SystemInformation, na app cliente crie uma pasta rmi e copie a classe dentro desta pasta.
Você também já verificou se não esqueceu de adicionar os jars à sua aplicação cliente?

Caro amigo, obrigado pela paciencia. Posso está fazendo besteira mas eu coloquei todos arquivos no mesmo diretório e estou tentando simular a execução numa mesma máquina. O erro está acontecendo na compilação, não estou coneguindo compilar porque o java acusa falta do SystemInfomation.class , mesmo existindo no diretorio.

Você tomou o cuidado de colocar os diretórios em hierarquia, dependendo do pacote?

Por exemplo, rmi.SystemInformation no server…
Você deve criar uma pasta rmi e adicionar o .java dentro dela, do mesmo modo que foi feito no servidor.

Removi a declaração package dos arquivos de modo a todos ficarem no mesmo pacote, diretorio atual.

Você fez o mesmo no servidor?

Que eu me lembre, as interfaces remotas nunca poderiam ficar na raiz. Não me lembro o motivo, mas não podem.
Tente refazer o tutorial, sem removê-las dos diretórios.

Refiz o prcedimento seguindo extamente como sugerido e a aplicação rodou. A dúvida é a seguinte: A classe RMIClient recebe o endereço do host ao qual ela vai solicitar a informação , public String getSystemInfo(String host throws Exception … mas não utiliza essa variavel internamente. Como o cliente direciona que maquina que ele vai fazer a consulta? no caso o SimpleSystemInformation.

Testei configurando qualquer IP na classe que invoca o metodo abaixo e sempre dá o mesmo resultado.
RMIClient client = new RMIClient();
try {
System.out.println(client.getSystemInfo(127.0.0.0)

É verdade, você tem razão.
Este projeto foi uma parte de um TCC para concusão de curso.
Eu fiz as alterações para usar IP externo.
No sábado eu viajo para minha cidade (estou morando fora) e aí eu passo as correções, ok?

Mas eu acho que as alterações foi no método lookup do RmeNaming no cliente

info = (SystemInformation)RmeNaming.lookup("rmeTeste"); 

Tente achar alguma coisa…
Sábado eu postarei as modificações

Abraços, att

Encontrei o ajuste que faltava, RmeNaming.lookup(host+"/rmeTeste");. Agora, sem querer abusar mas ja abusando da tua boa vontade. Fiquei com uma dúvida com relação a instanciação dos objetos. A classe servidor usa o metodo “RmeNaming.bind” sem que tivesse sido instaciado a classe, pelo menos explicitamente… A mesma situação ocorre com a classe RMIClient (info = (SystemInformation)[b]RmeNaming.lookup/b. Saberia a explicação?

A classe RmeNaming possui alguns métodos estáticos que não necessitam da instanciação do objeto, porque não utilizam atribuos da instância da classe.
Os métodos bind e lookup fucionam “a grosso modo” como uma função em C. Apenas executa um trecho de código e retorna um valor.

Só para ilustrar um exemplo…

public class HelloWorld
{
    public static void sayHello()
    {
        System.out.println("Hello World");
    }
}

public static void main(String[] args)
{
        HelloWorld.sayHello();
}

Concordo com o boone. Custo de dados é primordial no desenvolvimento móvel. Agora em relação ao SW eu fiz o minhas conexões via HTTP. Geralmente o pessoal usa um framework na retaguarda, e até você adicionar aquela camada de RMI, você vai e corre e faz com Servlets muito mais rápido. Eu conheço o Arcademis, foi um professor meu da PUC e outros da Federal MG que fizeram.

arec_metafora, achei teu tutorial e tentei testar, não sei porque o metodo bind do RmeNaming não esta sendo encontrado. Alguma ideia do porquê?!

:?:
Encontrei seu tutórial achei muito util, ao trabalhar com o exemplo do mesmo aqui postado verifiquei que a aplicação não está reconhecendo o metodo bind .

Para ser mais especifico.

Estou utlizando as IDEs Netbenas e eclipse, em ambos o mesmo problema é encontrado.
Alguma sugestão?