Artigo Sockets

19
Programação de Sockets em Java Atualmente, a programação em redes de computadores tem se tornado uma regra, deixando de ser apenas uma tecnologia presente em poucos sistemas, mas que vem ganhando espaço nos mais diversos tipos de aplicações. A principal vantagem nesse modelo de programação é a capacidade, física e lógica, de distribuir operações computacionalmente pesadas e grandes quantidades de dados e informações entre diversas máquinas que, trabalhando em conjunto, ocasionam a descentralização de tarefas. O modelo de programação distribuída, realizada através da separação de aplicações entre servidores (aplicações que fornecem serviços a serem consumidos) e clientes (aplicações que consomem serviços disponibilizados por outras máquinas), foi a principal arquitetura de distribuição nos anos de 1990. Por meio desta arquitetura, houve aumento na confiabilidade e redução de custos. A confiabilidade tornou-se um fator importante nesta arquitetura, pois, mesmo havendo falha durante um processo em uma determinada máquina, o sistema não se inviabilizava como um todo, e com a utilização de máquinas mais simples executando serviços isoladamente, houve uma grande diminuição nos custos para execução de operações por máquinas mais poderosas. Os aplicativos de servidor e cliente são programas que, executados em diferentes máquinas, trocam informações por meio de redes de computadores. A aplicação cliente é capaz de realizar requisições de serviços que são providos pela aplicação servidora. Para isso, o consumidor (ou cliente) deve conhecer o fornecedor dos serviços (ou servidor) a partir de um endereço além de ter conhecimento sobre o protocolo pré-estabelecido utilizado para realizar a solicitação. No entanto, pelo fato de haver a necessidade de trocas de mensagens entre as máquinas envolvidas no processo, há um custo e tempo adicional para efetivar essa troca. Outra característica importante é que os dispositivos envolvidos na troca de mensagens devem utilizar a mesma linguagem ou protocolo. Em uma rede de computadores há diversos protocolos, organizando regras sobre o modo como são realizadas as comunicações entre as partes envolvidas em uma comunicação. Um dos principais conjuntos de protocolos na programação de redes de computadores é estabelecido pela arquitetura TCP/IP, operando por meio de um software oferecido pelo sistema operacional a partir de uma máquina ligada na rede. A tecnologia Java suporta a troca de bytes entre um cliente e um servidor TCP através do estabelecimento de uma conexão entre eles. Pode-se entender uma conexão como uma ligação direta realizada entre dois processos, na qual dados podem fluir nos dois sentidos. Para estabelecer uma conexão TCP, as extremidades das máquinas envolvidas nos processos de servidor e cliente devem ser identificadas. As extremidades dessa conexão são denominadas de sockets ou soquetes, identificados por um endereço de rede e um número de porta.

Transcript of Artigo Sockets

Page 1: Artigo Sockets

Programação de Sockets em Java

Atualmente, a programação em redes de computadores tem se tornado uma regra, deixando

de ser apenas uma tecnologia presente em poucos sistemas, mas que vem ganhando espaço

nos mais diversos tipos de aplicações. A principal vantagem nesse modelo de programação é a

capacidade, física e lógica, de distribuir operações computacionalmente pesadas e grandes

quantidades de dados e informações entre diversas máquinas que, trabalhando em conjunto,

ocasionam a descentralização de tarefas.

O modelo de programação distribuída, realizada através da separação de aplicações entre

servidores (aplicações que fornecem serviços a serem consumidos) e clientes (aplicações que

consomem serviços disponibilizados por outras máquinas), foi a principal arquitetura de

distribuição nos anos de 1990. Por meio desta arquitetura, houve aumento na confiabilidade e

redução de custos.

A confiabilidade tornou-se um fator importante nesta arquitetura, pois, mesmo havendo falha

durante um processo em uma determinada máquina, o sistema não se inviabilizava como um

todo, e com a utilização de máquinas mais simples executando serviços isoladamente, houve

uma grande diminuição nos custos para execução de operações por máquinas mais poderosas.

Os aplicativos de servidor e cliente são programas que, executados em diferentes máquinas,

trocam informações por meio de redes de computadores. A aplicação cliente é capaz de

realizar requisições de serviços que são providos pela aplicação servidora. Para isso, o

consumidor (ou cliente) deve conhecer o fornecedor dos serviços (ou servidor) a partir de um

endereço além de ter conhecimento sobre o protocolo pré-estabelecido utilizado para realizar

a solicitação.

No entanto, pelo fato de haver a necessidade de trocas de mensagens entre as máquinas

envolvidas no processo, há um custo e tempo adicional para efetivar essa troca.

Outra característica importante é que os dispositivos envolvidos na troca de mensagens

devem utilizar a mesma linguagem ou protocolo. Em uma rede de computadores há diversos

protocolos, organizando regras sobre o modo como são realizadas as comunicações entre as

partes envolvidas em uma comunicação. Um dos principais conjuntos de protocolos na

programação de redes de computadores é estabelecido pela arquitetura TCP/IP, operando por

meio de um software oferecido pelo sistema operacional a partir de uma máquina ligada na

rede.

A tecnologia Java suporta a troca de bytes entre um cliente e um servidor TCP através do

estabelecimento de uma conexão entre eles.

Pode-se entender uma conexão como uma ligação direta realizada entre dois processos, na

qual dados podem fluir nos dois sentidos.

Para estabelecer uma conexão TCP, as extremidades das máquinas envolvidas nos processos

de servidor e cliente devem ser identificadas. As extremidades dessa conexão são

denominadas de sockets ou soquetes, identificados por um endereço de rede e um número de

porta.

Page 2: Artigo Sockets

Neste artigo, você irá aprender os conceitos de computação distribuída por meio de sockets

utilizando a tecnologia Java, bem como aplicar todo o conhecimento adquirido, facilitando

assim o desenvolvimento e comunicação de aplicações cliente-servidor.

O que são sockets?

Socket ou soquete é apenas um conceito ou uma abstração. O termo socket é utilizado para

representar um ponto de conexão para uma rede de computadores que utiliza o protocolo

TCP/IP. Quando dois computadores necessitam manter uma comunicação, cada um deles

utiliza um socket.

Um computador, denominado server ou servidor, disponibiliza um socket e aguarda o

recebimento de uma solicitação de conexão, enquanto outro computador, denominado client

ou cliente, executa um socket para se comunicar à máquina servidora, conforme demonstrado

na Figura 1. Para estabelecer uma nova conexão, é necessário apenas um endereço de destino

e um número de porta.

Caso não ocorra nenhum problema, o servidor aceita a conexão gerando um novo socket em

uma porta qualquer do seu lado, criando assim um canal de comunicação entre o cliente e o

servidor. A Figura 2 demonstra esse canal de comunicação.

Cada computador em uma rede TCP/IP possui um endereço exclusivo. As portas representam

conexões individuais dentro desse endereço, porém os dados transmitidos passam por um

processo de roteamento dentro de cada computador a partir de um número da porta. Quando

um socket é criado, ele necessita estar associado a uma determinada porta e deve haver

apenas um socket associado a essa porta.

A principal característica de uma máquina servidora é a de receber e manipular conexões a

partir de solicitações dos clientes, portanto, seu comportamento típico é o de permanecer em

loop aguardando novas conexões e criando sockets com a finalidade de atender as requisições

dos clientes, conforme demonstrado na Figura 3.

Figura 1. Socket de servidor aguardando conexões.

Page 3: Artigo Sockets

Figura 2. Canal de comunicação criado após conexão de cliente ser recebida.

Figura 3. Aplicação cliente-servidor baseada em conexões.

Sockets em Java

Assim como todas as outras funcionalidades fornecidas pela tecnologia Java, existe um

pacote específico que contém os mecanismos necessários para se trabalhar com sockets: o

pacote java.net.

O pacote java.net contém todas as classes necessárias para criar aplicações de rede. As

classes ServerSocket e Socket também fazem parte desse pacote e são utilizadas para

aplicações que fazem uso do protocolo TCP. Além dessas classes, também existem outras para

conexão com servidores web, criação de secure sockets ou sockets seguros, entre outras

funcionalidades.

Page 4: Artigo Sockets

Para mais informações, veja a seção “Modos de transmissão de sockets”.

A classe ServerSocket é responsável pela manipulação de sockets do lado do servidor. Por

meio dela, é possível disponibilizar um socket para receber conexões e manipular um novo

socket quando uma requisição de conexão é realizada. Uma vez que uma requisição de

conexão é realizada, o socket de servidor criará um novo socket para que seja possível manter

uma conexão direta com o cliente que requisitou o canal de comunicação.

A classe Socket é responsável pela manipulação de sockets do lado do cliente.

Utilizando-se um socket desse tipo, é possível realizar a solicitação de conexão com um socket

fornecido por um servidor, desde que este esteja aguardando uma requisição por meio de

uma porta previamente disponibilizada.

O estabelecimento da conexão entre duas aplicações dá-se por meio da instanciação de um

objeto da classe Socket, a partir do processo cliente. Uma vez que a conexão entre o cliente e

servidor tenha sido estabelecida pela criação dos correspondentes sockets, os dados da

aplicação podem fluir através de streams a ela associados.

Quando há a necessidade de utilização do protocolo UDP, deve-se utilizar as classes

DatagramSocket e DatagramPacket também presentes no pacote java.net.

A classe DatagramSocket é responsável pelo envio e recebimento de datagramas. Utiliza-se

esse tipo de socket tanto para aplicações servidoras quanto para clientes. Como não há

controle da sequência de envio e recebimento de datagramas, múltiplos pacotes enviados de

uma máquina a outra podem ser roteados de forma diferente e até chegar em ordens

diferentes, além de que a entrega de pacotes não é garantida.

A classe DatagramPacket é responsável pela inserção de dados na forma de bytes em um

pacote UDP denominado datagrama.

Os pacotes de datagramas são utilizados para implementar um serviço de entrega de pacotes

sem conexão. Cada mensagem é roteada da máquina remetente até a destinatária baseando-

se na informação contida dentro do datagrama.

Nota Devman 1: Datagrams ou Datagramas: Um datagrama é, em uma rede de computadores, uma estrutura unitária utilizada para transmitir uma

sequência de dados em um canal de comunicação que utilize a comutação de pacotes. Uma informação a ser transmitida geralmente é quebrada em

inúmeros pacotes, facilitando e agilizando seu envio pela rede. Juntamente com a informação, o datagrama possui um cabeçalho, contendo

informações que auxiliam sua transmissão, como os endereços do destinatário e remetente, soma para checagem de erros (checksum), definição

de prioridades, entre outros dados.

Modos de transmissão de sockets

A tecnologia de sockets permite o desenvolvimento de aplicações distribuídas a partir de dois

modos principais de operação: o modo baseado em conexões e o modo sem conexão.

Analogicamente, é possível comparar o modo baseado em conexões com um telefone;

eles têm de estabelecer uma conexão e suspender a ligação. Todos os dados que fluem entre

esses dois eventos chegam na mesma ordem em que foram transmitidos. Entretanto, os

sockets não orientados a conexão podem ser comparados ao sistema de correios, onde a

entrega não é garantida, e os diferentes itens de correspondência podem chegar em uma

ordem diferente daquela em que foram enviados.

Page 5: Artigo Sockets

A escolha do modo a ser utilizado é determinada a partir das necessidades de uma aplicação

distribuída. Caso a confirmação de entrega e a sequência de dados sejam importantes, então a

operação baseada em conexões é, sem dúvidas, a melhor opção. Por exemplo, os servidores

de arquivos precisam fazer todos os seus dados chegarem corretamente e em sequência. Se

alguma parte dos dados se perdesse, a utilidade da aplicação servidora seria invalidada.

Porém, o processo realizado pelas aplicações para garantir a sequência e a correção dos dados

exigem processamento extra e maior utilização da memória, ocasionando uma redução no

tempo de resposta de um servidor.

O modo baseado em conexões utiliza o TCP ou Transport Control Protocol para se estabelecer

uma conexão com o dispositivo de destino antes de transmitir os dados.

Assim que a conexão for estabelecida, os sockets são acessados por uma interface de fluxo de

dados, representando as operações de: abertura, leitura, escrita e fechamento.

Por meio dessa conexão, tudo que é enviado por um socket é recebido pela outra extremidade

da conexão, na mesma ordem em que foi transmitido. Apesar de a operação baseada em

conexões ser menos eficiente, pelo tempo extra de processamento e resposta, do que a

operação sem conexão, ela é mais garantida e confiável.

O modo sem conexão utiliza o UDP ou User Datagram Protocol. O termo datagram ou

datagrama é utilizado para determinar uma unidade autônoma que possui todas as

informações necessárias para tentar realizar sua entrega. Analogicamente, um datagrama

possui as mesmas características que um envelope, pois possui os endereços de destinatário e

remetente além de conter em seu interior os dados a serem transmitidos. Um socket nesse

modo de operação não necessita estabelecer uma conexão com um socket de destino; ele

simplesmente envia o datagrama. Caso o dispositivo de destino esteja esperando o datagrama,

ele o recebe; caso contrário, o datagrama é perdido. A operação sem conexão é rápida e

eficiente, porém não garante a entrega nem a ordem dos dados.

Um pouco sobre streams...

Em Java, utiliza-se o termo stream para conceituar quase todas as operações que se baseiam

nas mais diferentes formas de se realizar a leitura e escrita de dados a partir das mais diversas

fontes e para os mais diferentes destinos.

Uma stream é uma abstração que representa uma fonte genérica de entrada de dados ou um

destino genérico para escrita de dados, de acesso sequencial e independente de dispositivos

físicos, formatos e até mesmo de mecanismos de otimização de leitura e escrita. Por ser uma

abstração, deve sempre ser associada a uma entidade física de suporte a dados, como um

disco rígido, um CD-ROM, um DVD-ROM, outro computador da rede, entre outros dispositivos.

A Figura 4 exemplifica o uso de streams.

Page 6: Artigo Sockets

Figura 4. Streams como abstração de leitura e escrita.

Para que seja possível ler uma determinada informação, um programa abre uma stream sobre

uma fonte de dados, por exemplo, um socket, e lê essa informação sequencialmente, byte a

byte ou caractere a caractere.

Inversamente, um programa pode enviar informação para um destino externo abrindo uma

stream de escrita e escrevendo dados de modo sequencial no fluxo de informações.

Para a leitura sequencial de bytes utiliza-se um objeto da classe InputStream, obtido como

retorno do método getInputStream(). Para transferência de texto, a ponte DataInputStream

pode ser utilizada para usar um reader. Similarmente, para a transferência de dados no

sentido deste socket para o outro extremo da conexão, utiliza-se um objeto da classe

OutputStream. Para transferência de texto neste sentido, a ponte utilizada é

DataOutputStream, permitindo a obtenção de um writer associado a esse stream.

Desenvolvimento de sockets TCP

Conforme apresentado anteriormente, sockets TCP ou orientado a conexões são altamente

utilizados quando se necessita de confirmação de conexão entre o cliente e o servidor para

que seja possível a transmissão correta e confiável de dados. Para essa comunicação, também

se torna necessária a definição de um fluxo de dados, para que os dados sejam enviados e

recebidos sequencialmente, garantindo o sucesso da operação.

Implementação de socket TCP de servidor

A principal funcionalidade do socket do lado do servidor é a de aguardar por solicitações de

conexões e, após serem requeridas, tratá-las e servi-las.

Para realizar a implementação de um socket de servidor, existem alguns passos a serem

seguidos:

1. Estabelecer um socket de servidor que monitore uma determinada porta para receber

conexões. Para isso, deve-se criar uma instância da classe ServerSocket, seguindo uma das

quatro maneiras possíveis:

a. ServerSocket() - Simplesmente cria uma instância de ServerSocket conforme sua

implementação nativa do Java, buscando os valores padrões;

Page 7: Artigo Sockets

b. ServerSocket(int porta) - Cria uma instância de ServerSocket e vincula o socket a uma

determinada porta;

c. ServerSocket(int porta, int backlog) - Além de criar uma instância de ServerSocket e vinculá-

la a uma determinada porta, também define o tamanho da fila de requisições;

d. ServerSocket(int porta, int backlog, InetAddress endereco) - Cria uma instância de

ServerSocket, vincula-a a uma determinada porta estabelecendo o tamanho da fila de

requisições e associa-a ao endereço InetAddress passado como parâmetro.

ServerSocket server = new ServerSocket(1234);

2. A próxima etapa é determinar a criação de uma socket assim que uma requisição de

conexão for realizada.

Para isso, deve-se recuperar a instância de Socket utilizando-se o método accept() do socket

do servidor;

Socket socket = server.accept();

3. Aceitando a requisição de conexão vinda de um cliente e tendo recuperado o socket por

meio do método accept(), deve-se criar um canal de comunicação direita entre o servidor e o

cliente. Para ser possível receber dados pelo canal de comunicação, recupera-se uma instância

de DataInputStream do socket do cliente por meio do método getInputStream(). Para ser

possível enviar dados pelo canal de comunicação, recupera-se uma instância de

DataOutputStream do socket recuperado durante a conexão por meio do método

getOutputStream().

DataInputStream dis = new DataInputStream

(socket.getInputStream());

DataOutputStream dos = new DataOutputStream

(socket.getOutputStream());

4. Utilizando-se essas duas instâncias do socket recuperado e invocando os métodos

necessários, já é possível receber e enviar dados de diversos tipos como boolean, byte, char,

double, float, int, long, short e cadeias de caracteres por meio de UTF (Unicode Transformation

Format). Para exemplificar segue, respectivamente, as codificações para recebimento e envio

de dados UTF, utilizando-se os métodos readUTF() e writeUTF();

String mensagem = dis.readUTF();

dos.writeUTF(mensagem);

5. Assim que todo o processo de requisição e processamento com o cliente for concluído, o

servidor deve encerrar os canais de comunicação utilizando-se o método close() das instâncias

de DataInputStream e DataOutputStream, assim como a instância do socket do cliente e

também do socket do servidor.

Page 8: Artigo Sockets

dis.close();

dos.close();

socket.close();

server.close();

A Listagem 1 apresenta uma implementação de socket TCP de servidor. Em resumo, é criada

uma instância de ServerSocket, aguardando conexões na porta “1234”. Assim que uma nova

conexão é solicitada a esse servidor, um socket de cliente é recuperado pelo método accept().

Por meio do socket recuperado, obtém-se uma instância para controlar o fluxo de dados para

recebimento de dados (DataInputStream) e outra para o envio de dados (DataOutputStream).

Utilizando-se o método readUTF() aguardamos o recebimento de uma string pelo canal de

comunicação que, em seguida, é exibido no console Java. Em resposta, o servidor envia outra

cadeia de caracteres pelo canal de comunicação por meio do método writeUTF() e encerra

tanto as instâncias de controle de dados stream como os sockets por meio do método close().

package servidor;

import java.io.DataInputStream;

import java.io.DataOutputStream;

import java.net.ServerSocket;

import java.net.Socket;

import java.util.Date;

public class SocketServidor {

private static final int PORTA = 1234;

public static void main(String[] args) {

try {

ServerSocket server = new ServerSocket(PORTA);

Socket socket = server.accept();

DataInputStream dis = new DataInputStream

(socket.getInputStream());

DataOutputStream dos = new DataOutputStream

(socket.getOutputStream());

String mensagem = dis.readUTF();

System.out.println("Servidor: Mensagem recebida =>

\"" + mensagem + "\"");

mensagem = "Mensagem recebida com sucesso às " + new Date();

System.out.println("Servidor: Mensagem enviada =>

\"" + mensagem + "\"");

dos.writeUTF(mensagem);

dis.close();

dos.close();

socket.close();

server.close();

} catch (Exception ex) {

/* tratar exceção */

}

}

}

Listagem 1. SocketServidor.java: Implementação de socket TCP de servidor

Tendo desenvolvido a aplicação do lado do servidor, basta implementar a aplicação do lado do

cliente, responsável por conectar-se ao socket e realizar as requisições de envio e recebimento

de dados.

UTF ou Unicode Transformation Format: É um padrão estabelecido pela Unicode Consortium

que permite aos computadores representar e manipular, de maneira consistente, textos de

qualquer escrita existente, sendo também compatível com o ASCII.

Page 9: Artigo Sockets

Implementação de socket TCP de cliente

O principal objetivo do socket do lado do cliente é o de solicitar uma conexão com um socket

de servidor e comunicar-se por meio de um canal de comunicação estabelecido logo após a

requisição de conexão. A implementação de uma aplicação cliente baseada em conexões pode

ser realizada seguindo as instruções:

1. O primeiro passo é solicitar a conexão com um servidor, seguindo uma das duas maneiras

disponíveis:

a. Criando uma instância de Socket. Para isso, deve-se informar apenas o nome ou IP do host

de conexão e a porta disponibilizada pelo socket de servidor. Para criar uma instância de

Socket, é necessário utilizar um dos sete construtores disponíveis na classe Socket, sendo os

mais usados:

• Socket() - Simplesmente cria uma instância de Socket conforme sua implementação nativa

do Java, buscando os valores padrões;

• Socket(InetAddress endereco, int porta) - Cria uma instância de Socket, conectando-se a um

endereço específico e a uma determinada porta;

• Socket(String host, int porta) - Cria uma instância de Socket, conectando-se a um host

específico e a uma determinada porta.

Socket socket = new Socket(“127.0.0.1”, 1234);

b. Conectando ao servidor sem a passagem de parâmetros durante a instanciação de Socket.

Neste caso, torna-se necessário utilizar a classe SocketAddress.

Socket socket = new Socket();

socket.connect(new SocketAddress(“127.0.0.1”, 1234));

2. A criação das instâncias de manipulação de stream do lado do cliente é exatamente igual a

do servidor. Para ser possível receber dados pelo canal de comunicação, recupera-se uma

instância de DataInputStream do socket recuperado por meio do método getInputStream().

Para ser possível enviar dados pelo canal de comunicação, recupera-se uma instância de

DataOutputStream do socket do cliente durante a conexão por meio do método

getOutputStream().

DataInputStream dis = new DataInputStream

(socket.getInputStream());

DataOutputStream dos = new DataOutputStream

(socket.getOutputStream());

3. Utilizando-se as instâncias de entrada e saída de dados do canal de comunicação,

recuperados a partir do socket conectado, já é possível receber e enviar informações de

Page 10: Artigo Sockets

diversos tipos. Para exemplificar segue, respectivamente, as instruções utilizadas para

recebimento e envio de dados UTF, por meio dos métodos readUTF() e writeUTF():

String mensagem = dis.readUTF();

dos.writeUTF(mensagem);

4. Assim que todo o processo de requisição e processamento com o cliente for concluído, o

servidor deve encerrar os canais de comunicação utilizando-se o método close() das instâncias

de DataInputStream e DataOutputStream, bem como finalizar as instâncias dos sockets do

cliente e do servidor.

dis.close();

dos.close();

socket.close();

server.close();

A Listagem 2 apresenta uma implementação de socket TCP de cliente. Um socket de cliente é

criado e tenta conectar-se ao endereço “127.0.0.1” por meio da porta “1234”. Utiliza-se o

endereço “127.0.0.1” ou “localhost” quando se deseja realizar testes de conexão na máquina

local, funcionando tanto como servidor como cliente. Caso o servidor esteja sendo executado

em alguma outra máquina na rede, necessita-se recuperar o IP da máquina e trocá-lo na

aplicação desenvolvida.

Assim que o socket da aplicação cliente obter sucesso na conexão com o socket da aplicação

servidora, é necessário obter uma instância para controlar o fluxo de dados para recebimento

de dados (DataInputStream) e outra para o envio de dados (DataOutputStream). Utilizando-se

o método writeUTF() é possível enviar uma string pelo canal de comunicação criado entre o

cliente e o servidor. Após o envio da cadeia de caracteres da aplicação cliente para a aplicação

servidora, utiliza-se o método readUTF() para fazer com que a aplicação aguarde o

recebimento de dados pelo canal de comunicação. Assim que uma string for transmitida em

resposta pelo servidor, ela será exibida no console Java. Ao final do fluxo de dados e do

processamento, encerram-se tanto as instâncias de controle de dados stream como os sockets

através do método close().

package cliente;

import java.io.DataInputStream;

import java.io.DataOutputStream;

import java.net.Socket;

import java.util.Date;

public class SocketCliente {

private static final String HOST = "127.0.0.1";

private static final int PORTA = 1234;

public static void main(String[] args) {

try {

Socket socket = new Socket(HOST, PORTA);

DataInputStream dis = new DataInputStream

(socket.getInputStream());

DataOutputStream dos = new DataOutputStream

(socket.getOutputStream());

String mensagem = "Conectado às " + new Date();

System.out.println("Cliente: Mensagem enviada =>

\"" + mensagem + "\"");

Page 11: Artigo Sockets

dos.writeUTF(mensagem);

mensagem = dis.readUTF();

System.out.println("Cliente: Mensagem recebida =>

\"" + mensagem + "\"");

dis.close();

dos.close();

socket.close();

} catch (Exception ex) {

/* tratar exceção */

}

}

}

Listagem 2. SocketCliente.java: Implementação de socket TCP de cliente.

Após o desenvolvimento do servidor, conforme Listagem 1, e do cliente, demonstrado na

Listagem 2, é possível testar a comunicação entre essas duas aplicações.

Para isso, deve-se executar, primeiramente, o servidor e, logo em seguida, o cliente. Assim que

a conexão for estabelecida com sucesso, os aplicativos Java serão capazes de enviar e receber

mensagens na forma de string pelo canal de comunicação criado por meio dos sockets de rede.

Desenvolvimento de sockets UDP

Conforme apresentado anteriormente, sockets UDP ou não orientado a conexões são

utilizados quando não há a necessidade de estabelecer uma conexão entre a máquina cliente e

a servidora. Um socket UDP envia um datagram ou datagrama que, similar a um envelope,

contém informações sobre o destinatário, remetente e os dados a serem enviados.

Comparado ao modelo orientado a conexão, o protocolo UDP é mais rápido e eficiente, porém

a entrega e sequência das informações não é garantida ou confirmada, cabendo ao programa

se responsabilizar pela validação e segurança. Com um socket UDP, caso o destinatário não

esteja aguardando uma mensagem, ela é perdida. A Figura 5 demonstra o envio de um

datagrama por meio de um socket. O cabeçalho UDP é extremamente simples, contendo

apenas os números das portas de origem e destino, comprimento da mensagem e checksum,

um campo utilizado para conferência da integridade dos dados transmitidos.

Os campos em laranja, exibidos na Figura 5, são opcionais.

Figura 5. Envio de datagrama por socket UDP.

Page 12: Artigo Sockets

Para o desenvolvimento de aplicações que fazem uso do protocolo UDP, a tecnologia Java

conta com as classes DatagramSocket e DatagramPacket, presentes no pacote java.net, assim

como as classes ServerSocket e Socket.

Ao contrário da classe Socket, um DatagramSocket não estabelece uma conexão com uma

máquina remota, porém necessita apenas de uma conexão local com a rede de computadores

para que seja possível enviar e receber pacotes.

Os pacotes enviados e recebidos por meio de sockets UDP são instâncias da classe

DatagramPacket, contendo informações sobre o remetente e destinatário e os dados a serem

transmitidos.

Implementação de socket UDP de servidor

A implementação de sockets que utilizam o protocolo UDP é realizada por meio de instâncias

de DatagramSocket e DatagramPacket. Utilizando-se uma instância de DatagramSocket, é

possível determinar qual porta será reservada para o envio e recebimento de dados na forma

de pacotes, enquanto a de DatagramPacket é utilizada para a montagem de uma datagrama a

ser enviado, informando-se o host e porta do cliente, e até mesmo para aguardar informações

transmitidas por outros sockets UDP. Para o desenvolvimento de sockets UDP, existem alguns

passos a serem seguidos:

1. Estabelecer um socket de datagrama que disponibilize uma porta para as operações de

envio e recebimento de dados. Para isso, deve-se criar uma instância de DatagramSocket,

conforme apresentado a seguir:

a. DatagramSocket(int porta) - Cria uma instância de DatagramSocket e vincula o socket a uma

determinar porta, pela qual serão realizados o envio e recebimento de informações.

DatagramSocket datagramSocket =

new DatagramSocket(1234);

2. Recuperar uma instância de InetAddress, responsável por armazenar o host a ser utilizado

no processo de envio de datagramas.

Para isso, deve-se criar uma instância de InetAddress utilizando-se getByName() desta mesma

classe, informando, como parâmetro, uma string com o nome ou IP do host;

InetAddress address = InetAddress.

getByName(“127.0.0.1”);

3. Preparar um arranjo (array) de bytes, com tamanho pré-definido, a ser utilizado como buffer

durante a troca de dados;

byte[] buffer = new byte[1024];

4. Criar uma instância de DatagramPacket, responsável por ser um datagrama de envio e

recebimento de dados. A classe DatagramPacket pode ser instanciada de duas formas:

Page 13: Artigo Sockets

a. DatagramPacket(byte[] buffer, int tamanho) - Cria uma instância de DatagramPacket

utilizada para o recebimento de dados. As informações recuperadas serão armazenadas em

um arranjo de bytes, passado como parâmetro do construtor, juntamente com o tamanho

determinado;

DatagramPacket datagramPacket = new Datagram

Packet(buffer, buffer.length);

b. DatagramPacket(byte[] buffer, int tamanho, InetAddress endereço, int porta) - Cria uma

instância de DatagramPacket utilizada para o envio de dados. Os dados a serem transmitidos

devem ser transformados em um arranjo de bytes por meio do método getBytes() da classe

String. O arranjo de bytes é passado como parâmetro do construtor da classe DatagramPacket.

String mensagem = "Mensagem recebida com sucesso às " + new Date();

buffer = mensagem.getBytes();

DatagramPacket datagramPacket = new DatagramPacket(buffer, buffer.length, address,

1235);

5. Possuindo as instâncias de DatagramSocket e DatagramPacket, torna-se possível realizar as

operações de envio e recebimento de dados. Conforme mencionado anteriormente, utiliza-se

as mesmas instâncias para as duas tarefas.

a. Para o recebimento de dados, utiliza-se o método receive(DatagramPacket datagrama),

invocado a partir da instância de DatagramSocket.

Em seguida, deve-se recuperar os bytes transmitidos, utilizando-se o método getData() e

transformá-los em uma cadeia de caracteres, passando o arranjo de bytes como parâmetro do

construtor da classe String.

datagramSocket.receive(datagramPacket);

buffer = datagramPacket.getData();

String mensagem = new String(buffer);

b. Para o envio de dados, utiliza-se o método send(DatagramPacket datagrama), invocado a

partir da instância de DatagramSocket.

datagramSocket.send(datagramPacket);

6. Após o processamento das operações, deve-se concluir a aplicação encerrando o datagrama

por meio do método close().

datagramSocket.close();

A Listagem 3 apresenta uma implementação de socket UDP de servidor.

Page 14: Artigo Sockets

O objetivo desta aplicação é aguardar o recebimento de um datagrama por uma porta e, logo

em seguida, enviar um novo datagrama para uma máquina por meio de um host e de uma

porta, sem a necessidade de se estabelecer uma conexão.

package servidor;

import java.net.DatagramPacket;

import java.net.DatagramSocket;

import java.net.InetAddress;

import java.util.Date;

public class SocketServidor {

private static final String HOST = "127.0.0.1";

private static final int PORTA_SERVIDOR = 1234;

private static final int PORTA_CLIENTE = 1235;

private static int BUFFER_LENGTH = 1024;

public static void main(String[] args) {

try {

DatagramSocket datagramSocket =

new DatagramSocket(PORTA_SERVIDOR);

InetAddress address = InetAddress.getByName(HOST);

byte[] buffer = new byte[BUFFER_LENGTH];

DatagramPacket datagramPacket =

new DatagramPacket(buffer, buffer.length);

datagramSocket.receive(datagramPacket);

buffer = datagramPacket.getData();

String mensagem = new String(buffer);

System.out.println("Servidor: Mensagem recebida =>

\"" + mensagem + "\"");

mensagem = "Mensagem recebida com sucesso às " + new Date();

System.out.println("Servidor: Mensagem enviada =>

\"" + mensagem + "\"");

buffer = mensagem.getBytes();

datagramPacket = new DatagramPacket

(buffer, buffer.length, address, PORTA_CLIENTE);

datagramSocket.send(datagramPacket);

datagramSocket.close();

} catch (Exception ex) {

System.out.println(ex.getMessage());

}

}

}

Listagem 3. SocketServidor.java: Implementação de socket UDP de servidor.

Tendo desenvolvido a aplicação do lado do servidor, basta implementar a aplicação do lado do

cliente, responsável por aguardar o recebimento de dados e transmitir um datagrama de

resposta.

Implementação de socket UDP de cliente

Conforme informado anteriormente, tanto a aplicação servidora quanto a cliente são

implementadas por meio de instâncias de DatagramSocket e DatagramPacket. Portanto, a

aplicação cliente será desenvolvida utilizando-se os mesmos conceitos já apresentados.

A Listagem 4 apresenta uma implementação de socket UDP de cliente.

package cliente;

import java.net.DatagramPacket;

import java.net.DatagramSocket;

import java.net.InetAddress;

import java.util.Date;

public class SocketCliente {

private static final String HOST = "127.0.0.1";

private static final int PORTA_SERVIDOR = 1234;

private static final int PORTA_CLIENTE = 1235;

private static int BUFFER_LENGTH = 1024;

Page 15: Artigo Sockets

public static void main(String[] args) {

try {

DatagramSocket datagramSocket =

new DatagramSocket(PORTA_CLIENTE);

InetAddress address = InetAddress.getByName(HOST);

String mensagem = "Conectado às " + new Date();

System.out.println("Cliente: Mensagem enviada =>

\"" + mensagem + "\"");

byte[] buffer = mensagem.getBytes();

DatagramPacket datagramPacket =

new DatagramPacket(buffer, buffer.length,

address, PORTA_SERVIDOR);

datagramSocket.send(datagramPacket);

buffer = new byte[BUFFER_LENGTH];

datagramPacket = new DatagramPacket

(buffer, buffer.length);

datagramSocket.receive(datagramPacket);

buffer = datagramPacket.getData();

mensagem = new String(buffer);

System.out.println("Cliente: Mensagem recebida =>

\"" + mensagem + "\"");

datagramSocket.close();

} catch (Exception ex) {

System.out.println(ex.getMessage());

}

}

}

Listagem 4. SocketCliente.java: Implementação de socket UDP de cliente.

O objetivo desta aplicação é preparar uma mensagem para ser enviada por um datagrama

para um determinado host e porta e aguardar o recebimento de dados por meio de uma porta

disponibilizada pela aplicação, sem a necessidade de se estabelecer uma conexão.

Com os exemplos das Listagens 3 e 4 pode-se perceber que, utilizando-se sockets no modo

UDP, a aplicação não necessita estabelecer uma conexão entre as máquinas, portanto,

também não é possível garantir a entrega e a sequência correta de dados, já que nem sempre

a máquina destinatária pode estar preparada para receber informações enviadas pela máquina

remetente. O modo sem conexão (ou UDP) é muito mais rápido do que o modo baseado em

conexões (ou TCP) pelo fato de não fazer verificações e por não estabelecer sessões entre

máquinas.

Desenvolvimento de sockets para transmissão e recebimento de arquivos

A tecnologia de Sockets em Java também permite não só a troca de informações por canais de

comunicação, mas também o compartilhamento de arquivos lógicos como textos, imagens,

vídeos, entre outros formatos de dados.

Para implementar uma arquitetura capaz de transmitir e receber arquivos, é necessário

utilizar duas classes já apresentadas: ServerSocket e Socket. Por meio dessas classes,

estabelece-se uma conexão e um canal de comunicação, por onde serão transmitidas as

informações. Em resumo, para o servidor transmitir um arquivo, necessita-se passar por um

processo de transformação de arquivo lógico para um arranjo de bytes. Para o cliente,

receptor do arquivo, o processo ocorre de maneira contrária, transformando-se o arranjo de

bytes em arquivo lógico.

Implementação de socket de servidor para transmissão de arquivos

Page 16: Artigo Sockets

Conforme informado anteriormente, a classe ServerSocket também pode ser utilizada para

transmitir arquivos lógicos como textos e imagens.

A diferença se dá pelo fato de que o arquivo necessita passar por um processo de

decodificação, sendo transformado em um arranjo de bytes, utilizando-se o método read() de

uma instância de BufferedInputStream. Após essa transformação, a informação pode ser

transmitida assim como qualquer outro tipo de dado, por meio do método write() de uma

instância de DataOutputStream.

A Figura 6 ilustra o arquivo a ser transmitido pela aplicação servidora.

A Listagem 5 apresenta uma implementação de socket de servidor que disponibiliza a

transmissão de um arquivo de imagem. O objetivo desta aplicação é preparar um arquivo para

ser enviado assim que uma conexão for estabelecida.

Figura 6. Logotipo “Google Android”

package servidor;

import java.io.BufferedInputStream;

import java.io.DataOutputStream;

import java.io.File;

import java.io.FileInputStream;

import java.net.ServerSocket;

import java.net.Socket;

public class SocketServidor {

private static final int PORTA = 1234;

public static void main(String[] args) {

try {

ServerSocket server = new ServerSocket(PORTA);

Socket socket = server.accept();

DataOutputStream dos = new DataOutputStream

(socket.getOutputStream());

File arquivo = new File("C:\\Users\\

Guilherme\\Desktop\\android.gif");

byte[] buffer = new byte[(int) arquivo.length()];

FileInputStream fis = new FileInputStream(arquivo);

BufferedInputStream bis = new BufferedInputStream(fis);

bis.read(buffer, 0, buffer.length);

System.out.println("Enviando arquivo

\"" + arquivo.getName() + "\"");

dos.write(buffer, 0, buffer.length);

dos.flush();

dos.close();

socket.close();

server.close();

} catch (Exception ex) {

System.out.println(ex.getMessage());

Page 17: Artigo Sockets

}

}

}

Listagem 5. SocketServidor.java: Implementação de socket de servidor para transmissão de

arquivos.

Tendo desenvolvido a aplicação do lado do servidor, basta implementar a aplicação do lado do

cliente, responsável por conectar-se ao socket e realizar as requisições de recebimento de

dados e transformação do arranjo de bytes em um arquivo lógico.

Implementação de socket de cliente para recebimento de arquivos

Conforme informado anteriormente, a classe Socket também pode ser utilizada para receber

arquivos transmitidos por um servidor utilizando-se um canal de comunicação. A informação

recebida, por meio do método read() de uma instância de DataInputStream, é um arranjo de

bytes e para se tornar um arquivo legível deve passar por um processo de transformação. O

processo de transformação ocorre de maneira simples, onde os bytes são lidos no canal de

comunicação e, ao final da leitura, utiliza-se o método write() de uma instância de

BufferedOutputStream para “escrever” as informações lidas em um arquivo lógico dentro de

um diretório informado.

A Listagem 6 apresenta uma implementação de socket de cliente que aguarda o recebimento

de um arquivo de imagem. O objetivo desta aplicação é, além de receber informações no

formato de bytes, transformar os dados recebidos e armazenar o arquivo final em um diretório

especificado no código-fonte.

package cliente;

import java.io.BufferedOutputStream;

import java.io.DataInputStream;

import java.io.FileOutputStream;

import java.net.Socket;

public class SocketCliente {

private static final String HOST = "127.0.0.1";

private static final int PORTA = 1234;

private static final int FILE_SIZE = 6022386;

public static void main(String[] args) {

try {

Socket socket = new Socket(HOST, PORTA);

DataInputStream dis = new DataInputStream

(socket.getInputStream());

byte[] mybytearray = new byte[FILE_SIZE];

FileOutputStream fos = new FileOutputStream

("C:\\Users\\Guilherme\\Desktop\\android_copy.gif");

BufferedOutputStream bos = new BufferedOutputStream(fos);

int bytesRead = dis.read(mybytearray, 0, mybytearray.length);

int current = bytesRead;

do {

bytesRead = dis.read(mybytearray, current,

(mybytearray.length - current));

if (bytesRead >= 0) {

current += bytesRead;

}

} while (bytesRead > -1);

bos.write(mybytearray, 0, current);

System.out.println("Arquivo recebido com sucesso!");

bos.flush();

bos.close();

socket.close();

} catch (Exception ex) {

System.out.println(ex.getMessage());

}

}

}

Page 18: Artigo Sockets

Listagem 6. SocketCliente.java: Implementação de socket de cliente para recebimento de

arquivos.

Com o desenvolvimento das aplicações cliente e servidor das Listagens 5 e 6 foi possível

perceber a facilidade e praticidade quando se deseja compartilhar arquivos entre processos

executados em máquinas diferentes por meio de sockets TCP. Esta ideia pode ser modificada

para aplicativos que gerenciam relatórios, gráficos, imagens e outros documentos onde o

processamento pode ou deve permanecer isolado apenas em uma máquina responsável por

prover serviços solicitados pelos clientes.

Conclusão

O conceito de computação distribuída não é recente e nem tão pouco utilizada, estando

presente em quase todos os locais e situações, porém o tema, por muitas vezes, não recebe a

importância devida, seja no ambiente acadêmico ou corporativo, cabendo aos

desenvolvedores e analistas realizar pesquisas em tecnologias antes ignoradas.

Graças ao aumento da necessidade e crescente uso da rede de computadores, principalmente

pela popularização da Internet, tornou-se necessário adaptar o processo de desenvolvimento

de software para que as aplicações trabalhem de maneira distribuída e descentralizada.

Este artigo apresentou os conceitos e mecanismos utilizados para a implementação de

aplicativos que fazem uso de redes de computadores para a troca de informações e

processamento de dados. A tecnologia Java nos provê classes e interfaces adequadas para a

implementação de aplicações que, por meio de um canal de comunicação, possam enviar e

receber dados de diversos tipos, facilitando o desenvolvimento de projetos baseados na

arquitetura distribuída.

Além dos conceitos e do embasamento teórico, o artigo apresentou, de forma didática,

alguns exemplos de aplicações que fazem uso de classes relacionadas ao desenvolvimento

com sockets, demonstrando que não é necessário possuir conhecimentos sobre detalhes da

implementação de algoritmos ou hardware de rede para produzir aplicações distribuídas em

Java.

O que foi abordado neste artigo é apenas uma breve amostra de como a tecnologia de

sockets pode auxiliar no desenvolvimento de aplicações Java, incorporando características da

computação distribuída no desenvolvendo de software.

Há muito ainda a ser explorado sobre essa tecnologia pouco comentada, porém muito

utilizada, criando a possibilidade de novos artigos e trabalhos relacionados.

LINKS

1. http://download.oracle.com/javase/tutorial/networking/sockets/

Página oficial da Oracle (Sun) sobre sockets em Java.

2. http://java.sun.com/developer/technicalArticles/ALT/sockets/

Página oficial da Oracle (Sun) sobre programação avançada de sockets.

3. http://www.oracle.com/technetwork/java/socket-140484.html

Page 19: Artigo Sockets

Tutorial oficial da Oracle (Sun) sobre desenvolvimento de aplicações distribuídas

baseadas em sockets.

4. http://www.javaworld.com/javaworld/jw-12-1996/jw-12-sockets.html

Artigo sobre desenvolvimento de sockets em Java da InfoWorld - Java World.

5. http://www.devarticles.com/c/a/Java/Socket-Programming-in-Java/

Artigo sobre desenvolvimento de sockets em Java da DevArticles.

Autor: GUILHERME DE CLEVA FARTO, Revista Easy Java Magazine, Edição 6, 2011.