Packet Capture em Java com JPCAP

PCAP (Packet Capture) consiste numa API para a captura de pacotes de rede. Em sistemas baseados em Unix o Pcap é implementado na biblioteca LibPcap, no caso dos sistemas Windows este encontra-se implementado na biblioteca WinPcap.

Estas bibliotecas permitem que software consiga capturar e filtrar pacotes que viagem pela rede, enviar pacotes e até listar todas as interfaces de rede existentes no sistema bem como obter informações sobre os mesmos, tais como o nome, IP ou MAC.

É ainda possível guardar os pacotes capturados num ficheiro e mais tarde voltar esses pacotes a partir do ficheiro onde foram guardados. Tais recursos são muito utilizados em ferramentas de monitorização e análise de rede como por exemplo packet sniffers, network monitors, network intrusion detection and traffic-generators.

Jpcap é no fundo uma biblioteca intermédia entre o Java e as bibliotecas LibPcap e WinPcap, permite ao programador criar aplicações Java utilizando as funcionalidades destas bibliotecas. Existem bibliotecas semelhantes para as mais diversas linguagens tais como Python, Ruby, .NET, Perl.

Ao longo deste artigo iremos abordar alguns dos principais recursos desta biblioteca tais como:

  • Interfaces de rede disponíveis no sistema.
  • Preparar a interface de rede para captura.
  • Capturar pacotes da interface de rede.
  • Aplicar filtros à captura de pacotes de rede.
  • Gravar pacotes de rede capturados em ficheiros.
  • Ler pacotes de rede gravados em ficheiros.

Para isso iremos criar pequenas aplicações, exemplos simples que demonstram como utilizar cada um dos recursos mencionados acima.

Mas antes de tudo vamos precisar de preparar o nosso ambiente, para isso precisar do Jpcap que podemos encontrar em

http://netresearch.ics.uci.edu/kfujii/jpcap/doc/download.html

Aqui encontramos versões para vários sistemas operativos, podemos utilizar uma versão de instalação como é o caso do Windows ou Linux ou podemos utilizar a versão código-fonte, esta parte deixo ao critério de cada um, não existem vantagens ao nível de desenvolvimento.

Para instalar basta seguir os passos descritos na página http://netresearch.ics.uci.edu/kfujii/jpcap/doc/install.html

Depois da instalação vamos por mão à obra!

Interfaces de rede disponíveis no sistema

Nesta sessão vamos ver como é simples obter a lista de interfaces de rede que se encontram disponíveis no nosso sistema, para além disso vamos ver ainda como é fácil retirar alguma informação destas interfaces.

Para isso vamos ver um pequeno programa que demonstra como, usando o Jpcap, podemos obter todos estes dados de uma forma bastante simples e rápida.

import jpcap.JpcapCaptor;
import jpcap.NetworkInterface;
import jpcap.NetworkInterfaceAddress;

/**
 *
 * @author magician
 */
public class JpcapInterfaces {

    /**
     * Dado um array de bytes retorna a String equivalente.
     * @param input - Array de bytes.
     * @return String com a representação textual do array de bytes.
     */
    public static String hex2String(byte [] input){
        String output = "";
        for (int i = 0; i < input.length-1; i++) {
            output += Integer.toHexString(input[i] & 0xff) + ":";
        }
        output += Integer.toHexString(input[input.length-1] & 0xff);
        return output;
    }

    public static void main(String args[]) {

        //Obtém a lista de interfaces de rede no sistema.
        NetworkInterface[] interfaces = JpcapCaptor.getDeviceList();

        for (NetworkInterface ni : interfaces) {
            System.out.println("-------------------------------------------------");
            //Nome da interface.
            System.out.println("Nome: " + ni.name);
            //Descrição da interface caso exista.
            System.out.println("Descrição: " + ni.description);
            //DataLink da interface.
            System.out.println("Nome da DataLink : " + ni.datalink_name);
            //Descrição do DataLink caso exista.
            System.out.println("Descrição da DataLink : " + ni.datalink_description);
            //MAC Address da interface.
            System.out.println("MAC Address: "+ hex2String(ni.mac_address));

            for(NetworkInterfaceAddress a : ni.addresses){
                //Endereço de IP da interface.
                System.out.println("IP: " + a.address.getHostAddress());
                //Endereço de Broadcast da interface.
                System.out.println("BroadCast: " + a.broadcast);
                //Mascara de SubRede.
                System.out.println("SubNet: " + a.subnet.getHostAddress());
                //Em caso de ligações P2P o endereço de destino.
                System.out.println("Destinho P2P: " + a.destination);
            }
            System.out.println("-------------------------------------------------\n");
        }
    }
}

Com o exemplo acima podemos ver como é simples obter as interfaces de rede disponíveis no sistema bem como todos os seus dados, alguns deles até bastante importantes como o Nome, IP, MAC e SubNet, dados esses que podem ser utilizados mais tarde das mais diversas formas. O resultado obtido após a execução desta pequena aplicação será algo como

Nome: eth0
Descrição: null
Nome da DataLink : EN10MB
Descrição da DataLink : Ethernet
MAC Address: 0:16:36:b0:5c:d7
IP: 192.168.2.183
BroadCast: /192.168.2.255
SubNet: 255.255.255.0
Destinho P2P: null
IP: fe80:0:0:0:216:36ff:feb0:5cd7
BroadCast: null
SubNet: ffff:ffff:ffff:ffff:0:0:0:0
Destinho P2P: null
-------------------------------------------------

-------------------------------------------------
Nome: any
Descrição: Pseudo-device that captures on all interfaces
Nome da DataLink : LINUX_SLL
Descrição da DataLink : Linux cooked
MAC Address: 0:0:0:0:0:0
-------------------------------------------------

-------------------------------------------------
Nome: lo
Descrição: null
Nome da DataLink : EN10MB
Descrição da DataLink : Ethernet
MAC Address: 0:0:0:0:0:0
IP: 127.0.0.1
BroadCast: null
SubNet: 255.0.0.0
Destinho P2P: null
IP: 0:0:0:0:0:0:0:1
BroadCast: null
SubNet: ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
Destinho P2P: null
-------------------------------------------------

Este output corresponde a um sistema Linux, noutros sistemas podem aparecer diferenças nomeadamente nos nomes dos dispositivos. Por exemplo, pode aparecer algo como \Device\NPF_{C3F5996D-FB82-4311-A205-25B7761897B9} ao invés do simples eth0.

Para que as nossas aplicações consigam utilizar o jpcap correctamente estas devem ser executadas com permissões de admin, caso contrário é possível que não sejam obtidos resultados, uma vez que o sistema pcap necessita dessas permissões para realizar as operações sobre as interfaces.

Preparar a interface de rede para captura

Para preparar a captura de pacotes de rede basta utilizar o método static openDevice da classe JpcapCaptor, este método abre a interface escolhida e prepara a captura de pacotes. Abaixo podemos ver como usar este método

    NetworkInterface[] interfaces = JpcapCaptor.getDeviceList();

    //openDevice(NetworkInterface interface, int snaplen, boolean promics, int to_ms)
    JpcapCaptor captor = JpcapCaptor.openDevice(interfaces[0], 65535, false, 20);

Como podemos ver são passados quatro argumentos ao método openDevice, o primeiro argumento é a interface a ser utilizada na captura, neste caso vamos utilizar o primeiro interface da lista ou seja o “eth0”. O segundo argumento corresponde ao numero de máximo bytes a serem capturados de cada vez, o terceiro argumento irá dizer se a captura será em modo promiscuo (true) ou não (false). O modo promiscuo permite capturar pacotes de rede mesmo que a sua origem ou destino não seja a da interface aberto para captura. O modo não promiscuo apenas captura pacotes cujo destino ou origem se a interface escolhida. O último argumento corresponde ao timeout em milissegundos dado para a captura de pacotes.

Capturar pacotes da interface de rede

Vamos agora passar à captura dos pacotes, o processo pode ser feito de duas formas diferentes mas que atingem o mesmo fim. Podemos utilizar a forma callback ou one-by-one.

A forma callback passa pela utilização dos métodos processPacket ou loopPacket da classe JpcapCaptor e pela implementação da interface PacketReceiver. De uma forma resumida o que vamos fazer é criar uma classe que implementa a interface PacketReceiver onde implementamos o método receivePacket que irá conter o que deverá ser feito a cada pacote capturado. Depois de implementada a interface, usamos o método processPacket ou loopPacket, ambos atingem o mesmo fim com a diferença que no processPacket é compativel com timeout e modo non blocking.

A forma one-by-one é a mais “primitiva” e a mais flexível dado que é possível controlar todas as acções feitas sobre o pacote. Para isso utilizamos o método getPacket da classe JpcapCaptor, este método como o nome indica retorna um pacote capturado, assim apenas temos que criar um mecanismo que irá repetir o processo quantas vezes quisermos.

Vamos para isso ver um pequeno exemplo que captura 20 pacotes e imprime na consola o IP de origem/destino bem como as portas correspondentes e o tamanho do pacote.

import java.io.IOException;
import jpcap.JpcapCaptor;
import jpcap.NetworkInterface;
import jpcap.packet.Packet;
import jpcap.packet.TCPPacket;
import jpcap.packet.UDPPacket;

/**
 *
 * @author magician
 */
public class OpenInterface {

   public static void main(String args []){

        //Lista de interfaces de rede no sistema.
        NetworkInterface[] interfaces = JpcapCaptor.getDeviceList();

        try{
            //Abre a interface 0 da lista.
            JpcapCaptor captor = JpcapCaptor.openDevice(interfaces[0], 65535, false, 20);

            //Simples contador.
            int i = 0;
            Packet p = null;

            //Cliclo para capturar 20 pacotes.
            while(i < 20){
                //Captura um pacote.
                p = captor.getPacket();

                //Verifica se o pacote é do tipo TCPPacket
                if(p instanceof TCPPacket){
                    TCPPacket tcp = (TCPPacket) p;
                    System.out.println("SRC: " + tcp.src_ip.getHostAddress() + ":" + tcp.src_port +
                            "   \tDST: " + tcp.dst_ip.getHostAddress() +":" + tcp.dst_port +
                            "   \tSize = " + tcp.length + " bytes");

                }
                //Verifica se o pacote é do tipo UDPPacket
                else if(p instanceof UDPPacket){
                    UDPPacket udp = (UDPPacket) p;
                    System.out.println("SRC: " + udp.src_ip.getHostAddress() + ":" + udp.src_port +
                            "   \tDST: " + udp.dst_ip.getHostAddress() +":" + udp.dst_port +
                            "   \tSize = " + udp.length + " bytes");
                }
                i++;
            }

            //Fecha a captura de pacotes.
            captor.close();
        }
        catch(IOException io){
            System.out.println(io.getMessage());
        }
        catch(Exception e){
            System.out.println(e.getMessage());
        }
    }
}

Como podemos ver apenas foram processados os pacotes UDP e TCP, mas existem outros como por exemplo ARPPacket, DatalinkPacket, ICMPPacket entre outros. Este pequeno exemplo irá gerar um ouput semelhante ao que se segue a baixo sem os * que foram colocados por motivos óbvios de privacidade e segurança.

SRC: 7*.177.*05.30:1670         DST: 19*.16*.*.1*3:49153        Size = 40 bytes
SRC: 19*.16*.*.1*3:49153        DST: 7*.177.*05.30:1670         Size = 1119 bytes
SRC: 19*.16*.*.1*3:1900         DST: 19*.16*.*.176:1536         Size = 247 bytes
SRC: *3.*5*.40.166:51457        DST: 19*.16*.*.1*3:49153        Size = 88 bytes
SRC: *3.30.6.103:*74*           DST: 19*.16*.*.1*3:49153        Size = 40 bytes
SRC: 19*.16*.*.1*3:49153        DST: *3.30.6.103:*74*           Size = 328 bytes
SRC: 19*.16*.*.1*3:579*5        DST: *1.111.49.15*:33655        Size = 445 bytes
SRC: *4.1*3.19*.**4:60000       DST: 19*.16*.*.1*3:49794        Size = 58 bytes
SRC: 19*.16*.*.1*3:49794        DST: *4.1*3.19*.**4:60000       Size = 160 bytes
SRC: *7.93.3.*55:16*4           DST: 19*.16*.*.1*3:49153        Size = 40 bytes
SRC: 19*.16*.*.1*3:49153        DST: *7.93.3.*55:16*4           Size = 550 bytes
SRC: 91.*1.*50.*11:*7075        DST: 19*.16*.*.1*3:49153        Size = 73 bytes
SRC: 19*.16*.*.1*3:49153        DST: 91.*1.*50.*11:*7075        Size = 93 bytes
SRC: *0*.99.194.194:49*90       DST: 19*.16*.*.1*3:50043        Size = 58 bytes
SRC: 19*.16*.*.1*3:50043        DST: *0*.99.194.194:49*90       Size = 260 bytes
SRC: **.**7.*00.1*:60053        DST: 19*.16*.*.1*3:469**        Size = 313 bytes
SRC: 19*.16*.*.1*3:469**        DST: **.**7.*00.1*:60053        Size = 58 bytes
SRC: *17.13*.7*.9*:600*0        DST: 19*.16*.*.1*3:504*7        Size = 52 bytes
SRC: 19*.16*.*.1*3:504*7        DST: *17.13*.7*.9*:600*0        Size = 1410 bytes
SRC: 19*.16*.*.1*3:504*7        DST: *17.13*.7*.9*:600*0        Size = 306 bytes

Embora neste exemplo apenas tenhamos extraído estes dados é possível extrair ainda mais dados dos pacotes, como por exemplo os dados (data) enviados no ficheiro, headers, version, etc.. Iremos ver mais à frente um exemplo em que retiramos os dados enviados.

Aplicar filtros à captura de pacotes de rede

O Jpcap permite ainda a utilização de filtros de forma a limitar os pacotes capturados a um círculo restrito de possibilidades e não tudo o que passa pela interface como vimos até agora.

Vamos começar por ver a sintaxe utilizada pelo tcpdump que é a mesma aplicada ao Jpcap.

Filtragem por Host
Sintaxe Descrição Exemplo
host <host> Captura todos os pacotes que entram e saem do host host 192.168.1.100
src host <host> Captura todos os pacotes com origem no host src host 192.168.1.100
dst host <host> Captura todos os pacotes com o host como destino dst host 192.168.1.100
Filtragem por Porta
Sintaxe Descrição Exemplo
port <port> Captura todos os pacotes que entram ou saem da porta port 80
src port <port> Captura todos os pacotes com origem na porta src port 80
dst port <port> Captura todos os pacotes com a porta como destino dst port 80
Filtragem por Network
Sintaxe Descrição Exemplo
net <net> Captura todos os pacotes da rede <net> net 192.168
src net <net> Captura todos os pacotes que saem da rede <net> src net 192.168
dst net <net> Captura todos os pacotes que entram na rede <net> dst net 192.168
Filtragem por Protocolo
Sintaxe Descrição
ip Captura todos os pacotes de IP
arp Captura todos os pacotes ARP
rarp Captura todos os pacotes ARP inversos
tcp Captura todos os pacotes TCP
udp Captura todos os pacotes UDP
icmp Captura todos os pacotes ICPM
Combinação de filtros
Sintaxe Descrição Exemplo
not Negação not src net 192.168
and Concatenação tcp and src host 192.168.1.100
or Alternância port 80 or port 8080

Acima temos a sintaxe principal que é possível utilizar para criar filtros, como podemos ver podem ser criados filtros por host, port, network, tipo de pacote e podemos ainda fazer combinações de filtros utilizando operações lógicas. Vamos agora ver um exemplo da utilização de filtros e iremos também ver neste exemplo como é possível ver os dados que são enviados nos pacotes.

import java.io.IOException;
import jpcap.JpcapCaptor;
import jpcap.NetworkInterface;
import jpcap.packet.TCPPacket;

/**
 *
 * @author magician
 */
public class JpcapFilter {

    public static void main(String args []){

        //Lista de interfaces de rede no sistema.
        NetworkInterface[] interfaces = JpcapCaptor.getDeviceList();

        try{
            //Abre a interface 0 da lista.
            JpcapCaptor captor = JpcapCaptor.openDevice(interfaces[0], 65535, false, 20);

            //Captura apenas pacotes TCP com origem no host 192.168.1.100 e que
            //tem como destino a porta 80 ou seja HTTP
            captor.setFilter("tcp and src host 192.168.1.100 and dst port 80", true);

            //Simples contador.
            int i = 0;

            //Cliclo para capturar 20 pacotes.
            while(i < 20){
                //Captura um pacote e converte para TCPPacket dado que apenas
                //a capturar pacotes TCP.
                TCPPacket p = (TCPPacket) captor.getPacket();

                //Gera o output com a informação sobre o pacote
                System.out.println("SRC: " + p.src_ip.getHostAddress() + ":" + p.src_port +
                        "   \tDST: " + p.dst_ip.getHostAddress() +":" + p.dst_port +
                        "   \tSize = " + p.length + " bytes");

                //Caso o pacote contenha dados este são impressos e o programa para.
                if(p.data.length > 0){
                    System.out.println(new String(p.data));
                    break;
                }
                i++;
            }

            //Fecha a captura de pacotes.
            captor.close();
        }
        catch(IOException io){
            System.out.println(io.getMessage());
        }
        catch(Exception e){
            System.out.println(e.getMessage());
        }
    }
}

O que o programa vai fazer assumindo que o nosso IP corresponde ao 192.168.1.100 é capturar todos os pacotes TCP que são enviados pelo nosso computador e que tem como destino a porta 80. De uma forma resumida vai capturar todos os pedidos HTTP feitos por nós. Na realidade não irá capturar todos mas apenas alguns, ou seja, irá capturar até encontrar um pacote que contenha dados e irá gerar um output semelhante ao que se segue.

SRC: 192.168.1.100:49323        DST: 66.102.9.147:80    Size = 60 bytes
SRC: 192.168.1.100:49323        DST: 66.102.9.147:80    Size = 52 bytes
SRC: 192.168.1.100:49323        DST: 66.102.9.147:80    Size = 715 bytes
GET / HTTP/1.1
Host: www.google.pt
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.11) Gecko/20071204 Ubuntu/7.10 (gutsy) Firefox/2.0.0.11
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: pt-pt,pt;q=0.8,en;q=0.5,en-us;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: NID=17=BNGeDayJAh13ZHF6XFJ2mwo5N3rNTRu6WaFDUYmbgAC4pyT0kzPnSKN0qKAz_ajH2WjpqDZ92DXTdjhNyiG_s-Xwd4Mwr;
PREF=ID=8ed9295:TM=197383:LM=213911:IG=8:S=T7z-r65IhJ

O exemplo acima mostra o pedido feito pelo Firefox ao servidor www.google.pt, como podemos ver trata-se de um pedido HTTP ao servidor.

Assim encerramos este artigo, neste momento estamos prontos a criar desde grandes aplicações a pequenos utilitários de análise de pacotes de rede.

Links

JPCAP Home

JPCAP API

JPCAP Samples

LibPcap/TCPDump

Winpcap

Autoria

Este artigo foi originalmente escrito por Fábio Correia (magician) para a 18ª Edição da Revista PROGRAMAR (Fevereiro de 2009).

Revista Programar – 18ª Edição – Fevereiro de 2009

Revista PROGRAMAR


Edição 18 – Fevereiro de 2009

Dois meses depois, a Revista PROGRAMAR volta a publicar mais uma edição, a 18ª. Nesta edição em que se comemora o seu 3º aniversário, poderá encontrar a continuação dos artigos sobre Subversion e Fundamentos de Segurança em Redes, artigos sobre Cloud Computing e Packet Capture em Java, entre outros. Novamente, esperamos que esteja à altura das vossas expectativas.

Mais info.

Classes Remotas com ClassLoader

Este pequeno artigo irá demonstrar como utilizar classes que não se encontram presentes na aplicação e a partir delas instanciar objectos e manipula-los normalmente.

A classe abstracta ClassLoader é a responsável pelo carregamento das classes das nossas aplicações, entre outras funcionalidades ela permite a definição de classes, isto é a criação de objectos Class a partir de uma array de bytes que contem o corpo da classe.

Muitos devem pensar, “mas para que quero eu isso se tenho ali o ficheiro X.class??”. Podem existir muitas razões mas o que me levou a escrever este artigo e a meu meu o com fins mais práticos é o de termos classes remotas.

Imaginemos que temos um servidor com diversas classes, até mesmo centenas se quisermos ser amplos, e que essas classes pertencem a diversos programas diferente por exemplo o programa X precisa da classe Xa.class e Xb.class para poder funcionar. Em condições normais teríamos a nossa aplicação com essas três classes a X seria a main e a Xa e Xb classes lib para a X, nada de mais até agora. Mas se se o nosso programa tive-se apenas a classe X e cada vez que fosse executado fosse buscar “virtualmente” as classes que precisaria para trabalhar? Ou seja a nossa classe X iria ao servidor pedir a classe Xa e Xb, iria criar os objectos pretendidos destas classes e iria utiliza-los como pretendia nem que estas classes estivessem realmente no nosso computador.

E agora vem a pergunta, “e quais as vantagens ?? Afinal de contas teríamos de estar sempre a pedir as classes ao servidor, ficamos dependentes do servidor, da ligação e da carga no servidor!!!!”

Todos esses pontos são verdadeiros embora classes Java normalmente não seja muito “grandes”, poderia ser mau, caso o servidor fica-se congestionado ou caso a nossa aplicação precisar de algumas dezenas de classes podia gerar bastante tráfego na rede.

Mas e se forem apenas algumas classes, classes que sejam alteradas constantemente, classes que por segurança apenas seriam enviadas ao cliente depois de um processo de autenticação e sem o qual o programa não funcionaria ou até mesmo no caso de classes que não queremos simplesmente distribuir mas que são necessárias ao funcionamento do programa.

Aposto que aqui surgem muitas discussões umas contra outras a favor xD Cabe a cada um usar ou não este mecanismo, o objectivo aqui é apenas dar a conhecer que isso é possível e que funciona.

Para isso vamos utilizar um exemplo mesmo muito simples, um cliente, um servidor, um interface e uma classe a ser usada remotamente. Teremos então a seguinte estrutura.

.
|-- Cliente
|   |-- ClassesClient.java
|   `-- HelloInterface.java
`-- Servidor
    |-- ClassesServer.java
    |-- Hello.java
    `-- HelloInterface.java

Como podemos ver a parte do cliente apenas tem a classe principal e o interface da classe a ser usada remotamente. Por outro lado o servidor para além da classe principal e do interface tem ainda a classe remota que implementa o interface. Passemos agora ao código

/**
 *
 * @author magician
 */
public interface HelloInterface {

    //Imprime uma mensagem.
    public void printHello();

    //Imprime uma mensagem seguida do argumento
    public void printHello(String s);

    //Retorna uma String com uma mensagem.
    public String getHello();

}

[HelloInterface.java]

Este interface vai permitir ao cliente saber que métodos pode invocar da classe remota sem que ela esteja presente. Vejamos agora a classe Hello.java que implementa este interface.

/**
 *
 * @author magician
 */
public class Hello implements HelloInterface{

    //Imprime a mensagem "Hello xD"
    public void printHello() {
        System.out.println("Hello xD");
    }

    //Imprime a mensagem "Hello" seguida do argumento e por fim "xD"
    public void printHello(String s) {
        System.out.println("Hello " + s + " xD");
    }

    //Retorna uma String com a mensagem "Hello Remote xD"
    public String getHello() {
        return "Hello Remote xD";
    }
}

[Hello.java]

Esta será a classe remota, embora não esteja do lado do cliente ele irá utilizada, após a ter recebido do servidor sem a guardar fisicamente. Temos agora a classe ClassesServer que é o responsável por enviar a classe ao cliente.

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 *
 * @author magician
 */
public class ClassesServer {

    public static void main(String args []){        
        try{
            //Carrega o conteúdo do ficheiro Hello.class para um array de bytes.
            FileInputStream in = new FileInputStream("Hello.class");
            byte [] b = new byte [in.available()];  
            int lido = in.read(b,0,b.length);

            //Aguarda pedidos na porta 3434
            ServerSocket sv = new ServerSocket(3434);
            Socket svc = null;
            while( (svc = sv.accept() ) != null){
                //A cada pedido envia o array de bytes correspondente à classe.
                svc.getOutputStream().write(b,0,lido);
                svc.close();
            }

        }
        catch(FileNotFoundException fnf){
            fnf.printStackTrace();
        }
        catch(IOException io){
            io.printStackTrace();
        }
    }   
}

[ClassesServer.java]

O servidor é mesmo muito simples apenas para exemplificar o funcionamento do processo. Em seguida temos a classe ClassesClient que faz o pedido ao servidor, define a classe e utiliza-a.

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 *
 * @author magician
 */
public class ClassesClient extends ClassLoader {

    public static void main(String args []){
        try{
            //Liga-se ao servidor.
            Socket cl = new Socket("localhost", 3434);
            InputStream in = cl.getInputStream();

            //Carrega a resposta enviada pelo servidor para um array de bytes.
            byte [] b = new byte [in.available()];
            int lidos = in.read(b, 0, b.length);

            in.close();
            cl.close();

            /* Através do método defineClasse existente na classe ClassLoader
             * dando o nome da classe e o array de bytes, como resultado e se
             * tudo correr correctamente teremos um objecto Class da classe Hello
             */
            Class c = new ClassesClient().defineClass("Hello", b, 0, lidos);

            /* Criamos um objecto da classe e fazemos um cast para o tipo do interface
             * que se encontra localmente presente.
             */
            HelloInterface objHello = ( (HelloInterface) c.newInstance());

            //Usamos os métodos do objecto normalmente.
            objHello.printHello();
            objHello.printHello("Magician");
            System.out.println(objHello.getHello());

        }
        catch(UnknownHostException uh){
            uh.printStackTrace();
        }
        catch(IOException io){
            io.printStackTrace();
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}

[ClassesClient.java]

Como devem ter reparado a nossa classe estende à classe ClassLoader, isso acontece porque a ClassLoad é abstracta e o método defineClass é protected.

Agora apenas falta testar, para isso basta compilar todos os ficheiro java, colocar o servidor a correr e em seguida fazer os pedidos com o cliente, a cada execução do cliente deverá surgir o seguinte output

Hello xD
Hello Magician xD
Hello Remote xD

Como podemos ver este output vem da classe Hello.java que não existem no cliente!!

Espero que tenha ajudado e que o artigo seja util, para saberem mais sobre a classe ClassLoader pode visitar a API em

http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html

Por Fábio Correia (White Magician)

Revista Programar – 14ª Edição – Maio De 2008

Revista PROGRAMAR
Edição 14 – Maio de 2008

Apesar de um atraso de sensivelmente 2 semanas, pelo qual pedimos desculpa aos nossos assíduos leitores, dois meses depois do nosso segundo aniversário, trazemos até vós mais uma edição da Revista PROGRAMAR.

Numa edição que inclui variados temas, encontrará, entre outros assuntos, um artigo sobre interacção com MySQL a partir da linguagem Python, um artigo sobre engenharia de software, uma análise de um livro sobre Python e um excelente artigo sobre network scanning. Poderá ainda ler sobre dois importantes eventos: o Festival Nacional de Robótica o Techdays 2008.

Mais info.

Manipulação de Ficheiros com Ruby

Neste artigo vamos explorar a linguagem Ruby e os seus recursos para manipulação de ficheiros e directórios bem como as capacidades de Input/Ouput disponíveis. Para isso vão ser utilizadas as bibliotecas mais comuns como Dir, File e IO. Embora existam outras mais recentes e com mais recursos estão são as mais comuns, de simples utilização e que servem para base de bibliotecas mais recentes.

Directórios

Para trabalhar com directórios vamos utilizar a Classe Dir. Esta classe disponibiliza diversos métodos que permitem aceder e manipular directórios de forma rápida .Vamos começar por ver um exemplo simples, um pequeno script que verifica se o directório actual é o definido pelo programa. Depois de passar para o referido directório todo o conteúdo é impresso um a um.

1 WORK_DIR = "/home/magician/"
2
3 if Dir.pwd != WORK_DIR
4   Dir.chdir( WORK_DIR )
5 end
6
7 Dir.foreach( WORK_DIR ) { |nome| puts nome }

É possível ainda criar e eliminar directórios de forma muito simples, bastando usar os métodos mkdir e rmdir que a classe Dir dispõe.

1 Dir.mkdir( "/home/magician/Ruby", 755 )
2
3 Dir.rmdir( "/home/magician/Ruby" )

O exemplo acima não faz mais do que criar um directório no caminho dado e com as permissões dadas. Estas permissões são opcionais, podemos apenas dar o caminho. Em seguida eliminamos o directório criado com o método rmdir. Podíamos utilizar o método delete em detrimento ao método rmdir, pois ambos fazem exactamente o mesmo.

A classe dir também permite criar Streams a directórios, permitindo desta forma percorrer o conteúdo de directórios de forma mais flexível do que usando o método foreach mostrado anteriormente.

1  dir = Dir.open( "/home/magician/" )
2
3  dir.path #=> "/home/magician"
4
5  dir.tell #=> 0
6
7  dir.read #=> "Ficheiro 1"
8
9  dir.tell #=> 599320
10
11 dir.read #=> "Ficheiro 2"
12
13 dir.rewind
14
15 dir.close

Como podemos ver na linha 1 é aberta uma ligação ao directório. A partir dai podemos, entre outras coisas, percorrer todos os ficheiros e directórios contidos no directório aberto. O método tell (linha 5 e 9) retorna a localização do apontador sob forma de inteiro, o método read (linha 7 e 11) retorna sob forma de String a próxima entrada no directório, retornando nil quando não existirem mais entradas. Por fim o método rewind (linha 13) permite colocar o apontador da stream de novo no inicio e método close (linha 15) fecha a ligação ao directório.

Ficheiros

Criar um ficheiro não podia ser mais simples, basta utilizar a classe File

1 file = File.new( "exemplo.txt", "w" )

O exemplo acima cria o ficheiro exemplo.txt e abre o ficheiro em modo de escrita. Vamos agora ver que outros modos de abertura e criação de ficheiros existem.

  • “r” – Read-Only. Começa no inicio do ficheiro.
  • “r+” – Read-Write. Começa no inicio do ficheiro.
  • “w” – Write-Only. Passa o tamanho do ficheiro a zero ou cria um novo ficheiro para escrita.
  • “w+” – Read-Write. Passa o tamanho do ficheiro a zero ou cria um novo ficheiro para escrita e leitura.
  • “a” – Write-Only. Começa no fim do ficheiro caso este exista, caso contrário cria o ficheiro para escrita.
  • “a+” – Read-Write. Começa no fim do ficheiro caso este exista, caso contrário cria o ficheiro para escrita e leitura.
  • “b” – (DOS/WIN only) Binary.

Depois de criarmos um ficheiro, podemos precisar de apagar o ficheiro ou até mesmo mudar o nome desse ficheiro. Este processo é também muito simples, para isso a classe File disponibiliza os métodos rename e delete que permitem estas operações.

1 File.new( "exemplo.txt", "w" )
2 File.rename( "exemplo.txt", "novoExemplo.txt" )
3 File.delete( "novoExemplo.txt" )

O exemplo acima cria o ficheiro “exemplo.txt” (linha 1), em seguida o nome do ficheiro é modificado de “exemplo.txt” para “novoExemplo.txt” (linha 2), por fim o ficheiro é eliminado (linha 3).

A classe File permite ainda realizar mais algumas operações úteis, como por exemplo verificar se um ficheiro existe, se é um directório, se é possível ler ou escrever no ficheiro, entre outras. Vamos ver alguns exemplos:

1  File.exist?( "ficheiro.txt" )
2
3  File.file?( "ficheiro.txt" )
4
5  File.directory?( "ficheiro.txt" )
6
7  File.readable?( "ficheiro.txt" )
8
9  File.writable?( "ficheiro.txt" )
10
11 File.executable?( "ficheiro.txt" )
12
13 File.zero?( "ficheiro.txt" )
14
15 File.size( "ficheiro.txt" )
16
17 File.size?( "ficheiro.txt" )

O exemplo mostra algumas das mais importantes operações sobre ficheiros. Na linha 1, o método exist? verifica se o ficheiro existe retornando true ou false, em seguida nas linhas 3 e 5 verificam se o ficheiro dado no path é realmente um ficheiro ou se é um directório. Nas linhas 7 e 9 os métodos readable? e writable? verificam se é possível ler ou escrever no ficheiro e na linha 11 o método executable verifica se o ficheiro é ou não executável. No final do exemplo podemos ver os métodos zero?, size e size?. O método zero? verifica se o ficheiro tem ou não tamanho igual a zero, os métodos size e size? fazem exactamente o mesmo que retornar o tamanho do ficheiro em bytes, com a diferença que caso este seja nulo, o primeiro retorna 0 e o segundo nil. Para além destes métodos é ainda possível obter mais algumas informações sobre os ficheiros como por exemplo data de criação, de edição e do ultimo acesso. Vamos por isso ver um exemplo desta funcionalidades.

1 File.ctime( "ficheiro.txt" )  #=> Thu Jan 21 19:55:16 +0000 2008
2
3 File.mtime( "ficheiro.txt" )  #=> Thu Jan 21 19:55:16 +0000 2008
4
5 File.atime( "ficheiro.txt" )  #=> Thu Jan 25 19:55:16 +0000 2008

Como podemos ver, na linha 1 o método ctime retorna uma String como a apresentada acima com a data e hora a que o ficheiro foi criado, o mesmo se passando com os métodos mtime (linha 3) e atime (linha 5), mas neste caso estes métodos retornam a informação referente à ultima modificação e ao ultimo acesso do ficheiro respectivamente.

A classe File tem ainda mais um recurso bastante útil que é o chmod. Este método permite alterar as permissões de acesso ao ficheiro. As masks para as permissões são as seguintes.

r – read

w – write

x – execute

  • 0700 – rwx para o dono.
  • 0400 – r para o dono.
  • 0200 – w para o dono.
  • 0100 – x para o dono.
  • 0070 – rwx para o grupo.
  • 0040 – r para o grupo.
  • 0020 – w para o grupo.
  • 0010 – x para o grupo.
  • 0007 – rwx para os outros.
  • 0004 – r para os outros.
  • 0002 – w para os outros.
  • 0001 – x para os outros.
  • 4000 – Altera o user ID na execução.
  • 2000 – Altera o group ID na execução.
  • 1000 – Salva texto em swap mesmo depois de usar.

A utilização do método chmod é muito simples, basta abrir o ficheiro como foi mostrado anteriormente e executar o método chmod.

1 file = File.new( "ficheiro.txt", "r" )
2 file.chmod( 0644 )

O exemplo acima dá permissão de de leitura de escrita (4 + 2) ao dono e de leitura ao grupo e todos os outros utilizadores.

Input/Ouput de Ficheiros

Ruby tem ainda mais uma classe muito útil, a classe IO. Esta pode ser usada isoladamente ou em conjunto com a classe File, e permite trabalhar com fluxos de input e output. Em seguida vamos ver alguns exemplos da utilização dos recursos desta classe através da classe File.

1 src = File.open("exemplo.txt","r")
2
3 puts src.readline  #=> Linha 1
4 puts src.readline  #=> Linha 2
5 puts src.readline  #=> Linha 3
6
7 src.flush
8 src.close

O exemplo acima abre o ficheiro em modo de leitura e lê as três primeiras linhas do ficheiro. Podíamos ainda utilizar o método gets que tem exactamente o mesmo funcionamento, com excepção de que o readline lança uma excepção quando o ficheiro chega ao fim. Para além de ler é também possível escrever para o ficheiro.

1 dst = File.open("exemplo.txt","w")
2
3 dst.puts("Linha 1")
4 dst.puts("Linha 2")
5 dst.puts("Linha 3")
6
7 dst.flush
8 dst.close

Este exemplo utiliza o método puts para escrever no ficheiro de destino, a cada execução do puts é automaticamente inserido um “\n”, para escrever sem a inserção de nova linha podemos utilizar o método putc este método coloca um carácter e cada vez. Assim encerramos o artigo com toda a informação básica necessária para trabalhar com ficheiros.

API

Este artigo foi originalmente escrito por Fábio Correira (magician) para a 13ª Edição da Revista PROGRAMAR

Revista Programar – 13ª Edição Março 2008

Revista PROGRAMAR
Edição 13 – Março de 2008
A Revista Programar faz 2 Anos e para comemorar nada melhor do que, uma vez mais, lançar a tempo e horas uma nova edição cheia de informação e conhecimento para todos.Nesta edição poderá encontrar:
– Assinaturas Digitais em XML
– Introdução ao SOAP
– Programação em Lógica com PROLOG
– Algoritmia Clássica em C++
– Manipulação de Ficheiros com Ruby
– Bioinformática – O lado do programador
– Tecnologias Wireless

Mais info.

Java Sockets

Neste artigo vamos ficar a conhecer o suporte que Java oferece para a utilização do mecanismo de comunicação Socket, o mecanismo mais utilizado para a comunicação entre aplicações.Java permite o uso de socktes pelos seguintes modos utilização.

  • Modo Orientado à Conexão – Funciona com o protocolo TCP.
  • Modo Orientado ao Datagrama – Funciona com o protocolo UDP.
Ambos os modos funcionam sobre o protocolo IP(Internet Protocol).
Cada um destes modos tem a sua utilidade, vantagens e desvantagens na sua utilização.Modo Orientado á Conexão (TPC/IP).

  • Vantagens :

– Serviços confiáveis , sem perda de dados na rede e ordem dos pacotes.

– Possibilidade de usar DataStreams.

  • Desvantagens :

– É mais lento que o modo orientado ao datagrama.

– O comportamento do servidor é diferente do comportamento do cliente.Modo Orientado ao

Datagrama (UDP/IP).
  • Vantagens :

– É bastante mais rápido que o modo orientado a conexão.

  • Desvantagens :

– Serviços não confiáveis, mensagens perdidas na rede e perda da ordem das mensagens.

– Cada mensagem é um datagrama : [Remetente, Destinatário, Conteúdo].

Devido a uma maior utilização e estabilidade iremos apenas analisar e implementar o Modo Orientado à Conexão – TCP/IP.

Sockets TCP/IP

O processo de comunicação entre sockets TCP/IP, de uma forma simples, o servidor escolhe uma porta e aguarda conexões a essa porta, o cliente deve conter as seguintes informações :

– Endereço do Servidor (HOST).

– A porta usada pelo servidor(PORT).

Com essa informação o cliente solicita uma conexão ao servidor (Figura 1).

Figura1

Se após o pedido de conexão não ocorrer nenhum problema, o servidor aceita a conexão gerando um socket numa porta do servidor, o que vai criar um canal de comunicação entre o ciente e o servidor (Figura 2).

Figura2

Por norma o servidor funciona em ciclo (loop) esperando por novas conexões e criando sockets para solicitações de clientes.

Em seguida iremos ver as acções necessárias para implementar comunicações sobre TCP através de um socket cliente e um socket servidor.

Socket Client

1 – Abrir Conexão.

import java.io.*;
import java.net.*;
//Conectar ao servidor localhost na porta 8080.
Socket client = new Socket("127.0.0.1",8080);

2 – Obter Streams de entrada e saída para comunicação com o servidor.

//Cria um canal para receber dados.
DataInputStream in = new DataInputStream(client.getInputStream());
//Cria um canal para enviar dados.
DataOutputStream out = new DataOutputStream(client.getOutputStream());

3 – Realizar a comunicação com o servidor.

//Envia o inteiro 3000.
out.writeInt(3000);
//Envia a String “Olá - Socket Cliente.”.
out.writeUTF("Olá - Socket Cliente.");
//Espera pelo recebimento de um inteiro.
int valor = in.readInt();
//Espera pelo recebimento de uma String.
String texto = in.readUTF();

4 – Fechar as Streams e a conexão.

//Fecha o canal de entrada.
in.close();
//Fecha o canal de saída.
out.close();
//Fecha o Socket.
client.close();

Socket Servidor

1 – Criar Socket Server.

import java.io.*;
import java.net.*;
//Cria um socket servidor na porta 8080.
ServerSocket serveer = new ServerSocket(8080);

2 – Aguardar Conexões.

//O método accept retorna um socket para comunicação com o proximo cliente.
Socket sock = server.accept();

3 – Obter Streams de entrada e saída para comunicação com o cliente.

//Cria um canal para receber dados.
DataInputStream in = new DataInputStream(sock.getInputStream());
//Cria um canal para enviar dados.
DataOutputStream out = new DataOutputStream(sock.getOutputStream());

4 – Realizar a comunicação com o cliente.

//Espera pelo recebimento de um inteiro.
int valor = in.readInt();
//Espera pelo recebimento de uma String.
String texto = in.readUTF();
//Envia o inteiro 6000.
out.writeInt(6000);
//Envia a String “Olá - Socket Servidor.”.
out.writeUTF("Olá - Socket Servidor.");

5 – Fechar Streams e socket cliente.

//Fecha o canal de entrada.
in.close();
//Fecha o canal de saída.
out.close();
//Fecha o Socket que está a atender o cliente.
sock.close();

6 – Fechar o socket Servidor.

//Fecha o servidor.
server.close();

Em seguida podemos ver as classes Cliente e Servidor completamente implementadas.

Classe Cliente

1 import java.io.*;
2 import java.net.*;
3
4 public class Cliente{
5	public Socket client;
6	public DataInputStream in;
7	public DataOutputStream out;
8
9	public Cliente(){
10		try{
11			this.client = new Socket("127.0.0.1",8080);
12			this.in = new DataInputStream(client.getInputStream());
13			this.out = new DataOutputStream(client.getOutputStream());
14		}
15		catch(IOException e){
16			System.out.println(e.getMessage());
17		}
18	}
19
20	public static void main(String args[]){
21		try{
22			Cliente cli = new Cliente();
23			cli.out.writeInt(3000);
24			cli.out.writeUTF("Olá - Socket Cliente.");
25			int valor = cli.in.readInt();
26			String texto = cli.in.readUTF();
27			System.out.println(valor);
28			System.out.println(texto);
29			cli.in.close();
30			cli.out.close();
31			cli.client.close();
32		}
33		catch(IOException e){
34			System.out.println(e.getMessage());
35		}
36	}
37 }
Classe Servidor
1 import java.io.*;
2 import java.net.*;
3
4 public class Servidor{
5	public ServerSocket server;
6 	public Socket sock;
7	public DataInputStream in;
8	public DataOutputStream out;
9
10	public Servidor(){
11		try{
12			this.server = new ServerSocket(8080);
13			this.sock = this.server.accept();
14			this.in = new DataInputStream(sock.getInputStream());
15			this.out = new DataOutputStream(sock.getOutputStream());
16		}
17		catch(IOException e){
18			System.out.println(e.getMessage());
19		}
20	}
21
22	public static void main(String args[]){
23		try{
24			Servidor serv = new Servidor();
25			int valor = serv.in.readInt();
26			String texto = serv.in.readUTF();
27			System.out.println(valor);
28			System.out.println(texto);
29			serv.out.writeInt(6000);
30			serv.out.writeUTF("Olá - Socket Servidor.");
31			serv.in.close();
32			serv.out.close();
33			serv.sock.close();
34			serv.server.close();
35		}
36		catch(IOException e){
37			System.out.println(e.getMessage());
38		}
39	}
40 }

Este artigo foi originalmente escrito por Fábio Correira (magician) para a 6ª Edição da Revista PROGRAMAR

IzPack com novo site e nova versão.

O installer IzPack tem agora um novo site e nova versão, foi com grande alegria que deparei com todas estas novidades.

A mais recente versão 3.11.0 lançada a 12-02-2008 teve muitas melhorias e foram corrigidos muitos bugs podem ver no CHANGELOG as alterações.

O IzPack tem agora ainda um boa DOCUMENTAÇÃO e actulizada tornando ainda mais facil a criação de instaladores multi plataforma.

Captcha Dificil

Não podia deixar passar isto um colega enviou-me esta imagem sobre captcha, para quem pensa quem acho que tem o seu site bem protegido com um sistema de captcha então veja este.

Para passar este sistema para além de um bom OCR é preciso também um bom algoritmo matemático para resolver limites.

Serialização de Objectos em Java

Neste artigo iremos falar sobre serialização de objectos em Java. Mas afinal o que é a serialização de objectos? A serialização de objectos é o processo de conversão de um objecto a uma sequência de bytes, assim como o processo que permite passar a sequência de bytes para um objecto utilizável e válido.

A serialização em Java é bastante simples e a API disponível pela SUN para este efeito é muito directa e intuitiva. No decorrer do artigo iremos ver exemplos práticos de serialização binária, e serialização de XML embora esta não faça parte dos padrões de serialização do Java.
Vamos começar por criar uma classe simples que irá depois ser usada para serialização.

1 import java.io.Serializable;
2
3 public class Exemplo1 implements Serializable{
4
5     private int numero;
6     private String nome;
7
8    public Exemplo1(int numero, String nome) {
9         this.numero = numero;
10        this.nome = nome;
11    }
12
13    public String getNome() {
14        return nome;
15    }
16
17    public int getNumero() {
18        return numero;
19    }
20
21    public String toString() {
22        return new String("Numero = "+this.numero+" | Nome = "+this.nome);
23    }
24 }

[Exemplo1.java]

Como podemos ver é uma classe bastante simples e nada fora do normal com duas variáveis “numero” e “nome” (linhas 5 e 6) um construtor (linhas 8 a 11) e os métodos GET (linhas 13 a 19). Para além disso tem também um Override do método toString que retorna uma String com os valores das variáveis. A única parte digamos fora do normal é mesmo na linha 3 a implementação do interface Serializable importado na linha 1 da nossa classe. Este interface vai permitir que os Objectos gerados por esta classe possam ser serializados e vice versa.

Temos agora uma classe de teste que irá serializar os objectos criados da classe Exemplo1.

1 import java.io.File;
2 import java.io.FileInputStream;
3 import java.io.FileOutputStream;
4 import java.io.ObjectInputStream;
5 import java.io.ObjectOutputStream;
6
7 public class Teste1 {
8
9     public static void main(String args []){
10        Exemplo1 e1 = new Exemplo1(001,"White");
11        Exemplo1 e2 = new Exemplo1(002,"Magician");
12
13        System.out.println(e1.toString());
14        System.out.println(e2.toString());
15
16        ObjectOutputStream out;
17        ObjectInputStream in;
18
19        try{
20            out = new ObjectOutputStream(new FileOutputStream(System.getProperty("user.dir")+File.separator+"Exemplo1.bin"));
21            out.writeObject(e1);
22            out.writeObject(e2);
23            out.flush();
24            out.close();
25        }
26        catch(Exception e){
27            e.printStackTrace();
28        }
29
30        Exemplo1 e3;
31        Exemplo1 e4;
32
33        try {
34            in = new ObjectInputStream(new FileInputStream(System.getProperty("user.dir")+File.separator+"Exemplo1.bin"));
35            e3 = (Exemplo1) in.readObject();
36            e4 = (Exemplo1) in.readObject();
37
38            in.close();
39
40            System.out.println(e3.toString());
41            System.out.println(e4.toString());
42        }
43        catch (Exception e){
44            e.printStackTrace();
45        }
46    }
47 }

[Teste1.java]

Esta classe é constituída apenas pelo método main visto que tem por objectivo apenas demonstrar a serialização de objectos para um ficheiro binário. Começamos por criar alguns objectos neste caso dois (linhas 10 e 11) e o conteúdo desses Objectos será impresso no ecrã (linhas 13 e 14) com o auxilio do método toString implementado na classe Exemplo1. Na linha 20 inicializamos um ObjectOutputStream que irá criar um ficheiro binário de nome Exemplo1.bin na directoria actual. Este stream irá permitirá serializar os objectos no ficheiro. Agora basta ordenar que os objectos sejam serializados, para isso usamos o método writeObject(Object object) que irá guardar o objecto dado como argumento no ficheiro de destino sob a forma binária (linhas 21 e 22).
Agora vamos fazer o processo inverso, passar os objectos serializados no ficheiro binário para objectos Java válidos. Este processo é tão simples como o seu inverso. Para isso iremos começar por criar dois novos objectos da classe Exemplo1 sem os inicializar (linhas 30 e 31), em seguida inicializamos um ObjectInputStream (linha 34) que irá ler o ficheiro Exemplo1.bin criado anteriormente, este stream contém o método readObject() que lê um objecto serializado num ficheiro. Como podemos ver nas linhas 35 e 36 os objectos retornados pelo método readObject() são guardados nas variáveis e3 e e4 criadas anteriormente, podemos ver também que nestas mesmas linhas é feito um cast para (Exemplo1) o cast deve ser sempre feito porque embora os objectos serializados sejam do tipo Exemplo1 o método readObject() retorna o tipo genérico Object que depois dever ser convertido para o tipo original desse objecto.
Por fim vamos imprimir os objectos e3 e e4 tal como fizemos anteriormente para o e1 e e2 e se todo o processo correr normalmente o valor de e1 será igual a e3 e o mesmo acontece com e2 e e4, o output. Neste caso será algo como o que podemos ver em seguida.

Numero = 1 | Nome = White
Numero = 2 | Nome = Magician
Numero = 1 | Nome = White
Numero = 2 | Nome = Magician

Vamos agora ver uma outra forma de serialização, embora use o mesmo mecanismo que o exemplo anterior neste exemplo os objectos serão serializados para um ByteArray, está técnica pode ser bastante útil para envio de grandes quantidades de objectos pela rede.
Para isso vamos usar novamente a classe Exemplo1 e uma nova classe de teste semelhante á classe Teste1 usada anteriormente.

1 import java.io.ByteArrayInputStream;
2 import java.io.ByteArrayOutputStream;
3 import java.io.ObjectInputStream;
4 import java.io.ObjectOutputStream;
5
6 public class Teste2 {
7    
8    public static void main(String args []){
9        Exemplo1 e1 = new Exemplo1(001,"White");
10        Exemplo1 e2 = new Exemplo1(002,"Magician");
11        
12        System.out.println(e1.toString());
13        System.out.println(e2.toString());
14        
15        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
16        ObjectOutputStream out;
17        ObjectInputStream in;
18        
19        try{
20            out = new ObjectOutputStream(buffer);
21            out.writeObject(e1);
22            out.writeObject(e2);
23            out.flush();
24            out.close();
25        }
26        catch(Exception e){
27            e.printStackTrace();
28        }
29        
30        Exemplo1 e3;
31        Exemplo1 e4;
32        
33        try {
34            in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray()));
35            e3 = (Exemplo1) in.readObject();
36            e4 = (Exemplo1) in.readObject();
37            
38            in.close();
39            
40            System.out.println(e3.toString());
41            System.out.println(e4.toString());
42            
43        }
44        catch (Exception e){
45            e.printStackTrace();
46        }
47    }
48 }

[Teste2.java]

Há semelhança do exemplo anterior nesta classe são criados dois objectos da classe Exemplo1 e em seguida os seu valores são impressos.
As diferenças principais começam agora, na linha 15 criamos um ByteArrayOutputStream que será usado como buffer para guardar os objectos serializados, na linha 20 e á semelhança do exemplo anterior inicializamos o ObjectOutputStream mas ao contrário do primeiro exemplo em que damos como argumento um FileOutputStream usado para escrever no ficheiro neste caso dados como argumento o ByteArray criado, assim cada objecto serializado será guardado no ByteArray e não num ficheiro.
Nas linhas 21 e 22 são serializados dois objectos Exemplo1 para o ByteArray que será usado mais tarde.
ByteArrayOutputStream que criamos pode ser usado para varias coisas recorrendo ao método toByteArray() que retorna uma array de bytes com todos os dados do nosso ByteArrayOutputStream, as utilidades deste array são inúmeras mas neste casa vamos apenas usa-lo para construir um ObjectInputStream (linha 34), e assim iremos conseguir recuperar os nossos objectos serializados (linhas 35 e 36).
Por fim vamos imprimir valores dos nossos novos objectos e iremos ver que os valores coincidem tal como no exemplo anterior.

Numero = 1 | Nome = White
Numero = 2 | Nome = Magician
Numero = 1 | Nome = White
Numero = 2 | Nome = Magician

Para terminar este artigo sobre serialização de objectos em Java iremos fazer o processo usando XML ao invés do ficheiro binário como vimos anteriormente. Comecemos por criar uma classe Exemplo2.java.

1 public class Exemplo2 {
2    
3     private int numero;
4     private String nome;
5    
6     public Exemplo2 (){
7        
8     }
9    
10   public Exemplo2(int numero, String nome){
11        this.numero = numero;
12        this.nome = nome;
13   }
14
15   public void setNome(String nome) {
16        this.nome = nome;
17   }
18
19   public void setNumero(int numero) {
20        this.numero = numero;
21   }
22
23   public String getNome() {
24        return nome;
25   }
26
27   public int getNumero() {
28        return numero;
29   }
30    
31   public String toString() {
32        return new String("Numero = "+this.numero+" | Nome = "+this.nome);
33   }
34    
35 }

[Exemplo2.java]

Para começar vamos ver algumas diferenças de implementação na classe Exemplo2 para a classe Exemplo1 neste novo caso a classe não implementa a interface Serializable como acontecia nos exemplos anteriores, para além disso este processo requer alguns cuidados a classe deve conter os métodos set e get para todas as variáveis do objecto que queremos serializar em contras-te ao exemplos anteriores que apenas implementávamos os métodos que queríamos. Deve também ser implementado o construtor vazio (linhas 6 a 8 ). Atenção que a serialização para XML apenas consegue serializar dados com visibilidade public logo os métodos get e set bem como os construtor deve ser public caso contrario o valores não serão serializados.
Vejamos agora a classe que irá proceder á serialização.

1 import java.beans.XMLDecoder;
2 import java.beans.XMLEncoder;
3 import java.io.BufferedInputStream;
4 import java.io.BufferedOutputStream;
5 import java.io.File;
6 import java.io.FileInputStream;
7 import java.io.FileOutputStream;
8
9 public class SerialXML {
10    
11    public static void main(String args []){
12        Exemplo2 e1 = new Exemplo2(001,"White");
13        Exemplo2 e2 = new Exemplo2(002,"Magician");
14
15       System.out.println(e1.toString());
16        System.out.println(e2.toString());
17        
18        try{
19            XMLEncoder encoder = new XMLEncoder( new BufferedOutputStream(
                   new FileOutputStream(System.getProperty("user.dir")+File.separator+"Exemplo2.xml")));
20            encoder.writeObject(e1);
21            encoder.writeObject(e2);
22            encoder.flush();
23            encoder.close();
24        }
25        catch(Exception e){
26            e.printStackTrace();
27        }
28        
29        Exemplo2 e3;
30        Exemplo2 e4;
31        
32        try{
33            XMLDecoder decoder = new XMLDecoder(new BufferedInputStream(
                   new FileInputStream(System.getProperty("user.dir")+File.separator+"Exemplo2.xml")));
34            e3 = (Exemplo2)decoder.readObject();
35            e4 = (Exemplo2)decoder.readObject();
36            decoder.close();
37            
38            System.out.println(e3.toString());
39            System.out.println(e4.toString());
40        }
41        catch(Exception e){
42            e.printStackTrace();
43        }
44    }
45 }

[SerialXML.java]

Para este processo precisamos das classes java.beans.XMLEncoder para gravar os objectos e a classe java.beans.XMLDecoder para o recuperar do arquivo XML (linhas 1 e 2).
Tal como nos exemplos anteriores esta classe apenas contém o métodos main e começa por criar dois objectos da classe Exemplo2 e em seguida os seus valores são impressos.
Vamos agora passar ao processo de serialização, para isso começamos por criar um objecto XMLEncoder (linha 19) que vai permitir serializar os objectos em um ficheiro XML, para isso e á semelhança do exemplos anteriores iremos usar o método writeObject (linhas 20 e 21). Depois destes passos já devemos uma ficheiro Exemplo2.xml com o seguinte conteúdo.

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.6.0" class="java.beans.XMLDecoder">
 <object class="Exemplo2">
  <void property="nome">
   <string>White</string>
  </void>
  <void property="numero">
   <int>1</int>
  </void>
 </object>
 <object class="Exemplo2">
  <void property="nome">
   <string>Magician</string>
  </void>
  <void property="numero">
   <int>2</int>
  </void>
 </object>
</java>

Como se pode ver o ficheiro contém os objectos serializados a apresentação dos dados é bastante bem estruturada e simples de ler e até mesmo editar, alias esta é uma das vantagens da serialização face à serialização binaria.
Temos agora o processo inverso passar de XML para objectos, para isso vamos criar um objecto XMLDecoder (linha 33) e vamos usar o método readObject disponibilizado por este objecto (linhas 34 e 35).
Por fim e tal como nos exemplos anteriores teremos o seguinte output.

Numero = 1 | Nome = White
Numero = 2 | Nome = Magician
Numero = 1 | Nome = White
Numero = 2 | Nome = Magician

Para finalizar a serialização de objectos basta apenas acrescentar que na serialização binária é possível definir determinadas variáveis ou constantes de forma a que no momento da serialização estes não sejam serializados. Para isso basta usar a palavra reservada transient, a sua utilização é bastante simples basta colocar a palavra transient antes do tipo e nome da variável ou seja algo semelhante a private transient String password; assim a variável password não será serializada.

Podem ainda consultar as APIs utilizadas neste artigo em:
http://java.sun.com/javase/6/docs/api/java/io/Serializable.html
http://java.sun.com/javase/6/docs/api/java/io/ObjectOutputStream.html
http://java.sun.com/javase/6/docs/api/java/io/ObjectInputStream.html
http://java.sun.com/javase/6/docs/api/java/beans/XMLEncoder.html
http://java.sun.com/javase/6/docs/api/java/beans/XMLDecoder.html

Este artigo foi originalmente escrito por Fábio Correira (magician) para a 10ª Edição da Revista PROGRAMAR