Skip to content

9. Programação TCP-IP

9.1. Noções gerais

9.1.1. Os protocolos da Internet

Apresentamos aqui uma introdução aos protocolos de comunicação da Internet, também chamados de conjunto de protocolos TCP/IP (Transfer Control Protocol / Internet Protocol), em referência aos dois principais protocolos. É recomendável que o leitor tenha uma compreensão geral do funcionamento das redes e, em especial, dos protocolos TCP/IP antes de abordar a construção de aplicativos distribuídos.

O texto a seguir é uma tradução parcial de um texto encontrado no documento “Lan Workplace for Dos - Administrator's Guide” da NOVELL, documento do início dos anos 90.

-----------------------------------

O conceito geral de criar uma rede de computadores heterogêneos tem origem em pesquisas realizadas pela DARPA (Agência de Projetos de Pesquisa 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 se comuniquem entre si. Esses protocolos foram testados em uma rede chamada ARPAnet, que posteriormente se tornou a rede INTERNET. Os protocolos TCP/IP definem formatos e regras de transmissão e recepção independentes da organização das redes e dos equipamentos utilizados.

A rede projetada pelo DARPA e gerenciada pelos protocolos TCP/IP é uma rede de comutação de pacotes. Essa rede transmite as informações pela rede em pequenos pedaços chamados pacotes. Assim, se um computador transmitir um arquivo grande, este será dividido em pequenos pedaços que serão enviados pela rede para serem recompostos no destino. O TCP/IP define o formato desses pacotes, a saber:

  • origem do pacote
  • destino
  • comprimento
  • tipo

9.1.2. O modelo OSI

Os protocolos TCP/IP seguem, em linhas gerais, o modelo de rede aberta denominado OSI (Modelo de Referência para Interconexão de Sistemas Abertos), definido pela ISO (Organização Internacional de Padronização). Esse modelo descreve uma rede ideal na qual a comunicação entre máquinas pode ser representada por um modelo de sete camadas:

Cada camada recebe serviços da camada inferior e oferece os seus próprios serviços à camada superior. Suponhamos que duas aplicações localizadas em máquinas A e B diferentes desejam se comunicar: elas o fazem no nível da camada Application. Elas não precisam conhecer todos os detalhes do funcionamento da rede: cada aplicação entrega a informação que deseja transmitir à camada inferior: a camada Présentation. A aplicação precisa, portanto, conhecer apenas as regras de interface com a camada Présentation.

Uma vez que a informação está na camada Présentation, ela é encaminhada, de acordo com outras regras, para a camada Session e assim por diante, até que a informação chegue ao suporte físico e seja transmitida fisicamente para a máquina de destino. Lá, ela passará pelo processo inverso ao que ocorreu na máquina remetente.

Em cada camada, o processo remetente encarregado de enviar a informação a transmite a um processo receptor na outra máquina, pertencente à mesma camada que ele. Isso ocorre de acordo com certas regras, conhecidas como protocolo da camada. Assim, temos o seguinte esquema final de comunicação:

A função das diferentes camadas é a seguinte:

Physique
Assegura a transmissão de bits por meio de um suporte físico. Nesta camada, encontram-se equipamentos terminais de processamento de dados (E.T.T.D), tais como terminais ou computadores, bem como equipamentos de terminação de circuitos de dados (E.T.C.D), tais como moduladores/demoduladores, multiplexadores e concentradores. Os pontos de interesse neste 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).
Liaison de données
Oculta as características físicas da camada Física. Detecta e corrige erros de transmissão.
Réseau
Gerencia o caminho que as informações enviadas pela rede devem seguir. Isso é chamado de routage: determinar a rota que uma informação deve seguir para chegar ao seu destinatário.
Transport
Permite a comunicação entre duas aplicações, enquanto as camadas anteriores permitiam apenas a comunicação entre máquinas. Um serviço fornecido por essa camada pode ser o multiplexamento: a camada de transporte poderá utilizar uma mesma conexão de rede (de máquina para máquina) para transmitir informações pertencentes a várias aplicações.
Session
Nesta camada, encontramos serviços que permitem que uma aplicação abra e mantenha uma sessão de trabalho em uma máquina remota.
Présentation
Ela visa uniformizar a representação dos dados nas diferentes máquinas. Assim, os dados provenientes de uma máquina A serão “formatados” pela camada Présentation da máquina A, de acordo com um formato padrão, antes de serem enviados pela rede. Ao chegarem à camada Présentation da máquina destinatária B, que as reconhecerá graças ao seu formato padrão, elas serão formatadas de outra maneira para que o aplicativo da máquina B as reconheça.
Application
Nesse nível, encontram-se as aplicações geralmente próximas ao usuário, como o e-mail ou a transferência de arquivos.

9.1.3. O modelo TCP/IP

O modelo OSI é um modelo ideal que ainda não foi concretizado. O conjunto de protocolos TCP/IP se aproxima dele da seguinte forma:

Camada Física

Em redes locais, geralmente se utiliza a tecnologia Ethernet ou Token-Ring. Aqui, apresentamos apenas a tecnologia Ethernet.

Ethernet

Esse é o nome dado a uma tecnologia de redes locais com comutação de pacotes inventada pela Xerox no início da década de 1970 e padronizada pela Xerox, Intel e Digital Equipment em 1978. A rede é constituída fisicamente por um cabo coaxial com cerca de 1,27 cm de diâmetro e comprimento máximo de 500 m. Ela pode ser estendida por meio de répéteurs, sendo que duas máquinas não podem estar separadas por mais de dois repetidores. O cabo é passivo: todos os elementos ativos estão nos equipamentos conectados ao cabo. Cada equipamento é conectado ao cabo por meio de uma placa de acesso à rede que inclui:

  • um transmissor (transceiver) que detecta a presença de sinais no cabo e converte os sinais analógicos em sinais digitais e vice-versa.
  • um acoplador que recebe os sinais digitais do transmissor 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 conectadas ao mesmo cabo
  • Rede de difusão — Um equipamento que transmite envia informações pelo cabo com o endereço do equipamento destinatário. Todos os equipamentos conectados recebem essas informações, e apenas aquele a quem elas se destinam as retém.
  • O método de acesso é o seguinte: o transmissor que deseja transmitir escuta o cabo — ele detecta, então, a presença ou ausência de uma onda portadora, cuja presença significaria que uma transmissão está em andamento. Essa é a técnica CSMA (Carrier Sense Multiple Access). Na ausência de portadora, um transmissor pode decidir transmitir por sua vez. Vários transmissores podem tomar essa decisão. Os sinais emitidos se misturam: diz-se que ocorre uma colisão. O transmissor detecta essa situação: ao mesmo tempo em que transmite pelo cabo, ele escuta o que realmente está passando por ele. Se detectar que a informação que transita pelo cabo não é a que ele transmitiu, ele deduz que houve colisão e interrompe a transmissão. Os outros transmissores que estavam transmitindo farão o mesmo. Cada um retomará sua transmissão após um intervalo aleatório, que depende de cada transmissor. Essa técnica é chamada de CD (Detecção de Colisão). O método de acesso é, portanto, denominado CSMA/CD.
  • Um endereçamento de 48 bits. Cada máquina possui um endereço, denominado aqui endereço físico, que está gravado na placa que a conecta ao cabo. Esse endereço é chamado de endereço Ethernet da máquina.

Camada de Rede

Nesta camada, encontramos os protocolos IP, ICMP, ARP e RARP.

IP (Internet Protocol)
Transmite pacotes entre dois nós da rede
ICMP 
(Internet Control Message Protocol)
O ICMP estabelece a comunicação entre o programa do protocolo IP de uma máquina e o de outra máquina. Trata-se, portanto, de um protocolo de troca de mensagens dentro do próprio protocolo IP.
ARP
(Address Resolution Protocol)
faz a correspondência entre o endereço de Internet da máquina e o endereço físico da máquina
RARP
(Reverse Address Resolution Protocol)
faz a correspondência entre o endereço físico da máquina e o endereço de Internet da máquina

Camadas de Transporte/Sessão

Nesta camada, encontram-se os seguintes protocolos:

TCP (Transmission Control Protocol)
Garante a entrega confiável de informações entre dois clientes
UDP (User Datagram Protocol)
Garante a entrega não confiável de informações entre dois clientes

Camadas: Aplicação/Apresentação/Sessão

Aqui encontramos diversos protocolos:

TELNET
Emulador de terminal que permite que uma máquina A se conecte a uma máquina B como um terminal
FTP (File Transfer Protocol)
permite a transferência de arquivos
TFTP (Trivial File
Transfer Protocol)
permite a transferência de arquivos
SMTP (Simple Mail Transfer
protocol)
permite a troca de mensagens entre usuários da rede
DNS (Domain Name System)
converte um nome de máquina no endereço de Internet da máquina
XDR (eXternal Data 
Representation)
criado pela Sun MicroSystems, ele especifica uma representação padrão dos dados, independente das máquinas
RPC(Remote Procedures Call)
também definido pela Sun, é um protocolo de comunicação entre aplicativos remotos, independente da camada de transporte. Esse protocolo é importante: isenta o programador de conhecer os detalhes da camada de transporte e torna os aplicativos portáteis. Esse protocolo se baseia no protocolo XDR
NFS (Network File System)
também definido pela Sun; esse protocolo permite que uma máquina “veja” o sistema de arquivos de outra máquina. Ele se baseia no protocolo RPC mencionado anteriormente

9.1.4. Funcionamento dos protocolos da Internet

Os aplicativos desenvolvidos no ambiente TCP/IP geralmente utilizam vários dos protocolos desse ambiente. Um programa aplicativo se comunica com a camada mais alta dos protocolos. Essa camada repassa a informação para a camada inferior e assim por diante, até chegar ao suporte físico. Lá, a informação é fisicamente transferida para a máquina destinatária, onde atravessará as mesmas camadas, desta vez na direção oposta, até chegar ao aplicativo destinatário das informações enviadas. O esquema a seguir mostra o percurso da informação:

Vejamos um exemplo: a aplicação FTP, definida na camada Application e que permite a transferência de arquivos entre máquinas.

  • A aplicação fornece uma sequência de bytes a ser transmitida à camada transport.
  • A camada transport divide essa sequência de bytes em segments e TCP, e acrescenta, no início de cada segmento, o número do próprio segmento. Os segmentos são encaminhados para a camada de rede, regida pelo protocolo IP.
  • A camada IP cria um pacote que encapsula o segmento TCP recebido. No cabeçalho desse pacote, ela insere os endereços de Internet das máquinas de origem e de destino. Ela também determina o endereço físico da máquina destinatária. Tudo isso é encaminhado para a camada de enlace de dados e enlace físico, ou seja, para a placa de rede que conecta a máquina à rede física.
  • Lá, o pacote IP é, por sua vez, encapsulado em uma trama física e enviado ao seu destinatário pelo cabo.
  • Na máquina destinatária, a camada de enlace de dados e enlace físico faz o inverso: ela desencapsula o pacote IP da trama física e o repassa para a camada IP.
  • A camada IP verifica se o pacote está correto: ela calcula uma soma, com base nos bits recebidos (checksum), soma essa que deve ser encontrada no cabeçalho do pacote. Caso contrário, o pacote é rejeitado.
  • Se o pacote for considerado válido, a camada IP desencapsula o segmento TCP contido nele e o encaminha para a camada superior, a transport.
  • A camada transport — camada TCP em nosso exemplo — examina o número do segmento para restabelecer a ordem correta dos segmentos.
  • Ela também calcula uma soma de verificação para o segmento TCP. Se for considerada correta, a camada TCP envia um aviso de recebimento para a máquina de origem; caso contrário, o segmento TCP é rejeitado.
  • Resta à camada TCP apenas transmitir a parte de dados do segmento para o aplicativo destinatário desses dados na camada superior.

9.1.5. O endereçamento na Internet

Um noeud de uma rede pode ser um computador, uma impressora inteligente, um servidor de arquivos — na verdade, qualquer coisa capaz de se comunicar por meio dos protocolos TCP/IP. Cada nó possui um endereço físico cujo formato depende do tipo de rede. Em uma rede Ethernet, o endereço físico é codificado em 6 bytes. Um endereço de uma rede X25 é um número de 14 dígitos.

O endereço de Internet de um nó é um endereço lógico: ele é independente do hardware e da rede utilizada. Trata-se de um endereço de 4 bytes que identifica tanto uma rede local quanto um nó dessa rede. O endereço de Internet é normalmente representado na forma de 4 números, correspondentes aos valores dos 4 bytes, separados por um ponto. Assim, o endereço do computador Lagaffe da Faculdade de Ciências de Angers é 193.49.144.1 e o do computador Liny é 193.49.144.9. Deduz-se, portanto, que o endereço de Internet da rede local é 193.49.144.0. É possível ter até 254 nós nessa rede.

Como os endereços de Internet ou endereços IP são independentes da rede, um computador de uma rede A pode se comunicar com um computador de uma rede B sem se preocupar com o tipo de rede em que está: basta que ele conheça seu endereço IP. O protocolo IP de cada rede é responsável por fazer a conversão entre endereço IP e endereço físico, nos dois sentidos.

Todos os endereços IP devem ser diferentes entre si. Na França, é o INRIA que se encarrega de atribuir os endereços IP. Na verdade, esse órgão atribui um endereço para sua rede local, por exemplo, 193.49.144.0 para a rede da Faculdade de Ciências de Angers. O administrador dessa rede pode, então, atribuir os endereços IP 193.49.144.1 a 193.49.144.254 como entender. Esse endereço geralmente é registrado em um arquivo específico de cada máquina conectada à rede.

9.1.5.1. As classes de endereços IP

Um endereço IP é uma sequência de 4 bytes, frequentemente indicada como I1.I2.I3.I4, que na verdade contém dois endereços:

  • o endereço da rede
  • o endereço de um nó dessa rede

De acordo com o tamanho desses dois campos, os endereços IP são divididos em três classes: classes A, B e C.

Classe A

O endereço IP: I1.I2.I3.I4 tem o formato R1.N1.N2.N3, onde

R1
é o endereço da rede
N1.N2.N3
é o endereço de um computador nessa rede

Mais precisamente, o formato de um endereço IP da classe A é o seguinte:

O endereço de rede ocupa 7 bits e o endereço do nó, 24 bits. Portanto, é possível ter 127 redes de classe A, cada uma com até 2²⁴ nós.

Classe B

Aqui, o endereço IP: I1.I2.I3.I4 tem o formato R1.R2.N1.N2, onde

R1.R2
é o endereço da rede
N1.N2
é o endereço de uma máquina nessa rede

Mais precisamente, o formato de um endereço IP da classe B é o seguinte:

O endereço da rede ocupa 2 bytes (14 bits, exatamente), assim como o do nó. Portanto, é possível ter 2¹⁴ redes de classe B, cada uma com até 2¹⁶ nós.

Classe C

Nesta classe, o endereço IP: I1.I2.I3.I4 tem o formato R1.R2.R3.N1, onde

R1.R2.R3
é o endereço da rede
N1
é o endereço de uma máquina nessa rede

Mais precisamente, o formato de um endereço IP da classe C é o seguinte:

O endereço de rede ocupa 3 bytes (menos 3 bits) e o endereço do nó, 1 byte. Portanto, é possível ter 221 redes de classe C com até 256 nós.

Como o endereço do computador Lagaffe da Faculdade de Ciências de Angers é 193.49.144.1, vemos que o byte de peso forte vale 193, ou seja, em binário 11000001. Deduz-se, portanto, que a rede é da classe C.

Endereços reservados

  • Certos endereços, como IP, são endereços de rede, e não endereços de nós na rede. São aquelas em que o endereço do nó é 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ó de uma rede pode ter o endereço zero.
  • Quando, em um endereço IP, o endereço do nó contém apenas 1s, temos então um endereço de difusão: esse endereço designa todos os nós da rede.
  • Em uma rede de classe C, que teoricamente permite 2⁸ = 256 nós, se retirarmos os dois endereços proibidos, restam apenas 254 endereços autorizados.

9.1.5.2. Os protocolos de conversão Endereço da Internet <--> Endereço físico

Vimos que, durante a transmissão de informações de uma máquina para outra, essas informações, ao atravessarem a camada IP, eram encapsuladas em pacotes. Esses pacotes têm o seguinte formato:

O pacote IP contém, portanto, os endereços de Internet das máquinas de origem e de destino. Quando esse pacote for transmitido à camada responsável por enviá-lo para a rede física, outras informações são adicionadas a ele para formar a trama física que será finalmente enviada para a rede. Por exemplo, o formato de uma trama em uma rede Ethernet é o seguinte:

Na trama final, constam os endereços físicos dos computadores de origem e de destino. Como eles são obtidos?

O equipamento remetente, que conhece o endereço IP do equipamento com o qual deseja se comunicar, obtém o endereço físico deste último utilizando um protocolo específico chamado ARP (Address Resolution Protocol).

  • Ela envia um pacote de um tipo especial chamado pacote ARP, contendo o endereço IP da máquina cujo endereço físico está sendo procurado. Ela também se certificou de incluir nele seu próprio endereço IP, bem como seu endereço físico.
  • Esse pacote é enviado a todos os nós da rede.
  • Esses nós reconhecem a natureza especial do pacote. O nó que reconhece seu endereço IP no pacote responde enviando ao remetente do pacote seu endereço físico. Como ele consegue fazer isso? Ele encontrou no pacote os endereços IP e o endereço físico do remetente.
  • O remetente recebe, assim, o endereço físico que procurava. Ele o armazena na memória para poder usá-lo posteriormente, caso outros pacotes precisem ser enviados ao mesmo destinatário.

O endereço IP de uma máquina normalmente está registrado em um de seus arquivos, que ela pode consultar para obtê-lo. Esse endereço pode ser alterado: basta editar o arquivo. Já o endereço físico está registrado na memória da placa de rede e não pode ser alterado.

Quando um administrador deseja organizar sua rede de maneira diferente, ele pode precisar alterar os endereços IP de todos os nós e, portanto, editar os diversos arquivos de configuração de cada um deles. Isso pode ser trabalhoso e uma fonte de erros se houver muitas máquinas. Um método consiste em não atribuir um endereço IP às máquinas: insere-se, então, um código especial no arquivo no qual a máquina deveria encontrar seu endereço IP. Ao perceber que não possui o endereço IP, a máquina o solicita por meio de um protocolo chamado RARP (Reverse Address Resolution Protocol). Em seguida, ela envia pela rede um pacote especial chamado pacote RARP, análogo ao pacote ARP anterior, no qual inclui seu endereço físico. Esse pacote é enviado a todos os nós, que então reconhecem um pacote RARP. Um deles, chamado servidor RARP, possui um arquivo que fornece a correspondência entre endereço físico e endereço IP de todos os nós. Ele então responde ao remetente do pacote RARP, reenviando-lhe seu endereço IP. Um administrador que deseje reconfigurar sua rede precisa, portanto, apenas editar o arquivo de correspondências do servidor RARP. Este, normalmente, deve ter um endereço fixo IP, que deve ser capaz de identificar sem precisar utilizar ele próprio o protocolo RARP.

9.1.6. A camada de rede conhecida como camada IP da Internet

O protocolo IP (Protocolo de Internet) define a forma que os pacotes devem assumir e a maneira como devem ser gerenciados durante sua transmissão ou recepção. Esse tipo específico de pacote é chamado de datagrama IP. Já o apresentamos anteriormente:

O importante é que, além dos dados a serem transmitidos, o datagrama IP contém os endereços de Internet dos computadores de origem e de destino. Assim, o computador de destino sabe quem está enviando uma mensagem para ele.

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 é definido pelo software e, portanto, será o mesmo em diferentes redes físicas. Vimos que, ao descer da camada de rede para a camada física, o datagrama IP era encapsulado em um quadro físico. Demos o exemplo do quadro físico de uma rede Ethernet:

As tramas físicas circulam de nó em nó até seu destino, que pode não estar na mesma rede física que a máquina remetente. O pacote IP pode, portanto, ser encapsulado sucessivamente em diferentes quadros físicos nos nós que fazem a conexão entre duas redes de tipos diferentes. Também é possível que o pacote IP seja grande demais para ser encapsulado em um quadro físico. O software IP do nó onde esse problema ocorre divide, então, o pacote IP em fragments de acordo com regras precisas, sendo cada um deles enviado posteriormente pela rede física. Eles só serão remontados em seu destino final.

9.1.6.1. O roteamento

O roteamento é o método de encaminhamento dos pacotes IP até seu destino. Existem dois métodos: o roteamento direto e o roteamento indireto.

Roteamento direto

O roteamento direto refere-se ao encaminhamento de um pacote IP diretamente do remetente ao destinatário dentro da mesma rede:

  • A máquina remetente de um datagrama IP possui o endereço IP do destinatário.
  • Ela obtém o endereço físico do destinatário por meio do protocolo ARP ou em suas tabelas, caso esse endereço já tenha sido obtido.
  • Ela envia o pacote pela rede para esse endereço físico.

Roteamento indireto

O roteamento indireto refere-se ao encaminhamento de um pacote IP para um destino localizado em uma rede diferente daquela à qual pertence o remetente. Nesse caso, as partes de 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 esse fato. Ela então envia o pacote para um nó especial chamado roteador (router), nó que conecta uma rede local a outras redes e cujo endereço IP ela encontra em suas tabelas — endereço obtido inicialmente em um arquivo, em uma memória permanente ou ainda por meio de informações que circulam na rede.

Um roteador está conectado a duas redes e possui um endereço IP dentro dessas duas redes.

No nosso exemplo acima:

  • A rede nº 1 tem o endereço de Internet 193.49.144.0 e a rede nº 2, o endereço 193.49.145.0.
  • Dentro da rede nº 1, o roteador tem o endereço 193.49.144.6 e, dentro da rede nº 2, o endereço 193.49.145.3.

A função do roteador é transformar o pacote IP que ele recebe — e que está contido em uma trama física típica da rede nº 1 — em uma trama física capaz de circular na rede nº 2. Se o endereço IP do destinatário do pacote estiver na rede nº 2, o roteador enviará o pacote diretamente a ele; caso contrário, enviá-lo-á a outro roteador, conectando a rede nº 2 a uma rede nº 3 e assim por diante.

9.1.6.2. Mensagens de erro e de controle

Ainda na camada de rede, portanto no mesmo nível do protocolo IP, existe o protocolo ICMP (Internet Control Message Protocol). Ele serve para enviar mensagens sobre o funcionamento interno da rede: nós com falha, congestionamento em um roteador, etc... As mensagens ICMP são encapsuladas em pacotes IP e enviadas pela rede. As camadas IP dos diferentes nós tomam as medidas adequadas de acordo com as mensagens ICMP que recebem. Assim, um aplicativo, por si só, nunca percebe esses problemas específicos da rede. Um nó utilizará as informações ICMP para atualizar suas tabelas de roteamento.

9.1.7. A camada de transporte: os protocolos UDP e TCP

9.1.7.1. O protocolo UDP: Protocolo de Datagrama do Usuário

O protocolo UDP permite uma troca não confiável de dados entre dois pontos, ou seja, o encaminhamento correto de um pacote até seu destino não é garantido. O aplicativo, se desejar, pode gerenciar isso por conta própria, aguardando, por exemplo, após o envio de uma mensagem, um aviso de recebimento, antes de enviar a seguinte.

Por enquanto, no nível da rede, falamos de endereços IP de máquinas. No entanto, em uma máquina, podem coexistir simultaneamente diferentes processos, todos capazes de se comunicar. Portanto, ao enviar uma mensagem, é necessário indicar não apenas o endereço IP da máquina destinatária, mas também o “nome” do processo destinatário. Esse nome é, na verdade, um número, chamado número de porta. Alguns números são reservados para aplicativos padrão: a porta 69 para o aplicativo tftp (trivial file transfer protocol), por exemplo. Os pacotes gerenciados pelo protocolo UDP também são chamados de datagramas. Eles têm o seguinte formato:

Esses datagramas serão encapsulados em pacotes IP e, em seguida, em quadros físicos.

9.1.7.2. O protocolo TCP: Protocolo de Controle de Transferência

Para comunicações seguras, o protocolo UDP é insuficiente: o desenvolvedor de aplicativos deve criar ele mesmo um protocolo que lhe permita detectar o encaminhamento correto dos pacotes.

O protocolo TCP (Transfer Control Protocol) evita esses problemas. Suas características são as seguintes:

  • O processo que deseja transmitir estabelece, em primeiro lugar, uma conexão com o processo destinatário das informações que irá transmitir. Essa conexão é feita entre uma porta da máquina transmissora e uma porta da máquina receptora. Entre as duas portas, é criado um caminho virtual que ficará reservado exclusivamente aos dois processos que estabeleceram a conexão.
  • Todos os pacotes enviados pelo processo de origem seguem esse 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.
  • A informação transmitida tem um caráter contínuo. O processo emissor envia informações em seu próprio ritmo. Essas informações não são necessariamente enviadas imediatamente: o protocolo TCP aguarda até ter quantidade suficiente para enviá-las. Elas são armazenadas em uma estrutura chamada segmento TCP. Uma vez preenchido, esse segmento será transmitido para a camada IP, onde será encapsulado em um pacote IP.
  • Cada segmento enviado pelo protocolo TCP é numerado. O protocolo TCP destinatário verifica se está recebendo os segmentos na sequência correta. Para cada segmento recebido corretamente, ele envia um aviso de recebimento ao remetente.
  • Quando este último o recebe, ele informa o processo emissor. Assim, este pode saber que um segmento chegou ao destino, o que não era possível com o protocolo UDP.
  • Se, após um certo tempo, o protocolo TCP, que enviou um segmento, não receber uma confirmação de recebimento, ele retransmite o segmento em questão, garantindo assim a qualidade do serviço de encaminhamento da informação.
  • O circuito virtual estabelecido entre os dois processos que se comunicam é o full-duplex: isso significa que a informação pode transitar nos dois sentidos. Assim, o processo de destino pode enviar confirmações de recebimento mesmo enquanto o processo de origem continua enviando informações. Isso permite, por exemplo, que o protocolo de origem TCP envie vários segmentos sem esperar por um aviso de recebimento. Se, após um certo tempo, ele perceber que não recebeu o aviso de recebimento de um determinado segmento nº n, ele retomará a transmissão dos segmentos a partir desse ponto.

9.1.8. A camada de Aplicações

Acima dos protocolos UDP e TCP, existem diversos protocolos padrão:

TELNET

Esse protocolo permite que um usuário de uma máquina A da rede se conecte a uma máquina B (frequentemente chamada de máquina host). O TELNET emula na máquina A um terminal denominado universal. O usuário, portanto, age como se tivesse um terminal conectado à máquina B. O Telnet utiliza o protocolo TCP.

FTP: (Protocolo de Transferência de Arquivos)

Este protocolo permite a troca de arquivos entre duas máquinas remotas, bem como operações com arquivos, como a criação de diretórios, por exemplo. Ele se baseia no protocolo TCP.

TFTP: (Controle Trivial de Transferência de Arquivos)

Este protocolo é uma variante do FTP. Ele se baseia no protocolo UDP e é menos sofisticado que o FTP.

DNS: (Sistema de Nomes de Domínio)

Quando um usuário deseja trocar arquivos com uma máquina remota, por meio do FTP, por exemplo, ele precisa saber o endereço da Internet dessa máquina. Por exemplo, para realizar o FTP na máquina Lagaffe da Universidade de Angers, seria necessário executar o FTP da seguinte forma: FTP 193.49.144.1

Isso exige que haja um diretório que faça a correspondência entre máquina <--> endereço IP. Provavelmente, nesse diretório, as máquinas seriam designadas por nomes simbólicos, tais como:

máquina DPX2/320 da Universidade de Angers

máquina Sun da ISERPA de Angers

É evidente que seria mais prático designar uma máquina por um nome, em vez de por seu endereço IP. Surge, então, o problema da exclusividade do nome: existem milhões de máquinas interconectadas. Seria possível imaginar que um órgão centralizado atribuísse os nomes. Isso seria, sem dúvida, bastante complicado. O controle dos nomes foi, na verdade, distribuído por domínios. Cada domínio é gerenciado por um órgão geralmente muito ágil, que tem total liberdade quanto à escolha dos nomes das máquinas. Assim, as máquinas na França pertencem ao domínio fr, gerenciado pelo Inria de Paris. Para simplificar ainda mais as coisas, o controle é distribuído novamente: domínios são criados dentro do domínio fr. Assim, a Universidade de Angers pertence ao domínio univ-Angers. O serviço responsável por esse domínio tem total liberdade para nomear os computadores da rede da Universidade de Angers. Por enquanto, esse domínio não foi subdividido. Mas, em uma grande universidade com muitos computadores em rede, isso poderia ocorrer.

A máquina DPX2/320 da Universidade de Angers foi nomeada Lagaffe, enquanto uma máquina PC e uma 486DX50 foram nomeadas liny. Como referenciar essas máquinas externamente? Especificando a hierarquia dos domínios aos quais elas pertencem. Assim, o nome completo da máquina Lagaffe será:

Lagaffe.univ-Angers.fr

Dentro dos domínios, é possível usar nomes relativos. Assim, dentro do domínio fr e fora do domínio univ-Angers, a máquina Lagaffe poderá ser referenciada por

Lagaffe.univ-Angers

Por fim, dentro do domínio univ-Angers, ela poderá ser referenciada simplesmente por

Lagaffe

Uma aplicação pode, portanto, referenciar uma máquina pelo seu nome. No fim das contas, é preciso, ainda assim, obter o endereço de Internet dessa máquina. Como isso é feito? Suponhamos que, a partir de uma máquina A, queiramos nos comunicar com uma máquina B.

  • Se a máquina B pertencer ao mesmo domínio que a máquina A, provavelmente encontraremos seu endereço IP em um arquivo da máquina A.
  • Caso contrário, a máquina A encontrará, em outro arquivo ou no mesmo arquivo anterior, uma lista de alguns servidores de nomes com seus endereços IP. Um servidor de nomes é responsável por estabelecer a correspondência entre o nome de uma máquina e seu endereço IP. A máquina A enviará uma solicitação especial ao primeiro servidor de nomes de sua lista, chamada solicitação DNS, incluindo, portanto, o nome da máquina procurada. Se o servidor consultado tiver esse nome em seus registros, ele enviará à máquina A o endereço IP correspondente. Caso contrário, o servidor também encontrará em seus arquivos uma lista de servidores de nomes que pode consultar. Ele fará isso então. Assim, vários servidores de nomes serão consultados, não de forma aleatória, mas de maneira a minimizar o número de consultas. Se a máquina for finalmente encontrada, a resposta será enviada de volta à máquina A.

XDR: (Representação de dados eXternal)

Criado pela Sun MicroSystems, este protocolo especifica uma representação padrão dos dados, independente de máquinas.

RPC: (Chamada de Procedimento Remoto)

Também definido pela Sun, trata-se de um protocolo de comunicação entre aplicativos remotos, independente da camada de transporte. Esse protocolo é importante: libera o programador do conhecimento dos detalhes da camada de transporte e torna os aplicativos portáteis. Esse protocolo baseia-se no protocolo XDR

NFS: Sistema de Arquivos em Rede

Também definido pela Sun, esse protocolo permite que uma máquina “veja” o sistema de arquivos de outra máquina. Ele se baseia no protocolo RPC mencionado anteriormente.

9.1.9. Conclusão

Apresentamos nesta introdução algumas linhas gerais dos protocolos da Internet. Para aprofundar esse assunto, recomenda-se a leitura do excelente livro de Douglas Comer:

Título
TCP/IP: Arquitetura, Protocolos, Aplicações.
Autor
Douglas COMER
Editora
InterEditions

9.2. Gerenciamento de endereços de rede

Um computador na Internet é identificado de forma única por um endereço IP (Protocolo de Internet) no formato I1.I2.I3.I4, em que In é um número entre 1 e 254. Ele também pode ser identificado por um nome igualmente único. Esse nome não é obrigatório, pois os aplicativos acabam sempre utilizando os endereços IP dos computadores. Eles existem para facilitar a vida dos usuários. Assim, é mais fácil, com um navegador, acessar o endereço http://www.ibm.com do que o endereço URL http://129.42.17.99, embora ambos os métodos sejam possíveis. A associação entre os endereços IP <--> nomMachine é garantida por um serviço distribuído da internet chamado DNS (Domain Name System). A plataforma .NET oferece a classe Dns para gerenciar endereços da Internet:

Image

A maioria dos métodos da classe é estática. Vejamos aqueles que nos interessam:

Overloads Public Shared Function
 GetHostByAddress(ByVal address As
 String) As IPHostEntry
Retorna um endereço IPHostEntry a partir de um endereço IP na forma “I1.I2.I3.I4”. Lança uma exceção se a máquina address não puder ser encontrada.
Public Shared Function
 GetHostByName(ByVal hostName As
 String) As IPHostEntry
retorna um endereço IPHostEntry a partir de um nome de máquina. Lança uma exceção se a máquina name não puder ser encontrada.
Public Shared Function 
GetHostName() As String
retorna o nome da máquina na qual está sendo executado o programa que está executando esta instrução

Os endereços de rede do tipo IPHostEntry têm o seguinte formato:

As propriedades que nos interessam:

Public Property AddressList 
As IPAddress ()
lista de endereços IP de uma máquina. Se um endereço IP designa uma e apenas uma máquina física, uma máquina física pode ter vários endereços IP. Esse será o caso se ela tiver várias placas de rede que a conectem a redes diferentes.
Public Property Aliases 
As String ()
lista de aliases de uma máquina, que pode ser designada por um nome principal e por aliases
Public Property HostName 
As String
o nome da máquina, caso ela tenha um

Da classe IPAddress, destacaremos o construtor, as propriedades e os métodos a seguir:

Image

Um objeto [IPAddress] pode ser transformado em uma string I1.I2.I3.I4 com o método ToString(). Por outro lado, é possível obter um objeto IPAddress a partir de uma string I1.I2.I3.I4 usando o método estático IPAddress.Parse("I1.I2.I3.I4"). Consideremos o seguinte programa, que exibe o nome da máquina na qual está sendo executado e, em seguida, de forma interativa, apresenta as correspondências entre o endereço IP e o nome da máquina:

dos>address1
Machine Locale=tahe

Machine recherchée (fin pour arrêter) : istia.univ-angers.fr
Machine : istia.univ-angers.fr
Adresses IP : 193.49.146.171

Machine recherchée (fin pour arrêter) : 193.49.146.171
Machine : istia.istia.univ-angers.fr
Adresses IP : 193.49.146.171
Alias : 171.146.49.193.in-addr.arpa

Machine recherchée (fin pour arrêter) : www.ibm.com
Machine : www.ibm.com
Adresses IP : 129.42.17.99,129.42.18.99,129.42.19.99,129.42.16.99

Machine recherchée (fin pour arrêter) : 129.42.17.99
Machine : www.ibm.com
Adresses IP : 129.42.17.99

Machine recherchée (fin pour arrêter) : x.y.z
Impossible de trouver la machine [x.y.z]

Machine recherchée (fin pour arrêter) : localhost
Machine : tahe
Adresses IP : 127.0.0.1

Machine recherchée (fin pour arrêter) : 127.0.0.1
Machine : tahe
Adresses IP : 127.0.0.1

Machine recherchée (fin pour arrêter) : tahe
Machine : tahe
Adresses IP : 127.0.0.1

Machine recherchée (fin pour arrêter) : fin

A programação é a seguinte:


' opções
Option Explicit On 
Option Strict On

' espaços de nomes
Imports System
Imports System.Net
Imports System.Text.RegularExpressions

' módulo de teste
Public Module adresses

    Sub Main()
        ' exibe o nome da máquina local
        ' e, em seguida, fornece informações interativamente sobre as máquinas da rede
        ' identificadas por um nome ou endereço IP
        ' máquina local
        Dim localHost As String = Dns.GetHostName()
        Console.Out.WriteLine(("Machine Locale=" + localHost))

        ' perguntas e respostas interativas
        Dim machine As String
        Dim adresseMachine As IPHostEntry
        While True
            ' digitação do nome da máquina procurada
            Console.Out.Write("Machine recherchée (fin pour arrêter) : ")
            machine = Console.In.ReadLine().Trim().ToLower()
            ' concluído?
            If machine = "fin" Then
                Exit While
            End If

            ' endereço I1.I2.I3.I4 ou nome da máquina?
            Dim isIPV4 As Boolean = Regex.IsMatch(machine, "^\s*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\s*$")
            ' gestão de exceção
            Try
                If isIPV4 Then
                    adresseMachine = Dns.GetHostByAddress(machine)
                Else
                    adresseMachine = Dns.GetHostByName(machine)
                End If
                ' o nome
                Console.Out.WriteLine(("Machine : " + adresseMachine.HostName))
                ' os endereços IP
                Console.Out.Write(("Adresses IP : " + adresseMachine.AddressList(0).ToString))
                Dim i As Integer
                For i = 1 To adresseMachine.AddressList.Length - 1
                    Console.Out.Write(("," + adresseMachine.AddressList(i).ToString))
                Next i
                Console.Out.WriteLine()
                ' os aliases
                If adresseMachine.Aliases.Length <> 0 Then
                    Console.Out.Write(("Alias : " + adresseMachine.Aliases(0)))
                    For i = 1 To adresseMachine.Aliases.Length - 1
                        Console.Out.Write(("," + adresseMachine.Aliases(i)))
                    Next i
                    Console.Out.WriteLine()
                End If
            Catch
                ' a máquina não existe
                Console.Out.WriteLine("Impossible de trouver la machine [" + machine + "]")
            End Try
        End While
    End Sub
End Module

9.3. Programação TCP-IP

9.3.1. Generalidades

Consideremos a comunicação entre duas máquinas remotas A e B:

Quando um aplicativo AppA da máquina A deseja se comunicar com um aplicativo AppB da máquina B na Internet, ele precisa saber algumas informações:

  • o endereço IP ou o nome da máquina B
  • o número da porta com a qual a aplicação AppB opera. De fato, a máquina B pode hospedar várias aplicações que operam na Internet. Ao receber informações provenientes da rede, ela precisa saber a qual aplicação essas informações se destinam. Os aplicativos da máquina B têm acesso à rede por meio de interfaces, também chamadas de portas de comunicação. Essa informação está contida no pacote recebido pela máquina B para que ele seja entregue ao aplicativo correto.
  • Os protocolos de comunicação compreendidos pela máquina B. Em nosso estudo, utilizaremos apenas os protocolos TCP-IP.
  • o protocolo de comunicação aceito pelo aplicativo AppB. De fato, as máquinas A e B vão “se comunicar”. O que elas vão transmitir será encapsulado nos protocolos TCP-IP. No entanto, quando, no final da cadeia, o aplicativo AppB receber a informação enviada pelo aplicativo AppA, ele precisará ser capaz de interpretá-la. Isso é análogo à situação em que duas pessoas, A e B, se comunicam por telefone: o diálogo delas é transmitido pelo telefone. A fala será codificada na forma de sinais pelo telefone A, transportada pelas linhas telefônicas, chegará ao telefone B para ser decodificada. A pessoa B então ouvirá as palavras. É aí que entra o conceito de protocolo de diálogo: se A fala francês e B não entende essa língua, A e B não poderão dialogar de forma eficaz.

Portanto, os dois aplicativos que se comunicam devem chegar a um acordo sobre o tipo de diálogo que irão adotar. Por exemplo, o diálogo com um serviço ftp não é o mesmo que com um serviço pop: esses dois serviços não aceitam os mesmos comandos. Eles possuem um protocolo de diálogo diferente.

9.3.2. As características do protocolo TCP

Aqui, estudaremos apenas as comunicações de rede que utilizam o protocolo de transporte TCP. Vale lembrar, neste contexto, as características desse protocolo:

  • O processo que deseja transmitir estabelece, em primeiro lugar, uma conexão com o processo destinatário das informações que irá transmitir. Essa conexão é estabelecida entre uma porta da máquina transmissora e uma porta da máquina receptora. Entre as duas portas, é criado um caminho virtual que ficará reservado exclusivamente aos dois processos que estabeleceram a conexão.
  • Todos os pacotes enviados pelo processo de origem seguem esse caminho virtual e chegam na ordem em que foram enviados
  • A informação transmitida tem um caráter contínuo. O processo emissor envia informações em seu próprio ritmo. Essas informações não são necessariamente enviadas imediatamente: o protocolo TCP aguarda até ter quantidade suficiente para enviá-las. Elas são armazenadas em uma estrutura chamada segmento TCP. Esse segmento, uma vez preenchido, será transmitido para a camada IP, onde será encapsulado em um pacote IP.
  • Cada segmento enviado pelo protocolo TCP é numerado. O protocolo TCP destinatário verifica se está recebendo os segmentos na sequência correta. Para cada segmento recebido corretamente, ele envia um aviso de recebimento ao remetente.
  • Quando este último o recebe, ele informa o processo emissor. Assim, este pode saber que um segmento chegou ao destino.
  • Se, após um certo tempo, o protocolo TCP, que enviou um segmento, não receber uma confirmação de recebimento, ele retransmite o segmento em questão, garantindo assim a qualidade do serviço de encaminhamento da informação.
  • O circuito virtual estabelecido entre os dois processos que se comunicam é full-duplex: isso significa que a informação pode transitar nos dois sentidos. Assim, o processo de destino pode enviar confirmações de recebimento mesmo enquanto o processo de origem continua enviando informações. Isso permite, por exemplo, que o protocolo de origem TCP envie vários segmentos sem esperar pelo aviso de recebimento. Se, após um certo tempo, ele perceber que não recebeu o aviso de recebimento de um determinado segmento nº n, ele retomará a transmissão dos segmentos a partir desse ponto.

9.3.3. A relação cliente-servidor

Frequentemente, a comunicação na Internet é assimétrica: a máquina A inicia uma conexão para solicitar um serviço à máquina B, especificando que deseja estabelecer uma conexão com o serviço SB1 da máquina B. Esta aceita ou recusa. Se aceitar, a máquina A pode enviar suas solicitações ao serviço SB1. Essas solicitações devem estar em conformidade com o protocolo de comunicação compreendido pelo serviço SB1. Estabelece-se, assim, um diálogo de solicitação-resposta entre a máquina A, chamada de máquina cliente, e a máquina B, chamada de máquina servidor. Um dos dois parceiros encerrará a conexão.

9.3.4. Arquitetura de um cliente

A arquitetura de um programa de rede que solicita os serviços de um aplicativo 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

9.3.5. Arquitetura de um 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 servidor trata de maneira diferente a solicitação inicial de conexão de um cliente e suas solicitações posteriores para obter um serviço. O programa não presta o serviço propriamente dito. Se o fizesse, durante o tempo em que o serviço estivesse sendo prestado, ele não estaria mais à escuta das solicitações de conexão e, consequentemente, os clientes não seriam atendidos. Portanto, ele procede de outra forma: assim que uma solicitação de conexão é recebida na porta de escuta e, em seguida, aceita, o servidor cria uma tarefa encarregada de prestar o serviço solicitado pelo cliente. Esse serviço é prestado em outra porta da máquina servidora, chamada de porta de serviço. Dessa forma, é possível atender a vários clientes 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

9.3.6. A classe TcpClient

A classe TcpClient é a classe adequada para representar o cliente de um serviço TCP. Ela é definida da seguinte forma:

Image

Os construtores, métodos e propriedades que nos interessam são os seguintes:

Public Sub New(ByVal hostname
 As String,ByVal port As Integer)
cria uma conexão TCP com o servidor operando na porta indicada (port) da máquina indicada (hostname). Por exemplo, new TcpClient("istia.univ-angers.fr", 80) para se conectar à porta 80 da máquina istia.univ-angers.fr
Public Sub Close()
encerra a conexão com o servidor TCP
Public Function GetStream()
 As NetworkStream
obtém um fluxo NetworkStream de leitura e gravação com o servidor. É esse fluxo que permite as trocas entre cliente e servidor.

9.3.7. A classe NetworkStream

A classe NetworkStream representa o fluxo de rede entre o cliente e o servidor. A classe é definida da seguinte forma:

Image

A classe NetworkStream é derivada da classe Stream. Muitas aplicações cliente-servidor trocam linhas de texto terminadas pelos caracteres de fim de linha "\r\n". Portanto, é interessante utilizar os objetos StreamReader e StreamWriter para ler e gravar essas linhas no fluxo de rede. Quando duas máquinas se comunicam, há, em cada extremidade da conexão, um objeto TcpClient. O método GetStream desse objeto permite acessar o fluxo de rede (NetworkStream) que conecta as duas máquinas. Assim, se uma máquina M1 estabeleceu uma conexão com uma máquina M2 por meio de um objeto TcpClient client1 para que elas troquem linhas de texto, ela poderá criar seus fluxos de leitura e gravação da seguinte maneira:

Dim in1 as StreamReader=new StreamReader(client1.GetStream())
Dim out1 as StreamWriter=new StreamWriter(client1.GetStream())
out1.AutoFlush=true

A instrução

out1.AutoFlush=true

significa que o fluxo de gravação de client1 não passará por um buffer intermediário, mas irá diretamente para a rede. Esse ponto é importante. Geralmente, quando client1 envia uma linha de texto ao seu parceiro, ele espera uma resposta. Essa resposta nunca chegará se a linha tiver sido, na verdade, armazenada no buffer da máquina M1 e nunca tiver sido enviada. Para enviar uma linha de texto para a máquina M2, escrever-se-á:

client1.WriteLine("un texte")

Para ler a resposta de M2, escrever-se-á:

Dim réponse as String=client1.ReadLine()

9.3.8. Arquitetura básica de um cliente de internet

Agora temos os elementos necessários para definir a arquitetura básica de um cliente da Internet:


    Dim client As TcpClient = Nothing     ' le client
    Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
    Dim OUT As StreamWriter = Nothing     ' le flux d'écriture du client
    Dim demande As String = Nothing         ' demande du client
    Dim réponse As String = Nothing         ' réponse du serveur

    Try
      ' conectamos-nos ao serviço em execução na porta P da máquina M
      client = New TcpClient(nomServeur, port)

      ' são criados os fluxos de entrada e saída do cliente TCP
      [IN] = New StreamReader(client.GetStream())
      OUT = New StreamWriter(client.GetStream())
      OUT.AutoFlush = True

      ' ciclo de solicitação-resposta
      While True
        ' prepara-se a solicitação
        demande = ...
        ' envia-se ao servidor
        OUT.WriteLine(demande)
        ' lê-se a resposta do servidor
        réponse = [IN].ReadLine()
        ' processando a resposta
        ...
            End While
            ' concluído
            client.Close()
        Catch ex As Exception
      ' gerenciando a exceção
...
        End Try

9.3.9. A classe TcpListener

A classe TcpListener é a classe adequada para representar um serviço TCP. Ela é definida da seguinte forma:

Image

Os construtores, métodos e propriedades que nos interessam são os seguintes:

Public Sub New(ByVal localaddr 
As IPAddress,ByVal port As Integer)
cria um serviço TCP que aguardará (listen) as solicitações dos clientes em uma porta passada como parâmetro (port), chamada de porta de escuta da máquina local com endereço IP localadr.
Public Function AcceptTcpClient() 
As TcpClient
aceita a solicitação de um cliente. Retorna como resultado um objeto TcpClient associado a outra porta, chamada de porta de serviço.
Public Sub Start()
inicia a escuta de solicitações de clientes
Public Sub Stop()
interrompe a escuta das solicitações dos clientes

9.3.10. Arquitetura básica de um servidor de Internet

Com base no que foi visto anteriormente, é possível deduzir a estrutura básica de um servidor:


    ' cria-se o serviço de escuta
    Dim ecoute As TcpListener = Nothing
    Dim port As Integer = ...
    Try
      ' criamos o serviço
      ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
      ' é iniciado
      ecoute.Start()
      ' ciclo do serviço
      Dim liaisonClient As TcpClient = Nothing
            While not fini
                ' aguardando um cliente
                liaisonClient = ecoute.AcceptTcpClient()
                ' o serviço é executado por outra tarefa
                Dim tache As Thread = New Thread(New ThreadStart(AddressOf [méthode]))
                tache.Start()
            End While
        Catch ex As Exception
            ' o erro é sinalizado
....
        End Try
        ' fim do serviço
        ecoute.Stop()

A classe Service é um thread que poderia ter a seguinte aparência:


Public Class Service

    Private liaisonClient As TcpClient    ' liaison avec le client
    Private [IN] As StreamReader    ' flux d'entrée
    Private OUT As StreamWriter    ' flux de sortie

    ' construtor
    Public Sub New(ByVal liaisonClient As TcpClient, ...)
        Me.liaisonClient = liaisonClient
        ...
    End Sub

    ' método run
    Public Sub Run()
        ' retorna o serviço ao cliente
        Try
            ' fluxo de entrada
            [IN] = New StreamReader(liaisonClient.GetStream())
            ' fluxo de saída
            OUT = New StreamWriter(liaisonClient.GetStream())
            OUT.AutoFlush = True
            ' ciclo de leitura de solicitação/gravação de resposta
            Dim demande As String = Nothing
            Dim reponse As String = Nothing
            demande = [IN].ReadLine
            While Not (demande Is Nothing)
                ' processa-se a solicitação
                ...
                ' envia-se a resposta
                reponse = "[" + demande + "]"
                OUT.WriteLine(reponse)
                ' próxima solicitação
                demande = [IN].ReadLine
            End While
            ' fim da conexão
            liaisonClient.Close()
        Catch e As Exception
            ...
        End Try
        ' fim do serviço
    End Sub

9.4. Exemplos

9.4.1. Servidor de eco

Propomos escrever um servidor de eco que será iniciado a partir de uma janela DOS pelo comando:

serveurEcho port

O servidor opera na porta passada como parâmetro. Ele se limita a reenviar ao cliente a solicitação que este lhe enviou. O programa é o seguinte:


' opções
Option Explicit On 
Option Strict On

' espaços de nomes
Imports System.Net.Sockets
Imports System.Net
Imports System
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic

' chamada: serveurEcho porta
' servidor de eco
' retorna ao cliente a linha que este lhe enviou

Public Class serveurEcho
  Private Shared syntaxe As String = "Syntaxe : serveurEcho port"

  ' programa principal
  Public Shared Sub Main(ByVal args() As String)

    ' existe algum argumento
    If args.Length <> 1 Then
      erreur(syntaxe, 1)
    End If
    ' esse argumento deve ser um número inteiro maior que 0
    Dim port As Integer = 0
    Dim erreurPort As Boolean = False
    Dim E As Exception = Nothing
    Try
      port = Integer.Parse(args(0))
    Catch ex As Exception
      E = ex
      erreurPort = True
    End Try
    erreurPort = erreurPort Or port <= 0
    If erreurPort Then
      erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
    End If
    ' cria-se o serviço de escuta
    Dim ecoute As TcpListener = Nothing
    Dim nbClients As Integer = 0 ' nbre de clients traités
    Try
      ' cria-se o serviço
      ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
      ' ele é iniciado
      ecoute.Start()
      ' acompanhamento
      Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & port))
      Console.Out.WriteLine(ecoute.LocalEndpoint)

      ' loop do serviço
      Dim liaisonClient As TcpClient = Nothing
            While True
                ' loop infinito — será interrompido com Ctrl-C
                ' aguardando um cliente
                liaisonClient = ecoute.AcceptTcpClient()

                ' o serviço é prestado por outra tarefa
                nbClients += 1
                Dim tache As Thread = New Thread(New ThreadStart(AddressOf New traiteClientEcho(liaisonClient, nbClients).Run))
                tache.Start()
            End While
            ' retornamos à escuta de solicitações
        Catch ex As Exception
            ' o erro é sinalizado
            erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
        End Try
        ' fim do serviço
        ecoute.Stop()
    End Sub

    ' exibição de erros
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' exibição do erro
        System.Console.Error.WriteLine(msg)
        ' encerramento com erro
        Environment.Exit(exitCode)
    End Sub
End Class

' -------------------------------------------------------
' presta serviço a um cliente do servidor de eco
Public Class traiteClientEcho

    Private liaisonClient As TcpClient    ' liaison avec le client
    Private numClient As Integer    ' n° de client
    Private [IN] As StreamReader    ' flux d'entrée
    Private OUT As StreamWriter    ' flux de sortie

    ' construtor
    Public Sub New(ByVal liaisonClient As TcpClient, ByVal numClient As Integer)
        Me.liaisonClient = liaisonClient
        Me.numClient = numClient
    End Sub

    ' método run
    Public Sub Run()
        ' presta serviço ao cliente
        Console.Out.WriteLine(("Début de service au client " & numClient))
        Try
            ' fluxo de entrada
            [IN] = New StreamReader(liaisonClient.GetStream())
            ' fluxo de saída
            OUT = New StreamWriter(liaisonClient.GetStream())
            OUT.AutoFlush = True
            ' ciclo de leitura (solicitação)/gravação (resposta)
            Dim demande As String = Nothing
            Dim reponse As String = Nothing
            demande = [IN].ReadLine
            While Not (demande Is Nothing)
                ' acompanhamento
                Console.Out.WriteLine(("Client " & numClient & " : " & demande))
                ' o serviço é encerrado quando o cliente envia um marcador de fim de arquivo
                reponse = "[" + demande + "]"
                OUT.WriteLine(reponse)
                ' o serviço é encerrado quando o cliente envia “fim”
                If demande.Trim().ToLower() = "fin" Then
                    Exit While
                End If
                ' próxima solicitação
                demande = [IN].ReadLine
            End While
            ' fim da conexão
            liaisonClient.Close()
        Catch e As Exception
            erreur("Erreur lors de la fermeture de la liaison client (" + e.ToString + ")", 2)
        End Try
        ' fim do serviço
        Console.Out.WriteLine(("Fin de service au client " & numClient))
    End Sub

    ' exibição de erros
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' exibição de erro
        System.Console.Error.WriteLine(msg)
        ' interrupção com erro
        Environment.Exit(exitCode)
    End Sub
End Class

A estrutura do servidor está em conformidade com a arquitetura geral dos servidores TCP.

9.4.2. Um cliente para o servidor de eco

Agora vamos escrever um cliente para o servidor anterior. Ele será chamado da seguinte maneira:

clientEcho nomServeur port

Ele se conecta à máquina nomServeur na porta port e, em seguida, envia ao servidor linhas de texto que este lhe devolve como eco.


' opções
Option Explicit On 
Option Strict On

' espaços de nomes
Imports System.Net.Sockets
Imports System.Net
Imports System
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic

Public Class clientEcho

  ' conecta-se a um servidor de eco
  ' qualquer linha digitada no teclado é então recebida como eco
  Public Shared Sub Main(ByVal args() As String)
    ' sintaxe
    Const syntaxe As String = "pg machine port"

    ' número de argumentos
    If args.Length <> 2 Then
      erreur(syntaxe, 1)
    End If
    ' anota-se o nome do servidor
    Dim nomServeur As String = args(0)

    ' a porta deve ser um número inteiro maior que 0
    Dim port As Integer = 0
    Dim erreurPort As Boolean = False
    Dim E As Exception = Nothing
    Try
      port = Integer.Parse(args(1))
    Catch ex As Exception
      E = ex
      erreurPort = True
    End Try
    erreurPort = erreurPort Or port <= 0
    If erreurPort Then
      erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
    End If

        ' é possível trabalhar
    Dim client As TcpClient = Nothing ' le client
    Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
    Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
    Dim demande As String = Nothing ' demande du client
    Dim réponse As String = Nothing ' réponse du serveur
    Try
      ' conecta-se ao serviço em execução na porta P da máquina M
      client = New TcpClient(nomServeur, port)

      ' criam-se os fluxos de entrada e saída do cliente TCP
      [IN] = New StreamReader(client.GetStream())
      OUT = New StreamWriter(client.GetStream())
      OUT.AutoFlush = True

      ' ciclo de solicitação-resposta
      While True
        ' a solicitação vem do teclado
        Console.Out.Write("demande (fin pour arrêter) : ")
        demande = Console.In.ReadLine()
        ' ela é enviada ao servidor
        OUT.WriteLine(demande)
        ' lê-se a resposta do servidor
        réponse = [IN].ReadLine()
        ' processa-se a resposta
        Console.Out.WriteLine(("Réponse : " + réponse))
        ' concluído?
        If demande.Trim().ToLower() = "fin" Then
          Exit While
                End If
            End While
            ' terminou
            client.Close()
        Catch ex As Exception
      ' tratamos a exceção
      erreur(ex.Message, 3)
        End Try
    End Sub

    ' exibição dos erros
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' exibição de erro
        System.Console.Error.WriteLine(msg)
        ' encerramento com erro
        Environment.Exit(exitCode)
    End Sub
End Class

A estrutura desse cliente está em conformidade com a arquitetura geral dos clientes tcp.Voici, conforme os resultados obtidos na seguinte configuração:

  • o servidor é iniciado na porta 100 em uma janela do DOS
  • na mesma máquina, dois clientes são iniciados em duas outras janelas do DOS

Na janela do cliente 1, obtêm-se os seguintes resultados:

dos>clientEcho localhost 100
demande (fin pour arrêter) : ligne1
Réponse : [ligne1]
demande (fin pour arrêter) : ligne1B
Réponse : [ligne1B]
demande (fin pour arrêter) : ligne1C
Réponse : [ligne1C]
demande (fin pour arrêter) : fin
Réponse : [fin]

Na do cliente 2:

dos>clientEcho localhost 100
demande (fin pour arrêter) : ligne2A
Réponse : [ligne2A]
demande (fin pour arrêter) : ligne2B
Réponse : [ligne2B]
demande (fin pour arrêter) : fin
Réponse : [fin]

Na do servidor:

dos>serveurEcho 100
Serveur d'écho lancé sur le port 100
0.0.0.0:100
Début de service au client 1
Client 1 : ligne1
Début de service au client 2
Client 2 : ligne2A
Client 2 : ligne2B
Client 1 : ligne1B
Client 1 : ligne1C
Client 2 : fin
Fin de service au client 2
Client 1 : fin
Fin de service au client 1
^C

Observe-se que o servidor conseguiu atender dois clientes simultaneamente.

9.4.3. Um cliente genérico TCP

Muitos serviços criados nos primórdios da Internet funcionam segundo o modelo do servidor de eco estudado anteriormente: as trocas entre cliente e servidor ocorrem por meio de trocas de linhas de texto. Vamos escrever um cliente TCP genérico que será iniciado da seguinte maneira: cltgen servidor porta

Esse cliente TCP se conectará à porta port do servidor serveur. Feito isso, ele criará duas threads:

  1. um thread responsável por ler os comandos digitados no teclado e enviá-los ao servidor
  2. um thread responsável por ler as respostas do servidor e exibi-las na tela

Por que dois threads, se na aplicação anterior essa necessidade não se fez sentir? Nessa última, o protocolo de comunicação era conhecido: o cliente enviava uma única linha e o servidor respondia com uma única linha. Cada serviço tem seu protocolo específico, e também se encontram as seguintes situações:

  • o cliente precisa enviar várias linhas de texto antes de receber uma resposta
  • a resposta de um servidor pode conter várias linhas de texto

Portanto, o ciclo de envio de uma única linha ao servidor — recebimento de uma única linha enviada pelo servidor — nem sempre é adequado. Vamos, portanto, criar dois ciclos independentes:

  • um ciclo para ler os comandos digitados no teclado a serem enviados ao servidor. O usuário indicará o fim dos comandos com a palavra-chave fin.
  • um ciclo de recepção e exibição das respostas do servidor. Esse será um ciclo infinito que só será interrompido pelo fechamento do fluxo de rede pelo servidor ou pelo usuário no teclado, ao digitar o comando fin.

Para manter esses dois loops separados, precisamos de duas threads independentes. Vejamos um exemplo de execução em que nosso cliente TCP genérico se conecta a um serviço SMTP (Protocolo de Transferência SendMail). Esse serviço é responsável pelo encaminhamento de e-mails aos destinatários. Ele opera na porta 25 e utiliza um protocolo de comunicação do tipo troca de linhas de texto.

dos>cltgen 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 essas trocas entre cliente e servidor:

  • o serviço SMTP envia uma mensagem de boas-vindas quando um cliente se conecta a ele:
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
  • alguns serviços possuem um comando help que fornece informações sobre os comandos que podem ser utilizados com o serviço. Neste caso, isso não ocorre. Os comandos SMTP utilizados no exemplo são os seguintes:
    • mail from: expéditeur, para indicar o endereço de e-mail do remetente da mensagem
    • rcpt to: destinataire, para indicar o endereço de e-mail do destinatário da mensagem. Se houver vários destinatários, o comando rcpt to: deve ser repetido quantas vezes forem necessárias para cada um deles.
    • data, que sinaliza ao servidor SMTP que a mensagem será enviada. Conforme indicado na resposta do servidor, trata-se de uma sequência de linhas terminada por uma linha contendo apenas o caractere ponto. Uma mensagem pode ter cabeçalhos separados do corpo da mensagem por uma linha em branco. Em nosso exemplo, definimos um assunto com a palavra-chave Subject:
  • assim que a mensagem for enviada, é possível indicar ao servidor que terminamos com o comando quit. O servidor então encerra a conexão réseau.Le; o thread de leitura pode detectar esse evento e parar.
  • O usuário então digita “fin” no teclado para interromper também o thread de leitura dos comandos digitados no teclado.

Se verificarmos o e-mail recebido, temos o seguinte (Outlook):

Image

Observe-se que o serviço SMTP não consegue detectar se um remetente é válido ou não. Portanto, nunca se pode confiar no campo from de uma mensagem. Nesse caso, o remetente machin@univ-angers.fr não existia. Esse cliente TCP genérico nos permite descobrir o protocolo de comunicação dos serviços da Internet e, a partir daí, criar classes especializadas para os clientes desses serviços. Vamos descobrir o protocolo de comunicação do serviço POP (Post Office Protocol), que permite recuperar e-mails armazenados em um servidor. Ele opera na porta 110.

dos>cltgen 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:

  • user login, onde se insere o nome de usuário na máquina que hospeda nossos e-mails
  • pass password, onde se insere a senha associada ao login anterior
  • list, para obter a lista de mensagens no formato número, tamanho em bytes
  • retr i, para ler a mensagem nº i
  • quit, para encerrar a sessão.

Vamos agora conhecer o protocolo de comunicação entre um cliente e um servidor Web, que normalmente opera na porta 80:

dos>cltgen 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 seus comandos ao servidor de acordo com o seguinte esquema:

commande1
commande2
...
commanden
[ligne vide]

Somente após receber a linha vazia é que o servidor web responde. No exemplo, utilizamos apenas um comando:

GET /index.html HTTP/1.0

que solicita ao servidor o URL /index.html e indica que está trabalhando com o protocolo HTTP versão 1.0. A versão mais recente desse protocolo é a 1.1. O exemplo mostra que o servidor respondeu enviando o conteúdo do arquivo index.html e, em seguida, encerrou a conexão, já que é possível observar que o thread de leitura das respostas foi encerrado. Antes de enviar o conteúdo do arquivo index.html, o servidor web enviou uma série de cabeçalhos encerrada por uma linha em branco:

<-- 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 arquivo /index.html. O que vem acima é chamado de cabeçalhos HTTP (Protocolo de Transferência HyperText). Não vamos detalhar esses cabeçalhos aqui, mas vale lembrar que nosso cliente genérico permite acessá-los, o que pode ser útil para compreendê-los. A primeira linha, por exemplo:

<-- HTTP/1.1 200 OK

indica que o servidor web contatado compreende o protocolo HTTP/1.1 e que encontrou o arquivo solicitado (200 OK), sendo 200 um código de resposta HTTP. As linhas

<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html

informam ao cliente que ele receberá 11.251 bytes correspondentes ao texto HTML (HyperText Markup Language) e que, ao final do envio, a conexão será encerrada. Portanto, temos aqui um cliente TCP muito prático. Na verdade, esse cliente já existe nas máquinas, onde é chamado de telnet, mas foi interessante escrevê-lo nós mesmos. O programa do cliente TCP genérico é o seguinte:


' espaços de nomes
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic

' a classe
Public Class clientTcpGénérique
   
    
   ' recebe como parâmetro as características de um serviço na forma
   ' servidor porta
   ' se conecta ao serviço
   ' cria um thread para ler os comandos digitados no teclado
   ' esses comandos serão enviados ao servidor
   ' cria um thread para ler as respostas do servidor
   ' esses dados serão exibidos na tela
   ' tudo termina com o comando “fin” digitado no teclado
  Public Shared Sub Main(ByVal args() As String)

    ' sintaxe
    Const syntaxe As String = "pg serveur port"

    ' número de argumentos
    If args.Length <> 2 Then
      erreur(syntaxe, 1)
    End If
    ' anota-se o nome do servidor
    Dim serveur As String = args(0)

    ' a porta deve ser um número inteiro maior que 0
    Dim port As Integer = 0
    Dim erreurPort As Boolean = False
    Dim E As Exception = Nothing
    Try
      port = Integer.Parse(args(1))
    Catch ex As Exception
      E = ex
      erreurPort = True
    End Try
    erreurPort = erreurPort Or port <= 0
    If erreurPort Then
      erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
    End If
    Dim client As TcpClient = Nothing
    ' podem ocorrer problemas
    Try
      ' conectamos-nos ao serviço
      client = New TcpClient(serveur, port)
    Catch ex As Exception
      ' erro
      Console.Error.WriteLine(("Impossible de se connecter au service (" & serveur & "," & port & "), erreur : " & ex.Message))
      ' fim
      Return
        End Try
        ' são criadas as threads de leitura/gravação
        Dim thReceive As New Thread(New ThreadStart(AddressOf New clientReceive(client).Run))
        Dim thSend As New Thread(New ThreadStart(AddressOf New clientSend(client).Run))

        ' inicia-se a execução das duas threads
        thSend.Start()
        thReceive.Start()

        ' fim do thread principal
        Return
    End Sub

    ' exibição de erros
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' exibição de erro
        System.Console.Error.WriteLine(msg)
        ' interrupção com erro
        Environment.Exit(exitCode)
    End Sub
End Class

Public Class clientSend
    ' classe responsável por ler os comandos digitados no teclado
    ' e enviá-los a um servidor por meio de um cliente TCP passado ao construtor
    Private client As TcpClient    ' le client tcp

    ' construtor
    Public Sub New(ByVal client As TcpClient)
        ' observa-se o cliente TCP
        Me.client = client
    End Sub

    ' método Run da thread
    Public Sub Run()

        ' dados locais
        Dim OUT As StreamWriter = Nothing        ' flux d'écriture réseau
        Dim commande As String = Nothing        ' commande lue au clavier
        ' gestão de erros
        Try
            ' criação do fluxo de gravação de rede
            OUT = New StreamWriter(client.GetStream())
            OUT.AutoFlush = True
            ' ciclo de entrada e envio de comandos
            Console.Out.WriteLine("Commandes : ")
            While True
                ' leitura do comando digitado no teclado
                commande = Console.In.ReadLine().Trim()
                ' Concluído?
                If commande.ToLower() = "fin" Then
                    Exit While
                End If
                ' envio do comando ao servidor
                OUT.WriteLine(commande)
            End While
        Catch ex As Exception
            ' erro
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
        ' fim — fechando os fluxos
        Try
            OUT.Close()
            client.Close()
        Catch
        End Try
        ' notificando o fim do thread
        Console.Out.WriteLine("[fin du thread d'envoi des commandes au serveur]")
    End Sub
End Class


Public Class clientReceive
    ' classe responsável por ler as linhas de texto destinadas a um 
    ' cliente TCP passado ao construtor
    Private client As TcpClient    ' le client tcp

    ' construtor
    Public Sub New(ByVal client As TcpClient)
        ' registra-se o cliente TCP
        Me.client = client
    End Sub

    'construtor
    ' método Run da thread
    Public Sub Run()

        ' dados locais
        Dim [IN] As StreamReader = Nothing        ' flux lecture réseau
        Dim réponse As String = Nothing        ' réponse serveur
        ' gerenciamento de erros
        Try
            ' criação do fluxo de leitura de rede
            [IN] = New StreamReader(client.GetStream())
            ' loop de leitura de linhas de texto do fluxo IN
            While True
                ' leitura do fluxo de rede
                réponse = [IN].ReadLine()
                ' fluxo encerrado?
                If réponse Is Nothing Then
                    Exit While
                End If
                ' exibição
                Console.Out.WriteLine(("<-- " + réponse))
            End While
        Catch ex As Exception
            ' erro
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
        ' fim — fechando os fluxos
        Try
            [IN].Close()
            client.Close()
        Catch
        End Try
        ' notificando o fim do thread
        Console.Out.WriteLine("[fin du thread de lecture des réponses du serveur]")
    End Sub
End Class

9.4.4. Um servidor TCP genérico

Agora, vamos nos concentrar em um servidor

  • que exibe na tela os comandos enviados por seus clientes
  • e lhes envia, como resposta, as linhas de texto digitadas no teclado por um usuário. Portanto, é este último que atua como servidor.

O programa é iniciado por: srvgen portEcoute, onde portEcoute é a porta à qual os clientes devem se conectar. O atendimento ao cliente será garantido por duas threads:

  • um thread dedicado exclusivamente à leitura das linhas de texto enviadas pelo cliente
  • um thread dedicado exclusivamente à leitura das respostas digitadas pelo usuário. Este sinalizará, por meio do comando fin, que está encerrando a conexão com o cliente.

O servidor cria duas threads por cliente. Se houver n clientes, haverá 2n threads ativas ao mesmo tempo. O servidor, por sua vez, nunca é encerrado, a menos que o usuário pressione Ctrl-C no teclado. Vejamos alguns exemplos.

O servidor é iniciado na porta 100 e usamos o cliente genérico para nos comunicarmos com ele. A janela do cliente é a seguinte:

dos>cltgen 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 demais, do cliente para o servidor. A janela do servidor é a seguinte:

dos>srvgen 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 aquelas enviadas do cliente para o servidor. As linhas N: são as linhas enviadas do servidor para o cliente nº N. O servidor acima ainda está ativo, enquanto o cliente 1 já foi encerrado. Iniciamos um segundo cliente para o mesmo servidor:

dos>cltgen 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 assim:

dos>srvgen 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

Vamos agora simular um servidor web, iniciando nosso servidor genérico na porta 88:

dos>srvgen 88
Serveur générique lancé sur le port 88

Agora, vamos abrir um navegador e acessar a página http://localhost:88/exemple.html. O navegador se conectará à porta 88 da máquina localhost e, em seguida, solicitará a página /exemple.html:

Image

Vamos agora observar a janela do nosso servidor:

dos>srvgen 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
<--

Assim, identificamos os cabeçalhos HTTP enviados pelo navegador. Isso nos permite descobrir, aos poucos, o protocolo HTTP. Em um exemplo anterior, criamos um cliente web que enviava apenas o comando GET. Isso já havia sido suficiente. Vemos aqui que o navegador envia outras informações ao servidor. Elas têm como objetivo indicar ao servidor que tipo de cliente ele está atendendo. Também vemos que os cabeçalhos HTTP terminam com uma linha vazia. Vamos elaborar uma resposta para o nosso cliente. O usuário que digita no teclado é, neste caso, o verdadeiro servidor e pode elaborar uma resposta manualmente. Lembremo-nos da resposta fornecida por um servidor web em um 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 dar 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 fin encerra a conexão do servidor com o cliente. Limitamos nossa resposta 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 informamos o tamanho do arquivo que vamos enviar (Content-Length), mas nos limitamos a indicar que vamos encerrar a conexão (Connection: close) após o envio do mesmo. Isso é suficiente para o navegador. Ao perceber que a conexão foi encerrada, ele saberá que a resposta do servidor foi concluída e exibirá a página HTML que lhe foi enviada. Esta última é 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>

Em seguida, o usuário encerra a conexão com o cliente digitando o comando fin. O navegador reconhece então que a resposta do servidor foi concluída e pode exibi-la:

Image

Se, no exemplo acima, digitarmos Affichage/Source para ver o que o navegador recebeu, obtemos:

Image

ou seja, exatamente o que foi enviado pelo servidor genérico. O código do servidor genérico TCP é o seguinte:


' espaços de nomes
Imports System
Imports System.Net
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic

Public Class serveurTcpGénérique

    ' programa principal
    Public Shared Sub Main(ByVal args() As String)

        ' recebe a porta de escuta das solicitações dos clientes
        ' cria um thread para ler as solicitações do cliente
        ' estas serão exibidas na tela
        ' cria um thread para ler os comandos digitados no teclado
        ' esses comandos serão enviados como resposta ao cliente
        ' tudo termina com o comando “fin” digitado no teclado

        Const syntaxe As String = "Syntaxe : pg port"

        ' existe algum argumento?
        If args.Length <> 1 Then
            erreur(syntaxe, 1)
        End If
        ' esse argumento deve ser um número inteiro maior que 0
        Dim port As Integer = 0
        Dim erreurPort As Boolean = False
        Dim E As Exception = Nothing
        Try
            port = Integer.Parse(args(0))
        Catch ex As Exception
            E = ex
            erreurPort = True
        End Try
        erreurPort = erreurPort Or port <= 0
        If erreurPort Then
            erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
        End If
        ' criamos o serviço de escuta
        Dim ecoute As TcpListener = Nothing
        Dim nbClients As Integer = 0     ' nbre de clients traités
        Try
            ' cria-se o serviço
            ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
            ' ele é iniciado
            ecoute.Start()
            ' acompanhamento
            Console.Out.WriteLine(("Serveur générique lancé sur le port " & port))

            ' ciclo de atendimento aos clientes
            Dim client As TcpClient = Nothing
            While True        ' boucle infinie - sera arrêtée par Ctrl-C
                ' aguardando um cliente
                client = ecoute.AcceptTcpClient()

                ' o serviço é executado em threads separadas
                nbClients += 1
                ' thread de leitura das solicitações dos clientes
                Dim thReceive As New Thread(New ThreadStart(AddressOf New serveurReceive(client, nbClients).Run))
                ' thread de leitura das respostas digitadas pelo usuário
                Dim thSend As New Thread(New ThreadStart(AddressOf New serveurSend(client, nbClients).Run))

                ' inicia-se a execução das duas threads
                thSend.Start()
                thReceive.Start()
            End While
            ' retorna-se à escuta das solicitações
        Catch ex As Exception
            ' o erro é sinalizado
            erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
        End Try
    End Sub

    ' exibição dos erros
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' exibição do erro
        System.Console.Error.WriteLine(msg)
        ' interrupção com erro
        Environment.Exit(exitCode)
    End Sub
End Class


Public Class serveurSend
    ' classe responsável por ler as respostas digitadas no teclado
    ' e enviá-las a um cliente por meio de um cliente TCP passado ao construtor
    Private client As TcpClient    ' le client tcp
    Private numClient As Integer    ' n° de client

    ' construtor
    Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)
        ' registra-se o cliente TCP
        Me.client = client
        ' e seu número
        Me.numClient = numClient
    End Sub

    ' método Run da thread
    Public Sub Run()

        ' dados locais
        Dim OUT As StreamWriter = Nothing        ' flux d'écriture réseau
        Dim réponse As String = Nothing        ' réponse lue au clavier
        ' acompanhamento
        Console.Out.WriteLine(("Thread de lecture des réponses du serveur au client " & numClient & " lancé"))
        ' gerenciamento de erros
        Try
            ' criação do fluxo de gravação na rede
            OUT = New StreamWriter(client.GetStream())
            OUT.AutoFlush = True
            ' ciclo de entrada e envio de comandos
            While True
                ' identificação do cliente
                Console.Out.Write((numClient & " : "))
                ' leitura da resposta digitada no teclado
                réponse = Console.In.ReadLine().Trim()
                ' terminou?
                If réponse.ToLower() = "fin" Then
                    Exit While
                End If
                ' envio da resposta ao servidor
                OUT.WriteLine(réponse)
            End While
            ' próxima resposta
        Catch ex As Exception
            ' erro
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
        ' fim — fechando os fluxos
        Try
            OUT.Close()
            client.Close()
        Catch
        End Try
        ' notificando o fim do thread
        Console.Out.WriteLine(("[fin du Thread de lecture des réponses du serveur au client " & numClient & "]"))
    End Sub
End Class

Public Class serveurReceive
    ' classe responsável por ler as linhas de texto enviadas ao servidor 
    ' por meio de um cliente TCP passado ao construtor
    Private client As TcpClient     ' le client tcp
    Private numClient As Integer    ' n° de client

    ' construtor
    Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)
        ' observa-se o cliente TCP
        Me.client = client
        ' e seu número
        Me.numClient = numClient
    End Sub

    ' método Run da thread
    Public Sub Run()
        ' dados locais
        Dim [IN] As StreamReader = Nothing        ' flux lecture réseau
        Dim réponse As String = Nothing        ' réponse serveur
        ' acompanhamento
        Console.Out.WriteLine(("Thread de lecture des demandes du client " & numClient & " lancé"))
        ' gerenciamento de erros
        Try
            ' criação do fluxo de leitura de rede
            [IN] = New StreamReader(client.GetStream())
            ' loop de leitura de linhas de texto do fluxo IN
            While True
                ' leitura do fluxo de rede
                réponse = [IN].ReadLine()
                ' fluxo encerrado?
                If réponse Is Nothing Then
                    Exit While
                End If
                ' exibição
                Console.Out.WriteLine(("<-- " + réponse))
            End While
        Catch ex As Exception
            ' erro
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
        ' fim — fechando os fluxos
        Try
            [IN].Close()
            client.Close()
        Catch
        End Try
        ' notificando o fim do thread
        Console.Out.WriteLine(("[fin du Thread de lecture des demandes du client " & numClient & "]"))
    End Sub
End Class

9.4.5. Um cliente da Web

Vimos no exemplo anterior alguns dos cabeçalhos HTTP que um navegador enviava:

<-- 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 criar um cliente web ao qual seria passado como parâmetro um URL e que exibiria na tela o texto enviado pelo servidor. Suporemos que este suporte o protocolo HTTP 1.1. Dos cabeçalhos anteriores, utilizaremos apenas os seguintes:

<-- GET /exemple.html HTTP/1.1
<-- Host: localhost:88
<-- Connection: close
  • O primeiro cabeçalho indica qual página queremos
  • o segundo, qual servidor estamos consultando
  • o terceiro indica que queremos que o servidor feche a conexão após nos responder.

Se, no exemplo acima, substituirmos GET por HEAD, o servidor nos enviará apenas os cabeçalhos HTTP e não a página HTML.

Nosso cliente web será chamado da seguinte forma: clientweb URL cmd, onde URL é oURL desejada e cmd uma das duas palavras-chave GET ou HEAD para indicar se queremos 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>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

HTTP/1.1 302 Object moved

significa que a página solicitada mudou de endereço (ou seja, de URL). O novo endereço, URL, é fornecido pelo cabeçalho Location:

Location: /IISSamples/Default/welcome.htm

Se usarmos GET em vez de HEAD na chamada ao cliente Web:

dos>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:


' espaços de nomes
Imports System
Imports System.Net.Sockets
Imports System.IO


Public Class clientWeb1

    ' solicita um URL
    ' exibe o conteúdo dela na tela
    Public Shared Sub Main(ByVal args() As String)
        ' sintaxe
        Const syntaxe As String = "pg URI GET/HEAD"

        ' número de argumentos
        If args.Length <> 2 Then
            erreur(syntaxe, 1)
        End If
        ' observa-se o URI solicitado
        Dim URIstring As String = args(0)
        Dim commande As String = args(1).ToUpper()

        ' verificação da validade do URI
        Dim uri As Uri = Nothing
        Try
            uri = New Uri(URIstring)
        Catch ex As Exception
            ' URI incorreto
            erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
        End Try
        ' verificação do pedido
        If commande <> "GET" And commande <> "HEAD" Then
            ' pedido incorreto
            erreur("Le second paramètre doit être GET ou HEAD", 3)
        End If

        ' é possível trabalhar
        Dim client As TcpClient = Nothing        ' le client
        Dim [IN] As StreamReader = Nothing        ' le flux de lecture du client
        Dim OUT As StreamWriter = Nothing        ' le flux d'écriture du client
        Dim réponse As String = Nothing        ' réponse du serveur
        Try
            ' estabelecendo conexão com o servidor
            client = New TcpClient(uri.Host, uri.Port)

            ' criando os fluxos de entrada e saída do cliente TCP
            [IN] = New StreamReader(client.GetStream())
            OUT = New StreamWriter(client.GetStream())
            OUT.AutoFlush = True

            ' solicitando o URL - envio dos cabeçalhos HTTP
            OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))
            OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port))
            OUT.WriteLine("Connection: close")
            OUT.WriteLine()
            ' lê-se a resposta
            réponse = [IN].ReadLine()
            While Not (réponse Is Nothing)
                ' processa-se a resposta
                Console.Out.WriteLine(réponse)
                ' a resposta é lida
                réponse = [IN].ReadLine()
            End While
            ' concluído
            client.Close()
        Catch e As Exception
            ' gerenciamento da exceção
            erreur(e.Message, 4)
        End Try
    End Sub

    ' exibição dos erros
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' exibição do erro
        System.Console.Error.WriteLine(msg)
        ' interrupção com erro
        Environment.Exit(exitCode)
    End Sub
End Class

A única novidade neste programa é o uso da classe Uri. 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 Uri nos permite decompor a string do URL em seus diferentes elementos. Um objeto Uri é construído a partir da string URIstring recebida como parâmetro:


        ' verificação da validade do URI
        Dim uri As Uri = Nothing
        Try
            uri = New Uri(URIstring)
        Catch ex As Exception
            ' URI incorreto
            erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
        End Try

Se a string URI recebida como parâmetro não for válida (ausência do protocolo, do servidor, etc.), uma exceção é lançada. Isso nos permite verificar a validade do parâmetro recebido. Uma vez construído o objeto Uri, temos acesso aos diferentes elementos dessa Uri. Assim, se o objeto uri do código anterior tiver sido construído a partir da string http://serveur:port/cheminPageHTML?param1=val1;param2=val2;..., teremos:

uri.Host=serveur, uri.Port=port, uri.Path=cheminPageHTML, uri.Query=param1=val1;param2=val2;..., uri.pathAndQuery= cheminPageHTML?param1=val1;param2=val2;..., uri.Scheme=http.

9.4.6. Cliente da Web que gerencia redirecionamentos

O cliente web anterior não lida com um possível redirecionamento do URL que ele solicitou. O cliente seguinte lida com isso.

  1. ele lê a primeira linha dos cabeçalhos HTTP enviados pelo servidor para verificar se há a sequência “302 Object moved”, que indica um redirecionamento
  2. ele lê os cabeçalhos seguintes. Se houver redirecionamento, ele procura a linha “Location: url”, que fornece o novo URL da página solicitada, e anota esse URL.
  3. ele exibe o restante da resposta do servidor. Se houver redirecionamento, as etapas 1 a 3 são repetidas com o novo URL. O programa não aceita mais de um redirecionamento. Esse limite é definido por uma constante que pode ser alterada.

Veja um exemplo:

dos>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>

A programação é a seguinte:


' espaços de nomes
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic

' classe do cliente web
Public Class clientWeb
   
  ' solicita um URL e exibe seu conteúdo na tela
  Public Shared Sub Main(ByVal args() As String)
    ' sintaxe
    Const syntaxe As String = "pg URI GET/HEAD"

    ' número de argumentos
    If args.Length <> 2 Then
      erreur(syntaxe, 1)
    End If
    ' anota-se o URI solicitado
    Dim URIstring As String = args(0)
    Dim commande As String = args(1).ToUpper()

    ' verificação da validade do URI
    Dim uri As Uri = Nothing
    Try
      uri = New Uri(URIstring)
    Catch ex As Exception
      ' URI incorreto
      erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
    End Try 'catch
    ' verificação do pedido
    If commande <> "GET" And commande <> "HEAD" Then
      ' pedido incorreto
      erreur("Le second paramètre doit être GET ou HEAD", 3)
    End If

    ' é possível trabalhar
    Dim client As TcpClient = Nothing ' le client
    Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
    Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
    Dim réponse As String = Nothing ' réponse du serveur
    Const nbRedirsMax As Integer = 1 ' pas plus d'une redirection acceptée
    Dim nbRedirs As Integer = 0 ' nombre de redirections en cours
    Dim premièreLigne As String ' 1ère ligne de la réponse
    Dim redir As Boolean = False ' indique s'il y a redirection ou non
    Dim locationString As String = "" ' la chaîne URI d'une éventuelle redirection
    ' expressão regular para localizar um URL de redirecionamento
    Dim location As New Regex("^Location: (.+?)$") '

        ' gestão de erros
    Try
      ' é possível ter vários URL a serem solicitados caso haja redirecionamentos
      While nbRedirs <= nbRedirsMax
        ' conectamos-nos ao servidor
        client = New TcpClient(uri.Host, uri.Port)

        ' criam-se os fluxos de entrada e saída do cliente TCP
        [IN] = New StreamReader(client.GetStream())
        OUT = New StreamWriter(client.GetStream())
        OUT.AutoFlush = True

        ' enviam-se os cabeçalhos HTTP para solicitar o URL
        OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))
        OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port))
        OUT.WriteLine("Connection: close")
        OUT.WriteLine()

        ' lê-se a primeira linha da resposta
        premièreLigne = [IN].ReadLine()
        ' exibição na tela
        Console.Out.WriteLine(premièreLigne)

        ' redirecionamento?
        If Regex.IsMatch(premièreLigne, "302 Object moved$") Then
          ' há um redirecionamento
          redir = True
          nbRedirs += 1
                End If

                ' cabeçalhos HTTP a seguir até encontrar a linha vazia que indica o fim dos cabeçalhos
                Dim locationFound As Boolean = False
                réponse = [IN].ReadLine()
                While réponse <> ""
                    ' exibe-se a resposta
                    Console.Out.WriteLine(réponse)
                    ' se houver redirecionamento, procura-se o cabeçalho Location
                    If redir And Not locationFound Then
                        ' compara-se a linha com a expressão relacional location
                        Dim résultat As Match = location.Match(réponse)
                        If résultat.Success Then
                            ' se for encontrada, registra-se o URL de redirecionamento
                            locationString = résultat.Groups(1).Value
                            ' registra-se que foi encontrado
                            locationFound = True
                        End If
                    End If
                    ' linha seguinte
                    réponse = [IN].ReadLine()
                End While

                ' linhas seguintes da resposta
                Console.Out.WriteLine(réponse)
                réponse = [IN].ReadLine()
                While Not (réponse Is Nothing)
                    ' exibe-se a resposta
                    Console.Out.WriteLine(réponse)
                    ' linha seguinte
                    réponse = [IN].ReadLine()
                End While

                ' a conexão está sendo encerrada
                client.Close()
                ' já terminamos?
                If Not locationFound Or nbRedirs > nbRedirsMax Then
                    Exit While
                End If

                ' é necessário realizar um redirecionamento — estamos criando a nova URI
                URIstring = uri.Scheme + "://" & uri.Host & ":" & uri.Port & locationString
                uri = New Uri(URIstring)
                ' acompanhamento
                Console.Out.WriteLine((ControlChars.Lf + "<--Redirection vers l'URL " + URIstring + "-->" + ControlChars.Lf))
            End While
        Catch e As Exception
      ' tratando a exceção
      erreur(e.Message, 4)
        End Try
    End Sub

    ' exibição dos erros
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' exibição de erro
        System.Console.Error.WriteLine(msg)
        ' encerramento com erro
        Environment.Exit(exitCode)
    End Sub
End Class

9.4.7. Servidor de cálculo de impostos

Retomamos o exercício IMPOTS, já abordado de diversas formas. Vamos relembrar a última versão. Foi criada uma classe de imposto. Seus atributos são três tabelas de números:


Public Class impôt
    ' os dados necessários para o cálculo do imposto
    ' provêm de uma fonte externa
    Private limites(), coeffR(), coeffN() as double

A classe possui dois construtores:

  • um construtor ao qual são passadas as três tabelas de dados necessárias para o cálculo do imposto
     // fabricante 1
    Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
         ' inicializa as três matrizes de limites, coeffR, coeffN a partir
         ' dos parâmetros passados ao construtor
  • um construtor ao qual é passado o nome DSN de um banco de dados ODBC

    ' construtor 2
    Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
        ' inicializa as três matrizes de limites, coeffR, coeffN, a partir de
        ' do conteúdo da tabela Timpots do banco de dados ODBC DSNimpots
        ' colLimites, colCoeffR, colCoeffN são as três colunas dessa tabela
        ' pode gerar uma exceção

Um programa de teste havia sido escrito:

dos>vbc /r:impots.dll testimpots.vb

dos>test mysql-impots timpots limites coeffr coeffn
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22506 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 2 200000
impôt=33388 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 3 200000
impôt=16400 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 300000
impôt=50082 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 200000
impôt=22506 F

Nesse caso, o programa de teste e o objeto impôt estavam na mesma máquina. Propomos colocar o programa de teste e o objeto impôt em máquinas diferentes. Teremos uma aplicação cliente-servidor em que o objeto remoto impôt será o servidor. A nova classe se chama ServeurImpots e é derivada da classe impôt:


Public Class ServeurImpots
    Inherits impôt

    ' atributos
    Private portEcoute As Integer    ' le port d'écoute des demandes clients
    Private actif As Boolean    ' état du serveur

    ' construtor
    Public Sub New(ByVal portEcoute As Integer, ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
        MyBase.New(DSNimpots, Timpots, colLimites, colCoeffR, colCoeffN)
        ' observa-se a porta de escuta
        Me.portEcoute = portEcoute
        ' por enquanto inativo
        actif = False
        ' cria e inicia um thread para ler os comandos digitados no teclado
        ' o servidor será gerenciado a partir desses comandos
        Dim threadLecture As Thread = New Thread(New ThreadStart(AddressOf admin))
        threadLecture.Start()
    End Sub

O único parâmetro novo no construtor é a porta de escuta para as solicitações dos clientes. Os demais parâmetros são passados diretamente para a classe base impôt. O servidor de impostos é controlado por comandos digitados no teclado. Por isso, criamos um thread para ler esses comandos. Haverá dois comandos possíveis: start para iniciar o serviço e stop para encerrá-lo definitivamente. O método admin que gerencia esses comandos é o seguinte:


    Public Sub admin()
        ' lê os comandos de administração do servidor digitados no teclado
        ' em um loop infinito
        Dim commande As String = Nothing
        While True
            ' solicita
            Console.Out.Write("Serveur d'impôts>")
            ' leitura do comando
            commande = Console.In.ReadLine().Trim().ToLower()
            ' execução do comando
            If commande = "start" Then
                ' ativo?
                If actif Then
                    'erro
                    Console.Out.WriteLine("Le serveur est déjà actif")
                Else
                    ' iniciando o serviço de escuta
                    Dim threadEcoute As Thread = New Thread(New ThreadStart(AddressOf ecoute))
                    threadEcoute.Start()
                End If
            Else
                If commande = "stop" Then
                    ' fim de todas as threads de execução
                    Environment.Exit(0)
                Else
                    ' erro
                    Console.Out.WriteLine("Commande incorrecte. Utilisez (start,stop)")
                End If
            End If
        End While
    End Sub

Se o comando digitado no teclado for start, uma thread de escuta de solicitações de clientes é iniciada. Se o comando digitado for stop, todas as threads são interrompidas. A thread de escuta executa o método ecoute:


    Public Sub ecoute()
        ' thread de escuta das solicitações dos clientes
        ' criando o serviço de escuta
        Dim ecoute As TcpListener = Nothing
        Try
            ' criando o serviço
            ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), portEcoute)
            ' iniciando-o
            ecoute.Start()
            ' acompanhamento
            Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & portEcoute))

            ' ciclo do serviço
            Dim liaisonClient As TcpClient = Nothing
            While True            ' boucle infinie
                ' aguardando um cliente
                liaisonClient = ecoute.AcceptTcpClient()
                ' o serviço é executado por outra tarefa
                Dim threadClient As Thread = New Thread(New ThreadStart(AddressOf New traiteClientImpots(liaisonClient, Me).Run))
                threadClient.Start()
            End While
            ' retorna à escuta de solicitações
        Catch ex As Exception
            ' o erro é sinalizado
            erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
        End Try
    End Sub

    ' exibição dos erros
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' exibição do erro
        System.Console.Error.WriteLine(msg)
        ' encerramento com erro
        Environment.Exit(exitCode)
    End Sub

Temos um servidor TCP clássico escutando na porta portEcoute. As solicitações dos clientes são processadas pelo método Run de um objeto ao qual são passados dois parâmetros:

  1. o objeto TcpClient, que permitirá acessar o cliente
  2. o objeto impôt this, que dará acesso ao método this.calculer para o cálculo do imposto.

' -------------------------------------------------------
' presta serviço a um cliente do servidor de impostos
Public Class traiteClientImpots

    Private liaisonClient As TcpClient    ' liaison avec le client
    Private [IN] As StreamReader    ' flux d'entrée
    Private OUT As StreamWriter    ' flux de sortie
    Private objImpôt As impôt     ' objet Impôt

    ' construtor
    Public Sub New(ByVal liaisonClient As TcpClient, ByVal objImpôt As impôt)
        Me.liaisonClient = liaisonClient
        Me.objImpôt = objImpôt
    End Sub

O método Run processa as solicitações dos clientes. Essas solicitações podem assumir duas formas:

  1. cálculo para casados (s/n) nbEnfants salaireAnnuel
  2. cálculos finais

A forma 1 permite o cálculo de um imposto; a forma 2 encerra a conexão cliente-servidor.


    ' método Run
    Public Sub Run()
        ' presta serviço ao cliente
        Try
            ' fluxo de entrada
            [IN] = New StreamReader(liaisonClient.GetStream())
            ' fluxo de saída
            OUT = New StreamWriter(liaisonClient.GetStream())
            OUT.AutoFlush = True
            ' envio de uma mensagem de boas-vindas ao cliente
            OUT.WriteLine("Bienvenue sur le serveur d'impôts")

            ' ciclo de leitura de solicitação/gravação de resposta
            Dim demande As String = Nothing
            Dim champs As String() = Nothing            ' les éléments de la demande
            Dim commande As String = Nothing            ' la commande du client : calcul ou fincalculs
            demande = [IN].ReadLine()
            While Not (demande Is Nothing)
                ' decomponha a solicitação em campos
                champs = Regex.Split(demande.Trim().ToLower(), "\s+")
                ' duas solicitações aceitas: cálculo e fim dos cálculos
                commande = champs(0)
                Dim erreur As Boolean = False
                If commande <> "calcul" And commande <> "fincalculs" Then
                    ' erro do cliente
                    OUT.WriteLine("Commande incorrecte. Utilisez (calcul,fincalculs).")
                End If
                If commande = "calcul" Then
                    calculerImpôt(champs)
                End If
                If commande = "fincalculs" Then
                    ' mensagem de despedida ao cliente
                    OUT.WriteLine("Au revoir...")
                    ' liberação dos recursos
                    Try
                        OUT.Close()
                        [IN].Close()
                        liaisonClient.Close()
                    Catch
                    End Try
                    ' fim
                    Return
                End If
                ' nova solicitação
                demande = [IN].ReadLine()
            End While
        Catch e As Exception
            erreur("L'erreur suivante s'est produite (" + e.ToString + ")", 2)
        End Try
    End Sub

O cálculo do imposto é realizado pelo método calculerImpôt, que recebe como parâmetro a tabela de campos da solicitação feita pelo cliente. A validade da solicitação é verificada e, se for o caso, o imposto é calculado e devolvido ao cliente.


    ' cálculo de impostos
    Public Sub calculerImpôt(ByVal champs() As String)
        ' processa a solicitação: cálculo para casados nbEnfants salaireAnnuel
        ' decomposta em campos na tabela de campos
        Dim marié As String = Nothing
        Dim nbEnfants As Integer = 0
        Dim salaireAnnuel As Integer = 0

        ' validade dos argumentos
        Try
            ' são necessários pelo menos 4 campos
            If champs.Length <> 4 Then
                Throw New Exception
            End If
            ' casado
            marié = champs(1)
            If marié <> "o" And marié <> "n" Then
                Throw New Exception
            End If
            ' filhos
            nbEnfants = Integer.Parse(champs(2))
            ' salário
            salaireAnnuel = Integer.Parse(champs(3))
        Catch
            OUT.WriteLine(" syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel")
            ' concluído
            Exit Sub
        End Try
        ' é possível calcular o imposto
        Dim impot As Long = objImpôt.calculer(marié = "o", nbEnfants, salaireAnnuel)
        ' envia-se a resposta ao cliente
        OUT.WriteLine(impot.ToString)
    End Sub

    ' exibição de erros
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' exibição de erro
        System.Console.Error.WriteLine(msg)
        ' encerramento com erro
        Environment.Exit(exitCode)
    End Sub

Esta classe é compilada por

dos>vbc /r:impots.dll /r:system.dll /t:library srvimpots.vb

onde impots.dll contém o código da classe impôt. Um programa de teste poderia ser o seguinte:


' espaços de nomes
Imports System
Imports System.IO
Imports Microsoft.VisualBasic

Public Class testServeurImpots
    Public Shared syntaxe As String = "Syntaxe : pg port dsnImpots Timpots colLimites colCoeffR colCoeffN"

    ' programa principal
    Public Shared Sub Main(ByVal args() As String)

        ' são necessários 6 argumentos
        If args.Length <> 6 Then
            erreur(syntaxe, 1)
        End If
        ' a porta deve ser um número inteiro >0
        Dim port As Integer = 0
        Dim erreurPort As Boolean = False
        Dim E As Exception = Nothing
        Try
            port = Integer.Parse(args(0))
        Catch ex As Exception
            E = ex
            erreurPort = True
        End Try
        erreurPort = erreurPort Or port <= 0
        If erreurPort Then
            erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
        End If
        ' criando o servidor de impostos
        Try
            Dim srvimots As ServeurImpots = New ServeurImpots(port, args(1), args(2), args(3), args(4), args(5))
        Catch ex As Exception
            'erro
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
        End Try
    End Sub

    ' exibição de erros
    Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' exibição de erro
        System.Console.Error.WriteLine(msg)
        ' interrupção com erro
        Environment.Exit(exitCode)
    End Sub
End Class

Passamos ao programa de teste os dados necessários para a construção de um objeto ServeurImpots e, a partir daí, ele cria esse objeto. Esse programa de teste é compilado por:

dos>vbc /r:srvimpots.dll /r:impots.dll testimpots.vb

Aqui está um primeiro teste:

dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop

A linha

dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn

cria um objeto ServeurImpots que ainda não está escutando as solicitações dos clientes. É o comando “start”, digitado no teclado, que inicia essa escuta. O comando “stop” encerra o servidor. Vamos agora utilizar um cliente. Usaremos o cliente genérico criado anteriormente. O servidor está em execução:

dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124

O cliente genérico é iniciado em outra janela do DOS:

dos> clttcpgenerique localhost 124Commandes :
<-- Bienvenue sur le serveur d'impôts

Percebe-se que o cliente recebeu corretamente 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 encerrá-lo:

dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop