9. Programação TCP-IP
9.1. Generalidades
9.1.1. Os protocolos da Internet
Apresentamos aqui uma introdução aos protocolos de comunicação da Internet, também conhecidos como conjunto de protocolos TCP/IP (Transfer Control Protocol / Internet Protocol), cujo nome deriva dos dois protocolos principais. É aconselhável que o leitor tenha uma compreensão geral do funcionamento das redes e, em particular, dos protocolos TCP/IP antes de abordar a criação de aplicações distribuídas.
O texto que se segue é uma tradução parcial de um texto que se encontra no documento «Lan Workplace for Dos - Administrator's Guide» da NOVELL, um documento do início dos anos 90.
O conceito geral de criar uma rede de computadores heterogéneos tem origem em investigações realizadas pela DARPA (Defense Advanced Research Projects Agency) nos Estados Unidos. A DARPA desenvolveu o conjunto de protocolos conhecido como TCP/IP, que permite que máquinas heterogéneas comuniquem entre si. Estes protocolos foram testados numa rede denominada ARPAnet, rede que mais tarde se tornou a rede INTERNET. Os protocolos TCP/IP definem formatos e regras de transmissão e receção independentes da organização das redes e do equipamento utilizado.
A rede concebida pelo DARPA e gerida pelos protocolos TCP/IP é uma rede de comutação de pacotes. Uma rede deste tipo transmite a informação pela rede em pequenos pedaços chamados pacotes. Assim, se um computador transmitir um ficheiro 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 destes pacotes, nomeadamente:
- 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 (Open Systems Interconnection Reference Model), definido pela ISO (Organização Internacional de Normalização). Este modelo descreve uma rede ideal em que a comunicação entre máquinas pode ser representada por um modelo de sete camadas:
![]() |
Cada camada recebe serviços da camada inferior e fornece os seus à camada superior. Suponhamos que duas aplicações localizadas em máquinas A e B diferentes pretendem comunicar: fazem-no ao nível da camada Application. Não precisam de conhecer todos os detalhes do funcionamento da rede: cada aplicação entrega a informação que pretende transmitir à camada inferior: a camada Présentation. A aplicação só precisa, portanto, de conhecer as regras de interface com a camada Présentation.
Assim que a informação chega à camada Présentation, é encaminhada, de acordo com outras regras, para a camada Session e assim sucessivamente, até que a informação chegue ao suporte físico e seja transmitida fisicamente para a máquina de destino. Aí, será submetida ao processo inverso ao que sofreu na máquina remetente.
Em cada camada, o processo remetente encarregado de enviar a informação transmite-a a um processo recetor na outra máquina, pertencente à mesma camada que ele. Faz-o de acordo com determinadas regras a que se chama protocolo da camada. Temos, portanto, o seguinte esquema de comunicação final:
![]() |
A função das diferentes camadas é a seguinte:
Assegura a transmissão de bits num 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 a este nível são: . a escolha do tipo de codificação da informação (analógica ou digital) . a escolha do modo de transmissão (síncrono ou assíncrono). | |
Oculta as características físicas da camada Física. Deteta e corrige os erros de transmissão. | |
Gere o percurso que as informações enviadas pela rede devem seguir. A isto chama-se routage: determinar o percurso que uma informação deve seguir para chegar ao seu destinatário. | |
Permite a comunicação entre duas aplicações, enquanto as camadas anteriores apenas permitiam a comunicação entre máquinas. Um serviço prestado por esta camada pode ser o multiplexamento: a camada de transporte poderá utilizar uma mesma ligaçã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 a uma aplicação abrir e manter uma sessão de trabalho numa máquina remota. | |
O seu objetivo é 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, serão formatadas de outra forma para que a aplicação da máquina B as reconheça. | |
Nesta fase, encontram-se as aplicações geralmente mais próximas do utilizador, tais como o correio eletrónico ou a transferência de ficheiros. |
9.1.3. O modelo TCP/IP
O modelo OSI é um modelo ideal que ainda nunca foi concretizado. O conjunto de protocolos TCP/IP aproxima-se dele da seguinte forma:
![]() |
Camada Física
Numa rede local, encontra-se geralmente a tecnologia Ethernet ou Token-Ring. Aqui, apresentamos apenas a tecnologia Ethernet.
Ethernet
É o nome dado a uma tecnologia de redes locais de comutação de pacotes inventada na PARC Xerox no início da década de 1970 e normalizada 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 um comprimento máximo de 500 m. Pode ser estendida por meio de répéteurs, não podendo duas máquinas estar separadas por mais de dois repetidores. O cabo é passivo: todos os elementos ativos encontram-se nos equipamentos ligados ao cabo. Cada equipamento está ligado ao cabo através de uma placa de acesso à rede que inclui:
- um transmissor (transceiver) que deteta 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/segundo.
- Topologia em barramento: todos os equipamentos estão ligados ao mesmo cabo
![]() |
- Rede de difusão — Um equipamento que transmite transfere informações pelo cabo com a morada do equipamento destinatário. Todos os equipamentos ligados recebem então essas informações e apenas aquele a quem se destinam as retém.
- O método de acesso é o seguinte: o transmissor que pretende transmitir escuta o cabo — deteta então a presença ou ausência de uma onda portadora, cuja presença significaria que está em curso uma transmissão. Trata-se da técnica CSMA (Carrier Sense Multiple Access). Na ausência de portadora, um transmissor pode decidir transmitir à sua vez. Podem ser vários os que tomam essa decisão. Os sinais emitidos misturam-se: diz-se que ocorre uma colisão. O transmissor deteta esta situação: ao mesmo tempo que emite no cabo, escuta o que realmente passa por ele. Se detetar que a informação que transita pelo cabo não é a que emitiu, deduz que há uma colisão e deixará de emitir. Os outros transmissores que estavam a emitir farão o mesmo. Cada um retomará a sua transmissão após um intervalo aleatório, que depende de cada transmissor. Esta técnica é designada por CD (Detecção de Colisão). O método de acesso é, assim, designado por CSMA/CD.
- Um endereçamento de 48 bits. Cada máquina possui um endereço, aqui denominado endereço físico, que está gravado na placa que a liga ao cabo. Este endereço é designado por 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 no próprio âmbito do protocolo IP. | |
estabelece 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:
Assegura uma entrega fiável de informações entre dois clientes | |
Assegura a entrega não fiável de informações entre dois clientes |
Camadas Aplicação/Apresentação/Sessão
Aqui encontram-se vários protocolos:
Emulador de terminal que permite que uma máquina A se ligue a uma máquina B como terminal | |
permite a transferência de ficheiros | |
permite a transferência de ficheiros | |
permite a troca de mensagens entre utilizadores da rede | |
converte um nome de computador num endereço de Internet do computador | |
criado pela Sun MicroSystems, especifica uma representação padrão dos dados, independente dos computadores | |
também definido pela Sun, é um protocolo de comunicação entre aplicações remotas, independente da camada de transporte. Este protocolo é importante: liberta o programador do conhecimento dos detalhes da camada de transporte e torna as aplicações portáveis. Este protocolo baseia-se no protocolo XDR | |
também definido pela Sun; este protocolo permite que uma máquina «veja» o sistema de ficheiros de outra máquina. Baseia-se no protocolo RPC anterior |
9.1.4. Funcionamento dos protocolos da Internet
As aplicações desenvolvidas no ambiente TCP/IP utilizam geralmente vários dos protocolos desse ambiente. Um programa de aplicação comunica com a camada mais elevada dos protocolos. Esta transmite a informação à camada inferior e assim sucessivamente até chegar ao suporte físico. Aí, a informação é fisicamente transferida para o equipamento de destino, onde voltará a atravessar as mesmas camadas, desta vez no sentido inverso, até chegar à aplicação de destino das informações enviadas. O esquema seguinte mostra o percurso da informação:
![]() |
Vejamos um exemplo: a aplicação FTP, definida ao nível da camada Application e que permite a transferência de ficheiros entre máquinas.
- A aplicação fornece uma sequência de bytes a transmitir à camada transport.
- A camada transport divide esta sequência de bytes em segments e TCP, e acrescenta, no início de cada segmento, o número do mesmo. 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 deste pacote, coloca os endereços de Internet das máquinas de origem e de destino. Determina também o endereço físico da máquina de destino. Tudo isto é encaminhado para a camada de ligação de dados e ligação física, ou seja, para a placa de rede que liga a máquina à rede física.
- Aí, o pacote IP é, por sua vez, encapsulado numa trama física e enviado ao seu destinatário através do cabo.
- Na máquina de destino, a camada de Ligação de Dados e Ligação Física faz o inverso: desencapsula o pacote IP da trama física e passa-o para a camada IP.
- A camada IP verifica se o pacote está correto: calcula uma soma, em função dos bits recebidos (checksum), soma essa que deve ser encontrada no cabeçalho do pacote. Se não for esse o caso, o pacote é rejeitado.
- Se o pacote for considerado correto, a camada IP desencapsula o segmento TCP que se encontra no seu interior e transmite-o à camada superior, a transport.
- A camada transport — a camada TCP no nosso exemplo — examina o número do segmento para restabelecer a ordem correta dos segmentos.
- Calcula também uma soma de verificação para o segmento TCP. Se for considerada correta, a camada TCP envia um aviso de receção à máquina de origem; caso contrário, o segmento TCP é rejeitado.
- Resta agora à camada TCP transmitir a parte de dados do segmento à aplicação destinatária 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 ficheiros, ou qualquer coisa que possa comunicar utilizando os protocolos TCP/IP. Cada nó possui um endereço físico cujo formato depende do tipo de rede. Numa rede Ethernet, o endereço físico é codificado em 6 octetos. 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: é independente do equipamento e da rede utilizada. Trata-se de um endereço de 4 octetos que identifica simultaneamente uma rede local e um nó dessa rede. O endereço de Internet é normalmente representado sob a forma de 4 números, correspondentes aos valores dos 4 octetos, 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. Esta rede pode ter até 254 nós.
Como as moradas de Internet ou moradas IP são independentes da rede, um computador de uma rede A pode comunicar com um computador de uma rede B sem se preocupar com o tipo de rede em que se encontra: basta que conheça a sua morada IP. O protocolo IP de cada rede encarrega-se de efetuar a conversão entre a morada IP e a morada física, em ambos os sentidos.
As endereços IP devem ser todas diferentes. Em França, é a INRIA que se encarrega de atribuir as endereços IP. Na verdade, esta entidade atribui um endereço à sua rede local, por exemplo, 193.49.144.0 para a rede da Faculdade de Ciências de Angers. O administrador dessa rede pode, em seguida, atribuir os endereços IP 193.49.144.1 a 193.49.144.254 como entender. Este endereço é geralmente registado num ficheiro específico de cada máquina ligada à rede.
9.1.5.1. As classes de endereços IP
Um endereço IP é uma sequência de 4 octetos, frequentemente indicada como I1.I2.I3.I4, que contém, na verdade, dois endereços:
- o endereço da rede
- o endereço de um nó dessa rede
Dependendo do tamanho destes 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 a forma R1.N1.N2.N3, em que
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 de classe A é o seguinte:
![]() |
O endereço de rede ocupa 7 bits e o endereço do nó, 24 bits. Assim, podem existir 127 redes de classe A, cada uma com até 224 nós.
Classe B
Neste caso, o endereço IP: I1.I2.I3.I4 tem o formato R1.R2.N1.N2, em que
R1.R2 | é a morada da rede |
N1.N2 | é o endereço de um computador nessa rede |
Mais precisamente, o formato de um endereço IP de classe B é o seguinte:
![]() |
O endereço da rede ocupa 2 octetos (14 bits, exatamente), tal como o do nó. Assim, podem existir 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, em que
R1.R2.R3 | é a morada da rede |
N1 | é o endereço de um computador nessa rede |
Mais precisamente, o formato de um endereço IP de classe C é o seguinte:
![]() |
O endereço de rede ocupa 3 octetos (menos 3 bits) e o endereço do nó ocupa 1 octeto. Assim, podem existir 221 redes de classe C com até 256 nós.
Sendo o endereço do computador Lagaffe da Faculdade de Ciências de Angers 193.49.144.1, verifica-se que o byte de peso forte vale 193, ou seja, em binário 11000001. Deduz-se, assim, que a rede é de classe C.
Endereços reservados
- Algumas moradas IP são moradas de rede, em vez de moradas de nós na rede. São aquelas em que a parte do nó é definida 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, numa morada IP, a morada do nó é composta apenas por 1, temos então uma morada de difusão: esta morada designa todos os nós da rede.
- Numa rede de classe C, que permite teoricamente 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 de Internet <--> Endereço físico
Vimos que, durante a transmissão de informações de uma máquina para outra, estas, ao atravessarem a camada IP, eram encapsuladas em pacotes. Estes têm a seguinte forma:
![]() |
O pacote IP contém, portanto, os endereços de Internet das máquinas de origem e de destino. Quando este pacote for transmitido à camada responsável por o enviar para a rede física, são-lhe adicionadas outras informações para formar a trama física que será finalmente enviada para a rede. Por exemplo, o formato de uma trama numa rede Ethernet é o seguinte:
![]() |
Na trama final, constam os endereços físicos dos computadores de origem e de destino. Como é que estes são obtidos?
O equipamento remetente, conhecendo o endereço IP do equipamento com o qual pretende comunicar, obtém o endereço físico deste último utilizando um protocolo específico denominado ARP (Address Resolution Protocol).
- Envia um pacote de um tipo especial, denominado pacote ARP, que contém a morada IP da máquina cuja morada física se pretende obter. Também se certificou de incluir nesse pacote a sua própria morada IP, bem como a sua morada física.
- Este pacote é enviado a todos os nós da rede.
- Estes reconhecem a natureza especial do pacote. O nó que reconhece a sua morada IP no pacote responde enviando ao remetente do pacote a sua morada física. Como é que isso é possível? Encontrou no pacote as moradas IP e a morada física do remetente.
- O remetente recebe, assim, a morada física que procurava. Armazena-a na memória para poder utilizá-la posteriormente, caso seja necessário enviar outros pacotes para o mesmo destinatário.
O endereço IP de uma máquina está normalmente registado num dos seus ficheiros, pelo que pode consultá-lo para o conhecer. Este endereço pode ser alterado: basta editar o ficheiro. O endereço físico, por sua vez, está registado na memória da placa de rede e não pode ser alterado.
Quando um administrador pretende organizar a sua rede de forma diferente, pode ser necessário alterar os endereços IP de todos os nós e, por conseguinte, editar os diferentes ficheiros de configuração de cada um deles. Isto pode ser moroso e dar origem a erros, caso haja 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 ficheiro onde a máquina deveria encontrar o seu endereço IP. Ao verificar que não possui o endereço IP, a máquina solicita-o através de um protocolo denominado RARP (Reverse Address Resolution Protocol). Em seguida, envia para a rede um pacote especial denominado pacote RARP, análogo ao pacote ARP anterior, no qual inclui a sua morada física. Este pacote é enviado a todos os nós, que reconhecem então um pacote RARP. Um deles, denominado servidor RARP, possui um ficheiro que indica a correspondência entre a morada física e a morada IP de todos os nós. Responde então ao remetente do pacote RARP, reenviando-lhe a sua morada IP. Um administrador que pretenda reconfigurar a sua rede só tem, portanto, de editar o ficheiro de correspondências do servidor RARP. Este deve, normalmente, ter uma morada fixa IP, que deve poder conhecer sem ter de utilizar ele próprio o protocolo RARP.
9.1.6. A camada de rede denominada camada IP da Internet
O protocolo IP (Internet Protocol) define a forma que os pacotes devem assumir e a forma como devem ser geridos durante a sua transmissão ou receção. Este tipo específico de pacote é denominado datagrama IP. Já o apresentámos:
![]() |
O importante é que, para além dos dados a transmitir, o datagrama IP contém os endereços de Internet dos equipamentos de origem e de destino. Assim, o equipamento de destino sabe quem lhe está a enviar uma mensagem.
Ao contrário de uma trama de rede, cujo comprimento é determinado pelas características físicas da rede pela qual transita, o comprimento do datagrama IP é definido pelo software e será, portanto, 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 numa trama física. Apresentámos o exemplo da trama física de uma rede Ethernet:
![]() |
As tramas físicas circulam de nó em nó até ao seu destino, que pode não se encontrar na mesma rede física que a máquina remetente. O pacote IP pode, portanto, ser encapsulado sucessivamente em diferentes tramas físicas nos nós que fazem a ligação entre duas redes de tipos diferentes. Também é possível que o pacote IP seja demasiado grande para ser encapsulado numa trama física. O software IP do nó onde este problema ocorre divide então o pacote IP em fragments de acordo com regras precisas, sendo cada um deles posteriormente enviado pela rede física. Só serão reagrupados no seu destino final.
9.1.6.1. O encaminhamento
O encaminhamento é o método utilizado para encaminhar os pacotes IP até ao seu destino. Existem dois métodos: o encaminhamento direto e o encaminhamento indireto.
Roteamento direto
O encaminhamento direto refere-se ao encaminhamento de um pacote IP diretamente do remetente para o destinatário dentro da mesma rede:
- A máquina remetente de um datagrama IP tem a morada IP do destinatário.
- Obtém a morada física deste último através do protocolo ARP ou nas suas tabelas, caso essa morada já tenha sido obtida.
- Envia o pacote pela rede para essa morada física.
Roteamento indireto
O encaminhamento indireto refere-se ao encaminhamento de um pacote IP para um destino situado numa rede diferente daquela a que pertence o remetente. Neste 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 este facto. Envia então o pacote para um nó especial denominado router (router), nó que liga uma rede local a outras redes e cujo endereço IP encontra nas suas tabelas, endereço obtido inicialmente num ficheiro, numa memória permanente ou ainda através de informações que circulam na rede.
Um router está ligado a duas redes e possui um endereço IP no interior 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 tem o endereço 193.49.145.0.
- Dentro da rede n.º 1, o router 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 router consiste em converter o pacote IP que recebe — e que está contido numa trama física típica da rede n.º 1 — numa trama física que possa circular na rede n.º 2. Se a morada IP do destinatário do pacote estiver na rede n.º 2, o router enviará o pacote diretamente para ele; caso contrário, enviá-lo-á para outro router, ligando a rede n.º 2 a uma rede n.º 3 e assim sucessivamente.
9.1.6.2. Mensagens de erro e de controlo
Ainda na camada de rede, ou seja, ao mesmo nível que o protocolo IP, existe o protocolo ICMP (Internet Control Message Protocol). Este serve para enviar mensagens sobre o funcionamento interno da rede: nós em avaria, congestionamento num router, 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, uma aplicação, por si só, nunca deteta estes problemas específicos da rede. Um nó utilizará as informações ICMP para atualizar as suas tabelas de encaminhamento.
9.1.7. A camada de transporte: os protocolos UDP e TCP
9.1.7.1. O protocolo UDP: Protocolo de Datagrama do Utilizador
O protocolo UDP permite uma troca não fiável de dados entre dois pontos, ou seja, o encaminhamento correto de um pacote para o seu destino não é garantido. A aplicação, se assim o desejar, pode gerir isso por si própria, aguardando, por exemplo, após o envio de uma mensagem, um aviso de receção, antes de enviar a seguinte.
Por enquanto, ao nível da rede, falámos de endereços IP de máquinas. No entanto, numa mesma máquina podem coexistir simultaneamente diferentes processos, todos eles capazes de comunicar entre si. Por isso, ao enviar uma mensagem, é necessário indicar não só o endereço IP da máquina destinatária, mas também o «nome» do processo destinatário. Este nome é, na verdade, um número, denominado número de porta. Alguns números estão reservados para aplicações padrão: a porta 69 para a aplicação tftp (trivial file transfer protocol), por exemplo. Os pacotes geridos pelo protocolo UDP são também designados por datagramas. Têm a seguinte forma:
![]() |
Estes datagramas serão encapsulados em pacotes IP e, posteriormente, em tramas físicas.
9.1.7.2. O protocolo TCP: Protocolo de Controlo de Transferência
Para comunicações seguras, o protocolo UDP é insuficiente: o programador de aplicações deve criar ele próprio um protocolo que lhe permita detetar o encaminhamento correto dos pacotes.
O protocolo TCP (Transfer Control Protocol) evita estes problemas. As suas características são as seguintes:
- O processo que pretende transmitir estabelece, em primeiro lugar, uma ligação com o processo destinatário das informações que vai transmitir. Esta ligação é estabelecida entre uma porta da máquina emissora e uma porta da máquina recetora. Entre as duas portas é criado um caminho virtual, que ficará reservado exclusivamente aos dois processos que estabeleceram a ligação.
- Todos os pacotes enviados pelo processo de origem seguem este caminho virtual e chegam na ordem em que foram enviados, o que não era garantido no protocolo UDP, uma vez que os pacotes podiam seguir caminhos diferentes.
- A informação transmitida tem um caráter contínuo. O processo emissor envia informações ao seu próprio ritmo. Estas não são necessariamente enviadas de imediato: o protocolo TCP aguarda até ter quantidade suficiente para as enviar. São armazenadas numa estrutura denominada segmento TCP. Este segmento, uma vez preenchido, será transmitido para a camada IP, onde será encapsulado num pacote IP.
- Cada segmento enviado pelo protocolo TCP é numerado. O protocolo TCP destinatário verifica se recebe os segmentos na sequência correta. Por cada segmento recebido corretamente, envia um aviso de receção ao remetente.
- Quando este último o recebe, informa o processo emissor. Este pode, assim, saber que um segmento chegou ao destino, o que não era possível com o protocolo UDP.
- Se, após algum tempo, o protocolo TCP que emitiu um segmento não receber uma confirmação de receção, reenvia 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 comunicam entre si é o full-duplex: isto significa que a informação pode transitar nos dois sentidos. Assim, o processo de destino pode enviar confirmações de receção mesmo enquanto o processo de origem continua a enviar informações. Isto permite, por exemplo, que o protocolo de origem TCP envie vários segmentos sem esperar por um aviso de receção. Se, após algum tempo, verificar que não recebeu o aviso de receção de um determinado segmento n.º n, 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 vários protocolos padrão:
TELNET
Este protocolo permite que um utilizador de uma máquina A da rede se ligue a uma máquina B (frequentemente designada por máquina anfitriã). O TELNET emula na máquina A um terminal denominado universal. O utilizador comporta-se, assim, como se dispusesse de um terminal ligado à máquina B. O Telnet baseia-se no protocolo TCP.
FTP: (Protocolo de Transferência de Ficheiros)
Este protocolo permite a troca de ficheiros entre duas máquinas remotas, bem como operações com ficheiros, tais como a criação de diretórios, por exemplo. Baseia-se no protocolo TCP.
TFTP: (Controlo Trivial de Transferência de Ficheiros)
Este protocolo é uma variante do FTP. Baseia-se no protocolo UDP e é menos sofisticado do que o FTP.
DNS: (Sistema de Nomes de Domínio)
Quando um utilizador pretende trocar ficheiros com um computador remoto, por exemplo, através do FTP, tem de conhecer o endereço de Internet desse computador. Por exemplo, para efetuar FTP na máquina Lagaffe da Universidade de Angers, seria necessário executar o FTP da seguinte forma: FTP 193.49.144.1
Isto obriga a ter um diretório que estabeleç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 Universidade de Angers com o endereço ISERPA
É evidente que seria mais prático designar uma máquina por um nome em vez de pela sua morada IP. Coloca-se então o problema da unicidade do nome: existem milhões de máquinas interligadas. Poder-se-ia imaginar que uma entidade centralizada atribuísse os nomes. Isso seria, sem dúvida, bastante pesado. O controlo dos nomes foi, de facto, distribuído por domínios. Cada domínio é gerido por uma entidade geralmente muito ágil, que tem total liberdade na escolha dos nomes das máquinas. Assim, as máquinas em França pertencem ao domínio «fr», gerido pelo Inria de Paris. Para continuar a simplificar as coisas, o controlo é ainda mais distribuído: são criados domínios no interior do domínio «fr». Assim, a Universidade de Angers pertence ao domínio «univ-Angers». O serviço que gere este domínio tem total liberdade para nomear as máquinas da rede da Universidade de Angers. Por enquanto, este domínio não foi subdividido. Mas numa grande universidade com muitas máquinas em rede, tal subdivisão poderia ocorrer.
A máquina DPX2/320 da Universidade de Angers foi designada Lagaffe, enquanto um PC e um 486DX50 foram designados liny. Como referenciar estas máquinas a partir do exterior? Especificando a hierarquia dos domínios a que pertencem. Assim, o nome completo da máquina Lagaffe será:
Lagaffe.univ-Angers.fr
Dentro dos domínios, podem ser utilizados nomes relativos. Assim, dentro do domínio fr e fora do domínio univ-Angers, a máquina Lagaffe poderá ser referenciada por
Lagaffe.univ-Angers
Por fim, dentro do domínio univ-Angers, poderá ser referenciada simplesmente por
Lagaffe
Uma aplicação pode, portanto, referenciar uma máquina pelo seu nome. No fim de contas, é necessário obter o endereço de Internet dessa máquina. Como é que isso é feito? Suponhamos que, a partir de uma máquina A, se pretenda comunicar com uma máquina B.
- Se a máquina B pertencer ao mesmo domínio que a máquina A, provavelmente encontrar-se-á o seu endereço IP num ficheiro da máquina A.
- Caso contrário, a máquina A encontrará, noutro ficheiro ou no mesmo que anteriormente, uma lista de alguns servidores de nomes com os seus endereços IP. Um servidor de nomes é responsável por estabelecer a correspondência entre o nome de uma máquina e o seu endereço IP. A máquina A enviará um pedido especial ao primeiro servidor de nomes da sua lista, denominado pedido DNS, incluindo, portanto, o nome da máquina procurada. Se o servidor consultado tiver esse nome nos seus registos, enviará à máquina A o endereço IP correspondente. Caso contrário, o servidor também encontrará nos seus ficheiros uma lista de servidores de nomes que pode consultar. E assim o fará. Desta forma, serão consultados vários servidores de nomes, não de forma aleatória, mas de modo a minimizar o número de pedidos. Se a máquina for finalmente encontrada, a resposta será enviada de volta à máquina A.
XDR: (Representação de dados eXternal)
Criado pela Sun MicroSystems, este protocolo especifica uma representação padrão dos dados, independente do equipamento.
RPC: (Remote Procedure Call)
Também definido pela Sun, trata-se de um protocolo de comunicação entre aplicações remotas, independente da camada de transporte. Este protocolo é importante: liberta o programador do conhecimento dos detalhes da camada de transporte e torna as aplicações portáveis. Este protocolo baseia-se no protocolo XDR
NFS: Sistema de Ficheiros em Rede
Também definido pela Sun, este protocolo permite que uma máquina «veja» o sistema de ficheiros de outra máquina. Baseia-se no protocolo RPC anterior.
9.1.9. Conclusão
Apresentámos nesta introdução algumas linhas gerais dos protocolos da Internet. Para aprofundar este tema, pode-se ler o excelente livro de Douglas Comer:
Título | TCP/IP: Arquitetura, Protocolos, Aplicações. |
Autor | Douglas COMER |
Editora | InterEditions |
9.2. Gestão de endereços de rede
Um computador na Internet é identificado de forma única por um endereço IP (Protocolo de Internet) com o formato I1.I2.I3.I4, em que In é um número entre 1 e 254. Pode também ser identificado por um nome igualmente único. Este nome não é obrigatório, uma vez que as aplicações acabam sempre por utilizar os endereços IP dos computadores. Servem apenas para facilitar a vida dos utilizadores. Assim, é mais fácil, com um navegador, aceder ao endereço http://www.ibm.com do que ao endereço URL http://129.42.17.99, embora ambos os métodos sejam possíveis. A associação entre o endereço IP <--> nomMachine é assegurada por um serviço distribuído da Internet denominado DNS (Domain Name System). A plataforma .NET disponibiliza a classe Dns para gerir os 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 for 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 for encontrada. | |
retorna o nome da máquina na qual está a ser executado o programa que está a executar 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. Será esse o caso se tiver várias placas de rede que a liguem 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 tenha um |
Da classe IPAddress, destacamos o construtor, as propriedades e os métodos seguintes:

Um objeto [IPAddress] pode ser transformado numa cadeia I1.I2.I3.I4 com o método ToString(). Por outro lado, é possível obter um objeto IPAddress a partir de uma cadeia de caracteres I1.I2.I3.I4 utilizando o método estático IPAddress.Parse("I1.I2.I3.I4"). Consideremos o seguinte programa, que apresenta o nome da máquina na qual está a ser 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
O programa é o 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 do computador local
' e, em seguida, fornece informações de forma interativa sobre as máquinas da rede
' identificadas por um nome ou um 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
' introduçã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ções
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 uma aplicação AppA da máquina A pretende comunicar com uma aplicação AppB da máquina B na Internet, tem de saber várias coisas:
- o endereço IP ou o nome da máquina B
- o número da porta com a qual a aplicação AppB opera. Com efeito, a máquina B pode suportar várias aplicações que operam na Internet. Quando recebe informações provenientes da rede, tem de saber a que aplicação essas informações se destinam. As aplicações da máquina B têm acesso à rede através de interfaces, também denominadas portas de comunicação. Esta informação está contida no pacote recebido pela máquina B, para que seja entregue à aplicação correta.
- Os protocolos de comunicação compreendidos pela máquina B. No nosso estudo, utilizaremos apenas os protocolos TCP-IP.
- O protocolo de diálogo aceite pela aplicação AppB. Com efeito, as máquinas A e B vão «comunicar» entre si. O que vão dizer será encapsulado nos protocolos TCP-IP. No entanto, quando, no final da cadeia, a aplicação AppB receber a informação enviada pela aplicação AppA, terá de ser capaz de a interpretar. Isto é análogo à situação em que duas pessoas, A e B, comunicam por telefone: o seu diálogo é transportado pelo telefone. A fala será codificada sob a forma de sinais pelo telefone A, transportada por linhas telefónicas, chegará ao telefone B para aí ser descodificada. A pessoa B ouve então as palavras. É aqui que entra o conceito de protocolo de diálogo: se A falar francês e B não compreender essa língua, A e B não poderão dialogar de forma útil.
Por isso, as duas aplicações que comunicam entre si têm de chegar a acordo quanto ao tipo de diálogo que vão adotar. Por exemplo, o diálogo com um serviço ftp não é o mesmo que com um serviço pop: estes dois serviços não aceitam os mesmos comandos. Têm um protocolo de diálogo diferente.
9.3.2. As características do protocolo TCP
Aqui, iremos analisar apenas as comunicações de rede que utilizam o protocolo de transporte TCP. Recorde-se aqui as características deste protocolo:
- O processo que pretende transmitir estabelece, em primeiro lugar, uma ligação com o processo destinatário das informações que vai transmitir. Esta ligação é estabelecida entre uma porta da máquina emissora e uma porta da máquina recetora. Entre as duas portas é criado um caminho virtual, que ficará reservado exclusivamente aos dois processos que estabeleceram a ligação.
- Todos os pacotes enviados pelo processo de origem seguem este caminho virtual e chegam na ordem em que foram enviados
- A informação transmitida tem um caráter contínuo. O processo emissor envia informações ao seu próprio ritmo. Estas não são necessariamente enviadas de imediato: o protocolo TCP aguarda até ter quantidade suficiente para as enviar. São armazenadas numa estrutura denominada segmento TCP. Este segmento, uma vez preenchido, será transmitido para a camada IP, onde será encapsulado num pacote IP.
- Cada segmento enviado pelo protocolo TCP é numerado. O protocolo TCP destinatário verifica se recebe os segmentos na sequência correta. Por cada segmento recebido corretamente, envia um aviso de receção ao remetente.
- Quando este último o recebe, informa o processo emissor. Este pode, assim, saber que um segmento chegou ao destino.
- Se, após algum tempo, o protocolo TCP que emitiu um segmento não receber uma confirmação de receção, reenvia 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 comunicam entre si é o full-duplex: isto significa que a informação pode transitar nos dois sentidos. Assim, o processo de destino pode enviar confirmações de receção mesmo enquanto o processo de origem continua a enviar informações. Isto permite, por exemplo, que o protocolo de origem TCP envie vários segmentos sem esperar por um aviso de receção. Se, após algum tempo, verificar que não recebeu o aviso de receção de um determinado segmento n.º n, 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 ligação para solicitar um serviço à máquina B, especificando que pretende estabelecer uma ligação com o serviço SB1 da máquina B. Esta aceita ou recusa. Se aceitar, a máquina A pode enviar os seus pedidos ao serviço SB1. Estes devem estar em conformidade com o protocolo de diálogo compreendido pelo serviço SB1. Estabelece-se assim um diálogo de pedido-resposta entre a máquina A, a que se chama máquina cliente, e a máquina B, a que se chama máquina servidor. Um dos dois parceiros encerrará a ligação.
9.3.4. Arquitetura de um cliente
A arquitetura de um programa de rede que recorre aos serviços de uma aplicação 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 presta 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 forma diferente o pedido de ligação inicial de um cliente das suas solicitações posteriores destinadas a obter um serviço. O programa não presta o serviço propriamente dito. Se o fizesse, durante o período em que o serviço estivesse a ser prestado, deixaria de estar à escuta dos pedidos de ligação e os clientes não seriam, então, atendidos. Por isso, procede de outra forma: assim que uma solicitação de ligação é recebida na porta de escuta e, em seguida, aceite, o servidor cria uma tarefa encarregada de prestar o serviço solicitado pelo cliente. Este serviço é prestado numa outra porta da máquina servidor, denominada porta de serviço. Desta forma, é possível atender 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. Está definida da seguinte forma:

Os construtores, métodos e propriedades que nos interessam são os seguintes:
cria uma ligação TCP com o servidor a operar na porta indicada (port) da máquina indicada (hostname). Por exemplo, new TcpClient("istia.univ-angers.fr",80) para se ligar à porta 80 da máquina istia.univ-angers.fr | |
encerra a ligação ao servidor TCP | |
obtém um fluxo NetworkStream de leitura e escrita para o servidor. É este fluxo que permite as trocas entre o cliente e o 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 deriva da classe Stream. Muitas aplicações cliente-servidor trocam linhas de texto terminadas pelos caracteres de fim de linha «\r\n». Por isso, é interessante utilizar os objetos StreamReader e StreamWriter para ler e escrever essas linhas no fluxo de rede. Quando duas máquinas comunicam entre si, existe, em cada extremidade da ligação, um objeto TcpClient. O método GetStream deste objeto permite aceder ao fluxo de rede (NetworkStream) que liga as duas máquinas. Assim, se uma máquina M1 tiver estabelecido uma ligação com uma máquina M2 através de um objeto TcpClient client1, pelo qual trocam linhas de texto, poderá criar os seus fluxos de leitura e escrita da seguinte forma:
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 escrita de client1 não passará por um buffer intermédio, mas irá diretamente para a rede. Este ponto é importante. Em geral, quando client1 envia uma linha de texto ao seu parceiro, espera uma resposta. Essa resposta nunca chegará se a linha tiver sido, na realidade, armazenada num buffer na 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
Temos agora os elementos necessários para definir a arquitetura básica de um cliente de 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
' estabelece-se ligação 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 pedido-resposta
While True
' prepara-se o pedido
demande = ...
' envia-se a solicitação para o servidor
OUT.WriteLine(demande)
' lê-se a resposta do servidor
réponse = [IN].ReadLine()
' processa-se a resposta
...
End While
' concluído
client.Close()
Catch ex As Exception
' gestiona-se a exceção
...
End Try
9.3.9. A classe TcpListener
A classe TcpListener é a classe adequada para representar um serviço TCP. Está definida da seguinte forma:

Os construtores, métodos e propriedades que nos interessam são os seguintes:
cria um serviço TCP que irá aguardar (listen) pelos pedidos dos clientes numa porta passada como parâmetro (port), denominada porta de escuta da máquina local com a morada IP localadr. | |
aceita o pedido de um cliente. Devolve como resultado um objeto TcpClient associado a outra porta, denominada porta de serviço. | |
inicia a escuta de pedidos dos clientes | |
interrompe a escuta de pedidos dos clientes |
9.3.10. Arquitetura básica de um servidor da 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
' cria-se o serviço
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
' inicia-se o serviço
ecoute.Start()
' ciclo do serviço
Dim liaisonClient As TcpClient = Nothing
While not fini
' aguarda um cliente
liaisonClient = ecoute.AcceptTcpClient()
' o serviço é assegurado por outra tarefa
Dim tache As Thread = New Thread(New ThreadStart(AddressOf [méthode]))
tache.Start()
End While
Catch ex As Exception
' é sinalizado o erro
....
End Try
' fim do serviço
ecoute.Stop()
A classe Service é um thread que poderia ter o seguinte aspeto:
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 pedido/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óximo pedido
demande = [IN].ReadLine
End While
' fim da ligaçã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 através do comando:
O servidor opera na porta indicada como parâmetro. Limita-se a reenviar ao cliente o pedido 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
' envia de volta 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 >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)
' inicia-se o serviço
ecoute.Start()
' acompanhamento
Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & port))
Console.Out.WriteLine(ecoute.LocalEndpoint)
' ciclo do serviço
Dim liaisonClient As TcpClient = Nothing
While True
' loop infinito — será interrompido com Ctrl-C
' à espera de um cliente
liaisonClient = ecoute.AcceptTcpClient()
' o serviço é assegurado por outra tarefa
nbClients += 1
Dim tache As Thread = New Thread(New ThreadStart(AddressOf New traiteClientEcho(liaisonClient, nbClients).Run))
tache.Start()
End While
' volta-se a ficar à escuta de pedidos
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 o 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 (pedido)/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 termina quando o cliente envia um marcador de fim de ficheiro
reponse = "[" + demande + "]"
OUT.WriteLine(reponse)
' o serviço termina quando o cliente envia «fim»
If demande.Trim().ToLower() = "fin" Then
Exit While
End If
' próximo pedido
demande = [IN].ReadLine
End While
' fim da ligaçã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)
' paragem 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
Vamos agora escrever um cliente para o servidor anterior. Será chamado da seguinte forma:
Este liga-se à 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
' liga-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 >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
' liga-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 pedido-resposta
While True
' a solicitação provém do teclado
Console.Out.Write("demande (fin pour arrêter) : ")
demande = Console.In.ReadLine()
' envia-se para o 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
' Está concluído
client.Close()
Catch ex As Exception
' trata-se da 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 do erro
System.Console.Error.WriteLine(msg)
' encerramento com erro
Environment.Exit(exitCode)
End Sub
End Class
A estrutura deste 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 numa janela do DOS
- na mesma máquina, são iniciados dois clientes 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
Note-se que o servidor conseguiu, de facto, atender dois clientes em simultâneo.
9.4.3. Um cliente genérico TCP
Muitos dos serviços criados nos primórdios da Internet funcionam segundo o modelo do servidor de eco analisado anteriormente: as interações cliente-servidor realizam-se através da troca de linhas de texto. Vamos escrever um cliente TCP genérico que será iniciado da seguinte forma: cltgen servidor porta
Este cliente TCP irá ligar-se à porta port do servidor serveur. Feito isto, criará duas threads:
- um thread encarregado de ler os comandos digitados no teclado e de os enviar para o servidor
- um thread encarregado de ler as respostas do servidor e de as apresentar no ecrã
Por que razão são necessários dois threads, quando 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 o seu protocolo específico e também se verificam as seguintes situações:
- o cliente tem de enviar várias linhas de texto antes de obter uma resposta
- a resposta de um servidor pode conter várias linhas de texto
Por isso, o ciclo de envio de uma única linha ao servidor — receção de uma única linha enviada pelo servidor — nem sempre é adequado. Vamos, portanto, criar dois ciclos separados:
- um ciclo de leitura dos comandos digitados no teclado para serem enviados ao servidor. O utilizador indicará o fim dos comandos com a palavra-chave fin.
- um ciclo de receção e exibição das respostas do servidor. Este será um ciclo infinito que só será interrompido pelo encerramento do fluxo de rede pelo servidor ou pelo utilizador, que digitará no teclado o comando fin.
Para que estes dois ciclos fiquem separados, precisamos de dois threads independentes. Vejamos um exemplo de execução em que o nosso cliente TCP genérico se liga a um serviço SMTP (SendMail Transfer Protocol). Este serviço é responsável pelo encaminhamento do correio eletrónico aos destinatários. Funciona 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 estas trocas entre cliente e servidor:
- o serviço SMTP envia uma mensagem de boas-vindas quando um cliente se liga a ele:
- alguns serviços dispõem de um comando help que fornece indicações sobre os comandos que podem ser utilizados com o serviço. Neste caso, não é esse o caso. 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: é repetido tantas vezes quantas forem necessárias para cada um dos destinatários.
- data, que sinaliza ao servidor SMTP que se vai enviar a mensagem. Conforme indicado na resposta do servidor, trata-se de uma sequência de linhas terminada por uma linha que contém apenas o caractere ponto. Uma mensagem pode ter cabeçalhos separados do corpo da mensagem por uma linha em branco. No nosso exemplo, colocámos um assunto com a palavra-chave Subject:
- assim que a mensagem for enviada, pode-se indicar ao servidor que terminámos com o comando quit. O servidor encerra então a ligação réseau.Le; o thread de leitura pode detetar este evento e parar.
- O utilizador digita então «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):

Note-se que o serviço SMTP não consegue detetar se um remetente é válido ou não. Por isso, nunca se pode confiar no campo from de uma mensagem. Neste caso, o remetente machin@univ-angers.fr não existia. Este cliente TCP genérico permite-nos descobrir o protocolo de comunicação dos serviços da Internet e, a partir daí, criar classes especializadas para os clientes desses serviços. Vamos descobrir o protocolo de comunicação do serviço POP (Post Office Protocol), que permite recuperar os e-mails armazenados num servidor. Funciona 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 introduz o nome de utilizador na máquina que armazena os nossos e-mails
- pass password, onde se introduz a palavra-passe associada ao nome de utilizador anterior
- list, para obter a lista de mensagens com o número e o tamanho em bytes
- retr i, para ler a mensagem n.º i
- quit, para encerrar a sessão.
Vamos agora descobrir 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 os seus comandos ao servidor de acordo com o seguinte esquema:
Só depois de receber a linha vazia é que o servidor Web responde. No exemplo, utilizámos apenas um comando:
que solicita ao servidor o URL /index.html e indica que está a trabalhar com o protocolo HTTP versão 1.0. A versão mais recente deste protocolo é a 1.1. O exemplo mostra que o servidor respondeu enviando o conteúdo do ficheiro index.html e, em seguida, encerrou a ligação, uma vez que se observa que o thread de leitura das respostas terminou. Antes de enviar o conteúdo do ficheiro index.html, o servidor web enviou uma série de cabeçalhos terminada 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 ficheiro /index.html. O que precede denomina-se cabeçalhos HTTP (Protocolo de Transferência HyperText). Não vamos detalhar aqui estes cabeçalhos, mas convém lembrar que o nosso cliente genérico permite o acesso aos mesmos, o que pode ser útil para os compreender. A primeira linha, por exemplo:
indica que o servidor Web contactado compreende o protocolo HTTP/1.1 e que encontrou o ficheiro solicitado (200 OK), sendo 200 um código de resposta HTTP. As linhas
informam ao cliente que este irá receber 11 251 bytes correspondentes ao texto HTML (HyperText Markup Language) e que, no final da transmissão, a ligação será encerrada. Temos, portanto, aqui um cliente TCP muito prático. Na verdade, este cliente já existe nas máquinas, onde se chama telnet, mas foi interessante escrevê-lo nós próprios. 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
' liga-se ao serviço
' cria um thread para ler os comandos digitados no teclado
' estas serão enviadas para o servidor
' cria um thread para ler as respostas do servidor
' estas serão apresentadas no ecrã
' e o processo 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 >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
' estabelece-se a ligação 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
' criam-se os 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)
' paragem com erro
Environment.Exit(exitCode)
End Sub
End Class
Public Class clientSend
' classe responsável por ler os comandos digitados no teclado
' e de as enviar para um servidor através de um cliente TCP passado ao construtor
Private client As TcpClient ' le client tcp
' construtor
Public Sub New(ByVal client As TcpClient)
' indica-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 escrita de rede
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' ciclo de introdução 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 para o 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 - encerramos os fluxos
Try
OUT.Close()
client.Close()
Catch
End Try
' notifica-se o fim do thread
Console.Out.WriteLine("[fin du thread d'envoi des commandes au serveur]")
End Sub
End Class
Public Class clientReceive
' classe encarregada de 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)
' regista-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
' gestão 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 - encerramento dos fluxos
Try
[IN].Close()
client.Close()
Catch
End Try
' notifica-se 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 centrar-nos num servidor
- que exibe no ecrã os comandos enviados pelos seus clientes
- e lhes envia, como resposta, as linhas de texto digitadas no teclado por um utilizador. É, portanto, este último que funciona como servidor.
O programa é iniciado com: srvgen portEcoute, em que portEcoute é a porta à qual os clientes devem ligar-se. O serviço ao cliente será assegurado por duas threads:
- um thread dedicado exclusivamente à leitura das linhas de texto enviadas pelo cliente
- um thread dedicado exclusivamente à leitura das respostas digitadas pelo utilizador. Este indicará, através do comando «fin», que encerra a ligação com o cliente.
O servidor cria duas threads por cliente. Se houver n clientes, haverá 2n threads ativas ao mesmo tempo. O servidor, por sua vez, nunca se encerra, a não ser que o utilizador prima Ctrl-C no teclado. Vejamos alguns exemplos.
O servidor é iniciado na porta 100 e utiliza-se o cliente genérico para comunicar 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 por <-- são as enviadas do servidor para o cliente; as restantes são as enviadas 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 por <-- são as enviadas do cliente para o servidor. As linhas N: são as enviadas do servidor para o cliente n.º N. O servidor acima ainda está ativo, embora o cliente 1 já tenha terminado. Inicia-se 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 então 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 o nosso servidor genérico na porta 88:
Vamos agora abrir um navegador e aceder à página http://localhost:88/exemple.html. O navegador irá então ligar-se à porta 88 da máquina localhost e, em seguida, solicitar a página /exemple.html:

Vejamos agora 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
<--
Descobrimos assim os cabeçalhos HTTP enviados pelo navegador. Isto permite-nos descobrir, pouco a pouco, o protocolo HTTP. Num exemplo anterior, criámos um cliente Web que enviava apenas o comando GET. Isso tinha sido suficiente. Vemos aqui que o navegador envia outras informações para o servidor. O objetivo destas informações é indicar ao servidor que tipo de cliente tem diante de si. Vemos também que os cabeçalhos HTTP terminam com uma linha em branco. Vamos elaborar uma resposta para o nosso cliente. O utilizador ao teclado é, neste caso, o verdadeiro servidor e pode elaborar uma resposta manualmente. Recordemos a resposta dada por um servidor Web num exemplo anterior:
<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
Vamos tentar 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 por 2: são enviadas do servidor para o cliente n.º 2. O comando fin encerra a ligação do servidor ao cliente. Limitaram-nos, na 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 indicamos o tamanho do ficheiro que vamos enviar (Content-Length), mas limitamo-nos a indicar que vamos encerrar a ligação (Connection: close) após o envio do mesmo. Isso é suficiente para o navegador. Ao verificar que a ligação foi encerrada, o navegador saberá que a resposta do servidor está concluída e apresentará a página HTML que lhe foi enviada. Esta página é a seguinte:
2 : <html>
2 : <head><title>Serveur generique</title></head>
2 : <body>
2 : <center>
2 : <h2>Reponse du serveur generique</h2>
2 : </center>
2 : </body>
2 : </html>
Em seguida, o utilizador encerra a ligação ao cliente digitando o comando fin. O navegador fica então a saber que a resposta do servidor está concluída e pode, assim, apresentá-la:

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

ou seja, exatamente o que foi enviado a partir do 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 os pedidos do cliente
' estas serão apresentadas no ecrã
' cria um thread para ler os comandos digitados no teclado
' estas serão enviadas 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 >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
' criamos o serviço
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
' inicia-se o serviço
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
' aguarda um cliente
client = ecoute.AcceptTcpClient()
' o serviço é executado em threads separadas
nbClients += 1
' thread de leitura dos pedidos dos clientes
Dim thReceive As New Thread(New ThreadStart(AddressOf New serveurReceive(client, nbClients).Run))
' thread de leitura das respostas digitadas pelo utilizador
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
' volta-se a ficar à espera de pedidos
Catch ex As Exception
' é sinalizado o erro
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
End Class
Public Class serveurSend
' classe responsável por ler as respostas digitadas no teclado
' e de as enviar a um cliente através 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)
' regista-se o cliente TCP
Me.client = client
' e o seu n.º
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é"))
' gestão de erros
Try
' criação do fluxo de gravação na rede
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' ciclo de introdução 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()
' terminado?
If réponse.ToLower() = "fin" Then
Exit While
End If
' envio da resposta para o servidor
OUT.WriteLine(réponse)
End While
' resposta seguinte
Catch ex As Exception
' erro
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' fim - encerramento dos fluxos
Try
OUT.Close()
client.Close()
Catch
End Try
' notifica-se 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
' através 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)
' regista-se o cliente TCP
Me.client = client
' e o 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é"))
' gestão de erros
Try
' criação do fluxo de leitura de rede
[IN] = New StreamReader(client.GetStream())
' ciclo 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 - encerrar fluxos
Try
[IN].Close()
client.Close()
Catch
End Try
' notifica-se 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 Web
No exemplo anterior, vimos 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 no ecrã o texto enviado pelo servidor. Partiremos do princípio de que este suporta o protocolo HTTP 1.1. Dos cabeçalhos anteriores, utilizaremos apenas os seguintes:
- O primeiro cabeçalho indica qual a página que pretendemos
- o segundo, a que servidor estamos a enviar a consulta
- o terceiro indica que queremos que o servidor encerre a ligação depois de nos ter respondido.
Se, no exemplo acima, substituirmos GET por HEAD, o servidor enviar-nos-á apenas os cabeçalhos HTTP e não a página HTML.
O nosso cliente web será chamado da seguinte forma: clientweb URL cmd, em que URL é aURL pretendida e «cmd» uma das duas palavras-chave «GET» ou «HEAD» para indicar se se pretende 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 local (ou seja, de URL). O novo endereço URL é indicado pelo cabeçalho Location:
Se utilizarmos 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>
Obtenemos 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 da mesma no ecrã
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
' regista-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
' encomenda incorreta
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
' ligamo-nos ao servidor
client = New TcpClient(uri.Host, uri.Port)
' estão a ser criados os fluxos de entrada e saída do cliente TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' solicita-se 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)
' leitura da resposta
réponse = [IN].ReadLine()
End While
' concluído
client.Close()
Catch e As Exception
' gestão 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)
' encerramento com erro
Environment.Exit(exitCode)
End Sub
End Class
A única novidade neste programa é a utilização da classe Uri. O programa recebe um URL (Uniform Resource Locator) ou URI (Uniform Resource Identifier) com o formato http://serveur:port/cheminPageHTML?param1=val1;param2=val2;.... A classe Uri permite-nos decompor a cadeia do URL nos seus diferentes elementos. É criado um objeto Uri a partir da cadeia 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 cadeia «URI» recebida como parâmetro não for válida (ausência do protocolo, do servidor, etc.), é lançada uma exceção. Isto permite-nos verificar a validade do parâmetro recebido. Depois de 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 cadeia 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 Web que gere os redirecionamentos
O cliente Web anterior não gere uma eventual redireção do URL que solicitou. O cliente seguinte gere-a.
- Ele lê a primeira linha dos cabeçalhos HTTP enviados pelo servidor para verificar se contém a cadeia «302 Object moved», que indica um redirecionamento
- Lê os cabeçalhos seguintes. Se houver redirecionamento, procura a linha «Location: url», que indica o novo URL da página solicitada, e regista esse URL.
- Exibe o resto da resposta do servidor. Se houver redirecionamento, os passos 1 a 3 são repetidos com o novo URL. O programa não aceita mais do que um redirecionamento. Este limite é definido por uma constante que pode ser alterada.
Eis 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>
O programa é o 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 apresenta o seu conteúdo no ecrã
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
' regista-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
' encomenda incorreta
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 encontrar um URL de redirecionamento
Dim location As New Regex("^Location: (.+?)$") '
' gestão de erros
Try
' é possível ter vários URL a consultar, caso haja redirecionamentos
While nbRedirs <= nbRedirsMax
' estabelece-se ligação 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
' envia-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()
' eco no ecrã
Console.Out.WriteLine(premièreLigne)
' redirecionamento?
If Regex.IsMatch(premièreLigne, "302 Object moved$") Then
' existe um redirecionamento
redir = True
nbRedirs += 1
End If
' seguem-se os cabeçalhos HTTP 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, regista-se o URL de redirecionamento
locationString = résultat.Groups(1).Value
' regista-se que foi encontrada
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 ligação está a ser encerrada
client.Close()
' já terminámos?
If Not locationFound Or nbRedirs > nbRedirsMax Then
Exit While
End If
' é necessário efetuar um redirecionamento — estamos a construir 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
' a exceção está a ser tratada
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)
' 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 várias formas. Recorde-se a última versão. Foi criada uma classe «imposto». Os 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 tem dois construtores:
- um construtor ao qual se passam os três tabuletos de dados necessários 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 tabelas de limites, coeffR, coeffN a partir
' dos parâmetros passados ao construtor
- um construtor ao qual se passa o nome DSN de uma base 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 da base de dados ODBC DSNimpots
' colLimites, colCoeffR, colCoeffN são as três colunas desta tabela
' pode lançar uma exceção
Tinha sido escrito um programa de teste:
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
Neste 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 chama-se ServeurImpots e deriva 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á gerido a partir destes 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 das solicitações dos clientes. Os restantes 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 o encerrar definitivamente. O método admin que gere estes comandos é o seguinte:
Public Sub admin()
' lê os comandos de administração do servidor introduzidos pelo teclado
' num ciclo 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
' inicia-se 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 todos os 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, é iniciado um thread para ouvir os pedidos dos clientes. Se o comando digitado for stop, todos os threads são parados. O thread de escuta executa o método ecoute:
Public Sub ecoute()
' thread de escuta de pedidos dos clientes
' criação do serviço de escuta
Dim ecoute As TcpListener = Nothing
Try
' criação do serviço
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), portEcoute)
' inicia-se o serviç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
' aguarda um cliente
liaisonClient = ecoute.AcceptTcpClient()
' o serviço é assegurado por outra tarefa
Dim threadClient As Thread = New Thread(New ThreadStart(AddressOf New traiteClientImpots(liaisonClient, Me).Run))
threadClient.Start()
End While
' volta-se a ficar à escuta de pedidos
Catch ex As Exception
' é sinalizado o erro
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
Encontramos um servidor TCP clássico a escutar na porta portEcoute. Os pedidos dos clientes são processados pelo método Run de um objeto ao qual são passados dois parâmetros:
- o objeto TcpClient, que permitirá aceder ao 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 os pedidos dos clientes. Estes podem assumir duas formas:
- cálculo para casados (s/n) nbEnfants salaireAnnuel
- cálculos financeiros
A forma 1 permite o cálculo de um imposto; a forma 2 encerra a ligação cliente-servidor.
' método Run
Public Sub Run()
' presta 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
' envio de uma mensagem de boas-vindas ao cliente
OUT.WriteLine("Bienvenue sur le serveur d'impôts")
' ciclo de leitura de pedido/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)
' decompõe-se a solicitação em campos
champs = Regex.Split(demande.Trim().ToLower(), "\s+")
' duas solicitações aceites: 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...")
' libertação de 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 é efetuado pelo método calculerImpôt, que recebe como parâmetro a tabela de campos do pedido efetuado pelo cliente. A validade do pedido é 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 o pedido: cálculo para casados nbEnfants salaireAnnuel
' descomposto 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
' a criar 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 erros
System.Console.Error.WriteLine(msg)
' encerramento com erro
Environment.Exit(exitCode)
End Sub
End Class
Passam-se ao programa de teste os dados necessários para a construção de um objeto ServeurImpots e, a partir daí, este cria esse objeto. Este programa de teste é compilado por:
Eis 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á a ouvir os pedidos dos clientes. É o comando «start», digitado no teclado, que inicia essa escuta. O comando «stop» encerra o servidor. Vamos agora utilizar um cliente. Utilizaremos 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 noutra janela do DOS:
Vê-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 o desligar:















