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:
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). | |
Oculta as características físicas da camada Física. Detecta e corrige erros de transmissão. | |
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. | |
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. | |
Nesta camada, encontramos serviços que permitem que uma aplicação abra e mantenha uma sessão de trabalho em uma máquina remota. | |
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. | |
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.
Transmite pacotes entre dois nós da rede | |
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. | |
faz a correspondência entre o endereço de Internet da máquina e o endereço físico da máquina | |
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:
Garante a entrega confiável de informações entre dois clientes | |
Garante a entrega não confiável de informações entre dois clientes |
Camadas: Aplicação/Apresentação/Sessão
Aqui encontramos diversos protocolos:
Emulador de terminal que permite que uma máquina A se conecte a uma máquina B como um terminal | |
permite a transferência de arquivos | |
permite a transferência de arquivos | |
permite a troca de mensagens entre usuários da rede | |
converte um nome de máquina no endereço de Internet da máquina | |
criado pela Sun MicroSystems, ele especifica uma representação padrão dos dados, independente das máquinas | |
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 | |
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:

A maioria dos métodos da classe é estática. Vejamos aqueles que nos interessam:
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. | |
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. | |
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:
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. | |
lista de aliases de uma máquina, que pode ser designada por um nome principal e por aliases | |
o nome da máquina, caso ela tenha um |
Da classe IPAddress, destacaremos o construtor, as propriedades e os métodos a seguir:

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:

Os construtores, métodos e propriedades que nos interessam são os seguintes:
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 | |
encerra a conexão com o servidor TCP | |
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:

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
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-á:
Para ler a resposta de M2, escrever-se-á:
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:

Os construtores, métodos e propriedades que nos interessam são os seguintes:
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. | |
aceita a solicitação de um cliente. Retorna como resultado um objeto TcpClient associado a outra porta, chamada de porta de serviço. | |
inicia a escuta de solicitações de clientes | |
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:
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:
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:
- um thread responsável por ler os comandos digitados no teclado e enviá-los ao servidor
- 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:
- 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):

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:
Somente após receber a linha vazia é que o servidor web responde. No exemplo, utilizamos apenas um comando:
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:
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
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:
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:

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:

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

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:
- 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
significa que a página solicitada mudou de endereço (ou seja, de URL). O novo endereço, URL, é fornecido pelo cabeçalho Location:
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.
- 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
- 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.
- 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:
- o objeto TcpClient, que permitirá acessar o cliente
- 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:
- cálculo para casados (s/n) nbEnfants salaireAnnuel
- 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
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:
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
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:
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:















