8. Programação TCP-IP
8.1. Informações gerais
8.1.1. Protocolos de Internet
Apresentamos aqui uma introdução aos protocolos de comunicação da Internet, também conhecidos como conjunto TCP/IP (Transmission Control Protocol / Internet Protocol), cujo nome deriva dos dois protocolos principais. É aconselhável que o leitor tenha um conhecimento geral sobre o funcionamento das redes e, em particular, sobre os protocolos TCP/IP, antes de se dedicar ao desenvolvimento de aplicações distribuídas.
O texto que se segue é uma tradução parcial de uma passagem encontrada no «LAN Workplace for DOS – Administrator’s Guide» da NOVELL, um documento do início da década de 1990.
O conceito geral de criação de uma rede de computadores heterogéneos tem origem na investigação realizada pela DARPA (Agência de Projetos de Investigação Avançada de Defesa) nos Estados Unidos. A DARPA desenvolveu o conjunto de protocolos conhecido como TCP/IP, que permite que máquinas heterogéneas comuniquem entre si. Estes protocolos foram testados numa rede chamada ARPAnet, que mais tarde se tornou a Internet. Os protocolos TCP/IP definem formatos e regras para a transmissão e receção que são independentes da arquitetura da rede e do hardware utilizado.
A rede concebida pela DARPA e gerida pelos protocolos TCP/IP é uma rede de comutação de pacotes. Uma rede deste tipo transmite informação pela rede em pequenos pedaços chamados pacotes. Assim, se um computador transmitir um ficheiro de grandes dimensões, este será dividido em pequenos pedaços que são enviados pela rede para serem remontados no destino. O TCP/IP define o formato destes pacotes, nomeadamente:
- origem do pacote
- destino
- comprimento
- tipo
8.1.2. O Modelo OSI
Os protocolos TCP/IP seguem, em linhas gerais, o modelo de rede aberta conhecido como OSI (Modelo de Referência para Interconexão de Sistemas Abertos), definido pela ISO (Organização Internacional de Normalização). Este modelo descreve uma rede ideal onde a comunicação entre máquinas pode ser representada por um modelo de sete camadas:

Cada camada recebe serviços da camada abaixo dela e fornece os seus próprios serviços à camada acima dela. Suponha que duas aplicações localizadas em máquinas diferentes, A e B, queiram comunicar: fazem-no na camada de Aplicação. Não precisam de conhecer todos os detalhes de como a rede funciona: cada aplicação passa a informação que deseja transmitir para a camada abaixo dela: a camada de Apresentação. A aplicação, portanto, só precisa de conhecer as regras para interagir com a camada de Apresentação.
Assim que a informação chega à camada de Apresentação, é passada de acordo com outras regras para a camada de Sessão, e assim sucessivamente, até que a informação chegue ao meio físico e seja fisicamente transmitida para a máquina de destino. Aí, será submetida ao processo inverso daquele a que foi submetida na máquina de envio.
Em cada camada, o processo emissor responsável pelo envio da informação envia-a a um processo recetor na outra máquina pertencente à mesma camada que ele próprio. Faz-o de acordo com certas regras conhecidas como protocolo da camada. Temos, portanto, o seguinte diagrama de comunicação final:

As funções das diferentes camadas são as seguintes:
Assegura a transmissão de bits através de um meio físico. Esta camada inclui equipamentos terminais de processamento de dados (DPTE), tais como terminais ou computadores, bem como equipamentos de terminação de circuitos de dados (DCTE), tais como moduladores/demoduladores, multiplexadores e concentradores. Os pontos-chave a este nível são: . a escolha da codificação da informação (analógica ou digital) . a escolha do modo de transmissão (síncrono ou assíncrono). | |
Oculta as características físicas da Camada Física. Deteta e corrige erros de transmissão. | |
Gere o caminho que a informação enviada pela rede deve seguir. A isto chama-se encaminhamento: determinar a rota que a informação deve seguir para chegar ao seu destino. | |
Permite a comunicação entre duas aplicações, enquanto as camadas anteriores apenas permitiam a comunicação entre máquinas. Um serviço prestado por esta camada pode ser a multiplexação: a camada de transporte pode utilizar uma única ligação de rede (de máquina para máquina) para transmitir dados pertencentes a várias aplicações. | |
Esta camada fornece serviços que permitem a uma aplicação abrir e manter uma sessão de trabalho numa máquina remota. | |
Tem como objetivo padronizar a representação de dados entre diferentes máquinas. Assim, os dados provenientes da máquina A serão «formatados» pela camada de apresentação da máquina A de acordo com um formato padrão antes de serem enviados pela rede. Ao chegarem à camada de apresentação da máquina de destino B, que os reconhecerá graças ao seu formato padrão, serão formatados de forma diferente para que a aplicação na máquina B os possa reconhecer. | |
Neste nível, encontramos aplicações que estão geralmente próximas do utilizador, tais como o e-mail ou a transferência de ficheiros. |
8.1.3. O Modelo TCP/IP
O modelo OSI é um modelo ideal que nunca foi totalmente concretizado. O conjunto de protocolos TCP/IP aproxima-se dele da seguinte forma:

Camada Física
Nas redes locais, utiliza-se geralmente a tecnologia Ethernet ou Token Ring. Aqui, iremos centrar-nos exclusivamente na tecnologia Ethernet.
Ethernet
Este é o nome dado a uma tecnologia de rede de área local com comutação de pacotes, inventada na Xerox PARC no início da década de 1970 e padronizada pela Xerox, Intel e Digital Equipment em 1978. A rede consiste fisicamente num cabo coaxial com aproximadamente 1,27 cm de diâmetro e até 500 m de comprimento. Pode ser estendida utilizando repetidores, com um máximo de dois repetidores a separar quaisquer duas máquinas. O cabo é passivo: todos os componentes ativos estão localizados nas máquinas ligadas ao cabo. Cada máquina está ligada ao cabo através de uma placa de acesso à rede que inclui:
- um transceptor que deteta a presença de sinais no cabo e converte sinais analógicos em sinais digitais e vice-versa.
- um acoplador que recebe sinais digitais do transceptor e os transmite ao computador para processamento, ou vice-versa.
As principais características da tecnologia Ethernet são as seguintes:
- Capacidade de 10 megabits por segundo.
- Topologia em barramento: todas as máquinas estão ligadas ao mesmo cabo

- Rede de difusão — Uma máquina transmissora envia informações pelo cabo com o endereço da máquina receptora. Todas as máquinas ligadas recebem então estas informações, e apenas o destinatário pretendido as retém.
- O método de acesso é o seguinte: o transmissor que deseja transmitir escuta o cabo — detecta então se existe ou não uma onda portadora, cuja presença indicaria que está em curso uma transmissão. Esta é a técnica CSMA (Carrier Sense Multiple Access). Na ausência de uma onda portadora, um transmissor pode decidir transmitir à sua vez. Vários transmissores podem tomar essa decisão. Os sinais transmitidos misturam-se: isto é chamado de colisão. O transmissor deteta esta situação: enquanto transmite pelo cabo, também escuta o que está realmente a passar por ele. Se detetar que a informação que circula pelo cabo não é a que transmitiu, conclui que ocorreu uma colisão e interrompe a transmissão. Os outros transmissores que estavam a transmitir farão o mesmo. Cada um retomará a transmissão após um atraso aleatório, dependendo do transmissor individual. Esta técnica é chamada de CD (Collision Detect). O método de acesso é, portanto, chamado de CSMA/CD.
- Endereçamento de 48 bits. Cada máquina tem um endereço, aqui referido como endereço físico, que está escrito na placa que a liga ao cabo. Este endereço é chamado de endereço Ethernet da máquina.
Camada de Rede
Nesta camada, encontramos os protocolos IP, ICMP, ARP e RARP.
IP (Protocolo de Internet) | Envia pacotes entre dois nós de rede |
ICMP (Protocolo de Mensagens de Controlo da Internet) | O ICMP facilita a comunicação entre o programa do protocolo IP numa máquina e o de outra máquina. É, portanto, um protocolo de troca de mensagens dentro do próprio protocolo IP. |
ARP (Protocolo de Resolução de Endereços) | associa o endereço de Internet de um computador ao seu endereço físico |
RARP (Protocolo de Resolução de Endereços Inverso) | mapeia o endereço físico de uma máquina para o seu endereço de Internet |
Camadas de Transporte/Sessão
Esta camada inclui os seguintes protocolos:
TCP (Protocolo de Controlo de Transmissão) | Garante a entrega fiável de informações entre dois clientes |
UDP (Protocolo de Datagrama do Utilizador) | Garante a entrega não fiável de informações entre dois clientes |
Camadas de Aplicação/Apresentação/Sessão
Aqui encontram-se vários protocolos:
Um emulador de terminal que permite que a máquina A se ligue à máquina B como um terminal | |
Permite a transferência de ficheiros | |
permite a transferência de ficheiros | |
permite a troca de mensagens entre utilizadores da rede | |
converte o nome de um computador no endereço de Internet desse computador | |
Criado pela Sun Microsystems, especifica uma representação de dados padrão e independente da máquina | |
também definido pela Sun, é um protocolo de comunicação entre aplicações remotas, independente da camada de transporte. Este protocolo é importante: dispensa o programador de conhecer os detalhes da camada de transporte e torna as aplicações portáteis. Este protocolo baseia-se no protocolo XDR | |
Também definido pela Sun, este protocolo permite que uma máquina «veja» o sistema de ficheiros de outra máquina. Baseia-se no protocolo RPC mencionado acima |
8.1.4. Como funcionam os protocolos da Internet
As aplicações desenvolvidas no ambiente TCP/IP utilizam geralmente vários dos protocolos deste ambiente. Um programa de aplicação comunica com a camada mais elevada dos protocolos. Esta camada transmite a informação para a camada abaixo dela, e assim sucessivamente, até chegar ao meio físico. Aí, a informação é transferida fisicamente para a máquina de destino, onde passará novamente pelas mesmas camadas, desta vez na direção oposta, até chegar à aplicação destinada a receber a informação enviada. O diagrama seguinte mostra o percurso da informação:

Vejamos um exemplo: a aplicação FTP, definida na camada de Aplicação, que permite a transferência de ficheiros entre máquinas.
- A aplicação entrega uma sequência de bytes a ser transmitida à camada de transporte.
- A camada de transporte divide esta sequência de bytes em segmentos TCP e adiciona o número do segmento ao início de cada segmento. Os segmentos são passados para a camada de rede, que é regida pelo protocolo IP.
- A camada IP cria um pacote que encapsula o segmento TCP recebido. No cabeçalho deste pacote, coloca os endereços de Internet das máquinas de origem e de destino. Determina também o endereço físico da máquina de destino. O pacote completo é passado para a camada de ligação de dados e física, ou seja, para a placa de rede que liga a máquina à rede física.
- Aí, o pacote IP é, por sua vez, encapsulado numa trama física e enviado ao seu destinatário através do cabo.
- Na máquina de destino, a Camada de Ligação de Dados e Física faz o oposto: desencapsula o pacote IP da trama física e passa-o para a camada IP.
- A camada IP verifica se o pacote está correto: calcula uma soma de verificação com base nos bits recebidos, que deve encontrar no cabeçalho do pacote. Se não for esse o caso, o pacote é rejeitado.
- Se o pacote for considerado válido, a camada IP desencapsula o segmento TCP nele contido e o passa para a camada de transporte.
- A camada de transporte — a camada TCP no nosso exemplo — examina o número do segmento para garantir que os segmentos estão na ordem correta.
- Calcula também uma soma de verificação para o segmento TCP. Se esta for considerada correta, a camada TCP envia um aviso de receção à máquina de origem; caso contrário, o segmento TCP é rejeitado.
- Tudo o que resta à camada TCP é transmitir a parte de dados do segmento para a aplicação destinada a recebê-la na camada superior.
8.1.5. Resolução de problemas na Internet
Um nó de rede pode ser um computador, uma impressora inteligente, um servidor de ficheiros — qualquer coisa, na verdade, que possa comunicar utilizando protocolos TCP/IP. Cada nó tem um endereço físico com um formato que depende do tipo de rede. Numa rede Ethernet, o endereço físico é codificado em 6 bytes. Um endereço de rede X.25 é um número de 14 dígitos.
O endereço de Internet de um nó é um endereço lógico: é independente do hardware e da rede utilizada. É um endereço de 4 bytes que identifica tanto uma rede local como um nó nessa rede. O endereço de Internet é normalmente representado por quatro números — os valores dos quatro bytes — separados por um ponto. Assim, o endereço da máquina Lagaffe na Faculdade de Ciências de Angers é 193.49.144.1, e o da máquina Liny é 193.49.144.9. A partir daí, podemos deduzir que o endereço de Internet da rede local é 193.49.144.0. Podem existir até 254 nós nesta rede.
Como os endereços de Internet, ou endereços IP, são independentes da rede, uma máquina na rede A pode comunicar com uma máquina na rede B sem se preocupar com o tipo de rede em que se encontra: basta saber o seu endereço IP. O protocolo IP de cada rede trata da conversão entre endereço IP e endereço físico, em ambos os sentidos.
Todos os endereços IP devem ser únicos. As organizações oficiais são responsáveis pela sua atribuição. Na verdade, estas organizações atribuem um endereço às redes locais, por exemplo 193.49.144.0 para a rede da Faculdade de Ciências de Angers. O administrador desta rede pode então atribuir os endereços IP 193.49.144.1 a 193.49.144.254 conforme entender. Este endereço é geralmente armazenado num ficheiro específico em cada máquina ligada à rede.
8.1.5.1. Classes de endereços IP
Um endereço IP é uma sequência de 4 bytes, frequentemente escrita como I1.I2.I3.I4, que na verdade contém dois endereços:
- o endereço de rede
- o endereço de um nó nessa rede
Dependendo do tamanho destes dois campos, os endereços IP são divididos em 3 classes: classes A, B e C.
Classe A
O endereço IP: I1.I2.I3.I4 tem o formato R1.N1.N2.N3, em que
R1 é o endereço de rede
N1.N2.N3 é o endereço de uma máquina nessa rede
Mais precisamente, o formato de um endereço IP de Classe A é o seguinte:

O endereço de rede tem 7 bits e o endereço de host tem 24 bits. Por conseguinte, podem existir 127 redes de Classe A, cada uma contendo até 2²⁴ hosts.
Classe B
Aqui, o endereço IP: I1.I2.I3.I4 tem a forma R1.R2.N1.N2, em que
R1.R2 é o endereço de rede
N1.N2 é o endereço de uma máquina nessa rede
Mais precisamente, o formato de um endereço IP de Classe B é o seguinte:

O endereço de rede tem 2 bytes (14 bits, para ser exato), tal como o endereço do nó. Isto significa que podem existir 2¹⁴ redes de Classe B, cada uma contendo até 2¹⁶ nós.
Classe C
Nesta classe, o endereço IP: I1.I2.I3.I4 tem a forma R1.R2.R3.N1, em que
R1.R2.R3 é o endereço de rede
N1 é o endereço de uma máquina nessa rede
Mais precisamente, o formato de um endereço IP de Classe C é o seguinte:

O endereço de rede ocupa 3 bytes (menos 3 bits) e o endereço do anfitrião ocupa 1 byte. Assim, podem existir 2²¹ redes de Classe C, cada uma contendo até 256 anfitriões.
Uma vez que o endereço da máquina Lagaffe na Faculdade de Ciências de Angers é 193.49.144.1, vemos que o byte mais significativo é 193, que é 11000001 em binário. Podemos deduzir que a rede é uma rede de Classe C.
Endereços reservados
. Alguns endereços IP são endereços de rede, em vez de endereços de nó dentro da rede. Estes são aqueles em que o endereço do nó está definido como 0. Assim, o endereço 193.49.144.0 é o endereço IP da rede da Faculdade de Ciências de Angers. Consequentemente, nenhum nó numa rede pode ter o endereço zero.
. Quando o endereço de nó num endereço IP é composto inteiramente por 1, trata-se de um endereço de difusão: este endereço refere-se a todos os nós da rede.
. Numa rede de Classe C, que teoricamente permite 2⁸ = 256 nós, se retirarmos os dois endereços proibidos, ficamos apenas com 254 endereços autorizados.
8.1.5.2. Protocolos de conversão de endereços de Internet <--> endereços físicos
Vimos que, quando os dados são transmitidos de uma máquina para outra, são encapsulados em pacotes à medida que passam pela camada IP. Estes pacotes têm a seguinte forma:

O pacote IP contém, portanto, os endereços de Internet das máquinas de origem e de destino. Quando este pacote é transmitido para a camada responsável pelo seu envio através da rede física, são adicionadas informações adicionais para formar a trama física que será, em última instância, enviada pela rede. Por exemplo, o formato de uma trama numa rede Ethernet é o seguinte:

No quadro final, encontram-se os endereços físicos das máquinas de origem e de destino. Como é que estes são obtidos?
A máquina remetente, conhecendo o endereço IP da máquina com a qual pretende comunicar, obtém o endereço físico desta última utilizando um protocolo específico chamado ARP (Address Resolution Protocol).
- Envia um tipo especial de pacote chamado pacote ARP, contendo o endereço IP da máquina cujo endereço físico está a ser procurado. Também inclui o seu próprio endereço IP e endereço físico no pacote.
- Este pacote é enviado a todos os nós da rede.
- Estes nós reconhecem a natureza especial do pacote. O nó que reconhece o seu endereço IP no pacote responde enviando o seu endereço físico ao remetente do pacote. Como é que consegue fazer isto? Encontrou os endereços IP e físicos do remetente no pacote.
- O remetente recebe assim o endereço físico que procurava. Armazena-o na memória para poder utilizá-lo mais tarde, caso seja necessário enviar outros pacotes para o mesmo destinatário.
O endereço IP de uma máquina é normalmente armazenado num dos seus ficheiros de configuração, que pode ser consultado para o recuperar. Este endereço pode ser alterado: basta editar o ficheiro. O endereço físico, no entanto, é armazenado na memória da placa de rede e não pode ser alterado.
Quando um administrador deseja reorganizar a rede, pode ser necessário alterar os endereços IP de todos os nós e, assim, editar os vários ficheiros de configuração de cada nó. Isto pode ser tedioso e propenso a erros se houver muitas máquinas. Um método envolve não atribuir um endereço IP às máquinas: em vez disso, é inserido um código especial no ficheiro onde a máquina normalmente encontraria o seu endereço IP. Ao descobrir que não tem endereço IP, a máquina solicita um usando um protocolo chamado RARP (Reverse Address Resolution Protocol). Em seguida, envia um pacote especial chamado pacote RARP pela rede — semelhante ao pacote ARP anterior — contendo o seu endereço físico. Este pacote é enviado a todos os nós que reconhecem um pacote RARP. Um deles, chamado servidor RARP, mantém um ficheiro contendo os mapeamentos de endereço físico <--> endereço IP para todos os nós. Em seguida, responde ao remetente do pacote RARP, devolvendo o seu endereço IP. Um administrador que pretenda reconfigurar a sua rede precisa, portanto, apenas de editar o ficheiro de mapeamento no servidor RARP ( ). Este servidor deve normalmente ter um endereço IP fixo que deve ser capaz de conhecer sem ter de utilizar o próprio protocolo RARP.
8.1.6. A camada de rede, conhecida como camada IP da Internet
O IP (Internet Protocol) define o formato que os pacotes devem assumir e como devem ser tratados durante a transmissão ou receção. Este tipo específico de pacote é chamado de datagrama IP. Já o discutimos:

O ponto importante é que, além dos dados a serem transmitidos, o datagrama IP contém os endereços de Internet das máquinas de origem e de destino. Desta forma, a máquina recetora sabe quem lhe está a enviar uma mensagem.
Ao contrário de um quadro de rede, cujo comprimento é determinado pelas características físicas da rede pela qual transita, o comprimento do datagrama IP é fixado pelo software e será, portanto, o mesmo em diferentes redes físicas. Vimos que, à medida que descemos da camada de rede para a camada física, o datagrama IP é encapsulado dentro de um quadro físico. Demos o exemplo do quadro físico de uma rede Ethernet:

Os quadros físicos viajam de nó em nó em direção ao seu destino, que pode não estar na mesma rede física que a máquina emissora. O pacote IP pode, portanto, ser sucessivamente encapsulado em diferentes quadros físicos nos nós que ligam duas redes de tipos diferentes. Também é possível que o pacote IP seja demasiado grande para ser encapsulado num único quadro físico. O software IP no nó onde este problema surge divide então o pacote IP em fragmentos de acordo com regras específicas, sendo cada um deles enviado pela rede física. Só serão reagrupados no seu destino final.
8.1.6.1. Roteamento
O encaminhamento é o método utilizado para direcionar os pacotes IP para o seu destino. Existem dois métodos: o encaminhamento direto e o encaminhamento indireto.
Roteamento direto
O encaminhamento direto refere-se à transmissão de um pacote IP diretamente do remetente para o destinatário dentro da mesma rede:
- A máquina que envia um datagrama IP possui o endereço IP do destinatário.
- Obtém o endereço físico do destinatário através do protocolo ARP ou a partir das suas tabelas, caso esse endereço já tenha sido obtido.
- Envia o pacote pela rede para esse endereço físico.
Roteamento indireto
O encaminhamento indireto refere-se à transmissão de um pacote IP para um destino localizado numa rede diferente daquela a que o remetente pertence. Neste caso, as partes do endereço de rede dos endereços IP das máquinas de origem e de destino são diferentes. A máquina de origem reconhece isso. Em seguida, envia o pacote para um nó especial chamado router, um nó que liga uma rede local a outras redes e cujo endereço IP encontra nas suas tabelas — um endereço inicialmente obtido a partir de um ficheiro, da memória permanente ou através de informações que circulam na rede.
Um router está ligado a duas redes e possui um endereço IP em ambas as redes.

No nosso exemplo acima:
- A Rede n.º 1 tem o endereço de Internet 193.49.144.0 e a Rede n.º 2 tem o endereço 193.49.145.0.
- Dentro da rede n.º 1, o router tem o endereço 193.49.144.6 e o endereço 193.49.145.3 dentro da rede n.º 2.
A função do router é pegar no pacote IP que recebe — que está contido num quadro físico típico da rede n.º 1 — e colocá-lo num quadro físico que possa viajar pela rede n.º 2. Se o endereço IP do destino do pacote estiver na rede n.º 2, o router enviará o pacote diretamente para lá; caso contrário, enviá-lo-á para outro router, ligando a rede n.º 2 à rede n.º 3, e assim sucessivamente.
8.1.6.2. Mensagens de erro e de controlo
Também dentro da camada de rede — no mesmo nível que o protocolo IP — está o ICMP (Internet Control Message Protocol). É utilizado para enviar mensagens relativas ao funcionamento interno da rede: nós com falha, congestionamento num router, etc. As mensagens ICMP são encapsuladas em pacotes IP e enviadas pela rede. As camadas IP dos vários nós tomam as medidas adequadas com base nas mensagens ICMP que recebem. Assim, uma aplicação em si nunca vê estas questões específicas da rede.
Um nó utilizará as informações ICMP para atualizar as suas tabelas de encaminhamento.
8.1.7. A camada de transporte: os protocolos UDP e TCP
8.1.7.1. O protocolo UDP: Protocolo de Datagrama do Utilizador
O protocolo UDP permite uma troca de dados não fiável entre dois pontos, o que significa que a entrega bem-sucedida de um pacote no seu destino não é garantida. A aplicação, se assim o desejar, pode lidar com isso por si própria, por exemplo, aguardando uma confirmação após enviar uma mensagem antes de enviar a seguinte.
Até agora, ao nível da rede, discutimos os endereços IP das máquinas. No entanto, numa única máquina, podem coexistir simultaneamente diferentes processos, todos eles capazes de comunicar entre si. Por isso, ao enviar uma mensagem, é necessário especificar não só o endereço IP da máquina de destino, mas também o «nome» do processo de destino. Este nome é, na verdade, um número, chamado número de porta. Certos números estão reservados para aplicações padrão: a porta 69 para a aplicação TFTP (Trivial File Transfer Protocol), por exemplo.
Os pacotes tratados pelo protocolo UDP também são chamados de datagramas. Têm a seguinte forma:

Estes datagramas são encapsulados em pacotes IP e, em seguida, em quadros físicos.
8.1.7.2. O Protocolo TCP: Protocolo de Controlo de Transmissão
Para comunicações seguras, o protocolo UDP é insuficiente: o programador da aplicação deve criar ele próprio um protocolo para garantir que os pacotes sejam encaminhados corretamente.
O TCP (Protocolo de Controlo de Transmissão) evita estes problemas. As suas características são as seguintes:
- O processo que pretende transmitir estabelece primeiro uma ligação com o processo que irá receber a informação que está prestes a transmitir. Esta ligação é estabelecida entre uma porta na máquina emissora e uma porta na máquina recetora. É assim criado um caminho virtual entre as duas portas, que será reservado exclusivamente para os dois processos que estabeleceram a ligação.
- Todos os pacotes enviados pelo processo de origem seguem este caminho virtual e chegam na ordem em que foram enviados, o que não era garantido no protocolo UDP, uma vez que os pacotes podiam seguir caminhos diferentes.
- Os dados transmitidos parecem contínuos. O processo de envio envia dados ao seu próprio ritmo. Estes dados não são necessariamente enviados imediatamente: o protocolo TCP aguarda até ter dados suficientes para enviar. São armazenados numa estrutura denominada segmento TCP. Assim que este segmento estiver cheio, é transmitido para a camada IP, onde é encapsulado num pacote IP.
- Cada segmento enviado pelo protocolo TCP é numerado. O protocolo TCP de receção verifica se está a receber os segmentos em sequência. Por cada segmento recebido corretamente, envia um aviso de receção ao remetente.
- Quando o destinatário o recebe, notifica o remetente. O remetente pode assim confirmar que um segmento foi entregue com sucesso, o que não era possível com o protocolo UDP.
- Se, após um determinado período de tempo, o protocolo TCP que enviou um segmento não receber uma confirmação, ele retransmite o segmento em questão, garantindo assim a qualidade do serviço de entrega de informação.
- O circuito virtual estabelecido entre os dois processos em comunicação é full-duplex: isto significa que a informação pode fluir em ambas as direções. Assim, o processo de destino pode enviar confirmações mesmo enquanto o processo de origem continua a enviar informação. Isto permite, por exemplo, que o protocolo TCP de origem envie múltiplos segmentos sem esperar por uma confirmação. Se, após um determinado período de tempo, perceber que não recebeu um aviso de receção para um segmento específico n.º n, retomará o envio de segmentos a partir desse ponto.
8.1.8. A Camada de Aplicação
Acima dos protocolos UDP e TCP, existem vários protocolos padrão:
TELNET
Este protocolo permite que um utilizador na máquina A na rede se ligue à máquina B (frequentemente chamada de máquina anfitriã). O TELNET emula um chamado terminal universal na máquina A. O utilizador comporta-se, portanto, como se tivesse um terminal ligado à máquina B. O Telnet baseia-se no protocolo TCP.
FTP: (Protocolo de Transferência de Ficheiros)
Este protocolo permite a troca de ficheiros entre duas máquinas remotas, bem como operações com ficheiros, como a criação de diretórios, por exemplo. Baseia-se no protocolo TCP.
TFTP: (Protocolo Trivial de Transferência de Ficheiros)
Este protocolo é uma variante do FTP. Baseia-se no protocolo UDP e é menos sofisticado do que o FTP.
DNS: (Sistema de Nomes de Domínio)
Quando um utilizador pretende trocar ficheiros com uma máquina remota, por exemplo, através do FTP, tem de conhecer o endereço de Internet dessa máquina. Por exemplo, para utilizar o FTP na máquina Lagaffe da Universidade de Angers, teria de executar o FTP da seguinte forma: FTP 193.49.144.1
Isto requer um diretório que mapeie máquinas para endereços IP. Neste diretório, as máquinas seriam provavelmente designadas por nomes simbólicos, tais como:
Máquina DPX2/320 na Universidade de Angers
Máquina Sun no ISERPA em Angers
É evidente que seria mais conveniente referir-se a uma máquina por um nome do que pelo seu endereço IP. Isto levanta a questão da exclusividade dos nomes: existem milhões de máquinas interligadas. Poder-se-ia imaginar um organismo centralizado a atribuir os nomes. Isso seria, sem dúvida, bastante complicado. O controlo sobre os nomes foi, de facto, distribuído por **domínios**. Cada domínio é gerido por uma organização geralmente muito enxuta, que tem total liberdade na escolha dos nomes das máquinas. Assim, as máquinas em França pertencem ao domínio **fr**, que é gerido pelo Inria em Paris. Para simplificar ainda mais as coisas, o controlo é distribuído ainda mais: são criados domínios dentro do domínio **fr**. Assim, a Universidade de Angers pertence ao domínio **univ-Angers**. O departamento que gere este domínio tem total liberdade para nomear as máquinas na rede da Universidade de Angers. Por enquanto, este domínio não foi subdividido. Mas numa grande universidade com muitas máquinas em rede, isso poderia acontecer.
A máquina DPX2/320 da Universidade de Angers foi batizada *de Lagaffe*, enquanto um PC 486DX50 recebeu o nome *de liny*. Como se faz referência a estas máquinas a partir do exterior? Especificando a hierarquia de domínios a que pertencem. Assim, o nome completo da máquina Lagaffe será:
Dentro dos domínios, podem ser utilizados nomes relativos. Assim, dentro do domínio **fr** e fora do domínio **univ-Angers**, a máquina Lagaffe pode ser referenciada como
Por fim, dentro do domínio *univ-Angers*, pode ser referenciada simplesmente como
Uma aplicação pode, portanto, referir-se a uma máquina pelo seu nome. No entanto, em última análise, ainda é necessário obter o endereço IP da máquina. Como é que isso se faz? Suponhamos que, a partir da máquina A, queremos comunicar com a máquina B.
- Se a máquina B pertencer ao mesmo domínio que a máquina A, o seu endereço IP provavelmente estará num ficheiro na máquina A.
- Caso contrário, a máquina A encontrará, noutro ficheiro ou no mesmo de antes, uma lista de vários servidores de nomes com os seus endereços IP. Um servidor de nomes é responsável por mapear o nome de uma máquina para o seu endereço IP. A máquina A enviará um pedido especial ao primeiro servidor de nomes da sua lista, conhecido como consulta DNS, que inclui o nome da máquina procurada. Se o servidor consultado tiver esse nome nos seus registos, enviará o endereço IP correspondente à máquina A. Caso contrário, o servidor também encontrará nos seus ficheiros uma lista de servidores de nomes que pode consultar. E assim o fará. Assim, serão consultados vários servidores de nomes, não de forma aleatória, mas de uma forma que minimize o número de pedidos. Se a máquina for finalmente encontrada, a resposta será enviada de volta à máquina A.
XDR: (Representação de Dados Externos)
Criado pela Sun Microsystems, este protocolo especifica uma representação de dados padrão e independente da máquina.
RPC: (Chamada de Procedimento Remoto)
Também definido pela Sun, este é um protocolo de comunicação entre aplicações remotas, independente da camada de transporte. Este protocolo é importante: dispensa o programador de conhecer os detalhes da camada de transporte e torna as aplicações portáteis. Este protocolo baseia-se no protocolo XDR
NFS: Sistema de Ficheiros em Rede
Também definido pela Sun, este protocolo permite que uma máquina «veja» o sistema de ficheiros de outra máquina. Baseia-se no protocolo RPC descrito acima.
8.1.9. Conclusão
Nesta introdução, apresentámos algumas das principais características dos protocolos da Internet. Para aprofundar este tema, os leitores podem consultar o excelente livro de Douglas Comer:
Título: TCP/IP: Arquitetura, Protocolos, Aplicações.
Autor: Douglas COMER
Editora: InterEditions
8.2. Gestão de endereços de rede em Java
8.2.1. Definição
Cada máquina na Internet é identificada por um endereço ou nome único. Estas duas entidades são geridas em Java pela classe InetAddress, que inclui os seguintes métodos:
retorna os 4 bytes do endereço IP da instância atual de InetAddress | |
retorna o endereço IP da instância atual de InetAddress | |
retorna o nome de Internet da instância atual de InetAddress | |
retorna o endereço IP/nome na Internet da instância atual de InetAddress | |
cria a instância InetAddress para a máquina especificada por Host. Lança uma exceção se Host for desconhecido. Host pode ser o nome de Internet de uma máquina ou o seu endereço IP no formato I1.I2.I3.I4 | |
cria a instância InetAddress da máquina na qual o programa que contém esta instrução está a ser executado. |
8.2.2. Alguns exemplos
8.2.2.1. Identificar a máquina local
import java.net.*;
public class localhost{
public static void main (String arg[]){
try{
InetAddress adresse=InetAddress.getLocalHost();
byte[] IP=adresse.getAddress();
System.out.print("IP=");
int i;
for(i=0;i<IP.length-1;i++) System.out.print(IP[i]+".");
System.out.println(IP[i]);
System.out.println("adresse="+adresse.getHostAddress());
System.out.println("nom="+adresse.getHostName());
System.out.println("identité="+adresse);
} catch (UnknownHostException e){
System.out.println ("Erreur getLocalHost : "+e);
}// fin try
}// fine hand
}// fin class
Os resultados da execução são os seguintes:
Cada máquina tem um endereço IP interno, que é 127.0.0.1. Quando um programa utiliza este endereço de rede, está a referir-se à máquina na qual está a ser executado. A vantagem deste endereço é que não requer uma placa de rede. Isto significa que pode testar programas de rede sem estar ligado a uma rede. Outra forma de se referir à máquina local é utilizar o nome localhost.
8.2.2.2. Identificar qualquer máquina
import java.net.*;
public class getbyname{
public static void main (String arg[]){
String nomMachine;
// we retrieve the argument
if(arg.length==0)
nomMachine="localhost";
else nomMachine=arg[0];
// we try to obtain the machine address
try{
InetAddress adresse=InetAddress.getByName(nomMachine);
System.out.println("IP : "+ adresse.getHostAddress());
System.out.println("nom : "+ adresse.getHostName());
System.out.println("identité : "+ adresse);
} catch (UnknownHostException e){
System.out.println ("Erreur getByName : "+e);
}// fin try
}// fine hand
}// fin class
Com a chamada Java getByName, obtemos os seguintes resultados:
Com a chamada Java getbyname shiva.istia.univ-angers.fr, obtemos:
Com a chamada Java **getbyname www.ibm.com**, obtemos:
8.3. Programação TCP-IP
8.3.1. Informações gerais

Quando uma aplicação AppA na máquina A pretende comunicar com uma aplicação AppB na máquina B na Internet, precisa de saber várias coisas:
- o endereço IP ou nome de host da máquina B
- o número da porta utilizada pela aplicação AppB. Isto porque a máquina B pode hospedar muitas aplicações que operam na Internet. Quando recebe informações da rede, ela deve saber a que aplicação se destinam essas informações. As aplicações na máquina B acedem à rede através de interfaces também conhecidas como portas de comunicação. Esta informação está contida no pacote recebido pela máquina B, para que possa ser entregue à aplicação correta.
- Os protocolos de comunicação compreendidos pela máquina B. No nosso estudo, utilizaremos apenas protocolos TCP-IP.
- O protocolo de comunicação aceite pela aplicação AppB. De facto, as máquinas A e B irão «conversar» entre si. O que dizem será encapsulado nos protocolos TCP/IP. No entanto, quando, no final da cadeia, a aplicação AppB recebe a informação enviada pela aplicação AppA, tem de ser capaz de a interpretar. Isto é análogo à situação em que duas pessoas, A e B, comunicam por telefone: a sua conversa é transportada pelo telefone. A fala é codificada como sinais pelo telefone A, transmitida através de linhas telefónicas e recebida pelo telefone B, onde é descodificada. A pessoa B ouve então a fala. É aqui que entra em jogo o conceito de protocolo de comunicação: se A falar francês e B não compreender essa língua, A e B não conseguirão comunicar eficazmente.
Por conseguinte, as duas aplicações em comunicação têm de chegar a acordo quanto ao tipo de diálogo que irão utilizar. Por exemplo, o diálogo com um serviço FTP não é o mesmo que com um serviço POP: estes dois serviços não aceitam os mesmos comandos. Têm um protocolo de diálogo diferente.
8.3.2. Características do Protocolo TCP
Aqui, iremos examinar apenas as comunicações de rede que utilizam o protocolo de transporte TCP. Vamos rever as características deste protocolo:
- O processo que pretende transmitir dados estabelece primeiro uma ligação com o processo que irá receber a informação que está prestes a transmitir. Esta ligação é estabelecida entre uma porta na máquina emissora e uma porta na máquina recetora. É assim criado um caminho virtual entre as duas portas, que será reservado exclusivamente para os dois processos que estabeleceram a ligação.
- Todos os pacotes enviados pelo processo de origem seguem este caminho virtual e chegam na ordem em que foram enviados
- Os dados transmitidos parecem contínuos. O processo de envio envia dados ao seu próprio ritmo. Estes dados não são necessariamente enviados imediatamente: o protocolo TCP aguarda até ter dados suficientes para enviar. São armazenados numa estrutura denominada segmento TCP. Assim que este segmento estiver cheio, é transmitido para a camada IP, onde é encapsulado num pacote IP.
- Cada segmento enviado pelo protocolo TCP é numerado. O protocolo TCP de receção verifica se está a receber os segmentos em sequência. Por cada segmento recebido corretamente, envia um aviso de receção ao remetente.
- Quando o remetente recebe esta confirmação, notifica o processo de envio. O processo de envio pode assim confirmar que um segmento chegou em segurança.
- Se, após um determinado período de tempo, o protocolo TCP que enviou um segmento não receber um aviso de receção, retransmite o segmento em questão, garantindo assim a qualidade do serviço de entrega de informação.
- O circuito virtual estabelecido entre os dois processos em comunicação é full-duplex: isto significa que a informação pode fluir em ambas as direções. Assim, o processo de destino pode enviar confirmações mesmo enquanto o processo de origem continua a enviar informação. Isto permite, por exemplo, que o protocolo TCP de origem envie múltiplos segmentos sem esperar por uma confirmação. Se, após um determinado período de tempo, perceber que não recebeu um aviso de receção para um segmento específico n.º n, retomará o envio de segmentos a partir desse ponto.
8.3.3. A relação cliente-servidor
A comunicação pela Internet é frequentemente assimétrica: a máquina A inicia uma ligação para solicitar um serviço à máquina B, especificando que pretende estabelecer uma ligação com o serviço SB1 na máquina B. A máquina B aceita ou recusa. Se aceitar, a máquina A pode enviar os seus pedidos ao serviço SB1. Estes pedidos devem estar em conformidade com o protocolo de comunicação compreendido pelo serviço SB1. Estabelece-se assim um diálogo de pedido-resposta entre a máquina A, conhecida como máquina cliente, e a máquina B, conhecida como máquina servidor. Um dos dois parceiros encerrará a ligação.
8.3.4. Arquitetura do Cliente
A arquitetura de um programa de rede que solicita os serviços de uma aplicação de servidor será a seguinte:
ouvrir la connexion avec le service SB1 de la machine B
si réussite alors
tant que ce n'est pas fini
préparer une demande
l'émettre vers la machine B
attendre et récupérer la réponse
la traiter
fin tant que
finsi
8.3.5. Arquitetura do servidor
A arquitetura de um programa que oferece serviços será a seguinte:
ouvrir le service sur la machine locale
tant que le service est ouvert
se mettre à l'écoute des demandes de connexion sur un port dit port d'écoute
lorsqu'il y a une demande, la faire traiter par une autre tâche sur un autre port dit port de service
fin tant que
O programa do servidor trata o pedido de ligação inicial de um cliente de forma diferente dos seus pedidos de serviço subsequentes. O programa não presta o serviço propriamente dito. Se o fizesse, deixaria de estar à escuta de pedidos de ligação enquanto o serviço estivesse em curso, e os clientes não seriam atendidos. Por isso, procede de forma diferente: assim que um pedido de ligação é recebido na porta de escuta e depois aceite, o servidor cria uma tarefa responsável por prestar o serviço solicitado pelo cliente. Este serviço é prestado noutro porto da máquina do servidor, chamado porto de serviço. Isto permite que vários clientes sejam atendidos ao mesmo tempo.
Uma tarefa de serviço terá a seguinte estrutura:
tant que le service n'a pas été rendu totalement
attendre une demande sur le port de service
lorsqu'il y en a une, élaborer la réponse
transmettre la réponse via le port de service
fin tant que
libérer le port de service
8.3.6. A classe Socket
8.3.6.1. Definição
A ferramenta básica utilizada pelos programas que comunicam através da Internet é o socket. Esta palavra inglesa significa «tomada elétrica». Neste contexto, o seu significado é alargado para «tomada de rede». Para que uma aplicação envie e receba informações através da Internet, necessita de uma tomada de rede, um socket. Esta ferramenta foi originalmente criada nas versões Berkeley do Unix. Desde então, foi portada para todos os sistemas Unix, bem como para o ambiente Windows. Também existe em máquinas virtuais Java em duas formas: a classe Socket para aplicações cliente e a classe ServerSocket para aplicações servidor. Aqui explicamos alguns dos construtores e métodos da classe Socket:
abre uma ligação remota à porta port na máquina anfitriã |
retorna o número da porta local utilizada pelo socket | |||
retorna o número da porta remota à qual o socket está ligado | |||
retorna o InetAddress local ao qual o socket está vinculado | |||
retorna o InetAddress remoto ao qual o socket está vinculado | |||
retorna um fluxo de entrada usado para ler dados enviados pelo parceiro remoto | |||
retorna um fluxo de saída utilizado para enviar dados ao parceiro remoto | |||
fecha o fluxo de entrada do socket | |||
fecha o fluxo de saída do socket | |||
fecha o socket e os seus fluxos de E/S | |||
retorna uma string que "representa" o socket | |||
8.3.6.2. Estabelecer uma ligação com um servidor
Vimos que, para que a máquina A estabeleça uma ligação com um serviço na máquina B, são necessárias duas informações:
- o endereço IP ou nome de host da máquina B
- o número da porta onde o serviço pretendido está a ser executado
O construtor
cria um socket e liga-o à máquina host na porta port. Este construtor lança uma exceção em vários casos:
- endereço incorreto
- porta incorreta
- pedido recusado
- …
Precisamos de tratar esta exceção:
Socket sClient=null;
try{
sClient=new Socket(host,port);
} catch(Exception e){
// la connexion a échoué - on traite l'erreur
….
}
Se o pedido de ligação for bem-sucedido, é atribuída ao cliente uma porta local para comunicar com a máquina B. Assim que a ligação for estabelecida, esta porta pode ser obtida através do método:
Se a ligação for bem-sucedida, vimos que, do seu lado, o servidor tem outra tarefa a gerir o serviço numa chamada porta de serviço. Este número de porta pode ser obtido utilizando o método:
8.3.6.3. Envio de informações pela rede
Pode obter um fluxo de escrita no socket — e, portanto, na rede — utilizando o método:
Tudo o que for enviado para este fluxo será recebido na porta de serviço da máquina do servidor. Muitas aplicações utilizam uma interface baseada em texto, composta por linhas de texto seguidas de um caractere de nova linha. O método println é, portanto, muito útil nestes casos. Em seguida, convertemos o fluxo de saída OutputStream num fluxo PrintWriter, que disponibiliza o método println. A gravação pode gerar uma exceção.
8.3.6.4. Ler informações da rede
Pode obter um fluxo de leitura para os dados que chegam ao socket utilizando o método:
Tudo o que é lido a partir deste fluxo provém da porta de serviço da máquina do servidor. Para aplicações com uma caixa de diálogo composta por linhas de texto terminadas por uma nova linha, devemos utilizar o método readLine. Para tal, convertemos o InputStream num BufferedReader, que possui o método readLine(). A leitura pode lançar uma exceção.
8.3.6.5. Fechar a ligação
Isto é feito utilizando o método:
Este método pode lançar uma exceção. Os recursos utilizados, nomeadamente a porta de rede, são libertados.
8.3.6.6. Arquitetura do Cliente
Temos agora os elementos para descrever a arquitetura básica de um cliente de Internet:
Socket sClient=null;
try{
// connect to the service running on port P of machine M
sClient=new Socket(M,P);
// create client socket I/O streams
BufferedReader in=new BufferedReader(new InputStreamReader(sClient.getInputStream()));
PrintWriter out=new PrintWriter(sClient.getOutputStream(),true);
// request-response loop
boolean fini=false;
String demande;
String réponse;
while (! fini){
// preparing the application
demande=…
// we send it
out.println(demande);
// we read the answer
réponse=in.readLine();
// the answer is processed
…
}
// it's over
sClient.close();
} catch(Exception e){
// we handle the exception
….
}
Não tentámos tratar os vários tipos de exceções geradas pelo construtor Socket ou pelos métodos readLine, getInputStream, getOutputStream e close, de modo a manter o exemplo simples. Tudo foi consolidado numa única exceção.
8.3.7. A classe ServerSocket
8.3.7.1. Definição
Esta classe destina-se à gestão de sockets do lado do servidor. Aqui explicamos alguns dos construtores e métodos desta classe:
cria um socket de escuta na porta port | |
Igual ao anterior, mas define o tamanho da fila para count, ou seja, o número máximo de ligações de clientes que serão colocadas em fila se o servidor estiver ocupado quando a ligação do cliente chegar. |
retorna o número da porta de escuta utilizada pelo socket | |
Retorna o InetAddress local ao qual o socket está vinculado | |
Coloca o servidor num estado de espera por uma ligação (operação de bloqueio). Após a chegada de uma ligação de cliente, devolve um socket através do qual o serviço será prestado ao cliente. | |
Fecha o socket e os seus fluxos de E/S | |
retorna uma string que "representa" o socket | |
fecha o socket do serviço e liberta os recursos a ele associados |
8.3.7.2. Abrir o serviço
Isto é feito utilizando os dois construtores:
port é a porta de escuta do serviço: aquela para a qual os clientes enviam os seus pedidos de ligação. count é o tamanho máximo da fila do serviço (50 por predefinição), que armazena os pedidos de ligação dos clientes aos quais o servidor ainda não respondeu. Quando a fila está cheia, os pedidos de ligação recebidos são rejeitados. Ambos os construtores lançam uma exceção.
8.3.7.3. Aceitar um pedido de conexão
Quando um cliente envia um pedido de ligação para a porta de escuta do serviço, o serviço aceita-o utilizando o método:
Este método devolve uma instância de Socket: trata-se do socket do serviço, através do qual o serviço será prestado, na maioria das vezes por outro thread. O método pode lançar uma exceção.
8.3.7.4. Leitura/gravação através do socket do serviço
Uma vez que o socket de serviço é uma instância da classe Socket, consulte as secções anteriores onde este tópico foi abordado.
8.3.7.5. Identificação do cliente
Depois de obtido o socket de serviço, o cliente pode ser identificado utilizando o método
da classe Socket. Isto permite aceder ao endereço IP e ao nome do cliente.
8.3.7.6. Encerramento do serviço
Isto é feito utilizando o método
da classe ServerSocket. Isto liberta os recursos que estão a ser utilizados, em particular a porta de escuta. O método pode lançar uma exceção.
8.3.7.7. Arquitetura básica do servidor
Com base no que foi dito, podemos escrever a estrutura básica de um servidor:
SocketServer sEcoute=null;
try{
// ouverture du service
int portEcoute=…
int maxConnexions=…
sEcoute=new ServerSocket(portEcoute,maxConnexions);
// traitement des demandes de connexion
boolean fini=false;
Socket sService=null;
while( ! fini){
// attente et acceptation d'une demande
sService=sEcoute.accept();
// le service est rendu par une autre tâche à laquelle on passe la socket de service
new Service(sService).start();
// on se remet en attente des demandes de connexion
}
// c'est fini - on clôt le service
sEcoute.close();
} catch (Exception e){
// on traite l'exception
…
}
A classe Service é um segmento de código que pode ter o seguinte aspeto:
public class Service extends Thread{
Socket sService; // service socket
// manufacturer
public Service(Socket S){
sService=S;
}
// run
public void run(){
try{
// create input-output flows
BufferedReader in=new BufferedReader(new InputStreamReader(sService.getInputStream()));
PrinttWriter out=new PrintWriter(sService.getOutputStream(),true);
// request-response loop
boolean fini=false;
String demande;
String réponse;
while (! fini){
// we read the request
demande=in.readLine();
// we treat it
…
// we prepare the answer
réponse=…
// we send it
out.println(réponse);
}
// it's over
sService.close();
} catch(Exception e){
// we handle the exception
….
}// try
} // run
8.4. Aplicações
8.4.1. Servidor Echo
Propomos escrever um servidor de eco que será iniciado a partir de uma janela do DOS utilizando o comando:
O servidor funciona na porta passada como parâmetro. Ele simplesmente reenvia ao cliente o pedido que este lhe enviou, juntamente com a identidade do cliente (IP + nome). Aceita duas ligações na sua fila. Aqui temos todos os componentes de um servidor TCP. O programa é o seguinte:
// call: serveurEcho port
// echo server
// returns the line sent to the customer
import java.net.*;
import java.io.*;
public class serveurEcho{
public final static String syntaxe="Syntaxe : serveurEcho port";
public final static int nbConnexions=2;
// main program
public static void main (String arg[]){
// is there an argument
if(arg.length != 1)
erreur(syntaxe,1);
// this argument must be integer >0
int port=0;
boolean erreurPort=false;
Exception E=null;
try{
port=Integer.parseInt(arg[0]);
}catch(Exception e){
E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0;
if(erreurPort)
erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);
// create the listening socket
ServerSocket ecoute=null;
try{
ecoute=new ServerSocket(port,nbConnexions);
} catch (Exception e){
erreur("Erreur lors de la création de la socket d'écoute ("+e+")",3);
}
// follow-up
System.out.println("Serveur d'écho lancé sur le port " + port);
// service loop
boolean serviceFini=false;
Socket service=null;
while (! serviceFini){
// waiting for a customer
try{
service=ecoute.accept();
} catch (IOException e){
erreur("Erreur lors de l'acceptation d'une connexion ("+e+")",4);
}
// we identify the link
try{
System.out.println("Client ["+identifie(service.getInetAddress())+","+
service.getPort()+"] connecté au serveur [" + identifie (InetAddress.getLocalHost())
+ "," + service.getLocalPort() + "]");
} catch (Exception e) {
erreur("identification liaison",1);
}
// the service is provided by another task
new traiteClientEcho(service).start();
}// end while
}// fine hand
// error display
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
// identifies
private static String identifie(InetAddress Host){
// host identification
String ipHost=Host.getHostAddress();
String nomHost=Host.getHostName();
String idHost;
if (nomHost == null) idHost=ipHost;
else idHost=ipHost+","+nomHost;
return idHost;
}
}// end class
// provides service to an echo server client
class traiteClientEcho extends Thread{
private Socket service; // service socket
private BufferedReader in; // iNPUTS
private PrintWriter out; // output flow
// manufacturer
public traiteClientEcho(Socket service){
this.service=service;
}
// run method
public void run(){
// creation of input and output flows
try{
in=new BufferedReader(new InputStreamReader(service.getInputStream()));
} catch (IOException e){
erreur("Erreur lors de la création du flux déentrée de la socket de service ("+e+")",1);
}// fin try
try{
out=new PrintWriter(service.getOutputStream(),true);
} catch (IOException e){
erreur("Erreur lors de la création du flux de sortie de la socket de service ("+e+")",1);
}// fin try
// link identification is sent to the customer
try{
out.println("Client ["+identifie(service.getInetAddress())+","+
service.getPort()+"] connecté au serveur [" + identifie (InetAddress.getLocalHost())
+ "," + service.getLocalPort() + "]");
} catch (Exception e) {
erreur("identification liaison",1);
}
// loop read request/write response
String demande,reponse;
try{
// the service stops when the client sends an end-of-file marker
while ((demande=in.readLine())!=null){
// echo of demand
reponse="["+demande+"]";
out.println(reponse);
// service stops when client sends "end
if(demande.trim().toLowerCase().equals("fin")) break;
}// end while
} catch (IOException e){
erreur("Erreur lors des échanges client/serveur ("+e+")",3);
}// fin try
// close the socket
try{
service.close();
} catch (IOException e){
erreur("Erreur lors de la fermeture de la socket de service ("+e+")",2);
}// fin try
}// end run
// error display
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}// end error
// identifies
private String identifie(InetAddress Host){
// host identification
String ipHost=Host.getHostAddress();
String nomHost=Host.getHostName();
String idHost;
if (nomHost == null) idHost=ipHost;
else idHost=ipHost+","+nomHost;
return idHost;
}
}// fin class
As duas classes necessárias para o serviço foram combinadas num único ficheiro fonte. Apenas uma delas, a que contém a função principal, possui o atributo public. A estrutura do servidor está em conformidade com a arquitetura geral dos servidores TCP. Foi adicionado um método (identify) para identificar a ligação entre o servidor e um cliente. Aqui estão alguns resultados:
O servidor é iniciado pelo comando
Em seguida, exibe a seguinte mensagem na janela da consola:
Para testar este servidor, utilizamos o programa telnet, que está disponível tanto no Unix como no Windows. O Telnet é um cliente TCP universal adequado para todos os servidores que aceitam linhas de texto terminadas por um caractere de fim de linha na sua comunicação. É o caso do nosso servidor de eco. Iniciamos um primeiro cliente telnet no Windows (2000 neste exemplo) digitando telnet numa janela do DOS:
DOS>telnet
Microsoft (R) Windows 2000 (TM) version 5.00 (numéro 2195)
Client Telnet Microsoft
Client Telnet numéro 5.00.99203.1
Le caractère d'escapement is 'CTRL+$'
Microsoft Telnet> help
Les commandes peuvent être abrégées. Les commandes prises en charge sont :
close ferme la connexion en cours
display affiche les paramètres d'operation
open ouvre une connexion à un site
quit quitte telnet
set définit les options (entrez 'set ?' to display the list)
status affiche les informations d'status
unset annule les options (entrez 'unset ?' to display the list)
? ou help affiche des informations d'help
Microsoft Telnet> set ?
NTLM Active l'authentication NTLM.
LOCAL_ECHO Active l'local echo.
TERM x (où x est ANSI, VT100, VT52 ou VTNT))
CRLF Envoi de CR et de LF
Microsoft Telnet> set local_echo
Microsoft Telnet> open localhost 187
Por predefinição, o programa Telnet não repete os comandos digitados no teclado. Para ativar esta repetição, introduza o comando:
Para abrir uma ligação ao servidor, especificando a porta do serviço de eco (187) e o endereço da máquina em que está a ser executado (localhost), introduza o seguinte comando:
Na janela DOS do cliente, receberá então a mensagem:
Na janela do servidor, aparece a seguinte mensagem:
Serveur d'écho lancé sur le port 187
Client [127.0.0.1,tahe,1059] connecté au serveur [127.0.0.1,tahe,187]
Aqui, tahe e localhost referem-se à mesma máquina. Na janela do cliente Telnet, pode digitar linhas de texto. O servidor repete-as:
Client [127.0.0.1,tahe,1059] connectÚ au serveur [127.0.0.1,tahe,187]
je suis là
[je suis là]
au revoir
[au revoir]
Note-se que a porta do cliente (1059) é detetada corretamente, mas a porta do serviço (187) é idêntica à porta de escuta (187), o que é inesperado. Seria de esperar, de facto, obter a porta do socket do serviço em vez da porta de escuta. Devemos verificar se obtemos os mesmos resultados no Unix. Agora, vamos iniciar um segundo cliente telnet. A janela do servidor fica assim:
Serveur d'écho lancé sur le port 187
Client [127.0.0.1,tahe,1059] connecté au serveur [127.0.0.1,tahe,187]
Client [127.0.0.1,tahe,1060] connecté au serveur [127.0.0.1,tahe,187]
Na janela do segundo cliente, também pode digitar linhas de texto:
Client [127.0.0.1,tahe,1060] connecté au serveur [127.0.0.1,tahe,187]
ligne1
[ligne1]
ligne2
[ligne2]
Isto demonstra que o servidor de eco pode servir vários clientes ao mesmo tempo. Os clientes Telnet podem ser encerrados fechando a janela do DOS na qual estão a ser executados.
8.4.2. Um cliente Java para o servidor echo
Na secção anterior, utilizámos um cliente Telnet para testar o serviço de eco. Agora vamos escrever o nosso próprio cliente:
// call: clientEcho machine port
// echo server client
// sends lines to the server, which echoes them back to the server
import java.net.*;
import java.io.*;
public class clientEcho{
public final static String syntaxe="Syntaxe : clientEcho machine port";
// main program
public static void main (String arg[]){
// are there two arguments
if(arg.length != 2)
erreur(syntaxe,1);
// the first argument must be the name of an existing machine
String machine=arg[0];
InetAddress serveurAddress=null;
try{
serveurAddress=InetAddress.getByName(machine);
} catch (Exception e){
erreur(syntaxe+"\nMachine "+machine+" inaccessible (" + e +")",2);
}
// port must be integer >0
int port=0;
boolean erreurPort=false;
Exception E=null;
try{
port=Integer.parseInt(arg[1]);
}catch(Exception e){
E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0;
if(erreurPort)
erreur(syntaxe+"\nPort incorrect ("+E+")",3);
// connect to the server
Socket sClient=null;
try{
sClient=new Socket(machine,port);
} catch (Exception e){
erreur("Erreur lors de la création de la socket de communication ("+e+")",4);
}
// we identify the link
try{
System.out.println("Client : Client ["+identifie(InetAddress.getLocalHost())+","+
sClient.getLocalPort()+"] connecté au serveur [" + identifie (sClient.getInetAddress())
+ "," + sClient.getPort() + "]");
} catch (Exception e) {
erreur("identification liaison ("+e+")",5);
}
// creation of a flow for reading lines typed on the keyboard
BufferedReader IN=null;
try{
IN=new BufferedReader(new InputStreamReader(System.in));
} catch (Exception e){
erreur("Création du flux d'entrée clavier ("+e+")",6);
}
// creation of the input stream associated with the client socket
BufferedReader in=null;
try{
in=new BufferedReader(new InputStreamReader(sClient.getInputStream()));
} catch (Exception e){
erreur("Création du flux d'entrée de la socket client("+e+")",7);
}
// creation of the output stream associated with the client socket
PrintWriter out=null;
try{
out=new PrintWriter(sClient.getOutputStream(),true);
} catch (Exception e){
erreur("Création du flux de sortie de la socket ("+e+")",8);
}
// request-response loops
boolean serviceFini=false;
String demande=null;
String reponse=null;
// we read the message sent by the server just after connection
try{
reponse=in.readLine();
} catch (IOException e){
erreur("Lecture réponse ("+e+")",4);
}
// response display
System.out.println("Serveur : " +reponse);
while (! serviceFini){
// read a line typed on the keyboard
System.out.print("Client : ");
try{
demande=IN.readLine();
} catch (Exception e){
erreur("Lecture ligne ("+e+")",9);
}
// sending demand on the network
try{
out.println(demande);
} catch (Exception e){
erreur("Envoi demande ("+e+")",10);
}
// wait/read answer
try{
reponse=in.readLine();
} catch (IOException e){
erreur("Lecture réponse ("+e+")",4);
}
// response display
System.out.println("Serveur : " +reponse);
// is it over?
if(demande.trim().toLowerCase().equals("fin")) serviceFini=true;
}
// it's over
try{
sClient.close();
} catch(Exception e){
erreur("Fermeture socket ("+e+")",11);
}
}// hand
// error display
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
// identifies
private static String identifie(InetAddress Host){
// host identification
String ipHost=Host.getHostAddress();
String nomHost=Host.getHostName();
String idHost;
if (nomHost == null) idHost=ipHost;
else idHost=ipHost+","+nomHost;
return idHost;
}
}// fin class
A estrutura deste cliente está em conformidade com a arquitetura geral dos clientes TCP. Aqui, tratámos as várias exceções possíveis uma a uma, o que torna o programa mais pesado. Aqui estão os resultados obtidos ao testar este cliente:
Client : Client [127.0.0.1,tahe,1045] connecté au serveur [127.0.0.1,localhost,187]
Serveur : Client [127.0.0.1,localhost,1045] connectÚ au serveur [127.0.0.1,tahe,187]
Client : 123
Serveur : [123]
Client : abcd
Serveur : [abcd]
Client : je suis là
Serveur : [je suis là]
Client : fin
Serveur : [fin]
As linhas que começam com Cliente são as enviadas pelo cliente, e as que começam com Servidor são as respondidas pelo servidor.
8.4.3. Um cliente TCP genérico
Muitos serviços criados nos primórdios da Internet funcionam de acordo com o modelo de servidor de eco discutido anteriormente: a comunicação cliente-servidor consiste na troca de linhas de texto. Vamos escrever um cliente TCP genérico que será iniciado da seguinte forma: java cltTCPgenerique server port
Este cliente TCP irá ligar-se à porta port no servidor server. Uma vez ligado, irá criar duas threads:
- uma thread responsável por ler os comandos digitados no teclado e enviá-los para o servidor
- uma thread responsável por ler as respostas do servidor e exibi-las no ecrã
Porquê duas threads quando isso não era necessário na aplicação anterior? Nessa aplicação, o protocolo de comunicação era fixo: o cliente enviava uma única linha e o servidor respondia com uma única linha. Cada serviço tem o seu próprio protocolo específico e também nos deparamos com as seguintes situações:
- o cliente deve enviar várias linhas de texto antes de receber uma resposta
- a resposta de um servidor pode conter várias linhas de texto
Por isso, o ciclo que envia uma única linha para o servidor e recebe uma única linha do servidor nem sempre é adequado. Vamos, portanto, criar dois ciclos separados:
- um loop para ler os comandos digitados no teclado a serem enviados ao servidor. O utilizador sinalizará o fim dos comandos com a palavra-chave «fin».
- um ciclo para receber e exibir as respostas do servidor. Este será um ciclo infinito que só será interrompido pelo servidor ao fechar a ligação de rede ou pelo utilizador ao digitar o comando «end» no teclado.
Para ter estes dois loops separados, precisamos de dois threads independentes. Vejamos um exemplo de execução em que o nosso cliente TCP genérico se liga a um serviço SMTP (Simple Mail Transfer Protocol). Este serviço é responsável por encaminhar e-mails para os seus destinatários. Opera na porta 25 e utiliza um protocolo de troca baseado em texto.
Dos>java clientTCPgenerique istia.univ-angers.fr 25
Commandes :
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
help
<-- 502 5.3.0 Sendmail 8.11.6 -- HELP not implemented
mail from: machin@univ-angers.fr
<-- 250 2.1.0 machin@univ-angers.fr... Sender ok
rcpt to: serge.tahe@istia.univ-angers.fr
<-- 250 2.1.5 serge.tahe@istia.univ-angers.fr... Recipient ok
data
<-- 354 Enter mail, end with "." on a line by itself
Subject: test
ligne1
ligne2
ligne3
.
<-- 250 2.0.0 g4D6bks25951 Message accepted for delivery
quit
<-- 221 2.0.0 istia.univ-angers.fr closing connection
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Vamos comentar estas trocas cliente-servidor:
- O serviço SMTP envia uma mensagem de boas-vindas quando um cliente se liga a ele:
- Alguns serviços têm um comando «help» que fornece informações sobre os comandos disponíveis para esse serviço. Não é esse o caso aqui. Os comandos SMTP utilizados no exemplo são os seguintes:
- mail from: remetente, para especificar o endereço de e-mail do remetente
- rcpt to: destinatário, para especificar o endereço de e-mail do destinatário da mensagem. Se houver vários destinatários, o comando rcpt to: é repetido tantas vezes quantas forem necessárias para cada destinatário.
- data, que sinaliza ao servidor SMTP que a mensagem está prestes a ser enviada. Conforme indicado na resposta do servidor, isto consiste numa série de linhas que terminam com uma linha contendo apenas um ponto. Uma mensagem pode ter cabeçalhos separados do corpo da mensagem por uma linha em branco. No nosso exemplo, incluímos um assunto utilizando a palavra-chave Subject:
- Assim que a mensagem for enviada, podemos indicar ao servidor que terminámos utilizando o comando quit. O servidor encerra então a ligação de rede. O segmento de leitura pode detetar este evento e parar.
- O utilizador digita então «end» no teclado para também parar a thread que lê os comandos digitados no teclado.
Se verificarmos o e-mail recebido, vemos o seguinte (Outlook):

Note que o serviço SMTP não consegue detetar se um remetente é válido ou não. Por isso, nunca se pode confiar no campo «De» de uma mensagem. Neste caso, o remetente machin@univ-angers.fr não existia.
Este cliente TCP genérico permite-nos descobrir o protocolo de comunicação dos serviços da Internet e, a partir daí, criar classes especializadas para clientes desses serviços. Vamos explorar o protocolo de comunicação do serviço POP (Post Office Protocol), que nos permite recuperar e-mails armazenados num servidor. Funciona na porta 110.
Dos> java clientTCPgenerique istia.univ-angers.fr 110
Commandes :
<-- +OK Qpopper (version 4.0.3) at istia.univ-angers.fr starting.
help
<-- -ERR Unknown command: "help".
user st
<-- +OK Password required for st.
pass monpassword
<-- +OK st has 157 visible messages (0 hidden) in 11755927 octets.
list
<-- +OK 157 visible messages (11755927 octets)
<-- 1 892847
<-- 2 171661
...
<-- 156 2843
<-- 157 2796
<-- .
retr 157
<-- +OK 2796 octets
<-- Received: from lagaffe.univ-angers.fr (lagaffe.univ-angers.fr [193.49.144.1])
<-- by istia.univ-angers.fr (8.11.6/8.9.3) with ESMTP id g4D6wZs26600;
<-- Mon, 13 May 2002 08:58:35 +0200
<-- Received: from jaume ([193.49.146.242])
<-- by lagaffe.univ-angers.fr (8.11.1/8.11.2/GeO20000215) with SMTP id g4D6wSd37691;
<-- Mon, 13 May 2002 08:58:28 +0200 (CEST)
...
<-- ------------------------------------------------------------------------
<-- NOC-RENATER2 Tl. : 0800 77 47 95
<-- Fax : (+33) 01 40 78 64 00 , Email : noc-r2@cssi.renater.fr
<-- ------------------------------------------------------------------------
<--
<-- .
quit
<-- +OK Pop server at istia.univ-angers.fr signing off.
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Os principais comandos são os seguintes:
- login do utilizador, onde introduz o seu nome de utilizador na máquina que hospeda os seus e-mails
- pass password, onde introduz a palavra-passe associada ao login anterior
- list, para obter uma lista de mensagens no formato número, tamanho em bytes
- retr i, para ler a mensagem número i
- sair, para encerrar a sessão.
Vamos agora explorar o protocolo de comunicação entre um cliente e um servidor web, que normalmente funciona na porta 80:
Dos> java clientTCPgenerique istia.univ-angers.fr 80
Commandes :
GET /index.html HTTP/1.0
<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
<--
<-- <head>
<-- <meta http-equiv="Content-Type"
<-- content="text/html; charset=iso-8859-1">
<-- <meta name="GENERATOR" content="Microsoft FrontPage Express 2.0">
<-- <title>Bienvenue a l'ISTIA - Universite d'Angers</title>
<-- </head>
....
<-- face="Verdana"> - Dernire mise jour le <b>10 janvier 2002</b></font></p>
<-- </body>
<-- </html>
<--
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Um cliente web envia os seus comandos para o servidor de acordo com o seguinte padrão:
O servidor web só responde após receber a linha vazia. Neste exemplo, utilizámos apenas um comando:
que solicita a URL /index.html ao servidor e indica que está a utilizar a versão 1.0 do HTTP. A versão mais recente deste protocolo é a 1.1. O exemplo mostra que o servidor respondeu enviando o conteúdo do ficheiro index.html e, em seguida, encerrou a ligação, como podemos ver na mensagem «thread terminating» da resposta. Antes de enviar o conteúdo do ficheiro index.html, o servidor web enviou uma série de cabeçalhos seguidos de uma linha vazia:
<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
A linha <html> é a primeira linha do ficheiro /index.html. O texto que a precede é designado por cabeçalhos HTTP (HyperText Transfer Protocol). Não entraremos em pormenores sobre estes cabeçalhos aqui, mas tenha em mente que o nosso cliente genérico fornece acesso aos mesmos, o que pode ser útil para os compreender. Por exemplo, a primeira linha:
indica que o servidor web contactado suporta o protocolo HTTP/1.1 e que encontrou com sucesso o ficheiro solicitado (200 OK), sendo que 200 é um código de resposta HTTP. As linhas
indica ao cliente que irá receber 11 251 bytes de texto HTML (HyperText Markup Language) e que a ligação será encerrada assim que os dados forem enviados.
Portanto, aqui temos um cliente TCP muito útil. É certo que faz menos do que o programa telnet que utilizámos anteriormente, mas foi interessante escrevê-lo nós próprios. O programa cliente TCP genérico é o seguinte:
// imported packages
import java.io.*;
import java.net.*;
public class clientTCPgenerique{
// receives the characteristics of a service as a parameter in the form
// server port
// connects to the service
// creates a thread to read keyboard commands
// these will be sent to the
// creates a thread to read server responses
// these will be displayed on the screen
// the whole thing ends with the command end typed on the keyboard
// instance variable
private static Socket client;
public static void main(String[] args){
// syntax
final String syntaxe="pg serveur port";
// number of arguments
if(args.length != 2)
erreur(syntaxe,1);
// note the server name
String serveur=args[0];
// port must be integer >0
int port=0;
boolean erreurPort=false;
Exception E=null;
try{
port=Integer.parseInt(args[1]);
}catch(Exception e){
E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0;
if(erreurPort)
erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);
client=null;
// there may be problems
try{
// connect to the service
client=new Socket(serveur,port);
}catch(Exception ex){
// error
erreur("Impossible de se connecter au service ("+ serveur
+","+port+"), erreur : "+ex.getMessage(),3);
// end
return;
}//catch
// create read/write threads
new ClientSend(client).start();
new ClientReceive(client).start();
// end thread main
return;
}// hand
// error display
public static void erreur(String msg, int exitCode){
// error display
System.err.println(msg);
// stop with error
System.exit(exitCode);
}//error
}//class
class ClientSend extends Thread {
// class for reading keyboard commands
// and send them to a server via a tcp client passed in parameter
private Socket client; // tcp client
// manufacturer
public ClientSend(Socket client){
// we note the tcp client
this.client=client;
}//manufacturer
// thread Run method
public void run(){
// local data
PrintWriter OUT=null; // network write streams
BufferedReader IN=null; // keyboard flow
String commande=null; // command read from keyboard
// error management
try{
// network write stream creation
OUT=new PrintWriter(client.getOutputStream(),true);
// keyboard input stream creation
IN=new BufferedReader(new InputStreamReader(System.in));
// order entry-send loop
System.out.println("Commandes : ");
while(true){
// read command typed on keyboard
commande=IN.readLine().trim();
// finished?
if (commande.toLowerCase().equals("fin")) break;
// send order to server
OUT.println(commande);
// next order
}//while
}catch(Exception ex){
// error
System.err.println("Envoi : L'erreur suivante s'est produite : " + ex.getMessage());
}//catch
// end - we close the feeds
try{
OUT.close();client.close();
}catch(Exception ex){}
// signals the end of the thread
System.out.println("[Envoi : fin du thread d'envoi des commandes au serveur]");
}//run
}//class
class ClientReceive extends Thread{
// class responsible for reading lines of text intended for a
// tcp client passed as parameter
private Socket client; // tcp client
// manufacturer
public ClientReceive(Socket client){
// we note the tcp client
this.client=client;
}//manufacturer
// thread Run method
public void run(){
// local data
BufferedReader IN=null; // network read stream
String réponse=null; // server response
// error management
try{
// create network read stream
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
// loop read text lines from IN stream
while(true){
// network streaming
réponse=IN.readLine();
// closed flow?
if(réponse==null) break;
// display
System.out.println("<-- "+réponse);
}//while
}catch(Exception ex){
// error
System.err.println("Réception : L'erreur suivante s'est produite : " + ex.getMessage());
}//catch
// end - we close the feeds
try{
IN.close();client.close();
}catch(Exception ex){}
// signals the end of the thread
System.out.println("[Réception : fin du thread de lecture des réponses du serveur]");
}//run
}//class
8.4.4. Um servidor TCP genérico
Agora vamos ver um servidor
- que exibe no ecrã os comandos enviados pelos seus clientes
- e lhes envia, em resposta, as linhas de texto digitadas no teclado por um utilizador. É, portanto, o utilizador que atua como servidor.
O programa é iniciado por: java genericTCPserver listeningPort, onde listeningPort é a porta à qual os clientes devem ligar-se. O serviço ao cliente será tratado por duas threads:
- uma thread dedicada exclusivamente à leitura das linhas de texto enviadas pelo cliente
- uma thread dedicada exclusivamente à leitura das respostas digitadas pelo utilizador. Esta thread sinalizará, utilizando o comando `fin`, que está a encerrar a ligação com o cliente.
O servidor cria duas threads por cliente. Se houver n clientes, haverá 2n threads ativas ao mesmo tempo. O próprio servidor nunca pára, a menos que o utilizador pressione Ctrl-C no teclado. Vejamos alguns exemplos.
O servidor está a funcionar na porta 100 e usamos o cliente genérico para comunicar com ele. A janela do cliente tem este aspeto:
E:\data\serge\MSNET\c#\réseau\client tcp générique> java clientTCPgenerique localhost 100
Commandes :
commande 1 du client 1
<-- réponse 1 au client 1
commande 2 du client 1
<-- réponse 2 au client 1
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]
As linhas que começam com <-- são as enviadas do servidor para o cliente; as outras são do cliente para o servidor. A janela do servidor é a seguinte:
Dos> java serveurTCPgenerique 100
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
1 : Thread de lecture des demandes du client 1 lancé
<-- commande 1 du client 1
réponse 1 au client 1
1 : <-- commande 2 du client 1
réponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des réponses du serveur au client 1]
As linhas que começam com <-- são as enviadas do cliente para o servidor. As linhas N: são as enviadas do servidor para o cliente N. O servidor acima continua ativo, mesmo que o cliente 1 tenha terminado. Lançamos um segundo cliente para o mesmo servidor:
Dos> java clientTCPgenerique localhost 100
Commandes :
commande 3 du client 2
<-- réponse 3 au client 2
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]
A janela do servidor fica então com este aspeto:
Dos> java serveurTCPgenerique 100
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
1 : Thread de lecture des demandes du client 1 lancé
<-- commande 1 du client 1
réponse 1 au client 1
1 : <-- commande 2 du client 1
réponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des réponses du serveur au client 1]
Thread de lecture des réponses du serveur au client 2 lancé
2 : Thread de lecture des demandes du client 2 lancé
<-- commande 3 du client 2
réponse 3 au client 2
2 : [fin du Thread de lecture des demandes du client 2]
fin
[fin du Thread de lecture des réponses du serveur au client 2]
^C
Agora vamos simular um servidor web, iniciando o nosso servidor genérico na porta 88:
Dos> java serveurTCPgenerique 88
Serveur générique lancé sur le port 88
Agora vamos abrir um navegador e solicitar a URL http://localhost:88/exemple.html. O navegador irá então ligar-se à porta 88 na máquina localhost e solicitar a página /example.html:

Agora vamos dar uma olhada na janela do nosso servidor:
Dos>java serveurTCPgenerique 88
Serveur générique lancé sur le port 88
Thread de lecture des réponses du serveur au client 2 lancé
2 : Thread de lecture des demandes du client 2 lancé
<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, */*
<-- Accept-Language: fr
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.0.2
914)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--
Isto revela os cabeçalhos HTTP enviados pelo navegador. Isto permite-nos aprender gradualmente sobre o protocolo HTTP. Num exemplo anterior, criámos um cliente web que enviava apenas o comando GET. Isso era suficiente. Aqui vemos que o navegador envia informações adicionais para o servidor. Estas informações destinam-se a informar o servidor sobre o tipo de cliente com que está a lidar. Também vemos que os cabeçalhos HTTP terminam com uma linha vazia.
Vamos criar uma resposta para o nosso cliente. O utilizador ao teclado é, neste caso, o servidor e pode criar manualmente uma resposta. Lembre-se da resposta enviada por um servidor web num exemplo anterior:
<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
Vamos tentar fornecer uma resposta semelhante:
...
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--
2 : HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :
2 : <html>
2 : <head><title>Serveur generique</title></head>
2 : <body>
2 : <center>
2 : <h2>Reponse du serveur generique</h2>
2 : </center>
2 : </body>
2 : </html>
2 : fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du Thread de lecture des demandes du client 2]
[fin du Thread de lecture des réponses du serveur au client 2]
As linhas que começam com 2: são enviadas do servidor para o cliente n.º 2. O comando «end» encerra a ligação do servidor ao cliente. Na nossa resposta, limitámo-nos aos seguintes cabeçalhos HTTP:
HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :
Não especificamos o tamanho do ficheiro que estamos a enviar (Content-Length), mas limitamo-nos a indicar que iremos encerrar a ligação (Connection: close) após o envio. Isto é suficiente para o navegador. Ao verificar que a ligação foi encerrada, o navegador saberá que a resposta do servidor está completa e exibirá a página HTML que lhe foi enviada. A página é a seguinte:
2 : <html>
2 : <head><title>Serveur generique</title></head>
2 : <body>
2 : <center>
2 : <h2>Reponse du serveur generique</h2>
2 : </center>
2 : </body>
2 : </html>
O utilizador encerra então a ligação ao cliente digitando o comando «fin». O navegador fica assim a saber que a resposta do servidor está completa e pode exibi-la:

Se, no exemplo acima, selecionar «Ver/Fonte» para ver o que o navegador recebeu, obtém:

ou seja, exatamente o que foi enviado pelo servidor genérico.
O código para o servidor TCP genérico é o seguinte:
// packages
import java.io.*;
import java.net.*;
public class serveurTCPgenerique{
// main program
public static void main (String[] args){
// receives the port of listening to customer requests
// creates a thread to read client requests
// these will be displayed on the screen
// creates a thread to read keyboard commands
// these will be sent as a reply to the customer
// the whole thing ends with the command end typed on the keyboard
final String syntaxe="Syntaxe : pg port";
// instance variable
// is there an argument
if(args.length != 1)
erreur(syntaxe,1);
// port must be integer >0
int port=0;
boolean erreurPort=false;
Exception E=null;
try{
port=Integer.parseInt(args[0]);
}catch(Exception e){
E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0;
if(erreurPort)
erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);
// we create the listening service
ServerSocket ecoute=null;
int nbClients=0; // no. of customers handled
try{
// create the service
ecoute=new ServerSocket(port);
// follow-up
System.out.println("Serveur générique lancé sur le port " + port);
// customer service loop
Socket client=null;
while (true){ // infinite loop - will be stopped by Ctrl-C
// waiting for a customer
client=ecoute.accept();
// the service is provided by separate threads
nbClients++;
// create read/write threads
new ServeurSend(client,nbClients).start();
new ServeurReceive(client,nbClients).start();
// back to listening to requests
}// end while
}catch(Exception ex){
// we report the error
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
}//catch
}// fine hand
// error display
public static void erreur(String msg, int exitCode){
// error display
System.err.println(msg);
// stop with error
System.exit(exitCode);
}//error
}//class
class ServeurSend extends Thread{
// class responsible for reading typed responses
// and send them to a client via a tcp client passed to the
Socket client; // tcp client
int numClient; // customer no
// manufacturer
public ServeurSend(Socket client, int numClient){
// we note the tcp client
this.client=client;
// and its
this.numClient=numClient;
}//manufacturer
// thread Run method
public void run(){
// local data
PrintWriter OUT=null; // network write streams
String réponse=null; // answer read from keyboard
BufferedReader IN=null; // keyboard flow
// follow-up
System.out.println("Thread de lecture des réponses du serveur au client "+ numClient + " lancé");
// error management
try{
// network write stream creation
OUT=new PrintWriter(client.getOutputStream(),true);
// keyboard flow creation
IN=new BufferedReader(new InputStreamReader(System.in));
// order entry-send loop
while(true){
// customer identification
System.out.print("--> " + numClient + " : ");
// read response typed on keyboard
réponse=IN.readLine().trim();
// finished?
if (réponse.toLowerCase().equals("fin")) break;
// send response to server
OUT.println(réponse);
// following response
}//while
}catch(Exception ex){
// error
System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
}//catch
// end - we close the feeds
try{
OUT.close();client.close();
}catch(Exception ex){}
// signals the end of the thread
System.out.println("[fin du Thread de lecture des réponses du serveur au client "+ numClient+ "]");
}//run
}//class
class ServeurReceive extends Thread{
// class responsible for reading text lines sent to the server
// via a tcp client passed to the builder
Socket client; // tcp client
int numClient; // customer no
// manufacturer
public ServeurReceive(Socket client, int numClient){
// we note the tcp client
this.client=client;
// and its
this.numClient=numClient;
}//manufacturer
// thread Run method
public void run(){
// local data
BufferedReader IN=null; // network read stream
String réponse=null; // server response
// follow-up
System.out.println("Thread de lecture des demandes du client "+ numClient + " lancé");
// error management
try{
// create network read stream
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
// loop read text lines from IN stream
while(true){
// network streaming
réponse=IN.readLine();
// closed flow?
if(réponse==null) break;
// display
System.out.println("<-- "+réponse);
}//while
}catch(Exception ex){
// error
System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
}//catch
// end - we close the feeds
try{
IN.close();client.close();
}catch(Exception ex){}
// signals the end of the thread
System.out.println("[fin du Thread de lecture des demandes du client "+ numClient+"]");
}//run
}//class
8.4.5. Um cliente Web
No exemplo anterior, vimos alguns dos cabeçalhos HTTP enviados por um navegador:
<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, */*
<-- Accept-Language: fr
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.0.2
914)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--
Vamos escrever um cliente web que receba um URL como parâmetro e exiba o conteúdo desse URL no ecrã. Vamos assumir que o servidor web contactado para o URL suporta o protocolo HTTP 1.1. Dos cabeçalhos listados acima, vamos utilizar apenas os seguintes:
- O primeiro cabeçalho indica qual a página que pretendemos
- o segundo especifica qual o servidor que estamos a consultar
- O terceiro indica que queremos que o servidor feche a ligação depois de nos responder.
Se substituirmos GET por HEAD no exemplo acima, o servidor enviar-nos-á apenas os cabeçalhos HTTP e não a página HTML.
O nosso cliente web será chamado da seguinte forma: java clientweb URL cmd, em que URL é o endereço URL pretendido e cmd é uma das duas palavras-chave GET ou HEAD, para indicar se pretendemos apenas os cabeçalhos (HEAD) ou também o conteúdo da página (GET). Vejamos um primeiro exemplo. Iniciamos o servidor IIS e, em seguida, o cliente web na mesma máquina:
dos>java clientweb http://localhost HEAD
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:23:37 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=HMFNCCMDECBJJBPPBHAOAJNP; path=/
Cache-control: private
A resposta
significa que a página solicitada foi movida (ou seja, o seu URL foi alterado). O novo URL é fornecido pelo cabeçalho Location:
Se utilizarmos GET em vez de HEAD na chamada do cliente Web:
dos>java clientweb http://localhost GET
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:33:36 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=IMFNCCMDAKPNNGMGMFIHENFE; path=/
Cache-control: private
<head><title>L'objet a changé d'emplacement</title></head>
<body><h1>L'objet a changé d'emplacement</h1>Cet objet peut être trouvé <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>
Obtemos o mesmo resultado que com HEAD, além do corpo da página HTML. O programa é o seguinte:
// imported packages
import java.io.*;
import java.net.*;
public class clientweb{
// requests a URL
// displays its contents on the screen
public static void main(String[] args){
// syntax
final String syntaxe="pg URI GET/HEAD";
// number of arguments
if(args.length != 2)
erreur(syntaxe,1);
// note the URI required
String URLString=args[0];
String commande=args[1].toUpperCase();
// URI validity check
URL url=null;
try{
url=new URL(URLString);
}catch (Exception ex){
// URI incorrect
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
}//catch
// order verification
if(! commande.equals("GET") && ! commande.equals("HEAD")){
// incorrect order
erreur("Le second paramètre doit être GET ou HEAD",3);
}
// extract useful information from URL
String path=url.getPath();
if(path.equals("")) path="/";
String query=url.getQuery();
if(query!=null) query="?"+query; else query="";
String host=url.getHost();
int port=url.getPort();
if(port==-1) port=url.getDefaultPort();
// we can work
Socket client=null; // the customer
BufferedReader IN=null; // the customer's reading flow
PrintWriter OUT=null; // the customer's writing flow
String réponse=null; // server response
try{
// connect to the server
client=new Socket(host,port);
// create customer input/output flows TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// request URL - send HTTP headers
OUT.println(commande + " " + path + query + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
OUT.println("Connection: close");
OUT.println();
// we read the answer
while((réponse=IN.readLine())!=null){
// the answer is processed
System.out.println(réponse);
}//while
// it's over
client.close();
} catch(Exception e){
// we handle the exception
erreur(e.getMessage(),4);
}//catch
}//hand
// error display
public static void erreur(String msg, int exitCode){
// error display
System.err.println(msg);
// stop with error
System.exit(exitCode);
}//error
}//class
A única novidade neste programa é a utilização da classe URL. O programa recebe um URL (Uniform Resource Locator) ou URI (Uniform Resource Identifier) no formato http://serveur:port/cheminPageHTML?param1=val1;param2=val2;.... A classe URL permite-nos decompor a cadeia de caracteres da URL nos seus vários componentes. Um objeto URL é construído a partir da cadeia de caracteres da URL recebida como parâmetro:
// vérification validité de l'URL
URL url=null;
try{
url=new URL(URLString);
}catch (Exception ex){
// URI incorrecte
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
}//catch
Se a cadeia de caracteres da URL recebida como parâmetro não for uma URL válida (falta de protocolo, servidor, etc.), é lançada uma exceção. Isto permite-nos verificar a validade do parâmetro recebido. Assim que o objeto URL é construído, temos acesso aos seus vários elementos. Assim, se o objeto URL do código anterior foi construído a partir da cadeia de caracteres
teremos:
url.getHost() = servidor
url.getPort() = porta ou -1 se a porta não for especificada
url.getPath() = HTMLPagePath ou uma string vazia se não houver caminho
url.getQuery() = param1=val1;param2=val2;... ou null se não houver consulta
uri.getProtocol() = http
8.4.6. O cliente Web lida com redirecionamentos
O cliente web anterior não lida com qualquer redirecionamento do URL que solicitou. O cliente a seguir lida com isso.
- Ele lê a primeira linha dos cabeçalhos HTTP enviados pelo servidor para verificar se contém a string "302 Object moved", o que indica um redirecionamento
- ele lê os cabeçalhos seguintes. Se houver um redirecionamento, procura a linha «Location: url», que fornece o novo URL da página solicitada, e anota esse URL.
- Exibe o resto da resposta do servidor. Se houver um redirecionamento, os passos 1 a 3 são repetidos com a nova URL. O programa não aceita mais do que um redirecionamento. Este limite é definido por uma constante que pode ser modificada.
Eis um exemplo:
Dos>java clientweb2 http://localhost GET
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 11:38:55 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=PDGNCCMDNCAOFDMPHCJNPBAI; path=/
Cache-control: private
<head><title>L'objet a chang d'emplacement</title></head>
<body><h1>L'objet a chang d'emplacement</h1>Cet objet peut tre trouv <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>
<--Redirection vers l'URL http://localhost:80/IISSamples/Default/welcome.htm-->
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Connection: close
Date: Mon, 13 May 2002 11:38:55 GMT
Content-Type: text/html
Accept-Ranges: bytes
Last-Modified: Mon, 16 Feb 1998 21:16:22 GMT
ETag: "0174e21203bbd1:978"
Content-Length: 4781
<html>
<head>
<title>Bienvenue dans le Serveur Web personnel</title>
</head>
....
</body>
</html>
O programa é o seguinte:
// imported packages
import java.io.*;
import java.net.*;
import java.util.regex.*;
public class clientweb2{
// requests a URL
// displays its contents on the screen
public static void main(String[] args){
// syntax
final String syntaxe="pg URL GET/HEAD";
// number of arguments
if(args.length != 2)
erreur(syntaxe,1);
// note the URI required
String URLString=args[0];
String commande=args[1].toUpperCase();
// URI validity check
URL url=null;
try{
url=new URL(URLString);
}catch (Exception ex){
// URI incorrect
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
}//catch
// order verification
if(! commande.equals("GET") && ! commande.equals("HEAD")){
// incorrect order
erreur("Le second paramètre doit être GET ou HEAD",3);
}
// we can work
Socket client=null; // the customer
BufferedReader IN=null; // the customer's reading flow
PrintWriter OUT=null; // the customer's writing flow
String réponse=null; // server response
final int nbRedirsMax=1; // no more than one redirection accepted
int nbRedirs=0; // number of redirects in progress
String premièreLigne; // 1st line of the answer
boolean redir=false; // indicates redirection or not
String locationString=""; // the URL string of a possible redirection
// regular expression to find a URL redirect
Pattern location=Pattern.compile("^Location: (.+?)$");
// error management
try{
// you may have several URL to request if there are redirections
while(nbRedirs<=nbRedirsMax){
// extract useful information from URL
String protocol=url.getProtocol();
String path=url.getPath();
if(path.equals("")) path="/";
String query=url.getQuery();
if(query!=null) query="?"+query; else query="";
String host=url.getHost();
int port=url.getPort();
if(port==-1) port=url.getDefaultPort();
// connect to the server
client=new Socket(host,port);
// create customer input/output flows TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// request URL - send HTTP headers
OUT.println(commande + " " + path + query + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
OUT.println("Connection: close");
OUT.println();
// read the first line of the answer
premièreLigne=IN.readLine();
// screen echo
System.out.println(premièreLigne);
// redirection?
if(premièreLigne.endsWith("302 Object moved")){
// there is a redirection
redir=true;
nbRedirs++;
}//if
// next HTTP headers until you find the empty line signalling the end of the headers
boolean locationFound=false;
while(!(réponse=IN.readLine()).equals("")){
// the answer is displayed
System.out.println(réponse);
// if there is a redirection, we search for the Location header
if(redir && ! locationFound){
// compare the line with the relational expression location
Matcher résultat=location.matcher(réponse);
if(résultat.find()){
// if found, note the URL of redirection
locationString=résultat.group(1);
// we note that we found
locationFound=true;
}//if
}//if
// next header
}//while
// following lines of the answer
System.out.println(réponse);
while((réponse=IN.readLine())!=null){
// the answer is displayed
System.out.println(réponse);
}//while
// close the connection
client.close();
// are we done?
if ( ! locationFound || nbRedirs>nbRedirsMax)
break;
// there is a redirection to be made - the new URL is built
URLString=protocol +"://"+host+":"+port+locationString;
url=new URL(URLString);
// follow-up
System.out.println("\n<--Redirection vers l'URL "+URLString+"-->\n");
}//while
} catch(Exception e){
// we handle the exception
erreur(e.getMessage(),4);
}//catch
}//hand
// error display
public static void erreur(String msg, int exitCode){
// error display
System.err.println(msg);
// stop with error
System.exit(exitCode);
}//error
}//class
8.4.7. Servidor de Cálculo de Impostos
Estamos a revisitar o exercício TAXES, que já foi abordado de várias formas. Vamos rever a versão mais recente:
Foi criada uma classe base chamada **impots**. Os seus atributos são três matrizes de números:
public class impots{
// data required for tax calculation
// come from an external source
protected double[] limites=null;
protected double[] coeffR=null;
protected double[] coeffN=null;
// empty builder
protected impots(){}
// manufacturer
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
A classe impots tem dois construtores:
- um construtor que recebe as três matrizes de dados necessárias para calcular o imposto
- um construtor sem parâmetros utilizável apenas por classes derivadas
A classe **impotsJDBC** foi derivada desta classe, permitindo que as três matrizes limites*, *coeffR* e coeffN* fossem preenchidas a partir do conteúdo de uma base de dados:
public class impotsJDBC extends impots{
// addition of a constructor for building
// limit tables, coeffr, coeffn from table
// database taxes
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS: DSN database name
// userIMPOTS, mdpIMPOTS: database login/password
Foi desenvolvida uma aplicação gráfica. A aplicação utilizava um objeto da classe impotsJDBC. A aplicação e este objeto estavam na mesma máquina. Propomos colocar o programa de teste e o objeto impotsJDBC em máquinas diferentes. Teremos uma aplicação cliente-servidor em que o objeto impotsJDBC remoto atuará como servidor. A nova classe chama-se TaxServer e deriva da classe impotsJDBC:
// imported packages
import java.net.*;
import java.io.*;
import java.sql.*;
public class ServeurImpots extends impotsJDBC {
// attributes
int portEcoute; // the ability to listen to customer requests
boolean actif; // server status
// manufacturer
public ServeurImpots(int portEcoute,String DSNimpots, String USERimpots, String MDPimpots)
throws IOException, SQLException, ClassNotFoundException {
// parent construction
super(DSNimpots, USERimpots, MDPimpots);
// we note the listening port
this.portEcoute=portEcoute;
// currently inactive
actif=false;
// creates and launches a thread for reading keyboard commands
// the server will be managed using these commands
Thread admin=new Thread(){
public void run(){
try{
admin();
}catch (Exception ignored){}
}
};
admin.start();
}//ServeurImpots
O único parâmetro novo no construtor é a porta utilizada para escutar os pedidos dos clientes. Os outros parâmetros são passados diretamente para a classe base impotsJDBC. O servidor de impostos é controlado por comandos introduzidos através do teclado. Por isso, criamos um thread para ler esses comandos. Haverá dois comandos possíveis: start para iniciar o serviço e stop para o encerrar definitivamente. O método admin que trata desses comandos é o seguinte:
public void admin() throws IOException{
// reads server administration commands typed from the keyboard
// in an endless loop
String commande=null;
BufferedReader IN=new BufferedReader(new InputStreamReader(System.in));
while(true){
// invite
System.out.print("Serveur d'impôts>");
// read command
commande=IN.readLine().trim().toLowerCase();
// order execution
if(commande.equals("start")){
// active?
if(actif){
//error
System.out.println("Le serveur est déjà actif");
// we continue
continue;
}//if
// create and launch the listening service
Thread ecoute=new Thread(){
public void run(){
ecoute();
}
};
ecoute.start();
}//if
else if(commande.equals("stop")){
// end of all execution threads
System.exit(0);
}//if
else {
// error
System.out.println("Commande incorrecte. Utilisez (start,stop)");
}//if
}//while
}//admin
Se o comando introduzido através do teclado for start, é lançada uma thread que escuta os pedidos do cliente. Se o comando introduzido for stop, todas as threads são paradas. A thread de escuta executa o método listen:
public void ecoute(){
// thread for listening to customer requests
// we create the listening service
ServerSocket ecoute=null;
try{
// create the service
ecoute=new ServerSocket(portEcoute);
// follow-up
System.out.println("Serveur d'impôts lancé sur le port " + portEcoute);
// service loop
Socket liaisonClient=null;
while (true){ // infinite loop
// waiting for a customer
liaisonClient=ecoute.accept();
// the service is provided by another task
new traiteClientImpots(liaisonClient,this).start();
// back to listening to requests
}// end while
}catch(Exception ex){
// we report the error
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
}//catch
}//listening thread
Aqui temos um servidor TCP clássico a escutar na porta portEcoute. Os pedidos do cliente são tratados pelo método run da thread traiteCientImpots, à qual são passados dois parâmetros na construção:
- o objeto Socket liaisonClient, que permitirá o acesso ao cliente
- o objeto impotsJDBC, que fornece acesso ao método this.calculate para o cálculo do imposto.
// -------------------------------------------------------
// provides service to a tax server client
class traiteClientImpots extends Thread{
private Socket liaisonClient; // customer liaison
private BufferedReader IN; // iNPUTS
private PrintWriter OUT; // output flow
private impotsJDBC objImpots; // object Tax
// manufacturer
public traiteClientImpots(Socket liaisonClient,impotsJDBC objImpots){
this.liaisonClient=liaisonClient;
this.objImpots=objImpots;
}//manufacturer
O método run processa os pedidos dos clientes. Trata-se de linhas de texto que podem assumir duas formas:
- casado(s/n) n.º de filhos salárioAnual
- endCalculation
O formulário 1 calcula um imposto, enquanto o formulário 2 encerra a ligação cliente-servidor.
// run method
public void run(){
// renders service to the customer
try{
// iNPUTS
IN=new BufferedReader(new InputStreamReader(liaisonClient.getInputStream()));
// output flow
OUT=new PrintWriter(liaisonClient.getOutputStream(),true);
// send a welcome message to the customer
OUT.println("Bienvenue sur le serveur d'impôts");
// loop read request/write response
String demande=null;
String[] champs=null; // elements of the request
String commande=null; // customer order: calculation or fincalculs
while ((demande=IN.readLine())!=null){
// demand is broken down into fields
champs=demande.trim().toLowerCase().split("\\s+");
// two successful applications: calcul and fincalculs
commande=champs[0];
if(! commande.equals("calcul") && ! commande.equals("fincalculs")){
// customer error
OUT.println("Commande incorrecte. Utilisez (calcul,fincalculs).");
// next order
continue;
}//if
if(commande.equals("calcul")) calculerImpôt(champs);
if(commande.equals("fincalculs")){
// good-bye message to customer
OUT.println("Au revoir...");
// freeing up resources
try{ OUT.close();IN.close();liaisonClient.close();}
catch(Exception ex){}
// end
return;
}//if
//following request
}//while
}catch (Exception e){
erreur("L'erreur suivante s'est produite ("+e+")",2);
}// fin try
}// end Run
O cálculo do imposto é realizado pelo método calculateTax, que recebe como parâmetro a matriz de campos da solicitação do cliente. A validade da solicitação é verificada e, se válida, o imposto é calculado e devolvido ao cliente.
// tax calculation
public void calculerImpôt(String[] champs){
// processing the application: calculation married nbEnfants salaireAnnuel
// broken down into fields in the fields table
String marié=null;
int nbEnfants=0;
int salaireAnnuel=0;
// validity of arguments
try{
// at least 4 fields are required
if(champs.length!=4) throw new Exception();
// married
marié=champs[1];
if (! marié.equals("o") && ! marié.equals("n")) throw new Exception();
// children
nbEnfants=Integer.parseInt(champs[2]);
// salary
salaireAnnuel=Integer.parseInt(champs[3]);
}catch (Exception ignored){
// format error
OUT.println(" syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel");
// finish
return;
}//if
// tax can be calculated
long impot=objImpots.calculer(marié.equals("o"),nbEnfants,salaireAnnuel);
// we send the response to the customer
OUT.println(""+impot);
}//calculate
Um programa de teste poderia ter o seguinte aspecto:
// call: serveurImpots port dsnImpots userImpots mdpImpots
import java.io.*;
public class testServeurImpots{
public static final String syntaxe="Syntaxe : pg port dsnImpots userImpots mdpImpots";
// main program
public static void main (String[] args){
// you need 4 arguments
if(args.length != 4)
erreur(syntaxe,1);
// port must be integer >0
int port=0;
boolean erreurPort=false;
Exception E=null;
try{
port=Integer.parseInt(args[0]);
}catch(Exception e){
E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0;
if(erreurPort)
erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);
// we create the tax server
try{
new ServeurImpots(port,args[1],args[2],args[3]);
}catch(Exception ex){
//error
System.out.println("L'erreur suivante s'est produite : "+ex.getMessage());
}//catch
}//Main
// error display
public static void erreur(String msg, int exitCode){
// error display
System.err.println(msg);
// stop with error
System.exit(exitCode);
}//error
}// end class
Passamos os dados necessários para construir um objeto TaxServer para o programa de teste, e a partir daí ele cria esse objeto.
Vamos tentar executá-lo pela primeira vez:
dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop
O comando
cria um objeto TaxServer que ainda não está à escuta de pedidos de clientes. É o comando start digitado no teclado que inicia este processo de escuta. O comando stop desliga o servidor. Vamos agora utilizar um cliente. Utilizaremos o cliente genérico criado anteriormente. O servidor está em execução:
dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
O cliente genérico é iniciado noutra janela do DOS:
Podemos ver que o cliente recebeu com sucesso a mensagem de boas-vindas do servidor. Enviamos outros comandos:
x
<-- Commande incorrecte. Utilisez (calcul,fincalculs).
calcul
<-- syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel
calcul o 2 200000
<-- 22506
calcul n 2 200000
<-- 33388
fincalculs
<-- Au revoir...
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Voltamos à janela do servidor para o parar:
dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop
8.5. Exercícios
8.5.1. Exercício 1 - Gráfico genérico de cliente TCP
8.5.1.1. Visão geral da aplicação
Propomos criar um programa capaz de comunicar através da Internet com os principais serviços TCP. Chamaremos a isso um cliente TCP genérico. Depois de compreender esta aplicação, verá que todos os clientes TCP são semelhantes. A janela do programa tem o seguinte aspeto:

Os significados dos vários controlos são os seguintes:
N.º | Nome | Tipo | função |
1 | TxtRemoteHost | JTextField | nome da máquina que fornece o serviço pretendido |
2 | TxtPort | JTextField | porta do serviço solicitado |
3 | TxtSend | JTextField | texto da mensagem a ser enviada ao servidor pelo cliente |
4 | OptRCLF OptLF | JCheckBox | botões utilizados para definir como as linhas terminam na caixa de diálogo cliente/servidor RCLF: retorno de carro (#13) + avanço de linha (#10) LF: avanço de linha (#10) |
5 | LstSuivi | JList | exibe mensagens sobre o estado da comunicação entre o cliente e o servidor |
6 | LstDialogue | JList | exibe mensagens trocadas pelo cliente (->) e pelo servidor (<-) |
7 | CmdCancel | JButton | oculto - localizado abaixo da lista de diálogo - Aparece quando a ligação está em curso e permite-lhe encerrá-la se o servidor não responder |
As opções de menu disponíveis são as seguintes:
opção | subopções | função |
Conexão | Ligar | liga o cliente ao servidor |
Desligar | Encerra a ligação | |
Sair | Sai do programa | |
Mensagens | Enviar | Envia a mensagem do controlo TxtSend para o servidor |
ClearTracking | Limpa a lista LstSuivi | |
ClearDialogue | Limpa a lista LstDialogue | |
Autor | Exibe uma caixa de direitos de autor |
8.5.1.2. COMO FUNCIONA A APLICAÇÃO
Quando a janela principal da aplicação é carregada, ocorrem as seguintes ações:
- a folha é centralizada no ecrã
- Apenas as opções de menu «Iniciar sessão/Terminar sessão» e «Autor» estão ativas
- O botão Cancelar está oculto
- As listas LstSuivi e LstDialogue estão vazias
Esta opção só está disponível quando os campos «Host remoto» e «N.º da porta» não estão vazios e não existe nenhuma ligação ativa no momento. Ao clicar nesta opção, são realizadas as seguintes operações:
- A porta é validada: deve ser um número inteiro maior que 0
- É iniciado um segmento de execução para estabelecer a ligação ao servidor
- O botão Cancelar aparece para permitir que o utilizador interrompa a ligação em curso
- Todas as opções do menu são desativadas, exceto Sair e Autor
A ligação pode terminar de várias formas:
- O utilizador clicou no botão Cancelar: o segmento de conexão é interrompido e o menu é restaurado ao seu estado inicial. O registo indica que o utilizador encerrou a conexão.
- A ligação termina com um erro: fazemos o mesmo que antes e, além disso, no registo, indicamos a causa do erro.
- A ligação é concluída com sucesso: o botão Cancelar é removido, o registo indica que a ligação foi estabelecida, o menu Reiniciar Registo é ativado, o menu Ligar é desativado e o menu Desligar é ativado
Esta opção só está disponível quando existe uma ligação ao servidor. Quando ativada, encerra a ligação ao servidor e repõe o menu no seu estado inicial. O registo indica que a ligação foi encerrada pelo cliente.
Esta opção encerra qualquer ligação ativa ao servidor e sai da aplicação.
Esta opção só está disponível se as seguintes condições forem cumpridas:
-
foi estabelecida uma ligação ao servidor
-
existe uma mensagem para enviar
Se estas condições forem cumpridas, o texto no campo TxtSend (3) é enviado para o servidor, terminado pela sequência RCLF se a opção RCLF tiver sido marcada, ou pela sequência LF caso contrário. Quaisquer erros de transmissão são relatados na lista de acompanhamento.
Limpa as listas LstSuivi e LstDialogue, respetivamente. Estas opções são desativadas quando as listas correspondentes estão vazias.
Este botão, localizado na parte inferior do formulário, aparece apenas quando o cliente está a tentar ligar-se ao servidor. Esta ligação pode falhar porque o servidor não está a responder ou está a responder incorretamente. O botão Cancelar dá então ao utilizador a opção de abortar o pedido de ligação.
A lista LstSuivi (5) acompanha a ligação. Indica momentos-chave da ligação:
-
o seu estabelecimento pelo cliente
-
o seu encerramento pelo servidor ou pelo cliente
-
quaisquer erros que possam ocorrer enquanto a ligação estiver ativa
A lista LstDialogue (6) acompanha o diálogo estabelecido entre o cliente e o servidor. Um thread monitoriza em segundo plano o que acontece na socket de comunicação do cliente e apresenta-o na lista 6.
Este menu abre uma janela chamada Copyright:

Os erros de ligação são indicados na lista de acompanhamento 6, enquanto os erros relacionados com o diálogo cliente/servidor são indicados na lista de diálogo 7. Em caso de erro de ligação, o diálogo cliente/servidor é encerrado e o formulário é reposto no seu estado inicial, pronto para uma nova ligação.
8.5.1.3. TAREAS
Implemente o trabalho descrito acima em duas formas:
- aplicação autónoma
- applet
8.5.2. Exercício 2 - Um servidor de recursos
8.5.2.1. INTRODUÇÃO
Uma instituição possui vários servidores informáticos potentes acessíveis através da Internet. Qualquer máquina que pretenda utilizar estes serviços informáticos envia um ficheiro de dados para a porta 756 de um dos servidores. Este ficheiro contém várias informações: nome de utilizador, palavra-passe, comandos que especificam o tipo de cálculo pretendido e os dados sobre os quais o cálculo deve ser realizado. Se o ficheiro de dados for válido, o servidor informático selecionado processa-o e devolve os resultados ao cliente sob a forma de um ficheiro de texto.
As vantagens desta configuração são numerosas:
- qualquer tipo de cliente (PC, Mac, Unix, etc.) pode utilizar este serviço
- o cliente pode estar em qualquer lugar na Internet
- os recursos informáticos são otimizados: são necessárias apenas algumas máquinas potentes. Assim, uma pequena organização sem recursos informáticos pode utilizar este serviço mediante o pagamento de uma taxa calculada com base no tempo de computação utilizado.
Apesar da potência das máquinas, um cálculo pode, por vezes, demorar várias horas: o servidor fica então indisponível para outros clientes. Isto coloca o problema de um cliente ter de encontrar um servidor de computação disponível. Para resolver isto, é utilizado um «gestor de recursos de computação», doravante referido como servidor GRC. Este serviço é executado numa única máquina e opera na porta 864 no modo TCP. Um cliente que pretenda aceder a um servidor de computação contacta este serviço. O servidor GRC, que mantém uma lista completa de servidores de computação, responde enviando ao cliente o nome de um servidor atualmente inativo. O cliente envia então simplesmente os seus dados para o servidor designado.
Propomos desenvolver o servidor GRC.
8.5.2.2. A INTERFACE VISUAL
A interface visual será a seguinte:

A interface apresenta duas listas de servidores:
- à esquerda, a lista de servidores inativos, que estão, portanto, disponíveis para cálculos
- à direita, a lista de servidores ocupados pelos cálculos de um cliente.
A estrutura do menu é a seguinte:
Menu Principal | Menu Secundário | Função |
Serviço | Iniciar | Iniciar o serviço TCP na porta 864 |
Parar | Parar o serviço | |
Sair | Sair da aplicação | |
Autor | Informações sobre direitos de autor |
A estrutura dos controlos no formulário é a seguinte:
Nome | Tipo | Função |
listLibres | JList | Lista de servidores disponíveis |
listBusy | JList | Lista de servidores ocupados |
8.5.2.3. COMO FUNCIONA A APLICAÇÃO
Quando a aplicação é carregada, a lista listLibres é preenchida com a lista de nomes dos servidores de computação geridos pelo GRC. Estes são definidos num ficheiro Servers passado como parâmetro. Este ficheiro contém uma lista de nomes de servidores, um por linha, e é, portanto, utilizado para preencher a lista listLibres. O menu Iniciar está ativado; o menu Parar está desativado.
Esta opção
- inicia o serviço de escuta na porta 864 da máquina
- desativa o menu Iniciar
- ativa o menu Parar
Esta opção interrompe o serviço:
- a lista de servidores ocupados é apagada
- a lista de servidores livres é preenchida com o conteúdo do ficheiro Servidores
- o menu Iniciar é ativado
- o menu «Parar» é desativado
A aplicação é encerrada.
O diálogo cliente/servidor é realizado através da troca de linhas de texto terminadas pela sequência RCLF. O servidor GRC reconhece dois comandos: getserver e endservice. Detalhamos a função destes dois comandos:
- 1-getserver
O cliente pergunta se existe um servidor de computação disponível para ele.
O servidor GRC seleciona então o primeiro servidor encontrado na sua lista de servidores disponíveis e devolve o seu nome ao cliente no seguinte formato:
Além disso, transfere o servidor atribuído ao cliente para a lista de servidores ocupados no seguinte formato:
conforme ilustrado no exemplo a seguir, em que o servidor *calcul1.istia.univ-angers.fr* está ocupado a servir o cliente com o endereço IP *193.52.43.5*:

Um cliente não pode enviar um comando **getserver** se já lhe tiver sido atribuído um servidor de computação. Por conseguinte, antes de responder ao cliente, o servidor GRC verifica se o endereço IP do cliente já não se encontra entre os registados na lista de servidores ocupados. Se for esse o caso, o servidor GRC responde:
Por fim, há o caso em que nenhum servidor de computação está disponível: a lista de servidores livres está vazia. Neste caso, o servidor GRC responde:
Em todos os casos, após responder ao cliente, o servidor GRC encerra a ligação com o cliente para poder atender outros clientes.
- 2-finservice
O cliente indica que já não necessita do servidor de computação que estava a utilizar.
O servidor GRC verifica primeiro se o cliente é, de facto, aquele a quem prestava serviço. Para tal, verifica se o endereço IP do cliente se encontra entre os registados na lista de servidores ocupados. Se não for esse o caso, o servidor GRC responde:
Se o cliente for reconhecido, o servidor GRC responde:
e move o servidor de computação atribuído a esse cliente para a lista de servidores livres. Voltando ao exemplo anterior, se o cliente enviar o comando **de fim de serviço**, o ecrã do servidor GRC fica assim:

Após enviar a resposta, independentemente do seu conteúdo, o servidor GRC encerra a ligação.
8.5.2.4. TAREFA
Escreva a aplicação como um programa autónomo que possa ser testado, por exemplo, com um cliente telnet ou com o cliente TCP genérico do exercício anterior.
8.5.3. Exercício 3 - Um cliente SMTP
8.5.3.1. INTRODUÇÃO
Aqui, queremos criar um cliente para o serviço SMTP (Simple Mail Transfer Protocol), que permite enviar e-mails. No Unix ou no Windows, o programa telnet é um cliente que funciona com o protocolo TCP. Pode «comunicar» com qualquer serviço TCP que aceite comandos baseados em texto que terminem com a sequência RCLF, ou seja, os caracteres ASCII 13 e 10. Aqui está um exemplo de uma conversa com o serviço SMTP para o envio de e-mail:
$ telnet istia.univ-angers.fr 25 // appel du service smtp
// resposta do servidor SMTP
Trying 193.52.43.2...
Connected to istia.univ-angers.fr.
Escape character is '^]'.
220-Istia.Istia.Univ-Angers.fr Sendmail 8.6.10/8.6.9 ready at Tue, 16 Jan 1996 07:53:12 +0100
220 ESMTP spoken here
// comentários --------------
O programa telnet pode ligar-se a qualquer serviço utilizando a sintaxe
***telnet*** **máquina\_serviço porta\_serviço**
As trocas cliente/servidor utilizam linhas de texto terminadas pela sequência RCLF.
As respostas do serviço SMTP têm o seguinte formato:
**Número da mensagem ou**
**Número da mensagem**
O servidor SMTP pode enviar várias linhas de resposta. A última linha da resposta é indicada por um número seguido de um espaço, enquanto que nas linhas anteriores da resposta, o número é seguido de um hífen -.
Um número maior ou igual a 500 indica uma mensagem de erro.
// Fim dos comentários
// resposta do servidor SMTP
214-Commands:
214- HELO EHLO MAIL RCPT DATA
214- RSET NOOP QUIT HELP VRFY
214- EXPN VERB
214-For more info use "HELP <topic>".
214-To report bugs in the implementation send email to
214- sendmail@CS.Berkeley.EDU.
214-For local information send email to Postmaster at your site.
214 End of HELP info
mail from: serge.tahe@istia.univ-angers.fr // nouvelle commande émise au clavier
// comentários ---------
O comando mail tem a seguinte sintaxe:
**mail de: endereço de e-mail do remetente da mensagem**
// fim dos comentários
// resposta do servidor SMTP
// comentários
O servidor SMTP não verifica a validade do endereço do remetente: aceita-o tal como fornecido
// fim dos comentários
rcpt to: user1@istia.univ-angers.fr // nouvelle commande émise au clavier
// comentários ---------
O comando rcpt tem a seguinte sintaxe:
**rcpt para: endereço de e-mail do destinatário da mensagem**
Se o endereço de e-mail for um endereço na máquina onde o servidor SMTP está a ser executado, verifica-se se este existe; caso contrário, não é realizada qualquer verificação. Se a verificação foi realizada e foi detetado um erro, este será reportado com um número >= 500.
Pode utilizar o comando «rcpt» quantas vezes quiser: isto permite-lhe enviar uma mensagem a várias pessoas.
// fim dos comentários
// Resposta do servidor SMTP
// comentários ---------
O comando data tem a seguinte sintaxe:
**data**
**linha1**
**linha2**
**...**
**.**
Segue-se as linhas de texto que compõem a mensagem, que devem terminar com uma linha contendo apenas o caractere «ponto».
A mensagem é então enviada ao destinatário especificado pelo comando rcpt.
// fim dos comentários
// resposta do servidor SMTP
// texto da mensagem digitado no teclado
subject: essai smtp
essai smtp a partir de telnet
.
// comentários
Nas linhas de texto do comando data, pode incluir uma linha subject: para especificar o assunto do e-mail. Esta linha deve ser seguida por uma linha em branco.
// resposta do servidor SMTP
// comentários
**O comando quit encerra a ligação ao serviço** ***SMTP***
// fim dos comentários
// resposta do servidor SMTP
8.5.3.2. A INTERFACE VISUAL
Propomos a criação de um programa com a seguinte interface visual:

Os controlos têm as seguintes funções:
Número | Tipo | Função |
1 | JTextField | Uma lista de endereços de e-mail separados por vírgulas |
2 | JTextField | Texto do assunto da mensagem |
3 | JTextField | Lista de endereços de e-mail separados por vírgulas |
4 | JTextField | Uma lista de endereços de e-mail separados por vírgulas |
5 | JTextArea | Texto da mensagem |
6 | JList | Lista de acompanhamento |
7 | JList | lista de diálogos |
8 | JButton | Botão «Cancelar» não visível, que aparece quando o cliente solicita uma ligação ao servidor SMTP. Permite ao utilizador cancelar este pedido se o servidor não responder. |
8.5.3.3. MENUS
A estrutura do menu da aplicação é a seguinte:
Menu Principal | Menu Secundário | Função |
Correio | ||
Enviar | Enviar a mensagem a partir do controlo 5 | |
Sair | Sair da aplicação | |
Opções | ||
Ocultar rastreamento | Ocultar controlo 6 | |
Limpar lista de observação | Limpar a lista de observação 6 | |
Ocultar caixa de diálogo | Ocultar a lista de diálogo 7 | |
Limpar caixa de diálogo | Limpar a lista de diálogos 7 | |
Configurar | Permite ao utilizador especificar - o endereço do servidor SMTP utilizado pelo programa - o seu endereço de e-mail | |
Guardar... | Guarda a configuração anterior num ficheiro .ini | |
Autor | Informações sobre direitos de autor |
8.5.3.4. COMO FUNCIONA A APLICAÇÃO
Este menu apresenta a seguinte janela:

Ambos os campos devem ser preenchidos para que o botão OK fique ativo. Ambas as informações devem ser armazenadas em variáveis globais para que fiquem disponíveis para outros módulos.
Esta opção só está disponível se as seguintes condições forem cumpridas:
- a configuração tiver sido concluída
- existe uma mensagem para enviar
- existe um assunto
- existe pelo menos um destinatário nos campos 1, 3 e 4
Se estas condições forem cumpridas, a sequência de eventos é a seguinte:
- o formulário é colocado num estado em que todas as ações que possam interferir com o diálogo cliente/servidor são desativadas
- É estabelecida uma ligação na porta 25 do servidor especificado na configuração
- O cliente comunica então com o servidor SMTP de acordo com o protocolo descrito acima
- O campo «Mail From» utiliza o endereço de e-mail do remetente especificado na configuração
- O comando «rcpt to:» é utilizado para cada um dos endereços de e-mail encontrados nos campos 1, 3 e 4
- Nas linhas enviadas após o comando de dados, aparecerá o seguinte texto:
- uma linha Subject:: texto do assunto do controlo 2
- uma linha Cc:: endereços do teste 3
- uma linha "Cc": endereços do teste 4
- o texto da mensagem para o controlo 5
- o ponto final
Este botão, localizado na parte inferior do formulário, aparece apenas quando o cliente está a tentar ligar-se ao servidor SMTP. Esta ligação pode falhar porque o servidor SMTP não está a responder ou está a responder incorretamente. O botão Cancelar permite então ao utilizador abortar o pedido de ligação.
A lista (6) acompanha a ligação. Indica momentos-chave da ligação:
- o seu estabelecimento pelo cliente
- o seu encerramento pelo servidor ou pelo cliente
- todos os erros de ligação
A lista (7) acompanha o diálogo SMTP estabelecido entre o cliente e o servidor.
Estas duas listas estão associadas a opções de menu:
Ocultar Rastreamento | Oculta a lista de rastreamento 6 e o rótulo acima dela. Se a altura ocupada por estes dois controlos for H, todos os controlos localizados abaixo deles são movidos para cima numa altura de H, e o tamanho total do formulário é reduzido em H. Além disso, Ocultar Rastreamento oculta a opção Limpar Rastreamento abaixo. |
Limpar Rastreamento | Limpa a lista de rastreamento 6 |
Ocultar Diálogo | Oculta a lista de diálogo 7, o rótulo acima dela e a opção de menu Limpar Diálogo abaixo. Tal como com Ocultar Rastreamento, a posição dos controlos localizados abaixo (talvez o botão Cancelar) é recalculada e o tamanho da janela é reduzido. |
Limpar Diálogo | Limpa a lista de diálogos 7 |
Este menu abre uma janela chamada «Direitos de autor»:

Os erros de ligação são apresentados na lista de acompanhamento 6, enquanto os relacionados com o diálogo cliente/servidor são apresentados na lista de diálogo 7. Quando ocorre um erro, o utilizador é notificado através de uma caixa de erro e a lista que contém a causa do erro é apresentada, caso estivesse anteriormente oculta. Além disso, o diálogo cliente/servidor é fechado e o formulário é restaurado ao seu estado inicial.
8.5.3.5. GESTÃO DE UM ARQUIVO DE CONFIGURAÇÃO
É desejável que o utilizador não tenha de reconfigurar o software sempre que o utilizar. Para tal, se a opção Opções/Guardar configuração ao sair estiver marcada, ao fechar o programa são guardadas as duas informações definidas através da opção Opções/Configurar, bem como o estado das duas listas de acompanhamento, num ficheiro sendmail.ini localizado no mesmo diretório que o ficheiro .exe do programa. Este ficheiro tem o seguinte formato:
SmtpServer=shiva.istia.univ-angers.fr
ReplyAddress=serge.tahe@istia.univ-angers.fr
Suivi=0
Dialogue=1
As linhas SmtpServer e ReplyAddress contêm as duas informações introduzidas através do menu Opções/Configurar. As linhas Tracking e Dialogue indicam o estado das listas de acompanhamento e de diálogo: 1 (presente), 0 (ausente).
Quando o programa é carregado, o ficheiro sendmail.ini é lido, caso exista, e o formulário é configurado em conformidade. Se o ficheiro sendmail.ini não existir, o programa age como se existisse:
Se o ficheiro sendmail.ini existir mas estiver incompleto (faltando linhas), a linha em falta é substituída pela linha correspondente acima. Assim, se a linha Suivi=... estiver em falta, tratamo-la como se tivéssemos Suivi=1.
Todas as linhas que não correspondem ao padrão:
são ignoradas, assim como aquelas em que a palavra-chave é inválida. A palavra-chave pode estar em maiúsculas ou minúsculas: isso não importa.
No menu Opções/Configurar, são apresentados os valores atuais de SmtpServer e ReplyAddress. O utilizador pode então modificá-los, se desejar.
8.5.3.6. TAREAS A REALIZAR
Conclua a tarefa descrita acima. Recomenda-se tratar da gestão do ficheiro de configuração em último lugar.
8.5.4. Exercício 4 - Cliente POPPASS
8.5.4.1. Introdução
Propomos criar um cliente TCP capaz de comunicar com o servidor POPPASS a funcionar na porta 106. Este serviço permite-lhe alterar a sua palavra-passe numa máquina UNIX. O protocolo de comunicação Cliente/Servidor é o seguinte:
1 - A comunicação ocorre através da troca de mensagens terminadas pela sequência RCLF
2 - O cliente envia comandos ao servidor
- O servidor responde com mensagens que começam com um número de 3 dígitos: XXX. Se XXX=200, o comando foi executado com sucesso; caso contrário, ocorreu um erro.
3 - A sequência de trocas é a seguinte:
- O servidor responde com uma mensagem de boas-vindas
- O servidor responde solicitando a palavra-passe se o login for aceite; caso contrário, devolve um erro
- O servidor responde solicitando a nova palavra-passe se a palavra-passe for aceite; caso contrário, devolve um erro
- O servidor responde confirmando que a nova palavra-passe foi aceite; caso contrário, devolve um erro
- O servidor envia uma mensagem de fim e encerra a ligação
8.5.4.2. O formulário do cliente

O significado dos vários controlos é o seguinte:
N.º | nome | tipo | função |
1 | txtRemoteHost | JTextField | nome do servidor |
2 | txtLogin | JTextField | login do utilizador |
3 | txtPassword | JTextField | Senha do utilizador |
4 | txtNovaSenha | JTextField | Nova palavra-passe do utilizador |
5 | txtConfirmação | JTextField | Confirmação da nova palavra-passe |
6 | lstTracking | JList | Mensagens de rastreamento de conexão |
7 | lstDialogue | JList | Mensagens de diálogo cliente/servidor |
10 | cmdCancel | JButton | não mostrado - Botão que aparece quando a ligação ao servidor está em curso. Permite interrompê-la. |
8.5.4.3. Menus
Título | Nome do controlo | Função |
Conexão | loginmenu | |
Ligar | mnuconnect | inicia a ligação ao servidor |
Sair | mnuQuitter | encerra a aplicação |
Mensagens | mnuMessages | |
Limpar histórico | mnuClearTracking | limpa a lista lstSuivi |
LimparDiálogo | mnuClearDialog | limpa a lista lstDialogue |
Autor | mnuAuthor | exibe a caixa de direitos de autor |
8.5.4.4. Como funciona a aplicação
Quando a folha principal da aplicação é carregada, ocorrem as seguintes ações:
- a folha é centralizada no ecrã
- apenas as opções de menu Login/Logout e Autor estão ativas
- o botão Cancelar fica oculto
- as listas LstSuivi e LstDialogue estão vazias
Esta opção só está disponível quando os campos 1 a 5 tiverem sido preenchidos. Ao clicar nesta opção, são desencadeadas as seguintes ações:
- É iniciado um processo para estabelecer a ligação ao servidor
- o botão Cancelar aparece para permitir que o utilizador interrompa a ligação em curso
- todas as opções do menu são desativadas, exceto Sair e Autor
A sequência de eventos é então a seguinte:
- O utilizador clicou no botão Cancelar: o segmento de conexão é interrompido e o menu é restaurado ao seu estado inicial. O registo indica que o utilizador encerrou a conexão.
- O pedido de ligação é aceite pelo servidor. Iniciamos então o diálogo com o servidor para alterar a palavra-passe. As trocas neste diálogo são registadas na lista LstDialogue. Assim que o diálogo estiver concluído, a ligação com o servidor é encerrada e o menu do formulário é restaurado ao seu estado inicial.
- Enquanto o diálogo estiver ativo, o botão Cancelar permanece visível para permitir que o utilizador feche a ligação, se assim o desejar.
- Se ocorrer algum erro durante a comunicação, a ligação é encerrada e a causa do erro é apresentada na lista de acompanhamento LstSuivi.
Esta opção encerra todas as ligações ativas com o servidor e fecha a aplicação.
Limpa as listas LstSuivi e LstDialogue, respetivamente. Estas opções estão desativadas quando as listas correspondentes estão vazias.
Este botão, localizado na parte inferior do formulário, aparece apenas quando o cliente está a ligar-se ou está ligado ao servidor. O botão Cancelar permite ao utilizador terminar a comunicação com o servidor.
A lista LstSuivi (5) acompanha a ligação. Indica momentos-chave da ligação:
-
o seu estabelecimento pelo cliente
-
o seu encerramento pelo servidor ou pelo cliente
-
quaisquer erros que possam ocorrer enquanto a ligação estiver ativa
A lista LstDialogue (6) acompanha o diálogo entre o cliente e o servidor.
Este menu abre uma janela chamada «Janela de direitos de autor»:

Os erros de comunicação são relatados na lista de rastreamento 6, enquanto os relacionados com o diálogo cliente/servidor são relatados na lista de diálogo 7. Em caso de erro de ligação, o diálogo cliente/servidor é encerrado e o formulário é reposto no seu estado inicial, pronto para uma nova ligação.
8.5.4.5. TAREFA
Implemente o trabalho descrito acima como uma aplicação autónoma e, em seguida, como um applet.