Você está na página 1de 62

Programaode

RedesdeComputadores
emJava
RafaelSantos

Material reescrito para os alunos da disciplina CAP312 Programao


de Redes de Computadores do programa de ps-graduao em
Computao Aplicada do Instituto Nacional de Pesquisas Espaciais
(INPE)
ltima modificao: 17 de junho de 2006
http://www.lac.inpe.br/rafael.santos

ii

Sumrio
1

Introduo

Programao Cliente-Servidor
2.1 Introduo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2 Conceitos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1
1
3

Desenvolvimento de aplicaes cliente-servidor em Java

Clientes simples
4.1 Exemplo: Cliente de daytime . . . . . . . . . . . . . . . . . . . . . .
4.2 Exemplo: Cliente de echo . . . . . . . . . . . . . . . . . . . . . . . .

8
8
10

Servidores simples (requisies no-simultneas)


5.1 Exemplo: Servidor de strings revertidas . . . . . .
5.2 Exemplo: Servidor e cliente de instncias de classes
5.3 Exemplo: Servidor e cliente de nmeros aleatrios
5.4 Exemplo: Servidor simples de arquivos . . . . . .

.
.
.
.

13
13
17
21
25

Servidores para mltiplas requisies simultneas


6.1 O problema de mltiplas requisies simultneas . . . . . . . . . . . .
6.2 Linhas de execuo (threads) . . . . . . . . . . . . . . . . . . . . . . .
6.3 Exemplo: Servidor de nmeros aleatrios (para requisies simultneas)

31
31
35
39

Aplicaes baseadas em um servidor e vrios clientes


7.1 Exemplo: Servidor de jogo-da-velha . . . . . . . . . . . . . . . . . . .

42
43

Aplicaes baseadas em um cliente e vrios servidores


8.1 Exemplo: Cliente e servidores para clculo de integrais . . . . . . . . .

49
51

Mais informaes

58

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

Lista de Figuras
1
2
3
4
5
6
7

Algoritmo para leitura e processamento de uma string . . . . . . . . . .


Algoritmo para o servidor de strings . . . . . . . . . . . . . . . . . . .
Algoritmo integrado do cliente e servidor de strings . . . . . . . . . . .
Outro algoritmo integrado do cliente e servidores . . . . . . . . . . . .
Protocolo de comunicao entre o cliente e o servidor de daytime . . .
Protocolo de comunicao entre o cliente e o servidor de echo . . . . .
Protocolo de comunicao entre o cliente e o servidor de inverso de
strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Rafael Santos

5
5
6
7
8
11
14

Programao Cliente-Servidor Usando Java

iii

8
9
10
11
12
13
14
15
16
17

Exemplo de acesso ao servidor de strings invertidas usando telnet . .


Protocolo de comunicao entre o cliente e o servidor de instncias da
classe Livro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Protocolo de comunicao entre o cliente e o servidor de nmeros aleatrios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Protocolo de comunicao entre o cliente e o servidor de arquivos . . .
Protocolo de comunicao entre o cliente e o servidor de nmeros aleatrios (segunda verso) . . . . . . . . . . . . . . . . . . . . . . . . . .
Posies para jogo e linhas vencedoras no jogo-da-velha . . . . . . . .
Protocolo de comunicao entre o servidor e os clientes de jogo-da-velha
Exemplo de interao entre um cliente (telnet) e servidor de jogo-davelha . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Clculo de uma integral usando parties e trapzios . . . . . . . . . .
Protocolo para aplicao que faz o clculo de uma integral . . . . . . .

17
18
22
26
32
43
44
50
51
53

Lista de Listagens
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Cliente de daytime . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Cliente de echo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Servidor de strings invertidas . . . . . . . . . . . . . . . . . . . . . . .
A classe Livro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Servidor de instncias da classe Livro . . . . . . . . . . . . . . . . . .
Cliente para o servidor de livros . . . . . . . . . . . . . . . . . . . . .
Servidor de nmeros aleatrios . . . . . . . . . . . . . . . . . . . . . .
Cliente de nmeros aleatrios . . . . . . . . . . . . . . . . . . . . . . .
Servidor de arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . .
Cliente de arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Segunda verso do servidor de nmeros aleatrios . . . . . . . . . . . .
Segunda verso do cliente de nmeros aleatrios . . . . . . . . . . . .
Classe que representa um carro de corrida para simulao. . . . . . . .
Simulao usando instncias de CarroDeCorrida. . . . . . . . . . . .
Classe que representa um carro de corrida para simulao (herdando de
Thread). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Simulao usando instncias de CarroDeCorridaIndependente. . . .
Classe que implementa o servio de nmeros aleatrios . . . . . . . . .
Terceira verso do servidor de nmeros aleatrios . . . . . . . . . . . .
O servidor de Jogo-da-Velha . . . . . . . . . . . . . . . . . . . . . . .
O servidor de clculo de integrais . . . . . . . . . . . . . . . . . . . .
Uma linha de execuo para o cliente de clculo de integrais . . . . . .
O cliente de clculo de integrais . . . . . . . . . . . . . . . . . . . . .

Rafael Santos

8
10
13
17
18
20
21
24
26
29
31
34
35
36
37
38
40
41
45
52
55
57

Programao Cliente-Servidor Usando Java

Introduo

Este documento ilustra os conceitos bsicos de programao cliente-servidor usando


Java, usando protocolos, clientes e servidores desenvolvidos pelo programador. Para
melhor compreenso dos tpicos, alguns clientes simples para servidores j existentes
sero demonstrados.
Para a compreenso dos exemplos deste documento, o leitor deve ter os seguintes conhecimentos tcnicos:
Conhecer algoritmos e a implementao de diversas estruturas de controle em Java
(condicionais, estruturas de repetio);
Programao orientada a Objetos usando a linguagem Java. Em especial necessrio compreender os conceitos de encapsulamento e herana, mecanismo de excees e o uso de mtodos estticos;
Conhecer alguns dos mecanismos de entrada e sada em Java (streams);
Conhecer conceitos bsicos do funcionamento de um computador ligado em uma
rede.

Programao Cliente-Servidor

2.1

Introduo

Consideremos os seguintes cenrios:


1. Precisamos executar uma aplicao especfica mas o computador ao qual temos
acesso no pode execut-la por uma razo qualquer (exemplos podem ser falta de
hardware adequado, como disco, processador ou memria; necessidade de acesso
a um hardware especfico para execuo do software, etc.).
2. Precisamos de uma informao para efetuar um processamento ou tomar uma deciso, mas esta informao atualizada frequentemente. Existe um outro computador que tem a verso mais atual desta informao.
3. Precisamos executar uma tarefa usando computadores que precise de mediao,
ou seja, de uma maneira imparcial e independente de avaliar resultados. Frequentemente existiro vrias fontes de informao que precisam ser avaliadas, e em
muitos casos as fontes devem permanecer ocultas umas das outras.
4. Precisamos executar uma aplicao cujo tempo para concluso seria excessivamente longo no computador ao qual temos acesso, e possivelmente muito longo
mesmo usando hardware mais eficiente.
Rafael Santos

Programao Cliente-Servidor Usando Java

5. Precisamos obter informaes que esto em algum outro computador (de forma
semelhante ao caso 2) mas no sabemos onde estas informaes esto localizadas. Sabemos, no entanto, que um outro computador contm um catlogo destas
informaes.
No caso do exemplo 1, poderamos emprestar o hardware necessrio sem precisarmos mudar fisicamente de lugar. Basta que exista a possibilidade de enviarmos a tarefa
para este computador remoto e receber o resultado. Um exemplo deste cenrio seria
a consulta a bancos de dados do Genoma, que so grandes e complexos demais para
serem reproduzidos em computadores pessoais.
O caso do exemplo 2 muito similar ao uso da Internet para acesso a informaes. O
exemplo mais claro o acesso a jornais on-line: como a informao modificada a
cada dia (ou, em alguns casos, o tempo todo), um usurio pode simplesmente recarregar, atravs de uma pgina, a informao desejada atualizada. Um outro exemplo mais
simples seria de um registro global de tempo, que teria a hora certa em um computador
especial, e outros computadores poderiam acertar seus relgios internos usando a hora
fornecida por este computador especial.
Para o exemplo 3 podemos pensar em enviar as informaes ou resultados de diversas
fontes para um computador que tomaria a deciso assim que tivesse todas as fontes de
dados. A aplicao mais simples deste tipo de cenrio seriam jogos de computador,
onde cada jogador enviaria suas informaes (por exemplo, posies de um tabuleiro)
e o computador mediador decidiria se as jogadas foram vlidas e quem seria o vencedor.
Para o exemplo 4 podemos considerar uma soluo cooperativa: se o problema a ser resolvido puder ser dividido em vrios subproblemas menores independentes, poderamos
usar vrios computadores diferentes para resolver cada um destes pequenos problemas.
Um computador seria o responsvel para separar o problema em subproblemas, enviar estes subproblemas para diversos outros computadores e integrar o resultado. Um
exemplo deste tipo de cenrio a anlise de dados de genoma e proteoma, onde usurios cedem parte do tempo de seus computadores para execuo de uma tarefa global.
No caso do exemplo 5, poderamos ter os computadores dos usurios consultando um
computador central que no contm informaes mas sabe quais computadores as contm. Este computador central teria ento meta-informaes ou meta-dados, ou seja,
informaes sobre informaes. Este cenrio semelhante ao que ocorre em mecanismos de busca na Internet, que no contm pginas com uma informao especfica mas
podem indicar quais computadores as contm.
Em todos estes casos, estaremos usando alguma variante de aplicao cliente-servidor,
onde parte do processamento dos dados feito do lado do cliente ou usurio que deseja
Rafael Santos

Programao Cliente-Servidor Usando Java

processar a informao; e parte processamento do lado do servidor, ou do computador


que capaz de processar ou obter a informao desejada. Em alguns casos teremos a
relao um cliente para um servidor (como nos cenrios 1 e 2), mas possvel considerar
arquiteturas onde existem vrios clientes para um servidor (cenrio 3), vrios servidores
para um cliente (cenrio 4) ou mesmo vrias camadas de servidores (cenrio 5). Em
todos estes casos podemos supor que os computadores esto conectados via rede local,
Internet ou outro mecanismo remoto qualquer.
2.2

Conceitos

Alguns conceitos cuja compreenso se faz necessria so listados a seguir.


Servidor o computador que contm a aplicao que desejamos executar via remota.
Servio uma aplicao sendo executada no servidor. Para cada tipo de servio (aplicao) pode ser necessrio ter um tipo de cliente especfico, capaz de realizar a
comunicao da forma adequada. comum usar os termos servio e servidor
para designar a aplicao a ser executada (ex. servidor de FTP).
Cliente a aplicao remota que far a comunicao e interao com o servio/servidor.
Endereo a informao da localizao de um computador em uma rede local ou na
Internet. Podem ser usados como endereos o nmero IP (Internet Protocol) do
computador ou um nome que possa ser resolvido por um servidor DNS (Domain
Name System).
Porta um endereo local em um computador conectado a uma rede, identificado por
um nmero nico. Todos os dados originados ou destinados a um computador
na rede passam pela conexo (geralmente nica) daquele computador, identificada
pelo seu endereo. Como vrias aplicaes podem estar enviando e recebendo
dados, necessrio um segundo identificador para que o computador saiba que
dados devem ir para cada aplicao. Este segundo identificador a porta, associada
com um servio.
Porta Bem Conhecida uma porta cujo nmero menor que 1024, e que corresponde
a servios bem conhecidos. Alguns exemplos so as portas 7 (servio echo), 13
(servio daytime), 21 (servio ftp) e 80 (servio http). Usurios comuns (isto ,
sem privilgios de administrao) geralmente no podem usar nmeros de portas
bem conhecidas para instalar servios.
Protocolo o dilogo que ser travado entre cliente e servidor1 . Este dilogo muito
importante quando vamos criar clientes e servidores especficos, pois dados devem
ser enviados do cliente para o servidor em uma ordem e formatos predeterminados.
1O

termo protocolo mais conhecido para designar a maneira pela qual os dados sero recebidos e enviados entre cliente
e servidor; alguns exemplos mais conhecidos so TCP (Transmission Control Protocol) e UDP (User Datagram Protocol).

Rafael Santos

Programao Cliente-Servidor Usando Java

O protocolo define que partes do algoritmo que resolve o problema em questo


sero resolvidas pelo cliente, resolvidas pelo servidor ou enviadas de/para o cliente
e o servidor.
Socket o terminal de um canal de comunicaes de duas vias entre cliente e servidor.
Para que a comunicao entre cliente e servidor seja efetuada, o cliente e o servidor
criaro cada um um socket que sero associados um com o outro para o envio e
recebimento de informaes.

Desenvolvimento de aplicaes cliente-servidor em Java

Alguns dos exemplos comentados na seo 2.1 podem ser resolvidos com servidores e
clientes j existentes por exemplo, para receber notcias de um jornal basta usarmos
um navegador que acessar o servidor HTTP da empresa que mantm o jornal. Em muitos casos, entretanto, teremos que escrever o servidor pois ele far tarefas especficas
que no so especialidade de servidores j existentes. Em alguns casos, tambm deveremos escrever um cliente especial pois o protocolo de comunicao com o servidor e
os tipos de dados que sero enviados e recebidos devem ter tratamento especial.
Uma aplicao cliente-servidor em Java segue um padro relativamente simples de desenvolvimento: a parte complexa determinar que parte do processamento ser feita
pelo cliente e que parte pelo servidor, e como e quando os dados devero trafegar entre
o cliente e o servidor. Imaginemos uma aplicao onde o cliente deva recuperar uma
linha de texto enviada pelo servidor (a linha de texto pode conter a hora local, uma
cotao de moeda, etc.). O algoritmo do cliente (mostrado como um fluxograma ou
diagrama semelhante a um diagrama de atividades de UML) seria como o mostrado na
figura 1.

Rafael Santos

Programao Cliente-Servidor Usando Java

Cliente
Incio
Abre conexo

L string

Processa a string

Fim

Figura 1: Algoritmo para leitura e processamento de uma string

O diagrama/algoritmo para o servidor o mostrado na figura 2.

Servidor
Incio
Aguarda conexo do cliente

Cria conexo

Envia string

Figura 2: Algoritmo para o servidor de strings

O algoritmo mostrado na figura 1 no est completo: aps o passo Solicita conexo a


aplicao deveria aguardar a conexo entre o cliente e servidor ser estabelecida antes de
ler a string enviada pelo servidor. Da mesma forma, o algoritmo do servidor (mostrado
na figura 2) no cria conexes e envia strings a qualquer hora: estes passos ocorrem em
resposta a solicitaes do cliente. Os dois algoritmos devem ento estar sincronizados
para a aplicao funcionar corretamente.
Para descrevermos a aplicao como um todo usando diagramas similares aos de ati-

Rafael Santos

Programao Cliente-Servidor Usando Java

Cliente

Servidor

Incio

Incio

Solicita conexo

Aguarda conexo

Cria conexo

L string

Envia string

Imprime String lida

Fim
Figura 3: Algoritmo integrado do cliente e servidor de strings

vidades2 devemos descrever os algoritmos lado a lado, inclusive registrando os passos


nos quais os algoritmos devem estar sincronizados. Para isto, basta mudar o fluxo dos
passos para incluir a dependncia de passos no cliente e servidor. Um algoritmo que
mostra as atividades do cliente e servidor ilustrando os passos que precisam ser sincronizados mostrado na figura 3.
Um outro exemplo de diagrama simplificado que mostra a interao entre um cliente e
dois servidores mostrada na figura 4. Este algoritmo de uma aplicao hipottica na
qual um cliente verifica preos em dois servidores diferentes para comparao e deciso.
interessante observar no algoritmo da figura 4 que alguns passos poderiam estar em
ordem diferente sem afetar o funcionamento da aplicao como um todo. Por exemplo,
as conexes do cliente com os servidores poderiam ser estabelecidas uma logo aps a
outra assim como a leitura dos dados. Como no existe um tempo de processamento
grande entre o estabelecimento da conexo e a leitura dos dados, esta modificao no
causaria tempo de conexo sem uso desnecessariamente longo com o servidor.
2 Evitando

usar as notaes de bifurcao e unio para manter os diagramas simples

Rafael Santos

Programao Cliente-Servidor Usando Java

Cliente

Servidor 1

Incio

Incio

Solicita conexo

Aguarda conexo

Servidor 2

Cria conexo

L valor do produto

Envia valor

Incio
Solicita conexo

Aguarda conexo

Cria conexo

L valor do produto

Envia valor

Processa informaes

Fim
Figura 4: Outro algoritmo integrado do cliente e servidores

Rafael Santos

Programao Cliente-Servidor Usando Java

Clientes simples

Veremos nesta seo alguns clientes simples para servios existentes3 . Estes clientes
so totalmente funcionais, e servem para demonstrar o uso das classes para comunicao entre clientes e servidores.
4.1

Exemplo: Cliente de daytime

O servio daytime registrado na porta 13 e retorna, quando conectado e solicitado,


uma string contendo a data e hora do servidor. O protocolo de comunicao com um
servidor destes , ento, muito simples: basta estabelecer a conexo, ler uma nica
string e encerrar a conexo. Este protocolo mostrado na figura 5.

Cliente

Servidor

Incio

Incio

Solicita conexo

Aguarda conexo

Cria conexo

L string

Envia string

Imprime String lida

Fim
Figura 5: Protocolo de comunicao entre o cliente e o servidor de daytime

No sistema operacional Linux, o servio daytime pode ser acessado com o aplicativo
telnet, que deve receber como argumentos o nome do servidor e o nmero da porta,
separados por espao (por exemplo, telnet localhost 13). Ao invs de acessar o
servio desta forma, vamos escrever uma aplicao em Java que o faa para ns. A
aplicao mostrada na listagem 1.
Listagem 1: Cliente de daytime
1
2
3

package cap312;
import java.io.BufferedReader;
import java.io.IOException;
3 Os

servios utilizados nesta seo so servios padro do sistema operacional Linux, desabilitados por default. Para
computadores executando o sistema operacional Red Hat, verifique os arquivos no diretrio /etc/xinetd.d para habilitao
dos servios.

Rafael Santos

Programao Cliente-Servidor Usando Java

9
4
5
6

import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

/**
* Esta classe implementa um cliente simples para o servio daytime (porta 13).
* A classe simplesmente cria um socket para um servidor (no exemplo,
* localhost) usando aquela porta, obtm um BufferedReader (usando um
* InputStreamReader a partir do InputStream da conexo) e l uma linha do
* servidor.
*/
public class ClienteDeDaytime
{
public static void main(String[] args)
{
String servidor = "localhost";
// Tentamos fazer a conexo e ler uma linha...
try
{
Socket conexo = new Socket(servidor,13);
// A partir do socket podemos obter um InputStream, a partir deste um
// InputStreamReader e partir deste, um BufferedReader.
BufferedReader br =
new BufferedReader(
new InputStreamReader(conexo.getInputStream()));
String linha = br.readLine();
System.out.println("Agora so "+linha+" no servidor "+servidor);
// Fechamos a conexo.
br.close();
conexo.close();
}
// Se houve problemas com o nome do host...
catch (UnknownHostException e)
{
System.out.println("O servidor no existe ou est fora do ar.");
}
// Se houve problemas genricos de entrada e sada...
catch (IOException e)
{
System.out.println("Erro de entrada e sada.");
}
}
}

Os pontos interessantes e chamadas a APIs da listagem 1 so:


O primeiro passo do algoritmo (a criao de uma conexo) feita pela criao
de uma instncia da classe Socket. O construtor desta classe espera um nome
de servidor (ou o seu IP, na forma de uma string) e o nmero da porta para conexo. A criao de uma instncia de Socket pode falhar, criando a exceo
UnknownHostException se o endereo IP do servidor no puder ser localizado ou
IOException se houver um erro de entrada ou sada na criao do socket.
Podemos obter uma stream de entrada a partir da instncia de Socket (usando o
mtodo getInputStream), e a partir desta stream podemos criar uma instncia de
InputStreamReader (que transforma os bytes lidos pela stream em caracteres). A
Rafael Santos

Programao Cliente-Servidor Usando Java

10

partir desta instncia de InputStreamReader (usando-a como argumento para o


construtor) podemos criar uma instncia de BufferedReader que pode ser usada
para ler linhas de texto completas.
O resto do processamento realmente simples, lemos uma string do servidor (na
verdade, solicitamos a leitura, e o servidor criar esta string para ser enviada
quando for requisitado), imprimimos a string lida e fechamos a stream e conexo.
Uma nota interessante sobre a aplicao mostrada na listagem 1: aps terminar a leitura
da string vinda do servidor, devemos manualmente fechar a conexo com o mesmo
(pois sabemos, de acordo com seu protocolo, que somente uma string ser enviada a
cada conexo). Se usarmos o aplicativo telnet para acessar este servio, o mesmo
automaticamente encerrar a conexo com o servidor.
4.2

Exemplo: Cliente de echo

Um servio ligeiramente mais complexo que o daytime o echo, que responde por conexes na porta 7 e retorna cada string enviada pelo cliente de volta. O servidor reenvia
cada string enviada pelo cliente at que a string seja um sinal de trmino (o caracter de
controle Control+] no caso do cliente telnet) indicando o fim da interao. O cliente
precisa, ento, de um mecanismo que envie e receba dados do servidor, sendo que estes
dados vo ser somente texto (strings). A figura 6 ilustra o protocolo de comunicao
entre um cliente e um servidor de echo.
A diferena principal entre o diagrama mostrado na figura 6 e outros vistos anteriormente que este tem uma ramificao possvel (condicional), dependendo da entrada
do usurio um dos dois possveis caminhos ser tomado. Para a implementao em
Java, ao invs de usar o cdigo de controle do telnet, usaremos uma string composta
do caracter ponto (.). Se esta string for enviada, fecharemos a conexo com o servidor.
A implementao do cliente de echo mostrada na listagem 2.

Listagem 2: Cliente de echo


1
2
3
4
5
6
7
8

package cap312;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;

9
10
11
12

/**
* Esta classe implementa um cliente simples para o servio echo (porta 7).
* A classe simplesmente cria um socket para um servidor (no exemplo,

Rafael Santos

Programao Cliente-Servidor Usando Java

11

Cliente

Servidor

Incio

Incio

Solicita conexo

Aguarda conexo

Cria conexo

L string do teclado
Sinal de trmino?
sim
no

Envia string para o servidor

L string do cliente

L string do servidor

Envia string para o cliente

Imprime string lida

Encerra conexo

Fim
Figura 6: Protocolo de comunicao entre o cliente e o servidor de echo
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

* localhost) usando aquela porta, obtm um BufferedReader (usando um


* InputStreamReader a partir do InputStream da conexo) e um BufferedWriter
* (usando um OutputStreamWriter a partir do OutputStream da conexo), e repete
* o seguinte lao:
* 1 - L uma string do teclado
* 2 - Envia esta string para o servidor
* 3 - L uma string do servidor
* 4 - Imprime a string lida do servidor
* Se a string entrada via teclado for igual a um ponto (".") o lao ser
* interrompido.
*/
public class ClienteDeEcho
{
public static void main(String[] args)
{
String servidor = "localhost";
// Tentamos fazer a conexo e ler uma linha...
try
{
Socket conexo = new Socket(servidor,7);

Rafael Santos

Programao Cliente-Servidor Usando Java

12
// A partir do socket podemos obter um InputStream, a partir deste um
// InputStreamReader e partir deste, um BufferedReader.
BufferedReader br =
new BufferedReader(
new InputStreamReader(conexo.getInputStream()));
// A partir do socket podemos obter um OutputStream, a partir deste um
// OutputStreamWriter e partir deste, um Bufferedwriter.
BufferedWriter bw =
new BufferedWriter(
new OutputStreamWriter(conexo.getOutputStream()));
// Executamos este lao "para sempre":
while(true)
{
// Lemos uma linha do console.
String linhaEnviada = Keyboard.readString();
// Se o usurio tiver digitado Cancel, samos do lao.
if (linhaEnviada.equals(".")) break;
// Enviamos a linha para o servidor.
bw.write(linhaEnviada);
bw.newLine();
bw.flush();
// Lemos uma linha a partir do servidor e a imprimimos no console.
String linhaRecebida = br.readLine();
System.out.println(linhaRecebida);
}
// Fechamos a conexo.
br.close();
bw.close();
conexo.close();
}
// Se houve problemas com o nome do host...
catch (UnknownHostException e)
{
System.out.println("O servidor no existe ou est fora do ar.");
}
// Se houve problemas genricos de entrada e sada...
catch (IOException e)
{
System.out.println("Erro de entrada e sada.");
}
}

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

Os pontos interessantes da listagem 2 so:


Novamente o primeiro passo a criao de uma instncia da classe Socket, novamente usando como exemplo o servidor localhost mas com a porta 7.
Como preciso enviar e receber strings do cliente para o servidor e vice-versa,
precisamos criar mecanismos para o envio e recebimento de strings. Isto feito
separadamente: para o recebimento de strings enviadas do servidor, obtemos uma
stream de leitura com o mtodo getInputStream da instncia de Socket, usamos esta stream para criar uma instncia de InputStreamReader e usamos esta
instncia para criar uma instncia de BufferedReader. Similarmente, usamos
uma stream de escrita obtida com o mtodo getOutputStream da instncia de
Rafael Santos

Programao Cliente-Servidor Usando Java

13

Socket, para criar uma instncia de OutputStreamWriter e usamos esta instncia para criar uma instncia de BufferedWriter.
Aps a criao das streams de entrada e sada, entramos no lao principal do cliente, que recebe uma string do teclado, compara com a string de trmino, e se for
diferente, envia a string para o servidor, recebendo uma string de volta e repetindo
o lao. Notem que aps enviar uma string para o servidor, necessrio executar os mtodos newLine e flush da classe BufferedWriter para que os bytes
correspondentes string sejam realmente enviados.
Este exemplo usa a classe Keyboard para entrada de strings. Esta classe pode ser
copiada do site http://www.directnet.com.br/users/rafael.santos/4 .

Servidores simples (requisies no-simultneas)

5.1

Exemplo: Servidor de strings revertidas

Vamos ver um exemplo de servidor agora, ou seja, uma aplicao que vai enviar informaes a um cliente. O cliente pode ser um aplicativo simples como o prprio telnet.
Considere que seja necessrio por algum razo inverter uma string. A inverso de uma
string a simples modificao da ordem de seus caracteres para que a mesma seja lida
ao contrrio, ou da direita para a esquerda. Como um exemplo, a inverso da string
Java seria avaJ. O algoritmo de inverso simples, basta criar uma string vazia e
concatenar a esta string cada caracter da string original, lidos de trs para frente.
O protocolo de comunicao entre um cliente e o servidor de strings invertidas mostrado na figura 7.
O diagrama mostra uma nfase no passo Inverte string pois este realiza o que, em
princpio, o cliente no saberia como ou teria recursos para processar. Em muitos casos de aplicaes cliente-servidor este passo seria o mais crucial: o processamento da
informao do lado do servidor.
A implementao do servidor de strings invertidas mostrada na listagem 3.
Listagem 3: Servidor de strings invertidas
1
2
3
4
5

package cap312;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
4 Curiosamente,

se usarmos o mtodo showInputDialog da classe JOptionPane, a aplicao no ser terminada corretamente para isso deveremos usar o mtodo System.exit ao final da aplicao.

Rafael Santos

Programao Cliente-Servidor Usando Java

14

Cliente

Servidor

Incio

Incio

Solicita conexo

Aguarda conexo

Cria conexo

L string do teclado

Envia string para o servidor

L string do cliente

Inverte string

L string do servidor

Envia string para o cliente

Imprime string lida

Fim

Figura 7: Protocolo de comunicao entre o cliente e o servidor de inverso de strings


6
7
8
9

import
import
import
import

java.io.OutputStreamWriter;
java.net.BindException;
java.net.ServerSocket;
java.net.Socket;

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

/**
* Esta classe implementa um servidor simples que fica aguardando conexes de
* clientes. Quando uma conexo solicitada, o servidor recebe do cliente uma
* string, a inverte e envia este resuldado para o cliente, fechando a conexo.
*/
public class ServidorDeStringsInvertidas
{
// Mtodo que permite a execuo da classe.
public static void main(String[] args)
{
ServerSocket servidor;
try
{
// Criamos a instncia de ServerSocket que responder por solicitaes
// porta 10101.
servidor = new ServerSocket(10101);
// O servidor aguarda "para sempre" as conexes.

Rafael Santos

Programao Cliente-Servidor Usando Java

15
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

while(true)
{
// Quando uma conexo feita,
Socket conexo = servidor.accept();
// ... o servidor a processa.
processaConexo(conexo);
}
}
// Pode ser que a porta 10101 j esteja em uso !
catch (BindException e)
{
System.out.println("Porta j em uso.");
}
// Pode ser que tenhamos um erro qualquer de entrada ou sada.
catch (IOException e)
{
System.out.println("Erro de entrada ou sada.");
}
}

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

// Este mtodo atende a uma conexo feita a este servidor.


private static void processaConexo(Socket conexo)
{
try
{
// Criamos uma stream para receber strings, usando a stream de entrada
// associado conexo.
BufferedReader entrada =
new BufferedReader(new InputStreamReader(conexo.getInputStream()));
// Criamos uma stream para enviar strings, usando a stream de sada
// associado conexo.
BufferedWriter sada =
new BufferedWriter(new OutputStreamWriter(conexo.getOutputStream()));
// Lemos a string que o cliente quer inverter.
String original = entrada.readLine();
String invertida = "";
// Invertemos a string.
for(int c=0;c<original.length();c++)
{
invertida = original.charAt(c)+invertida;
}
// Enviamos a string invertida ao cliente.
sada.write(invertida);
sada.newLine();
sada.flush();
// Ao terminar de atender a requisio, fechamos as streams de entrada e sada.
entrada.close();
sada.close();
// Fechamos tambm a conexo.
conexo.close();
}
// Se houve algum erro de entrada ou sada...
catch (IOException e)
{
System.out.println("Erro atendendo a uma conexo !");
}
}
}

Existem vrios pontos interessantes na listagem 3, descritos a seguir:


Rafael Santos

Programao Cliente-Servidor Usando Java

16

Para comodidade e compreenso de tpicos intermedirios, a listagem foi dividida


em duas partes (dois mtodos): o mtodo main que representa o fluxo principal do
servidor (criao de sockets e lao principal) e o mtodo processaConexo que
ser responsvel por processar uma nica conexo com um cliente.
O mtodo main cria uma instncia de ServerSocket que ser responsvel por
aguardar conexes do cliente. Uma instncia de ServerSocket criada passandose para o seu construtor a porta na qual este servidor ir responder por conexes.
O mtodo main tambm contm um lao aparentemente infinito (while(true))
onde ficar aguardando conexes de clientes. Quando um cliente se conectar a
este servidor, o mtodo accept da classe ServerSocket criar uma instncia de
Socket para a comunicao com o cliente, e prosseguir a execuo do lado do
servidor. importante observar que enquanto o cliente no solicitar uma conexo,
o mtodo accept bloquear o processamento do servidor, como se a aplicao
estivesse pausada naquele ponto.
Assim que uma conexo do cliente for aceita, o mtodo processaConexo ser
executado, usando como argumento a instncia recm-criada da classe Socket,
correspondente conexo com o cliente.
Ainda no mtodo main temos os blocos catch responsveis pelo processamento
das excees que podem ocorrer neste mtodo: BindException que ser criada
caso o endereo desejado j esteja em uso e IOException que ser criada caso
ocorra algum erro de entrada e sada genrico.
O mtodo processaConexo ser responsvel por processar uma nica conexo
com este servidor, criada quando o mtodo accept foi executado. Este mtodo
criar streams para envio e recebimento de strings, conforme ilustrado e descrito
na seo 4.2. importante notar que a stream de entrada para o servidor ser associado stream de sada do cliente e vice-versa.
O mtodo processaConexo ento recebe uma string do cliente, a inverte com
um algoritmo simples e a envia para o cliente (garantindo que os bytes desta
string sero realmente enviados usando os mtodos newLine e flush da classe
BufferedWriter).
O mtodo processaConexo tambm processa em um bloco catch a exceo
IOException que pode ocorrer.
Para testar este servidor, um cliente simples como telnet ou mesmo a aplicao ClienteDeEcho (listagem 2) modificada para usar a porta 10101 poderia ser usado. Um
exemplo de execuo de interao com o servidor de strings invertidas usando o telnet
mostrado na figura 8.

Rafael Santos

Programao Cliente-Servidor Usando Java

17

5.2

Exemplo: Servidor e cliente de instncias de classes

At agora vimos clientes que se comunicam com servidores conhecidos ou servidores


que podem usar clientes existentes como telnet Veremos agora um exemplo mais especfico que exige que tanto o cliente quanto o servidor sejam escritos especialmente
para atender ao protocolo de comunicao entre cliente e servidor (ou para poder receber e enviar dados que no sejam strings). Em outras palavras, o servidor ser diferente
dos existentes (echo, daytime) e o cliente no poder ser o telnet.
Vamos ver como podemos escrever um servidor para servir instncias de classes em
Java. O cliente tambm dever ser capaz de receber do servidor estas instncias. Vamos
usar a classe Livro (listagem 4) para criar as instncias que sero enviadas.
Listagem 4: A classe Livro

1
2

package cap312;
import java.io.Serializable;

3
4
5
6
7
8

public class Livro implements Serializable


{
private String ttulo;
private String autores;
private int ano;

9
10
11
12
13
14
15

public Livro(String t,String aut,int a)


{
ttulo = t;
autores = aut;
ano = a;
}

16
17
18
19
20

public void imprime()


{
System.out.println(ttulo+" ("+autores+"),"+ano);
}

21
22

Para que instncias de classes possam ser enviadas e recebidas por servidores e clientes necessrio que elas sejam serializveis. Para isto, basta declarar a classe como
implementando a interface Serializable, sem precisar implementar nenhum mtodo

Figura 8: Exemplo de acesso ao servidor de strings invertidas usando telnet


Rafael Santos

Programao Cliente-Servidor Usando Java

18

adicional. A classe Livro (listagem 4) implementa esta interface.


O protocolo de comunicao entre o servidor e o cliente de instncias da classe Livro
bem simples, e similar a outros mostrados anteriormente. Este protocolo mostrado
na figura 9.

Cliente

Servidor

Incio

Incio

Solicita conexo

Aguarda conexo

Cria conexo

L instncia de Livro

Envia instncia de Livro

Executa mtodo da
instncia lida

Fim

Figura 9: Protocolo de comunicao entre o cliente e o servidor de instncias da classe Livro

O servidor de instncias da classe Livro segue o mesmo padro do servidor de strings


invertidas (listagem 3): a classe contm um mtodo main responsvel por criar o socket
do lado do servidor e atender a requisies dos clientes, que sero processadas pelo mtodo processaConexo. Este mtodo cria uma stream do tipo ObjectOutputStream
para enviar uma das instncias da classe Livro contidas na estrutura coleo.
O servidor de instncias da classe Livro implementado na classe ServidorDeLivros,
mostrada na listagem 5.
Listagem 5: Servidor de instncias da classe Livro

1
2
3
4
5
6
7

package cap312;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

8
9
10

/**
* Esta classe implementa um servidor simples que fica aguardando conexes de

Rafael Santos

Programao Cliente-Servidor Usando Java

19
11
12
13
14
15
16
17

* clientes. Este servidor, quando conectado, envia uma instncia da classe


* Livro para o cliente e desconecta.
*/
public class ServidorDeLivros
{
// Estrutura que armazenar uma coleo de livros (instncias da classe Livro).
private static ArrayList<Livro> coleo;

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

// Mtodo que permite a execuo da classe.


public static void main(String[] args)
{
// Criamos uma coleo de livros.
coleo = new ArrayList<Livro> ();
coleo.add(new Livro("Java 1.4 Game Programming",
"Andrew Mulholland, Glen Murphy",2003));
coleo.add(new Livro("Developing Games In Java","David Bracken",2003));
coleo.add(new Livro("Java 2 Game Programming","Thomas Petchel",2001));
coleo.add(new Livro("Board And Table Games From Many Civilizations",
"R.C.Bell",1969));
coleo.add(new Livro("A Gamut Of Games","Sid Sackson",1969));
try
{
// Criamos a instncia de ServerSocket que responder por solicitaes
// porta 12244.
ServerSocket servidor = new ServerSocket(12244);
// O servidor aguarda "para sempre" as conexes.
while(true)
{
// Quando uma conexo feita,
Socket conexo = servidor.accept();
// ... o servidor a processa.
processaConexo(conexo);
}
}
// Pode ser que a porta j esteja em uso !
catch (BindException e)
{
System.out.println("Porta j em uso.");
}
// Pode ser que tenhamos um erro qualquer de entrada ou sada.
catch (IOException e)
{
System.out.println("Erro de entrada ou sada.");
}
}

56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

// Este mtodo atende a uma conexo feita a este servidor.


private static void processaConexo(Socket conexo)
{
try
{
// Criamos uma stream para enviar instncias de classes, usando a stream
// de sada associado conexo.
ObjectOutputStream sada =
new ObjectOutputStream(conexo.getOutputStream());
// Escolhemos um livro aleatoriamente.
int qual = (int)(Math.random()*coleo.size());
sada.writeObject(coleo.get(qual));
// Ao terminar de atender a requisio, fechamos a stream de sada.
sada.close();
// Fechamos tambm a conexo.

Rafael Santos

Programao Cliente-Servidor Usando Java

20
conexo.close();
}
// Se houve algum erro de entrada ou sada...
catch (IOException e)
{
System.out.println("Erro atendendo a uma conexo !");
}
}

72
73
74
75
76
77
78
79
80

Para acessar este servio, no podemos usar clientes como telnet: uma conexo feita
via telnet para o servidor executando esta aplicao retornaria strings com alguns caracteres de controle (correspondente serializao de uma instncia da classe Livro)
que torna os resultados da interao praticamente inutilizveis. Precisamos de um cliente especializado em receber e processar instncias da classe Livro.
O cliente para o servidor mostrado na listagem 5 tambm simples e similar a outros
clientes vistos anteriormente. A principal diferena que ao invs de usar uma instncia de BufferedReader para ler dados do servidor, iremos usar uma instncia de
ObjectInputStream. O cliente (classe ClienteDeLivros) mostrado na figura 6.
Listagem 6: Cliente para o servidor de livros

1
2
3
4
5

package cap312;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;
import java.net.UnknownHostException;

6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

/**
* Esta classe implementa um cliente simples para o servio de livros.
* Ele se conecta ao servidor (pela porta 12244) e recebe uma instncia da classe
* Livro enviada pelo servidor.
*/
public class ClienteDeLivros
{
public static void main(String[] args)
{
String servidor = "localhost";
// Tentamos fazer a conexo e ler uma linha...
try
{
Socket conexo = new Socket(servidor,12244);
// A partir do socket podemos obter um InputStream, a partir deste um
// ObjectInputStream.
ObjectInputStream dis = new ObjectInputStream(conexo.getInputStream());
Livro umLivro = (Livro)dis.readObject();
System.out.println("Livro obtido do servidor:");
umLivro.imprime();
// Fechamos a conexo.
dis.close();
conexo.close();
}
// Se houve problemas com o nome do host...

Rafael Santos

Programao Cliente-Servidor Usando Java

21
catch (UnknownHostException e)
{
System.out.println("O servidor no existe ou est fora do ar.");
}
// Se houve problemas genricos de entrada e sada...
catch (IOException e)
{
System.out.println("Erro de entrada e sada.");
}
// Essa exceo poderia ocorrer se tivssemos o cliente sendo executado em
// outro computador e a classe Livro no tivesse sido distribuda.
catch (ClassNotFoundException e)
{
System.out.println("A classe Livro no est presente no cliente.");
}
}

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

Existe uma outra diferena entre este cliente e outros: este deve, obrigatoriamente, ter
um bloco catch para processar a exceo ClassNotFoundException, que ser criada
caso a classe cuja instncia desejamos recuperar no existir do lado do cliente. Embora
para finalidade de testes esta classe deva estar sempre presente, importante lembrar
que em aplicaes mais complexas o servidor pode enviar uma instncia de uma classe
que no exista no cliente quando a aplicao do lado cliente for distribuda para uso,
devemos sempre lembrar de incluir todas as classes que podem ser serializadas para
aquela aplicao.
5.3

Exemplo: Servidor e cliente de nmeros aleatrios

Consideremos como um outro exemplo um servidor que envie para o cliente um nmero
aleatrio5 entre zero e um, do tipo double. Embora qualquer computador que tenha a
mquina virtual Java possa facilmente obter nmeros aleatrios, podemos imaginar razes para fazer disto um servio: uma razo comum que o nmero deve ser coerente
com os enviados para outros clientes (no caso de jogos). O protocolo de comunicao
entre um cliente e um servidor de nmeros aleatrios mostrado na figura 10.
O protocolo de comunicao entre o cliente e servidor de nmeros aleatrios to simples quanto o protocolo de comunicao entre cliente e servidor de echo, de strings
invertidas ou de uma instncia de uma classe, a diferena que em vez de strings ou
instncias estaremos recebendo valores de um dos tipos nativos (no caso, double).
A implementao do servidor semelhante do servidor de strings invertidas ou de
instncias da classe Livro. O cdigo-fonte do servidor mostrado na listagem 7.
Listagem 7: Servidor de nmeros aleatrios
5 OK,

so pseudo-aleatrios, mas para as finalidades deste documento vamos ignorar a diferena entre nmeros realmente
aleatrios e os gerados pelo mtodo Math.random.

Rafael Santos

Programao Cliente-Servidor Usando Java

22

Cliente

Servidor

Incio

Incio

Solicita conexo

Aguarda conexo

Cria conexo

L double

Envia double

Imprime valor lido

Fim

Figura 10: Protocolo de comunicao entre o cliente e o servidor de nmeros aleatrios

1
2
3
4
5
6

package cap312;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

/**
* Esta classe implementa um servidor simples que fica aguardando conexes de
* clientes. Quando uma conexo solicitada, o servidor envia para o cliente
* um nmero aleatrio e fecha a conexo.
*/
public class ServidorDeNumerosAleatorios
{
// Mtodo que permite a execuo da classe.
public static void main(String[] args)
{
ServerSocket servidor;
try
{
// Criamos a instncia de ServerSocket que responder por solicitaes
// porta 9999.
servidor = new ServerSocket(9999);
// O servidor aguarda "para sempre" as conexes.
while(true)
{
// Quando uma conexo feita,
Socket conexo = servidor.accept();
// ... o servidor a processa.
processaConexo(conexo);
}
}

Rafael Santos

Programao Cliente-Servidor Usando Java

23
33
34
35
36
37
38
39
40
41
42
43

// Pode ser que a porta 9999 j esteja em uso !


catch (BindException e)
{
System.out.println("Porta j em uso.");
}
// Pode ser que tenhamos um erro qualquer de entrada ou sada.
catch (IOException e)
{
System.out.println("Erro de entrada ou sada.");
}
}

44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

// Este mtodo atende a uma conexo feita a este servidor.


private static void processaConexo(Socket conexo)
{
try
{
// Criamos uma stream para enviar valores nativos, usando a stream de sada
// associado conexo.
DataOutputStream sada =
new DataOutputStream(conexo.getOutputStream());
double rand = Math.random();
sada.writeDouble(rand);
// Para demonstrar que realmente funciona, vamos imprimir um log.
System.out.println("Acabo de enviar o nmero "+rand+" para o cliente "+
conexo.getRemoteSocketAddress());
// Ao terminar de atender a requisio, fechamos a stream de sada.
sada.close();
// Fechamos tambm a conexo.
conexo.close();
}
// Se houve algum erro de entrada ou sada...
catch (IOException e)
{
System.out.println("Erro atendendo a uma conexo !");
}
}
}

O cdigo-fonte do servidor segue praticamente os mesmos passos do servidor de strings


invertidas (listagem 3) exceto pela criao da classe que ser responsvel pelo envio
dos dados do servidor para o cliente. Usaremos, para este servidor, a instncia de
OutputStream retornada pelo mtodo getInputStream da classe Socket para criar
uma instncia da classe DataOutputStream. Para instncias desta classe no necessrio enviar terminadores de linhas nem executar mtodos que forcem o envio dos bytes.
Um ponto de interesse no cdigo do servidor (listagem 7) que quando um nmero aleatrio enviado para o cliente, uma mensagem impressa no terminal onde o servidor
est sendo executado. Isso facilita a depurao do servidor, caso seja necessrio, e serve
como ilustrao da criao de arquivos de registro (logs) como feito com servidores
mais complexos.
O servidor de nmeros aleatrios est pronto para receber conexes pela porta 9999,
mas os clientes existentes no so capazes de receber bytes correspondentes a valores
Rafael Santos

Programao Cliente-Servidor Usando Java

24

do tipo double: se executarmos o comando telnet localhost 9999 o mesmo retornar caracteres sem sentido. Precisamos de um cliente especfico para este tipo de
servio.
Um cliente adequado para receber um nico valor do tipo double de um servidor
mostrado na listagem 8. Novamente a diferena entre um cliente que processa strings,
como ClienteDeEcho (listagem 2) o tipo de classe que ser criado usando as streams
de entrada e sada obtidas da classe Socket. No caso do cliente, usamos uma instncia
de DataInputStream criada a partir do InputStream obtido pelo mtodo getInputStream da classe Socket.
Listagem 8: Cliente de nmeros aleatrios
1
2
3
4
5

package cap312;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;

6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

/**
* Esta classe implementa um cliente simples para o servio de nmeros aleatrios.
* Ele se conecta ao servidor (pela porta 9999) e recebe um nmero aleatrio
* gerado no servidor.
*/
public class ClienteDeNumerosAleatorios
{
public static void main(String[] args)
{
String servidor = "localhost";
// Tentamos fazer a conexo e ler uma linha...
try
{
Socket conexo = new Socket(servidor,9999);
// A partir do socket podemos obter um InputStream, a partir deste um
// DataInputStream.
DataInputStream dis = new DataInputStream(conexo.getInputStream());
double valor = dis.readDouble();
System.out.println("Li o valor "+valor+" do servidor "+servidor+".");
// Fechamos a conexo.
dis.close();
conexo.close();
}
// Se houve problemas com o nome do host...
catch (UnknownHostException e)
{
System.out.println("O servidor no existe ou est fora do ar.");
}
// Se houve problemas genricos de entrada e sada...
catch (IOException e)
{
System.out.println("Erro de entrada e sada.");
}
}
}

Rafael Santos

Programao Cliente-Servidor Usando Java

25

5.4

Exemplo: Servidor simples de arquivos

Vamos considerar mais um exemplo de aplicao cliente-servidor, onde o tipo de dados


e seu processamento ser diferenciado durante a execuo do protocolo de comunicao. Consideremos um servidor extremamente simples de arquivos, similar a um servidor de FTP mas com boa parte da funcionalidade (ex. acesso a diferentes diretrios,
autenticao de usurios) removida. Durante a interao com o cliente, este servidor
ter que se comunicar enviando e recebendo strings (comandos, listas de arquivos, etc.)
que sero impressas no terminal e tambm enviando bytes que sero gravados em um
arquivo pelo cliente.
O protocolo de comunicao entre o servidor e o cliente simples: o servidor envia para
o cliente a lista de arquivos, o cliente seleciona um, o servidor envia os bytes daquele
arquivo para o cliente que armazena estes bytes em um arquivo local. Este protocolo
ilustrado na figura 11.
A implementao do servidor de arquivos feita na classe ServidorDeArquivos, que
mostrada na listagem 9.

Rafael Santos

Programao Cliente-Servidor Usando Java

26

Cliente

Servidor

Incio

Incio

Solicita conexo

Aguarda conexo

Cria conexo

Imprime lista de arquivos

Envia lista de arquivos locais

Seleciona arquivo e envia

L nome do arquivo
selecionado

L contedo do arquivo
selecionado

Envia contedo do
arquivo selecionado

Armazena contedo em disco

Fim

Figura 11: Protocolo de comunicao entre o cliente e o servidor de arquivos

Listagem 9: Servidor de arquivos


1
2
3
4
5
6
7
8
9
10
11

package cap312;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;

12
13
14
15
16
17
18
19
20
21
22
23

/**
* Esta classe implementa um servidor simples que fica aguardando conexes de
* clientes. Este servidor, quando conectado, envia uma lista de arquivos locais
* para o cliente, aguarda a seleo de um nome de arquivo e envia o contedo
* deste arquivo para o cliente.
*/
public class ServidorDeArquivos
{
// Mtodo que permite a execuo da classe.
public static void main(String[] args)
{

Rafael Santos

Programao Cliente-Servidor Usando Java

27
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

try
{
// Criamos a instncia de ServerSocket que responder por solicitaes
// porta 2048.
ServerSocket servidor = new ServerSocket(2048);
// O servidor aguarda "para sempre" as conexes.
while(true)
{
// Quando uma conexo feita,
Socket conexo = servidor.accept();
// ... o servidor a processa.
processaConexo(conexo);
}
}
// Pode ser que a porta j esteja em uso !
catch (BindException e)
{
System.out.println("Porta j em uso.");
}
// Pode ser que tenhamos um erro qualquer de entrada ou sada.
catch (IOException e)
{
System.out.println("Erro de entrada ou sada.");
}
}

49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

// Este mtodo atende a uma conexo feita a este servidor.


private static void processaConexo(Socket conexo)
{
try
{
// Criamos uma stream para receber comandos do cliente - estes comandos
// sero somente strings.
BufferedReader entrada =
new BufferedReader(new InputStreamReader(conexo.getInputStream()));
// Criamos uma stream para enviar textos e dados para o cliente.
DataOutputStream sada =
new DataOutputStream(conexo.getOutputStream());
// Mandamos uma mensagem de boas vindas.
sada.writeUTF("Bem-vindo ao servidor de arquivos locais.");
sada.writeUTF("=========================================");
// Enviamos ao cliente a lista de arquivos locais.
File diretrio = new File("."); // bom para Linux, ser que funciona no Windows?
String[] arquivos = diretrio.list(); // oops, inclui diretrios tambm !
for(int a=0;a<arquivos.length;a++)
{
sada.writeUTF(arquivos[a]);
}
// Aguardamos a seleo do usurio.
sada.writeUTF("-----------------------------------------");
sada.writeUTF("Selecione um dos arquivos acima.");
// Informamos ao cliente que terminamos de mandar texto.
sada.writeUTF("#####");
// Garantimos que as mensagens foram enviadas ao cliente.
sada.flush();
// Lemos o nome de arquivo selecionado pelo cliente.
String nomeSelecionado = entrada.readLine();
// Criamos uma representao do arquivo.
File selecionado = new File(nomeSelecionado);
// Enviamos uma mensagem esclarecedora para o cliente.
sada.writeUTF("Enviando arquivo "+nomeSelecionado+" ("+

Rafael Santos

Programao Cliente-Servidor Usando Java

28
selecionado.length()+" bytes)");
sada.flush();
// Abrimos o arquivo localmente.
DataInputStream entradaLocal =
new DataInputStream(new FileInputStream(selecionado));
// Lemos todos os bytes do arquivo local, enviando-os para o cliente.
// Para maior eficincia, vamos ler e enviar os dados em blocos de 25 bytes.
byte[] arrayDeBytes = new byte[25];
while(true)
{
// Tentamos ler at 25 bytes do arquivo de entrada.
int resultado = entradaLocal.read(arrayDeBytes,0,25);
if (resultado == -1) break;
// Escrevemos somente os bytes lidos.
sada.write(arrayDeBytes,0,resultado);
}
// Ao terminar de ler o arquivo local, o fechamos.
entradaLocal.close();
// Ao terminar de atender a requisio, fechamos a stream de sada.
sada.close();
// Fechamos tambm a conexo.
conexo.close();
}
// Se houve algum erro de entrada ou sada...
catch (IOException e)
{
System.out.println("Erro atendendo a uma conexo !");
e.printStackTrace();
}
}

85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115

Alguns dos pontos de interesse na classe ServidorDeArquivos (listagem 9) so:


O servidor segue o mesmo padro de ter uma classe main e uma processaConexo
isto far o desenvolvimento de servidores capazes de atender mltiplas requisies (veja seo 6) mais simples.
Para receber os dados do cliente, o servidor usa uma instncia de BufferedReader,
pois assumimos que o cliente somente enviar strings para o servidor. Para enviar
os dados para o cliente, usando a mesma conexo, usamos uma instncia da classe
DataOutputStream, que capaz de enviar strings (usando o mtodo writeUTF) e
valores de tipos nativos, inclusive arrays de bytes, com o mtodo write.
Em determinado ponto da interao entre cliente e servidor, o servidor ir enviar
um nmero desconhecido de linhas de texto para o cliente (os nomes de arquivos
disponveis no servidor). Para facilitar a interao com o cliente, que no sabe
antecipadamente quantas linhas de texto sero enviadas, usamos um marcador para
informar ao cliente que no existem mais linhas de texto a ser lidas: quando o
cliente receber a string ##### ele saber que deve parar de ler linhas de texto do
servidor.
Quando o servidor for enviar os bytes do arquivo selecionado para o cliente, o
procedimento ser outro: o servidor enviar bytes enquanto existirem (em um lao
Rafael Santos

Programao Cliente-Servidor Usando Java

29

que ser encerrado quando no for mais possvel ler bytes do arquivo local) e o
cliente monitorar estes bytes lidos para verificar o final da transmisso. Desta
forma, demonstramos duas maneiras de verificar se todos os dados de uma sesso
de comunicao foram enviados pelo servidor ou cliente: um usando marcadores
ou tags que indicam final de transmisso e outro usando mtodos da API de Java.
Vejamos agora o cliente para este servidor de arquivos. No poderemos usar o cliente
telnet pois em determinado momento da comunicao o servidor enviar bytes para o
cliente, que devem ser armazenados em um arquivo e no mostrados na tela. Um cliente
adequado mostrado na classe ClienteDeArquivos (listagem 10).
Listagem 10: Cliente de arquivos
1
2
3
4
5
6
7
8
9
10

package cap312;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;

11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

/**
* Esta classe implementa um cliente simples para o servidor de arquivos.
* A principal diferena entre este cliente e um cliente simples de texto
* (telnet, por exemplo) que existe uma ordem no protocolo de comunicao
* entre cliente-servidor que pede que em determinado momento, ao invs de
* receber textos para mostrar no terminal, o cliente deve receber dados para
* armazenar em um arquivo.
*/
public class ClienteDeArquivos
{
public static void main(String[] args)
{
String servidor = "localhost";
// Tentamos fazer a conexo...
try
{
Socket conexo = new Socket(servidor,2048);
// A partir do socket podemos obter um InputStream, a partir deste um
// InputStreamReader e partir deste, um BufferedReader.
DataInputStream entrada =
new DataInputStream(conexo.getInputStream());
// A partir do socket podemos obter um OutputStream, a partir deste um
// OutputStreamWriter e partir deste, um BufferedWriter.
BufferedWriter sada =
new BufferedWriter(
new OutputStreamWriter(conexo.getOutputStream()));
// Lemos a lista de arquivos do servidor. Sabemos que vamos ler um
// nmero desconhecido de linhas do servidor. Usamos um lao que
// l linhas enquanto existirem dados a ser lidos. O servidor enviar
// a string ##### quando no houverem mais dados.
while(true)
{
String linha = entrada.readUTF();

Rafael Santos

Programao Cliente-Servidor Usando Java

30
if(linha.equals("#####")) break; // cdigo do servidor: acabaram as linhas.
System.out.println(linha);
}
// Lemos o nome do arquivo que desejamos do teclado e o
// enviamos para o servidor.
String arquivo = Keyboard.readString();
sada.write(arquivo);
sada.newLine();
sada.flush();
// O servidor envia para o cliente mais uma informao como texto.
String linha = entrada.readUTF();
System.out.println(linha);
// Abrimos o arquivo local. Vamos dar um nome diferente para ele s
// para no complicar a vida de quem estiver rodando cliente e servidor
// no mesmo computador.
DataOutputStream sadaParaArquivo =
new DataOutputStream(new FileOutputStream("_"+arquivo));
// A partir deste momento o servidor vai nos enviar bytes. Lemos todos os
// bytes enviados do servidor, gravando-os em um arquivo local.
// Para maior eficincia, vamos ler os dados em blocos de 25 bytes.
byte[] array = new byte[25];
while(true)
{
int resultado = entrada.read(array,0,25);
if (resultado == -1) break;
// Escrevemos somente os bytes lidos.
sadaParaArquivo.write(array,0,resultado);
}
// Fechamos as streams e a conexo.
sada.close();
entrada.close();
conexo.close();
}
// Se houve problemas com o nome do host...
catch (UnknownHostException e)
{
System.out.println("O servidor no existe ou est fora do ar.");
}
// Se houve problemas genricos de entrada e sada...
catch (EOFException e)
{
System.out.println("Erro de EOF.");
}
// Se houve problemas genricos de entrada e sada...
catch (IOException e)
{
System.out.println("Erro de entrada e sada.");
}
}

45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

O cliente mostrado na listagem 10 tem, como pontos de interesse, o esquema de leitura de vrias linhas do servidor, que continua lendo linhas do servidor at que a linha
##### seja enviada; e o trecho que l bytes do servidor (em blocos de 25 bytes) e
armazena estes bytes em um arquivo local.

Rafael Santos

Programao Cliente-Servidor Usando Java

31

Servidores para mltiplas requisies simultneas

At agora os servidores implementados (exceto pelos servidores j existentes como


echo e daytime) so capazes de atender uma nica requisio simultnea dos clientes
- se um cliente j estivesse acessando o servio e outros clientes tambm tentassem o
acesso, estes outros clientes teriam que aguardar a conexo com o primeiro cliente ser
encerrada.
Felizmente, os servidores demonstrados so muito simples, enviando dados para o cliente e imediatamente encerrando a conexo, permitindo a conexo por parte de outro
cliente logo em seguida. caso os servidores, por alguma razo, demorassem demais para
atender os clientes, outros clientes experimentariam uma demora no acesso ou mesmo
a negao do servio. Podemos assumir que servidores comuns (como, por exemplo,
http) so capazes de atender mltiplas requisies simultaneamente, caso contrrio um
usurio teria que esperar outro terminar a sua conexo em uma espcie de fila.
Nesta seo veremos por que que alguns servidores devem estar preparados para atender mltiplas requisies simultaneamente e como podemos preparar servidores para
esta tarefa.
6.1

O problema de mltiplas requisies simultneas

Consideremos uma pequena alterao no protocolo de comunicao entre o cliente e


o servidor de nmeros aleatrios: o cliente agora envia para o servidor o nmero de
valores que este deseja receber. O servidor, por sua vez, ao invs de enviar somente um
nmero aleatrio, envia a quantidade de nmeros solicitada pelo cliente.
O protocolo de comunicao para esta verso de cliente e servidor mostrado na figura 12.
Embora o diagrama da figura 12 parea mais complicado do que os outros, a nica diferena fundamental que existem dois laos, do lado do cliente e do lado do servidor,
que garantem que o mesmo nmero de valores ser requisitado e lido.
Considerando estas modificaes, a classe servidora de nmeros aleatrios foi reescrita
como mostra a listagem 11. Esta classe segue o mesmo padro de dois mtodos, um
main e um responsvel por processar uma requisio.
Listagem 11: Segunda verso do servidor de nmeros aleatrios
1
2
3
4

package cap312;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

Rafael Santos

Programao Cliente-Servidor Usando Java

32
5
6
7

import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

/**
* Esta classe implementa um servidor simples que fica aguardando conexes de
* clientes. Quando uma conexo solicitada, o servidor l do cliente a
* quantidade de nmeros aleatrios desejada e envia para o cliente
* estes nmeros aleatrios, terminando ento a conexo.
*/
public class SegundoServidorDeNumerosAleatorios
{
// Mtodo que permite a execuo da classe.
public static void main(String[] args)
{
ServerSocket servidor;
try
{
// Criamos a instncia de ServerSocket que responder por solicitaes

Cliente

Servidor

Incio

Incio

Solicita conexo

Aguarda conexo

Cria conexo

L valor inteiro do teclado

Envia inteiro para servidor

L inteiro do cliente
Aindaexistem
valores
aserenviados?

Aindaexistem
valores
aserlidos?

N
S

L double do servidor

Envia double para cliente

Imprime valor lido

Fim

Figura 12: Protocolo de comunicao entre o cliente e o servidor de nmeros aleatrios (segunda verso)
Rafael Santos

Programao Cliente-Servidor Usando Java

33
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

// porta 9999.
servidor = new ServerSocket(9999);
// O servidor aguarda "para sempre" as conexes.
while(true)
{
// Quando uma conexo feita,
Socket conexo = servidor.accept();
// ... o servidor a processa.
processaConexo(conexo);
}
}
// Pode ser que a porta 9999 j esteja em uso !
catch (BindException e)
{
System.out.println("Porta j em uso.");
}
// Pode ser que tenhamos um erro qualquer de entrada ou sada.
catch (IOException e)
{
System.out.println("Erro de entrada ou sada.");
}
}

46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

// Este mtodo atende a uma conexo feita a este servidor.


private static void processaConexo(Socket conexo)
{
try
{
// Criamos uma stream para receber valores nativos, usando a stream de entrada
// associado conexo.
DataInputStream entrada =
new DataInputStream(conexo.getInputStream());
// Criamos uma stream para enviar valores nativos, usando a stream de sada
// associado conexo.
DataOutputStream sada =
new DataOutputStream(conexo.getOutputStream());
// Lemos do cliente quantos nmeros ele deseja.
int quantidade = entrada.readInt();
// Enviamos para o cliente os nmeros.
for(int q=0;q<quantidade;q++)
{
double rand = Math.random();
sada.writeDouble(rand);
// Para demonstrar que realmente funciona, vamos imprimir um log.
System.out.println("Acabo de enviar o nmero "+rand+" para o cliente "+
conexo.getRemoteSocketAddress());
}
// Ao terminar de atender a requisio, fechamos as streams.
entrada.close();
sada.close();
// Fechamos tambm a conexo.
conexo.close();
}
// Se houve algum erro de entrada ou sada...
catch (IOException e)
{
System.out.println("Erro atendendo a uma conexo !");
}
}
}

Rafael Santos

Programao Cliente-Servidor Usando Java

34

O cliente para este servio tambm precisa ser reescrito. O cliente para a segunda verso
do servidor de nmeros aleatrios mostrado na listagem 12.
Listagem 12: Segunda verso do cliente de nmeros aleatrios
1
2
3
4
5
6

package cap312;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

public class SegundoClienteDeNumerosAleatorios


{
public static void main(String[] args)
{
String servidor = "localhost";
// Tentamos fazer a conexo e ler uma linha...
try
{
Socket conexo = new Socket(servidor,9999);
// A partir do socket podemos obter um InputStream, a partir deste um
// DataInputStream.
DataInputStream dis =
new DataInputStream(conexo.getInputStream());
// A partir do socket podemos obter um OutputStream, a partir deste um
// DataOutputStream.
DataOutputStream dos =
new DataOutputStream(conexo.getOutputStream());
// Quantos nmeros aleatrios vamos querer ?
System.out.print("Quantos nmeros:");
int quantos = Keyboard.readInt();
// Enviamos esta informao para o servidor.
dos.writeInt(quantos);
// Lemos os nmeros desejados do servidor.
for(int q=0;q<quantos;q++)
{
double valor = dis.readDouble();
System.out.println("O "+(q+1)+"o valor "+valor+".");
}
// Fechamos a conexo.
dis.close();
conexo.close();
}
// Se houve problemas com o nome do host...
catch (UnknownHostException e)
{
System.out.println("O servidor no existe ou est fora do ar.");
}
// Se houve problemas genricos de entrada e sada...
catch (IOException e)
{
System.out.println("Erro de entrada e sada.");
}
}
}

O cliente modificado mostrado na listagem 12 tambm bem simples, contendo somente um lao adicional quando comparado com outros clientes.
Rafael Santos

Programao Cliente-Servidor Usando Java

35

O cliente e servidor de mltiplos nmeros aleatrios funcionam corretamente, mas podem causar um srio problema. Imagine que este seja um servio concorrido, com
vrias pessoas a qualquer momento solicitando nmeros aleatrios. Se um usurio solicitar uma quantidade exageradamente grande de nmeros aleatrios, outro cliente estar
impossibilitado de usar o servio, pois o mtodo processaConexo do servidor deve
terminar o seu processamento para retornar o controle ao mtodo main, que ento poder atender outra conexo quando o mtodo accept for executado.
Temos algumas possibilidades para a soluo deste problema em potencial:
Fazer clientes aguardarem sua vez sem maiores informaes - no existe maneira
de informar aos clientes quanto tempo eles tero que esperar na fila para atendimento (analogia: telefone ocupado).
Executar mais de um servidor simultaneamente, em portas diferentes - se a primeira tentativa de conexo fracassar, o cliente dever descobrir, por conta prpria,
qual das portas que oferecem o servio est desocupada (analogia: vrios telefones
para a mesma pessoa, alguns ocupados).
Limitar a quantidade de nmeros a serem servidos para que a conexo seja liberada
o mais rpido possvel para outros clientes.
Evidentemente estas opes so muito restritivas. Uma outra alternativa, mais interessante, seria fazer uso de linhas de execuo ou threads. Basicamente threads permitem
que aplicaes executem mais de uma tarefa de forma aparentemente simultnea. Continuamos com uma aplicao que tem um nico mtodo main, mas durante a execuo
deste mtodo ele cria vrias instncias de classes que podem ser executadas concorrentemente. Para escrever um servidor capaz de tratar vrias requisies de forma aparentemente simultnea, podemos usar os conceitos de threads e as classes em Java que os
implementam.
6.2

Linhas de execuo (threads)

Para melhor compreender linhas de execuo, consideremos um exemplo simples no


relacionado com programao cliente-servidor. Imaginemos uma simulao que envolva alguns objetos que poderiam se comportar independentemente, como por exemplo, uma simulao de corrida onde cada carro tem uma velocidade e independente de
outros carros. Vamos criar uma classe CarroDeCorrida para representar um carro de
corrida para esta simulao. Esta classe mostrada na listagem 13.
Listagem 13: Classe que representa um carro de corrida para simulao.
1
2

package cap312;
/**

Rafael Santos

Programao Cliente-Servidor Usando Java

36
3
4
5
6
7
8
9

* Esta classe representa um carro da Corrida Maluca para uma simulao.


*/
public class CarroDeCorrida
{
private String nome;
private int distncia;
private int velocidade;

10
11
12
13
14
15
16
17
18
19

/**
* O construtor da classe inicializa o nome do carro e a velocidade do mesmo.
*/
public CarroDeCorrida(String n,int vel)
{
nome = n;
distncia = 0;
velocidade = vel;
}

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

/**
* Este mtodo imprime os passos percorridos pelo carro.
*/
public void executa()
{
while(distncia <= 1200)
{
System.out.println(nome+" rodou "+distncia+" km.");
distncia += velocidade;
// Pausa o processamento com um clculo intil.
for(int sleep=0;sleep<1000000;sleep++)
{
double x = Math.sqrt(Math.sqrt(Math.sqrt(sleep)));
}
}
}

37
38

A classe CarroDeCorrida (listagem 13) realmente simples, tendo como mtodos somente o construtor e um mtodo executa que executar a simulao para a instncia
da classe. A simulao consiste em mudar a distncia percorrida pelo carro, imprimir
na tela e fazer uma pequena pausa (artificialmente criada por um lao que executa uma
operao matemtica).
A simulao de uma corrida poderia ser feita atravs de uma classe com vrias instncias da classe CarroDeCorrida. A classe SimulacaoSemThreads, na listagem 14, cria
uma simulao simples.
Listagem 14: Simulao usando instncias de CarroDeCorrida.
1
2
3
4
5
6
7

package cap312;
/**
* Esta classe faz uma simulao de corrida usando instncias da classe
* CarroDeCorrida.
*/
public class SimulacaoSemThreads
{

Rafael Santos

Programao Cliente-Servidor Usando Java

37
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

// Este mtodo permite a execuo da classe.


public static void main(String[] args)
{
// Criamos instncias da classe CarroDeCorrida.
CarroDeCorrida penlope =
new CarroDeCorrida("Penlope Charmosa",60);
CarroDeCorrida dick =
new CarroDeCorrida("Dick Vigarista",100);
CarroDeCorrida quadrilha =
new CarroDeCorrida("Quadrilha da Morte",120);
// Criados os carros, vamos executar as simulaes.
penlope.executa();
dick.executa();
quadrilha.executa();
}
}

A classe SimulacaoSemThreads tambm relativamente simples: criamos as instncias de CarroDeCorrida e executamos os seus mtodos executa. O problema, para
uma simulao, que os trs mtodos so executados de forma dependente um do outro:
s podemos executar o mtodo executa da instncia quadrilha depois de ter terminado completamente a execuo dos mtodos das instncias penlope e dick. Para
uma simulao mais realista, isso no seria aceitvel os mtodos deveriam ser executados em paralelo. O resultado da execuo da classe SimulacaoSemThreads mostrar
a simulao para a instncia penlope, seguida da simulao para a instncia dick e
finalmente a simulao para a instncia quadrilha.
A forma mais simples para fazer com que as instncias possam ter um de seus mtodos
executados em paralelo com outros fazer a classe que contm o mtodo herdar da
classe Thread e implementar o mtodo run, que ser o mtodo a ser executado em
paralelo. Isto demonstrado na classe CarroDeCorridaIndependente, mostrada na
listagem 15.
Listagem 15: Classe que representa um carro de corrida para simulao (herdando de Thread).
1
2
3
4
5
6
7
8
9
10
11

package cap312;
/**
* Esta classe representa um carro da Corrida Maluca para uma simulao.
* Esta verso da classe herda da classe Thread, ento o mtodo run de
* instncias desta classe poder ser executado independentemente.
*/
public class CarroDeCorridaIndependente extends Thread
{
private String nome;
private int distncia;
private int velocidade;

12
13
14
15
16
17
18

/**
* O construtor da classe inicializa o nome do carro e a velocidade do mesmo.
*/
public CarroDeCorridaIndependente(String n,int vel)
{
nome = n;

Rafael Santos

Programao Cliente-Servidor Usando Java

38
distncia = 0;
velocidade = vel;
}

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

/**
* Este mtodo imprime a distncia percorrida at agora.
*/
public void run()
{
while(distncia <= 1200)
{
System.out.println(nome+" rodou "+distncia+" km.");
distncia += velocidade;
// Pausa o processamento com um clculo intil.
for(int sleep=0;sleep<1000000;sleep++)
{
double x = Math.sqrt(Math.sqrt(Math.sqrt(sleep)));
}
}
}

39
40

importante observar que a assinatura do mtodo run deve obrigatoriamente ser public
void este mtodo no deve receber argumentos nem relanar excees.
A classe SimulacaoComThreads (listagem 16) demonstra a simulao de instncias da
classe CarroDeCorridaIndependente.
Listagem 16: Simulao usando instncias de CarroDeCorridaIndependente.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

package cap312;
/**
* Esta classe faz uma simulao de corrida usando instncias da classe
* CarroDeCorridaIndependente.
*/
public class SimulacaoComThreads
{
// Este mtodo permite a execuo da classe.
public static void main(String[] args)
{
// Criamos instncias da classe CarroDeCorrida.
CarroDeCorridaIndependente penlope =
new CarroDeCorridaIndependente("Penlope Charmosa",60);
CarroDeCorridaIndependente dick =
new CarroDeCorridaIndependente("Dick Vigarista",100);
CarroDeCorridaIndependente quadrilha =
new CarroDeCorridaIndependente("Quadrilha da Morte",120);
// Criados os carros, vamos executar as simulaes.
penlope.start();
dick.start();
quadrilha.start();
}
}

Quando a classe SimulacaoComThreads (listagem 16) for executada, veremos que as


impresses dos nomes dos carros e respectivas posies aparecero intercaladas e em
Rafael Santos

Programao Cliente-Servidor Usando Java

39

ordem aparentemente imprevisvel, pois esto sendo executadas concorrentemente. Se


o trecho de cdigo que simula uma pausa no processamento for reescrito para a pausa
ser maior, este efeito de ordem imprevisvel ser aumentado. Se o trecho for eliminado,
quase sempre as simulaes sero executadas na mesma ordem em que os mtodos run
forem chamados, embora no exista garantia que isso sempre vai acontecer. Mesmo se
o tempo de pausa fosse simulado de forma aleatria para cada execuo, o resultado da
execuo da classe SimulacaoSemThreads seria o mesmo (na ordem de chamada do
mtodo executa) enquanto que seguramente os resultados seriam diferentes para cada
execuo da classe SimulacaoComThreads.
importante notar que declaramos o mtodo run na classe CarroDeCorridaIndependente (listagem 15) como sendo o ponto de entrada do processamento a ser feito em
paralelo, mas devemos executar o mtodo start (veja listagem 16) para dar incio
execuo do mtodo run.
O que possibilita a execuo de mtodos concorrentemente o fato do mtodo start
inicializar a execuo da thread e retornar imediatamente. No exemplo da classe SimulacaoComThreads (listagem 16), logo depois que a thread para processamento dos
dados da instncia penlope inicializada, o controle retorna para o mtodo main, que
executa o mtodo start para a instncia dick, retornando novamente o controle para
o mtodo main, que finalmente executa o mtodo start para a instncia quadrilha.
Enquanto o mtodo main continua sendo executado, os mtodos run das instncias da
classe CarroDeCorridaIndependente iam so executados concorrentemente.
6.3

Exemplo: Servidor de nmeros aleatrios (para requisies simultneas)

Vamos reescrever o servidor de nmeros aleatrios (segunda verso) para usar linhas
de execuo, assim fazendo com que o servidor no bloqueie usurios que tentem
se conectar enquanto uma sesso estiver em andamento. Usando o conceito de linhas de execuo, o servidor, ao invs de executar um mtodo de sua prpria classe
(processaConexo) quando tiver que atender um cliente, ir criar uma classe que herda
de Thread cujo mtodo run executa exatamente o que o mtodo processaConexo faria.
Os passos para transformar uma classe que implementa um servidor sem mltiplas linhas de execuo para classes que implementam servidores com mltiplas linhas de
execuo so, ento:
1. Criar uma classe que implementa o atendimento a uma nica conexo. Esta classe
dever herdar da classe Thread, ter um construtor (para possibilitar o armazenamento da instncia de Socket relacionada com a conexo que deve ser atendida
por uma instncia desta mesma classe) e o mtodo run, que dever ser responRafael Santos

Programao Cliente-Servidor Usando Java

40

svel pelo processamento do atendimento da conexo. Em geral, o contedo do


mtodo run deve ser o mesmo dos mtodos processaConexo usados em outros
exemplos.
2. Reescrever a classe do servidor para que a mesma, ao receber uma conexo (atravs do mtodo accept da classe ServerSocket, crie uma nova instncia da classe
responsvel pelo atendimento a uma nica conexo e execute o mtodo start
desta instncia.
Usando estes passos, reescreveremos o servidor de nmeros aleatrios. Primeiro, escrevemos a classe ServicoDeNumerosAleatorios cujas instncias sero responsveis
por processar uma nica conexo. A classe ServicoDeNumerosAleatorios mostrada na listagem 17 e contm somente o construtor e o mtodo run que executa todo o
atendimento conexo.
Listagem 17: Classe que implementa o servio de nmeros aleatrios
1
2
3
4
5

package cap312;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

6
7
8
9
10
11
12
13
14
15

/**
* Esta classe representa o processamento que deve ser feito quando uma
* nica requisio for feita a um servidor. A classe herda de Thread para que
* vrias instncias dela possam ser executadas concorrentemente.
*/
public class ServicoDeNumerosAleatorios extends Thread
{
// Precisamos armazenar o socket correspondente conexo.
private Socket conexo = null;

16
17
18
19
20
21
22
23
24

/**
* O construtor desta classe recebe como argumento uma instncia de Socket
* e o armazena em um campo da classe.
*/
public ServicoDeNumerosAleatorios(Socket conn)
{
conexo = conn;
}

25
26
27
28
29
30
31
32
33
34
35
36
37
38

/**
* Este mtodo executa a rotina de atendimento a uma conexo com o servidor.
*/
public void run()
{
try
{
// Criamos uma stream para receber valores nativos, usando a stream de entrada
// associado conexo.
DataInputStream entrada =
new DataInputStream(conexo.getInputStream());
// Criamos uma stream para enviar valores nativos, usando a stream de sada
// associado conexo.

Rafael Santos

Programao Cliente-Servidor Usando Java

41
DataOutputStream sada =
new DataOutputStream(conexo.getOutputStream());
// Lemos do cliente quantos nmeros ele deseja.
int quantidade = entrada.readInt();
// Enviamos para o cliente os nmeros.
for(int q=0;q<quantidade;q++)
{
double rand = Math.random();
sada.writeDouble(rand);
// Para demonstrar que realmente funciona, vamos imprimir um log.
System.out.println("Acabo de enviar o nmero "+rand+" para o cliente "+
conexo.getRemoteSocketAddress());
}
// Ao terminar de atender a requisio, fechamos as streams.
entrada.close();
sada.close();
// Fechamos tambm a conexo.
conexo.close();
}
// Se houve algum erro de entrada ou sada...
catch (IOException e)
{
System.out.println("Erro atendendo a uma conexo !");
}
}

39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

necessrio armazenar na classe ServicoDeNumerosAleatorios (listagem 17) uma


instncia da classe Socket que corresponde ao socket que foi criado para comunicao
com o cliente, pois no poderamos passar esta instncia como argumento para o mtodo run o mesmo deve ser sempre executado sem argumentos.
A classe que representa o servidor tambm foi modificada de acordo com os passos indicados. A classe TerceiroServidorDeNumerosAleatorios contm agora somente o
mtodo main que cria novas instncias de ServicoDeNumerosAleatorios quando novas conexes so estabelecidas, e que executa o mtodo start destas instncias, retornando o controle ao mtodo main imediatamente. A classe TerceiroServidorDeNumerosAleatorios mostrada na listagem 18.
Listagem 18: Terceira verso do servidor de nmeros aleatrios
1
2
3
4
5

package cap312;
import java.io.IOException;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;

6
7
8
9
10
11
12
13

/**
* Esta classe implementa um servidor simples que fica aguardando conexes de
* clientes. Quando uma conexo solicitada, o servidor l do cliente a
* quantidade de nmeros aleatrios desejada e envia para o cliente
* estes nmeros aleatrios, terminando ento a conexo.
*/
public class TerceiroServidorDeNumerosAleatorios

Rafael Santos

Programao Cliente-Servidor Usando Java

42
{
// Mtodo que permite a execuo da classe.
public static void main(String[] args)
{
ServerSocket servidor;
try
{
// Criamos a instncia de ServerSocket que responder por solicitaes
// porta 9999.
servidor = new ServerSocket(9999);
// O servidor aguarda "para sempre" as conexes.
while(true)
{
// Quando uma conexo feita,
Socket conexo = servidor.accept();
// ... criamos um servio para atender a esta conexo...
ServicoDeNumerosAleatorios s = new ServicoDeNumerosAleatorios(conexo);
// ... e executamos o servio (que ser executado independentemente).
s.start();
}
}
// Pode ser que a porta 9999 j esteja em uso !
catch (BindException e)
{
System.out.println("Porta j em uso.");
}
// Pode ser que tenhamos um erro qualquer de entrada ou sada.
catch (IOException e)
{
System.out.println("Erro de entrada ou sada.");
}
}
}

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

Quando o mtodo main da classe TerceiroServidorDeNumerosAleatorios (listagem 18) for executado, aguardar uma conexo do cliente. Quando esta conexo for
criada (ou seja, quando o mtodo accept for executado e retornar uma instncia de
Socket, uma instncia da classe ServicoDeNumerosAleatorios ser criada para tratar desta conexo, e o seu mtodo start ser executado. Imediatamente (isto , sem
aguardar o final da execuo do mtodo start ou run) o controle passar de volta para
o mtodo main, que estar liberado para aceitar novas conexes.
O cliente desta classe no precisa de modificaes, como cliente podemos usar a classe
SegundoClienteDeNumerosAleatorios (listagem 12).

Aplicaes baseadas em um servidor e vrios clientes

Veremos agora como funciona uma aplicao onde existe um servidor e vrios clientes
conectados simultaneamente, mas de forma que cada cliente tenha a sua vez de interagir
com o servidor (os clientes no so independentes). Este exemplo de interao ilustra o
papel de um servidor como mediador em um jogo, por exemplo.
Rafael Santos

Programao Cliente-Servidor Usando Java

43

7.1

Exemplo: Servidor de jogo-da-velha

Vejamos um exemplo de aplicao cliente-servidor com um servidor e dois clientes sincronizados (isto , que tem a sua vez para interagir com o servidor determinadas e em
ordem). O jogo-da-velha um bom exemplo: dois jogadores, atravs dos seus clientes,
se conectam com o servidor, e um depois do outro, enviam comandos para este. Alm
de receber e processar os movimentos feitos pelos clientes, o servidor responsvel por
analisar estes movimentos (isto , verificar se eles so vlidos), informar os jogadores
dos movimentos de ambos e determinar quando o jogo tiver um vencedor (terminando
as conexes com os clientes).
O algoritmo para controlar as jogadas e verificar se houve vencedores no jogo-da-velha
simples. Consideremos que o jogo implementado como um tabuleiro de 3x3 posies. Internamente este tabuleiro ser um array bidimensional de caracteres, onde cada
posio (caracter) poder ser igual a um X ou O se estiver ocupado ou espao se estiver
livre. Para que seja mais fcil para o jogador indicar a posio onde quer jogar, as linhas
do tabuleiro sero indicadas por A, B e C e as colunas por 1, 2 e 3. A figura 13 mostra
o tabuleiro com os indicadores de linhas e colunas.

A
B
C

Figura 13: Posies para jogo e linhas vencedoras no jogo-da-velha

O algoritmo que verifica se houve um vencedor para o jogo tambm simples. Existem
oito combinaes de posies que se forem ocupadas pelo mesmo jogador determinam
a vitria deste jogador: se as trs linhas, trs colunas ou duas diagonais do tabuleiro
forem totalmente preenchidas pelo mesmo jogador, este ganhou o jogo. As setas mostradas na figura 13 indicam estas combinaes.
Para que um jogo possa ser executado, so necessrios dois jogadores (dois clientes).
Conforme mencionado anteriormente, os clientes no se conectam e enviam/recebem
informaes do servidor simultaneamente (usando linhas de execuo): a comunicao
feita por vezes, uma vez sendo a de um cliente/jogador e a vez seguinte sendo do outro.

Rafael Santos

Programao Cliente-Servidor Usando Java

44

O protocolo do jogo simples: o servidor aguarda a conexo de dois clientes, e alterna


o recebimento de comandos (posies para jogo) de um cliente para o outro. Depois da
jogada de cada cliente, o servidor verifica se a jogada vlida, modificando o tabuleiro
do jogo se o for. Se houver vencedor, ou se no houver mais posies onde o jogador
da vez possa jogar, o resultado apresentado e a conexo com os clientes encerrada.
O protocolo de interao entre os clientes e o servidor mostrado na figura 14.

Cliente 1

Servidor

Cliente 2

Incio

Incio

Incio

Solicita conexo

Aguarda conexo

Solicita conexo

Cria conexo

Aguarda conexo

Cria conexo

Envia movimento

Recebe movimento
Venceuo
jogo?

S
N
Imprime situao

Envia situao

Imprime situao

Recebe movimento

Envia movimento

Venceuo
jogo?

N
S

Imprime situao

Fim

Envia situao

Imprime situao

Fim

Figura 14: Protocolo de comunicao entre o servidor e os clientes de jogo-da-velha

A implementao de uma classe que atua como servidora de jogo-da-velha (classe


ServidorDeJogoDaVelha) mostrada na listagem 19.
Rafael Santos

Programao Cliente-Servidor Usando Java

45
Listagem 19: O servidor de Jogo-da-Velha
1
2
3
4
5
6
7
8
9

package cap312;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;

10
11
12
13
14
15
16
17
18

/**
* Esta classe implementa um servidor de jogo-da-velha. Este servidor aguarda
* a conexo de dois clientes e gerencia o jogo entre eles.
*/
public class ServidorDeJogoDaVelha
{
// Declaramos uma matriz de caracteres como sendo o tabuleiro do jogo.
private static char[][] tabuleiro;

19
20
21
22
23

// Assumimos que toda a entrada e sada ser feita por strings.


// Declaramos instncias de BufferedReader e BufferedWriter para cada cliente.
private static BufferedReader entrada1,entrada2;
private static BufferedWriter sada1,sada2;

24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

// Mtodo que permite a execuo da classe.


public static void main(String[] args)
{
// Alocamos memria para o tabuleiro do jogo.
tabuleiro = new char[3][3];
try
{
// Criamos a instncia de ServerSocket que responder por solicitaes
// porta 12345.
ServerSocket servidor = new ServerSocket(12345);
// O servidor aguarda "para sempre" as conexes.
while(true)
{
// Aguardamos a primeira conexo...
Socket conexo1 = servidor.accept();
System.out.println("Conexo 1 aceita para o cliente "+
conexo1.getRemoteSocketAddress());
// Criamos as streams de entrada e sada para o cliente 1.
entrada1 =
new BufferedReader(new InputStreamReader(conexo1.getInputStream()));
sada1 =
new BufferedWriter(new OutputStreamWriter(conexo1.getOutputStream()));
// Mandamos uma mensagem de boas vindas.
enviaMensagem(sada1,"Ol, voc ser o primeiro jogador (X).");
enviaMensagem(sada1,"Por favor aguarde o outro jogador...");
// Aguardamos a segunda conexo...
Socket conexo2 = servidor.accept();
System.out.println("Conexo 2 aceita para o cliente "+
conexo2.getRemoteSocketAddress());
// Criamos as streams de entrada e sada para o cliente 1.
entrada2 =
new BufferedReader(new InputStreamReader(conexo2.getInputStream()));
sada2 =
new BufferedWriter(new OutputStreamWriter(conexo2.getOutputStream()));
// Mandamos uma mensagem de boas vindas.

Rafael Santos

Programao Cliente-Servidor Usando Java

46
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

enviaMensagem(sada2,"Ol, voc ser o segundo jogador (O).");


enviaMensagem(sada1,"Segundo jogador conectado.");
// Quando as duas conexes tiverem sido estabelecidas e as streams
// criadas, iniciamos o processamento.
processaJogo();
// Ao terminar de atender a requisio de jogo, fechamos os streams.
entrada1.close();
entrada2.close();
sada1.close();
sada2.close();
// Fechamos tambm as conexes.
conexo1.close();
conexo2.close();
}
}
// Pode ser que a porta 12345 j esteja em uso !
catch (BindException e)
{
System.out.println("Porta j em uso.");
}
// Pode ser que tenhamos um erro qualquer de entrada ou sada.
catch (IOException e)
{
System.out.println("Erro de entrada ou sada.");
}
}

86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

// Este mtodo executa a lgica do jogo, tendo acesso aos mecanismos de entrada
// e sada de duas conexes.
private static void processaJogo()
{
// A primeira coisa a fazer "limpar" o tabuleiro.
for(int linha=0;linha<3;linha++)
for(int coluna=0;coluna<3;coluna++)
tabuleiro[linha][coluna] = ;
// String que ser reusada para as entradas de comandos.
String jogada;
// Coordenadas da jogada.
int linha,coluna;
// Pea do vencedor.
char vencedor;
try
{
// Enviamos o tabuleiro para os dois jogadores pela primeira vez.
mostraTabuleiro();
// Executamos um lao "eterno"
while(true)
{
// Lemos e verificamos a jogada do primeiro jogador.
enviaMensagem(sada2,"Aguarde movimento do jogador X.");
enviaMensagem(sada1,"Jogador X, entre seu movimento.");
jogada = entrada1.readLine();
enviaMensagem(sada1,"Jogador X escolheu "+jogada);
enviaMensagem(sada2,"Jogador X escolheu "+jogada);
// Quais so as coordenadas da jogada ?
linha = jogada.charAt(0)-A; // A = 0; B = 1; C = 2;
coluna = jogada.charAt(1)-1; // 1 = 0; 2 = 1; 3 = 2;
// A jogada vlida ?
if ((linha >= 0) && (linha <= 2) &&
(coluna >= 0) && (coluna <= 2) &&
(tabuleiro[linha][coluna] == ))

Rafael Santos

Programao Cliente-Servidor Usando Java

47
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181

{
tabuleiro[linha][coluna] = X;
}
else
{
enviaMensagem(sada1,"Jogada do jogador X em posio invlida.");
enviaMensagem(sada2,"Jogada do jogador X em posio invlida.");
}
// Enviamos o tabuleiro para os dois jogadores.
mostraTabuleiro();
// Verificamos se algum venceu o jogo.
vencedor = verificaVencedor();
if (vencedor == V)
{
mostraTabuleiro();
enviaMensagem(sada1,"Empate!");
enviaMensagem(sada2,"Empate!");
break;
}
else if (vencedor != )
{
mostraTabuleiro();
enviaMensagem(sada1,"Vencedor: jogador "+vencedor);
enviaMensagem(sada2,"Vencedor: jogador "+vencedor);
break;
}
// Lemos e verificamos a jogada do segundo jogador.
enviaMensagem(sada1,"Aguarde movimento do jogador O.");
enviaMensagem(sada2,"Jogador O, entre seu movimento.");
jogada = entrada2.readLine();
enviaMensagem(sada1,"Jogador O escolheu "+jogada);
enviaMensagem(sada2,"Jogador O escolheu "+jogada);
// Quais so as coordenadas da jogada ?
linha = jogada.charAt(0)-A; // A = 0; B = 1; C = 2;
coluna = jogada.charAt(1)-1; // 1 = 0; 2 = 1; 3 = 2;
// A jogada vlida ?
if ((linha >= 0) && (linha <= 2) &&
(coluna >= 0) && (coluna <= 2) &&
(tabuleiro[linha][coluna] == ))
{
tabuleiro[linha][coluna] = O;
}
else
{
enviaMensagem(sada1,"Jogada do jogador O em posio invlida.");
enviaMensagem(sada2,"Jogada do jogador O em posio invlida.");
}
// Enviamos o tabuleiro para os dois jogadores.
mostraTabuleiro();
// Verificamos se algum venceu o jogo.
vencedor = verificaVencedor();
if (vencedor == V)
{
mostraTabuleiro();
enviaMensagem(sada1,"Empate!");
enviaMensagem(sada2,"Empate!");
break;
}
else if (vencedor != )
{
enviaMensagem(sada1,"Vencedor: jogador "+vencedor);

Rafael Santos

Programao Cliente-Servidor Usando Java

48
182
183
184
185
186
187
188
189
190
191
192

enviaMensagem(sada2,"Vencedor: jogador "+vencedor);


break;
}
} // fim do while true
} // fim do bloco try
// Se houve algum erro de entrada ou sada...
catch (IOException e)
{
System.out.println("Erro executando o lao principal do jogo !");
}
}

193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218

/**
* Este mtodo mostra o tabuleiro do jogo para os dois clientes. Ele monta
* uma string contendo o tabuleiro (inclusive movimentos j jogados) com alguma
* decorao para o usurio saber que posies ainda podem ser jogadas. A string
* ento enviada para os dois clientes.
*/
private static void mostraTabuleiro()
{
String tabuleiroFormatado = "";
String tempLinha;
tabuleiroFormatado += "
\n";
tabuleiroFormatado += " 1 2 3 \n";
tabuleiroFormatado += " +-+-+-+\n";
for(int linha=0;linha<3;linha++)
{
tempLinha = (char)(A+linha)+"|";
for(int coluna=0;coluna<3;coluna++)
tempLinha += tabuleiro[linha][coluna]+"|";
tempLinha += "\n";
tabuleiroFormatado += tempLinha;
tabuleiroFormatado += " +-+-+-+\n";
}
enviaMensagem(sada1,tabuleiroFormatado);
enviaMensagem(sada2,tabuleiroFormatado);
}

219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242

/**
* Este mtodo verifica se houve algum vencedor considerando a situao atual do
* tabuleiro. Se houver vencedor, o mtodo retorna o caracter correspondente ao
* vencedor (X ou O), seno retorna espao.
*/
private static char verificaVencedor()
{
// Vamos verificar as 8 possveis combinaes para ganhar o jogo.
// Primeira linha.
if ((tabuleiro[0][0] == tabuleiro[0][1]) &&
(tabuleiro[0][1] == tabuleiro[0][2])) return tabuleiro[0][0];
// Segunda linha.
else if ((tabuleiro[1][0] == tabuleiro[1][1]) &&
(tabuleiro[1][1] == tabuleiro[1][2])) return tabuleiro[1][0];
// Terceira linha.
else if ((tabuleiro[2][0] == tabuleiro[2][1]) &&
(tabuleiro[2][1] == tabuleiro[2][2])) return tabuleiro[2][0];
// Primeira coluna.
else if ((tabuleiro[0][0] == tabuleiro[1][0]) &&
(tabuleiro[1][0] == tabuleiro[2][0])) return tabuleiro[0][0];
// Segunda coluna.
else if ((tabuleiro[0][1] == tabuleiro[1][1]) &&
(tabuleiro[1][1] == tabuleiro[2][1])) return tabuleiro[0][1];

Rafael Santos

Programao Cliente-Servidor Usando Java

49
// Terceira coluna.
else if ((tabuleiro[0][2] == tabuleiro[1][2]) &&
(tabuleiro[1][2] == tabuleiro[2][2])) return tabuleiro[0][2];
// Diagonal descendente.
else if ((tabuleiro[0][0] == tabuleiro[1][1]) &&
(tabuleiro[1][1] == tabuleiro[2][2])) return tabuleiro[0][0];
// Diagonal ascendente.
else if ((tabuleiro[2][0] == tabuleiro[1][1]) &&
(tabuleiro[1][1] == tabuleiro[0][2])) return tabuleiro[2][0];
else // Nenhuma das combinaes existe.
{
// Ainda existe posies abertas no tabuleiro ?
int posiesAbertas = 0;
for(int linha=0;linha<3;linha++)
for(int coluna=0;coluna<3;coluna++)
if(tabuleiro[linha][coluna] == ) posiesAbertas++;
if (posiesAbertas == 0) return V; // Velha !
else return ; // Ningum venceu at agora.
}
}

243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263

/**
* Este mtodo facilita o envio de mensagens para os clientes, executando
* o mtodos write, newLine e flush de uma instncia de BufferedWriter.
*/
private static void enviaMensagem(BufferedWriter bw,String mensagem)
{
try
{
bw.write(mensagem);
bw.newLine();
bw.flush();
}
catch (IOException e)
{
System.out.println("Erro enviando mensagem.");
}
}

264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281

282

Um exemplo de interao de um cliente (telnet) com o servidor de jogo-da-velha pode


ser visto na figura 15.

Aplicaes baseadas em um cliente e vrios servidores

Se considerarmos que servidor o lado da aplicao que fornece uma informao ou


executa um processamento e cliente o lado que consume esta informao ou usa o
recurso do servidor, podemos imaginar aplicaes aonde ao invs de ter um ou mais
clientes usando os servios de um servidor, podemos ter um nico cliente usando os
servios de vrios servidores.
Consideremos uma tarefa computacional cujo tempo para execuo seja potencialmente
longo, mas que possa ser dividida em subtarefas menores. Podemos escrever uma apliRafael Santos

Programao Cliente-Servidor Usando Java

50

Figura 15: Exemplo de interao entre um cliente (telnet) e servidor de jogo-da-velha

cao servidora que ser executada ao mesmo tempo em vrios computadores, cada
uma responsvel pela soluo de uma parte do problema. Podemos criar uma aplicao
cliente que solicita a cada aplicao servidora a execuo de sua parte, e que integra as
solues em uma s.
Evidentemente este tipo de aplicao deve envolver o processamento de dados cuja
transmisso entre cliente e servidor gaste muito menos tempo do que o processamento,
caso contrrio a execuo da aplicao ser mais lenta. Cuidados especiais tambm devem ser tomados para garantir a integridade do resultado, caso um dos servidores falhe.
O lado cliente das aplicaes tambm precisa ser criado de forma a dar suporte a mltiplas linhas de execuo (seo 6.2), caso contrrio ele no poder enviar requisies
a vrios servidores simultaneamente. Tambm ser necessrio usar algum mecanismo
que informe aplicao cliente que todos os servidores j terminaram o processamento
Rafael Santos

Programao Cliente-Servidor Usando Java

51

f(x)

delta

Figura 16: Clculo de uma integral usando parties e trapzios

de seus dados.
Nesta seo veremos um exemplo de tarefa computacional que pode ser dividida, enviada a servidores para processamento e integrada pelo cliente, alcanando assim um
possvel tempo menor de execuo quando comparado com a execuo em um nico
computador.
8.1

Exemplo: Cliente e servidores para clculo de integrais

Um bom exemplo de problema que pode ser dividido em pequenos problemas menores
o clculo de integrais. Consideremos uma funo matemtica qualquer, considerada
entre os valores A e B. O valor da integral desta funo entre A e B a rea que a funo
projeta sobre o eixo X, como mostrado na figura 8.1.
Na parte superior da figura 8.1 temos uma funo f (x) definida entre os pontos A e B.
O valor da integral de f (x) entre A e B a rea mostrada em cinza na figura 8.1. Uma
maneira de calcular esta rea de forma aproximada dividir a rea em muitos pequeRafael Santos

Programao Cliente-Servidor Usando Java

52

nos trapzios que cobrem a mesma rea, como mostrado na parte inferior da figura 8.1.
Para calcular a rea coberta pela funo basta ento calcular a somatria das reas dos
trapzios. Considerando a funo f (x) entre os pontos A e B e que a rea da funo
ser dividida de forma que a largura da base de cada trapzio seja igual a delta, podemos calcular a rea do mesmo como sendo a somatria da rea do trapzio cuja base
delta, lado direito igual a f (a) e lado esquerdo igual a f (a + delta), ou seja, igual a
delta ( f (a) + f (a + delta))/2.
De posse do mtodo e equaes acima, fcil ver que podemos dividir o problema da
somatria em problemas menores, onde cada um deles, para ser executado, dever receber o valor de A (primeiro valor, ou valor inferior para clculo), B (segundo valor, ou
valor superior), delta e claro, a funo a ser integrada.
O protocolo de comunicao entre cliente e servidor(es) de clculo de integrais mostrado na figura 17. Nesta figura mostramos somente um cliente (o normal para este tipo
de aplicao) e um servidor, mas em casos normais o clculo dever ser efetuado usando
vrios servidores. Uma linha cinza usada para mostrar, do lado do cliente, que trecho
do cdigo ser executado concomitantemente pelas linhas de execuo do cliente. Para
cada linha de execuo do lado do cliente, teremos uma conexo com um dos servidores.
O cdigo para o servidor de clculo de integrais mostrado na listagem 20.
Listagem 20: O servidor de clculo de integrais
1
2
3
4
5
6
7

package cap312;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

/**
* Esta classe implementa um servidor simples que fica aguardando conexes de
* clientes. Quando uma conexo solicitada, o servidor l do cliente o
* intervalo e preciso para o clculo da funo, efetua o clculo e retorna
* para o cliente o resultado.
*/
public class ServidorDeCalculoDeIntegrais
{
// Mtodo que permite a execuo da classe.
public static void main(String[] args)
{
// O nmero da porta ser obtido da linha de comando.
int porta = Integer.parseInt(args[0]);
ServerSocket servidor;
try
{
// Criamos a instncia de ServerSocket que responder por solicitaes
// porta.
servidor = new ServerSocket(porta);

Rafael Santos

Programao Cliente-Servidor Usando Java

53

Cliente
Incio

Servidor 1
Incio

Divide tarefa em
subtarefas
Para cada subtarefa...

Solicita conexo

Aguarda conexo

Cria conexo

Envia valores A, B e delta

Recebe valores

Efetua clculo

Recebe resultado

Envia resultado

Aguarda todos os
resultados

Imprime resultado

Fim

Figura 17: Protocolo para aplicao que faz o clculo de uma integral
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

// O servidor aguarda "para sempre" as conexes.


while(true)
{
// Quando uma conexo feita,
Socket conexo = servidor.accept();
// ... o servidor a processa.
processaConexo(conexo);
}
}
// Pode ser que a porta j esteja em uso !
catch (BindException e)
{
System.out.println("Porta "+porta+" j em uso.");
}
// Pode ser que tenhamos um erro qualquer de entrada ou sada.
catch (IOException e)
{
System.out.println("Erro de entrada ou sada.");

Rafael Santos

Programao Cliente-Servidor Usando Java

54
}

46
47

48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

// Este mtodo atende a uma conexo feita a este servidor.


private static void processaConexo(Socket conexo)
{
try
{
// Criamos uma stream para receber valores nativos, usando a stream de entrada
// associado conexo.
DataInputStream entrada =
new DataInputStream(conexo.getInputStream());
// Criamos uma stream para enviar valores nativos, usando a stream de sada
// associado conexo.
DataOutputStream sada =
new DataOutputStream(conexo.getOutputStream());
// Lemos do cliente o intervalo da funo.
double incio = entrada.readDouble();
double fim = entrada.readDouble();
// Lemos do cliente a largura do trapzio para clculo da funo.
double delta = entrada.readDouble();
// Fazemos o clculo.
double resultado = calculaIntegral(incio,fim,delta);
// Enviamos para o cliente o valor do clculo.
sada.writeDouble(resultado);
// Para demonstrar que realmente funciona, vamos imprimir um log.
System.out.println("Acabo de enviar o resultado "+resultado+" para o cliente "+
conexo.getRemoteSocketAddress());
// Ao terminar de atender a requisio, fechamos as streams.
entrada.close();
sada.close();
// Fechamos tambm a conexo.
conexo.close();
}
// Se houve algum erro de entrada ou sada...
catch (IOException e)
{
System.out.println("Erro atendendo a uma conexo !");
}
}

86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

/**
* Este mtodo calcula a integral de uma funo entre os valores passados
* usando como delta o valor tambm passado. O mtodo usa a regra do
* trapzio.
*/
private static double calculaIntegral(double incio,double fim,double delta)
{
double a = incio;
double somaDasreas = 0;
// Vamos percorrendo os valores para argumentos da funo...
while((a+delta) <= fim)
{
double alturaA = funo(a);
double alturaB = funo(a+delta);
// Calculamos a rea do trapzio local.
double reaTrapzio = delta*(alturaA+alturaB)/2;
// Somamos rea total.
somaDasreas += reaTrapzio;
a += delta;
}

Rafael Santos

Programao Cliente-Servidor Usando Java

55
return somaDasreas;
}

107
108
109
110
111
112
113
114
115
116

/**
* Este mtodo calcula uma funo em determinado ponto.
*/
private static double funo(double argumento)
{
return Math.sin(argumento)*Math.sin(argumento);
}

117
118

Alguns pontos notveis no cdigo da classe ServidorDeCalculoDeIntegrais (listagem 20) so:


Quando executarmos a aplicao servidora devemos passar o nmero da porta
como parmetro. Em algumas situaes isso pode ser incmodo (o cliente dever saber em que endereos e em que portas ele deve se conectar), o uso de uma
porta padro recomendado (isso no foi feito para finalidades de testes).
O servidor usa o mesmo padro de desenvolvimento de outros servidores: um lao
eterno que aguarda conexes do cliente para receber seus dados, process-los e
retorn-los. Desta forma o mesmo servidor pode ser reutilizado por um ou mais
clientes, embora somente processe uma requisio de cada vez o servidor no
executa mltiplas linhas de execuo.
O mtodo processaConexo processa uma nica conexo do cliente, onde sero
recebidos os valores de A, B e delta; e enviado o valor da integral entre A e B. Este
mtodo executa o mtodo calculaIntegral usando estes parmetros, e calculando a somatria das reas de todos os trapzios entre A e B. Para isto, o mtodo
calculaIntegral executa o mtodo funo que calcula o valor da funo em
um determinado ponto. Para este exemplo, a funo foi codificada diretamente no
servidor, e a funo sin(x) sin(x), escolhida pois sua integral entre os valores 0
e /2 exatamente6 igual a /4, facilitando a verificao do algoritmo.
Conforme mencionado anteriormente, o cliente para esta aplicao dever ser capaz de
tratar com linhas de execuo. Mais exatamente, o cliente dever criar uma linha de
execuo para cada subtarefa que ser enviada por sua parte para um servidor. A classe
que representa uma subtarefa ou linha de execuo a classe LinhaDeExecucaoDeCalculoDeIntegrais, mostrada na listagem 21.
Listagem 21: Uma linha de execuo para o cliente de clculo de integrais
1
2

package cap312;
import java.io.DataInputStream;
6 Usaremos

o valor do resultado desejado (/4) para comparar o resultado obtido, mas j sabendo de antemo que os
resultados no sero exatamente iguais por causa de vrios fatores: impreciso do valor /4, erros de arredondamento naturais
e preciso do algoritmo de clculo dependente do valor delta.

Rafael Santos

Programao Cliente-Servidor Usando Java

56
3
4
5
6

import
import
import
import

java.io.DataOutputStream;
java.io.IOException;
java.net.Socket;
java.net.UnknownHostException;

7
8
9
10
11
12
13
14
15
16
17
18
19

/**
* Esta classe representa o processamento que deve ser feito quando uma
* requisio de clculo de intgrais for feita a um servidor.
* A classe herda de Thread para que vrias instncias dela possam ser
* executadas concorrentemente.
*/
public class LinhaDeExecucaoDeCalculoDeIntegrais extends Thread
{
// Precisamos armazenar o socket correspondente conexo.
private Socket conexo = null;
// Tambm precisamos armazenar os valores para clculo.
private double incio,fim,delta,resultado;

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

/**
* O construtor desta classe recebe como argumento o nmero da porta do
* servidor e o armazena em um campo da classe. O construtor tambm recebe
* como argumentos o intervalo e delta para clculo da integral.
*/
public LinhaDeExecucaoDeCalculoDeIntegrais(String servidor,int porta,
double i,double f,double d)
{
try
{
conexo = new Socket(servidor,porta);
}
catch (UnknownHostException e)
{
System.out.println("Host desconhecido !");
}
catch (IOException e)
{
System.out.println("Erro de entrada ou sada !");
}
incio = i;
fim = f;
delta = d;
}

45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

/**
* Este mtodo executa a rotina de atendimento a uma conexo com o servidor.
*/
public void run()
{
try
{
// Criamos uma stream para receber valores nativos, usando a stream de entrada
// associado conexo.
DataInputStream entrada =
new DataInputStream(conexo.getInputStream());
// Criamos uma stream para enviar valores nativos, usando a stream de sada
// associado conexo.
DataOutputStream sada =
new DataOutputStream(conexo.getOutputStream());
// Enviamos ao servidor o intervalo da funo.
sada.writeDouble(incio);
sada.writeDouble(fim);

Rafael Santos

Programao Cliente-Servidor Usando Java

57
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

// Enviamos ao servidor o nmero de intervalos da funo.


sada.writeDouble(delta);
// Lemos do servidor o resultado do clculo.
resultado = entrada.readDouble();
// Ao terminar de atender a requisio, fechamos as streams.
entrada.close();
sada.close();
// Fechamos tambm a conexo.
conexo.close();
}
// Se houve algum erro de entrada ou sada...
catch (IOException e)
{
System.out.println("Erro atendendo a uma conexo !");
}
}
/**
* Este mtodo permite recuperar o valor calculado.
*/
public double getResultado()
{
return resultado;
}

87
88

A classe LinhaDeExecucaoDeCalculoDeIntegrais (listagem 21) contm campos para


armazenar o nome e porta do servidor com o qual ir se comunicar, alm dos parmetros
para clculo da integral (A, B e delta). Estes campos so inicializados pelo construtor
da classe. Como a classe herda de Thread deve implementar seu mtodo run para
que suas instncias possam ser executadas concomitantemente. O mtodo run simplesmente cria a conexo com o servidor, envia os dados, recebe o valor da somatria e o
armazena para que outra aplicao o possa recuperar com o mtodo getResultado.
A classe que implementa o cliente, que por sua vez ir criar algumas instncias da classe
LinhaDeExecucaoDeCalculoDeIntegrais, a classe ClienteDeCalculoDeIntegrais, mostrada na listagem 22.
Listagem 22: O cliente de clculo de integrais
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

package cap312;
/**
* Esta classe implementa um cliente simples para o servio de clculo de
* integrais.
* Ele se conecta a dois servidores (pela portas nas quais estes servidores
* esto respondendo), envia trechos para clculo da integral e calcula a soma
* dos resultados.
*/
public class ClienteDeCalculoDeIntegrais
{
public static void main(String[] args)
{
String servidor = "localhost";
// Dividimos a tarefa em duas metades que sero executadas com diferentes
// precises.

Rafael Santos

Programao Cliente-Servidor Usando Java

58
LinhaDeExecucaoDeCalculoDeIntegrais l1 =
new LinhaDeExecucaoDeCalculoDeIntegrais("localhost",11111,
0,Math.PI/4,0.000001);
LinhaDeExecucaoDeCalculoDeIntegrais l2 =
new LinhaDeExecucaoDeCalculoDeIntegrais("localhost",11112,
Math.PI/4,Math.PI/2,0.0000001);
// Iniciamos a execuo das duas tarefas.
l1.start();
l2.start();
// Aguardamos at que as duas instncias tenham terminado de processar
// o mtodo run.
while(l1.isAlive() || l2.isAlive())
{
}
// Calculamos o resultado final e comparamos com o esperado.
double resFinal = l1.getResultado()+l2.getResultado();
System.out.println("Resultado:"+resFinal);
System.out.println("Esperado :"+Math.PI/4);
}

16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

35

A classe ClienteDeCalculoDeIntegrais (listagem 22) simplesmente cria duas instncias da classe LinhaDeExecucaoDeCalculoDeIntegrais, usando (para demonstrao), o servidor local e duas portas previamente definidas, onde servidores esto
aguardando conexes. Cada linha de execuo calcular metade dos trapzios correspondentes rea da integral, mas a segunda linha de execuo calcular a sua parte da
integral com um valor de delta mais preciso, para que o tempo de execuo seja definitivamente diferente.
Em algum ponto precisaremos verificar se as linhas de execuo j terminaram o seu
processamento, para evitar erros grosseiros de clculo. Estes erros podem acontecer facilmente pois a partir do momento em que o mtodo start da classe LinhaDeExecucaoDeCalculoDeIntegrais for executado, j ser possvel executar o mtodo getResultado das mesmas, que pode retornar zero se o clculo no tiver sido terminado
em outras palavras, se o mtodo run ainda estiver sendo executado.
Para garantir que o mtodo getResultado somente ser executado depois que o mtodo
run tiver terminado seu processamento, podemos criar um lao infinito que aguarda
at que o resultado dos mtodos isAlive das classes que herdam de Thread sejam
ambos false este mtodo verifica se uma linha de execuo ainda est viva, se
o mtodo retorna true se a linha de execuo ainda est sendo executada. Ao final
do processamento basta somar os resultados obtidos pelas duas linhas de execuo e
apresentar o resultado esperado para comparao.

Mais informaes

Considero este documento ainda inacabado existem outros tpicos interessantes que
podemos explorar, como por exemplo:
Rafael Santos

Programao Cliente-Servidor Usando Java

59

Criao de aplicaes que usem servidores em mltiplas camadas, como servidores de meta-dados, por exemplo. Estas aplicaes tem clientes que precisam de
dados mas no sabem onde estes dados esto: estes clientes se conectam a um
servidor de meta-dados, que por sua vez se comunica com vrios servidores (conhecidos) de dados para descobrir qual deles contm a informao que o cliente
necessita.
Balanceamento simples de carga: consideremos o exemplo do clculo de integrais
apresentado na seo 8.1. Duas linhas de execuo foram criadas, cada uma efetuando um clculo desbalanceado: a primeira linha de execuo demorou muito
mais do que a segunda. Podemos considerar esquemas simples de balanceamento,
que fazem com que servidores aparentemente ociosos recebam carga a mais, para
tentar chegar ao final do processamento como um todo em menos tempo.
Servios de descoberta de servidores e balanceamento dinmico, que combinam
as idias apresentadas nos dois tpicos acima. O cliente de clculo de integrais poderia ento tentar descobrir quais servidores existem e esto disposio, mesmo
enquanto estiver sendo executado, para obter melhor performance. Esta descoberta seria mediada por um servidor de meta-dados, que estaria sendo atualizado
permanentemente com informaes sobre os servidores e sua carga.
Explorar outros exemplos como renderizao de cenas por computadores distribudos, processamento de imagens distribudo, srevios de anlise e descoberta de
informaes, jogos multi-usurios, servios Peer to Peer, etc. como estes exemplos envolvem outras tecnologias e conhecimentos alm de programao, possivelmente sero tratados caso a caso, se houver interesse por parte de algum.
Como existiram restries de tempo quando escrevi este documento, e o contedo do
mesmo atende plenamente finalidade original (servir de subsdio aos alunos da disciplina Linguagem de Programao Orientada a Objetos do curso de Tecnlogo em Redes
de Computadores do Instituto Brasileiro de Tecnologia Avanada - IBTA, Unidade de
So Jos dos Campos), encerrei o documento com somente os tpicos apresentados. Em
outra ocasio, se houver demanda, poderei cobrir alguns dos tpicos sugeridos acima.

Rafael Santos
Maro de 2004

Rafael Santos

Programao Cliente-Servidor Usando Java

Você também pode gostar