9. Programação TCP/IP
9.1. Informações Gerais
9.1.1. Protocolos da Internet
Apresentamos aqui uma introdução aos protocolos de comunicação da Internet, também conhecidos como conjunto de protocolos TCP/IP (Transmission 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 se dedicar ao desenvolvimento de aplicações distribuídas.
O texto que se segue é uma tradução parcial de um texto encontrado no documento «LAN Workplace for DOS - Administrator's Guide» da NOVELL, um documento do início da década de 1990.
O conceito geral de criação de uma rede de computadores heterogéneos tem origem na investigação conduzida pela DARPA (Agência de Projetos de Investigação Avançada de Defesa) nos Estados Unidos. A DARPA desenvolveu o conjunto de protocolos conhecido como TCP/IP, que permite que máquinas heterogéneas comuniquem entre si. Estes protocolos foram testados numa rede chamada ARPAnet, que mais tarde se tornou a Internet. Os protocolos TCP/IP definem formatos e regras para a transmissão e receção que são independentes da arquitetura da rede e do hardware utilizado.
A rede concebida pela DARPA e gerida pelos protocolos TCP/IP é uma rede de comutação de pacotes. Uma rede deste tipo transmite informação pela rede em pequenos pedaços chamados pacotes. Assim, se um computador transmitir um ficheiro de grandes dimensões, este será dividido em pequenos pedaços que são enviados pela rede para serem reajuntados 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 geralmente o modelo de rede aberta conhecido como OSI (Modelo de Referência para Interconexão de Sistemas Abertos), definido pela ISO (Organização Internacional de Normalização). Este modelo descreve uma rede ideal onde a comunicação entre máquinas pode ser representada por um modelo de sete camadas:
![]() |
Cada camada recebe serviços da camada abaixo dela e fornece os seus próprios serviços à camada acima dela. Suponha que duas aplicações localizadas em máquinas diferentes, A e B, queiram comunicar: fazem-no na camada de Aplicação. Não precisam de conhecer todos os detalhes de como a rede funciona: cada aplicação passa a informação que deseja transmitir para a camada abaixo dela: a camada de Apresentação. A aplicação, portanto, só precisa de conhecer as regras para interagir com a camada de Apresentação.
Assim que a informação chega à camada de Apresentação, é passada de acordo com outras regras para a camada de Sessão, e assim sucessivamente, até que a informação chegue ao meio físico e seja fisicamente transmitida para a máquina de destino. Aí, será submetida ao processo inverso daquele a que foi submetida na máquina de envio.
Em cada camada, o processo emissor responsável pelo envio da informação envia-a a um processo recetor na outra máquina pertencente à mesma camada que ele próprio. Faz-o de acordo com certas regras conhecidas como protocolo da camada. Temos, portanto, o seguinte diagrama de comunicação final:
![]() |
As funções das diferentes camadas são as seguintes:
Assegura a transmissão de bits através de um meio físico. Esta camada inclui equipamentos terminais de processamento de dados (DPTE), tais como terminais ou computadores, bem como equipamentos de terminação de circuitos de dados (DCTE), tais como moduladores/demoduladores, multiplexadores e concentradores. Os pontos-chave neste nível são: . a escolha da codificação da informação (analógica ou digital) . a escolha do modo de transmissão (síncrono ou assíncrono). | |
Oculta as características físicas da Camada Física. Deteta e corrige erros de transmissão. | |
Gere o caminho que a informação enviada pela rede deve seguir. Isto denomina-se encaminhamento: determinar a rota que a informação deve seguir para chegar ao seu destino. | |
Permite a comunicação entre duas aplicações, enquanto as camadas anteriores apenas permitiam a comunicação entre máquinas. Um serviço prestado por esta camada pode ser a multiplexação: a camada de transporte pode utilizar uma única ligação de rede (de máquina para máquina) para transmitir dados pertencentes a várias aplicações. | |
Esta camada fornece serviços que permitem a uma aplicação abrir e manter uma sessão de trabalho numa máquina remota. | |
Tem como objetivo padronizar a representação de dados entre diferentes máquinas. Assim, os dados provenientes da máquina A serão «formatados» pela camada de apresentação da máquina A de acordo com um formato padrão antes de serem enviados pela rede. Ao chegarem à camada de apresentação da máquina de destino B, que os reconhecerá graças ao seu formato padrão, serão reformatados de modo a que a aplicação na máquina B os possa reconhecer. | |
Neste nível, encontramos aplicações que estão geralmente próximas do utilizador, tais como o e-mail ou a transferência de ficheiros. |
9.1.3. O Modelo TCP/IP
O modelo OSI é um modelo ideal que nunca foi totalmente concretizado. O conjunto de protocolos TCP/IP aproxima-se dele da seguinte forma:
![]() |
Camada Física
Nas redes locais, utiliza-se geralmente a tecnologia Ethernet ou Token Ring. Aqui, iremos centrar-nos exclusivamente na tecnologia Ethernet.
Ethernet
Este é o nome dado a uma tecnologia de rede de área local com comutação de pacotes, inventada na Xerox PARC no início da década de 1970 e padronizada pela Xerox, Intel e Digital Equipment em 1978. A rede consiste fisicamente num cabo coaxial com aproximadamente 1,27 cm de diâmetro e até 500 m de comprimento. Pode ser estendida utilizando repetidores, com um máximo de dois repetidores a separar quaisquer duas máquinas. O cabo é passivo: todos os componentes ativos estão localizados nas máquinas ligadas ao cabo. Cada máquina está ligada ao cabo através de uma placa de acesso à rede que inclui:
- um transceptor que deteta a presença de sinais no cabo e converte sinais analógicos em sinais digitais e vice-versa.
- um acoplador que recebe sinais digitais do transceptor e os transmite ao computador para processamento, ou vice-versa.
As principais características da tecnologia Ethernet são as seguintes:
- Capacidade de 10 megabits por segundo.
- Topologia de barramento: todas as máquinas estão ligadas ao mesmo cabo
![]() |
- Rede de difusão — Uma máquina transmissora envia informações pelo cabo com o endereço da máquina receptora. Todas as máquinas ligadas recebem então estas informações, e apenas o destinatário pretendido as retém.
- O método de acesso é o seguinte: o transmissor que pretende transmitir escuta o cabo — deteta então se existe ou não uma onda portadora, cuja presença indicaria que está em curso uma transmissão. Esta é a técnica CSMA (Carrier Sense Multiple Access). Na ausência de uma onda portadora, um transmissor pode decidir transmitir por sua vez. Vários transmissores podem tomar essa decisão. Os sinais transmitidos misturam-se: isto é chamado de colisão. O transmissor deteta esta situação: enquanto transmite pelo cabo, também escuta o que está realmente a passar por ele. Se detetar que a informação que circula pelo cabo não é a que transmitiu, conclui que ocorreu uma colisão e interrompe a transmissão. Os outros transmissores que estavam a transmitir farão o mesmo. Cada um retomará a transmissão após um atraso aleatório, dependendo do transmissor individual. Esta técnica é chamada de CD (Collision Detect). O método de acesso é, portanto, chamado de CSMA/CD.
- Endereçamento de 48 bits. Cada máquina possui um endereço, aqui designado por endereço físico, que está indicado 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 de rede | |
O ICMP facilita a comunicação entre o programa do protocolo IP numa máquina e o da outra máquina. É, portanto, um protocolo de troca de mensagens dentro do próprio protocolo IP. | |
mapeia o endereço de Internet de uma máquina para o seu endereço físico | |
mapeia o endereço físico de uma máquina para o seu endereço de Internet |
Camadas de transporte/sessão
Esta camada inclui os seguintes protocolos:
Garante a entrega fiável de informações entre dois clientes | |
Garante a entrega não fiável de informações entre dois clientes |
Camadas de Aplicação/Apresentação/Sessão
Aqui encontram-se vários protocolos:
Um emulador de terminal que permite que a máquina A se ligue à máquina B como um terminal | |
Permite a transferência de ficheiros | |
permite a transferência de ficheiros | |
permite a troca de mensagens entre utilizadores da rede | |
converte o nome de um computador no endereço de Internet desse computador | |
Criado pela Sun Microsystems, especifica um padrão de representação de dados independente da máquina | |
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 da necessidade de conhecer os detalhes da camada de transporte e torna as aplicações portáteis. Este protocolo baseia-se no protocolo XDR | |
Também definido pela Sun, este protocolo permite que uma máquina «veja» o sistema de ficheiros de outra máquina. Baseia-se no protocolo RPC mencionado acima |
9.1.4. Como funcionam os protocolos da Internet
As aplicações desenvolvidas no ambiente TCP/IP utilizam geralmente vários dos protocolos deste ambiente. Um programa de aplicação comunica com a camada mais elevada dos protocolos. Esta camada transmite a informação para a camada abaixo dela, e assim sucessivamente, até chegar ao meio físico. Aí, a informação é transferida fisicamente para a máquina de destino, onde passará novamente pelas mesmas camadas, desta vez na direção oposta, até chegar à aplicação destinada a receber a informação enviada. O diagrama seguinte mostra o percurso da informação:
![]() |
Vejamos um exemplo: a aplicação FTP, definida na camada de Aplicação, que permite a transferência de ficheiros entre máquinas.
- A aplicação envia uma sequência de bytes para ser transmitida à camada de transporte.
- A camada de transporte divide esta sequência de bytes em segmentos TCP e adiciona o número do segmento ao início de cada segmento. Os segmentos são passados para a camada de rede, que é regida pelo protocolo IP.
- A camada IP cria um pacote que encapsula o segmento TCP recebido. No cabeçalho deste pacote, coloca os endereços de Internet das máquinas de origem e de destino. Determina também o endereço físico da máquina de destino. O pacote completo é passado para a camada de ligação de dados e física, ou seja, para a placa de rede que liga a máquina à rede física.
- Aí, o pacote IP é, por sua vez, encapsulado numa trama física e enviado para o seu destino através do cabo.
- Na máquina de destino, a Camada de Ligação de Dados e Física faz o oposto: desencapsula o pacote IP da trama física e o passa para a camada IP.
- A camada IP verifica se o pacote é válido: calcula uma soma de verificação com base nos bits recebidos, que deve corresponder à soma de verificação no cabeçalho do pacote. Se não corresponder, o pacote é descartado.
- Se o pacote for considerado válido, a camada IP desencapsula o segmento TCP nele contido e o passa para a camada de transporte.
- A camada de transporte — a camada TCP no nosso exemplo — examina o número do segmento para garantir que os segmentos estão na ordem correta.
- Calcula também uma soma de verificação para o segmento TCP. Se for considerada correta, a camada TCP envia um aviso de receção à máquina de origem; caso contrário, o segmento TCP é rejeitado.
- Tudo o que resta à camada TCP é transmitir a parte de dados do segmento para a aplicação destinada a recebê-la na camada superior.
9.1.5. Endereçamento na Internet
Um nó de rede pode ser um computador, uma impressora inteligente, um servidor de ficheiros — qualquer coisa, na verdade, que possa comunicar utilizando protocolos TCP/IP. Cada nó tem um endereço físico com um formato que depende do tipo de rede. Numa rede Ethernet, o endereço físico é codificado em 6 bytes. Um endereço de rede X.25 é um número de 14 dígitos.
O endereço de Internet de um nó é um endereço lógico: é independente do hardware e da rede utilizada. É um endereço de 4 bytes que identifica tanto uma rede local como um nó nessa rede. O endereço de Internet é normalmente representado por quatro números — os valores dos quatro bytes — separados por um ponto. Assim, o endereço da máquina Lagaffe na Faculdade de Ciências de Angers é 193.49.144.1, e o da máquina Liny é 193.49.144.9. A partir daí, podemos deduzir que o endereço de Internet da rede local é 193.49.144.0. Podem existir até 254 nós nesta rede.
Como os endereços de Internet (endereços IP) são independentes da rede, uma máquina na rede A pode comunicar com uma máquina na rede B sem se preocupar com o tipo de rede em que se encontra: basta saber o seu endereço IP. O protocolo IP de cada rede trata da conversão entre endereço IP e endereço físico, em ambos os sentidos.
Todos os endereços IP devem ser únicos. Em França, o INRIA é responsável pela atribuição de endereços IP. Na verdade, esta organização 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 desta rede pode então atribuir os endereços IP 193.49.144.1 a 193.49.144.254 conforme entender. Este endereço é geralmente armazenado num ficheiro específico em cada máquina ligada à rede.
9.1.5.1. Classes de endereços IP
Um endereço IP é uma sequência de 4 bytes, frequentemente escrita como I1.I2.I3.I4, que na verdade contém dois endereços:
- o endereço de rede
- o endereço de um nó nessa rede
Dependendo do tamanho destes dois campos, os endereços IP são divididos em 3 classes: classes A, B e C.
Classe A
O endereço IP: I1.I2.I3.I4 tem o formato R1.N1.N2.N3, em que
R1 | é o endereço de rede |
N1.N2.N3 | é o endereço de uma máquina nessa rede |
Mais precisamente, o formato de um endereço IP de Classe A é o seguinte:
![]() |
O endereço de rede tem 7 bits e o endereço de host tem 24 bits. Por conseguinte, podem existir 127 redes de Classe A, cada uma contendo até 2²⁴ hosts.
Classe B
Aqui, o endereço IP: I1.I2.I3.I4 tem a forma R1.R2.N1.N2, em que
R1.R2 | é o endereço de rede |
N1.N2 | é o endereço de uma máquina nessa rede |
Mais precisamente, o formato de um endereço IP de Classe B é o seguinte:
![]() |
O endereço de rede tem 2 bytes (exatamente 14 bits), tal como o endereço do nó. Por conseguinte, podem existir 2¹⁴ redes de Classe B, cada uma contendo até 2¹⁶ nós.
Classe C
Nesta classe, o endereço IP: I1.I2.I3.I4 tem a forma R1.R2.R3.N1, em que
R1.R2.R3 | é o endereço de rede |
N1 | é o endereço de uma máquina nessa rede |
Mais precisamente, o formato de um endereço IP de Classe C é o seguinte:
![]() |
O endereço de rede ocupa 3 bytes (menos 3 bits) e o endereço do host ocupa 1 byte. Por conseguinte, podem existir 2²¹ redes de Classe C, cada uma contendo até 256 hosts.
Uma vez que o endereço da máquina Lagaffe na Faculdade de Ciências de Angers é 193.49.144.1, vemos que o byte mais significativo é 193, que é 11000001 em binário. Podemos deduzir que a rede é uma rede de Classe C.
Endereços reservados
- Alguns endereços IP são endereços de rede, em vez de endereços de nó dentro da rede. Estes são aqueles em que o endereço do nó está definido como 0. Assim, o endereço 193.49.144.0 é o endereço IP da rede da Faculdade de Ciências de Angers. Consequentemente, nenhum nó numa rede pode ter o endereço zero.
- Quando o endereço de nó num endereço IP é composto inteiramente por 1s, trata-se de um endereço de difusão: este endereço refere-se a todos os nós da rede.
- Numa rede de Classe C, que teoricamente permite 2⁸ = 256 nós, se retirarmos os dois endereços proibidos, ficamos apenas com 254 endereços autorizados.
9.1.5.2. Protocolos de conversão de endereços de Internet <--> endereços físicos
Vimos que, quando os dados são transmitidos de uma máquina para outra, são encapsulados em pacotes à medida que passam pela camada IP. Estes pacotes têm a seguinte forma:
![]() |
O pacote IP contém, portanto, os endereços de Internet das máquinas de origem e de destino. Quando este pacote é transmitido para a camada responsável pelo seu envio através da rede física, são adicionadas informações adicionais para formar a trama física que será, em última instância, enviada pela rede. Por exemplo, o formato de uma trama numa rede Ethernet é o seguinte:
![]() |
No quadro final, encontram-se os endereços físicos das máquinas de origem e de destino. Como são obtidos?
A máquina remetente, sabendo o endereço IP da máquina com a qual pretende comunicar, obtém o endereço físico dessa máquina utilizando um protocolo específico chamado ARP (Address Resolution Protocol).
- Envia um tipo especial de pacote chamado pacote ARP, contendo o endereço IP da máquina cujo endereço físico está a ser procurado. Também inclui o seu próprio endereço IP e endereço físico no pacote.
- Este pacote é enviado para todos os nós da rede.
- Estes nós reconhecem a natureza especial do pacote. O nó que reconhece o seu endereço IP no pacote responde enviando o seu endereço físico ao remetente do pacote. Como é que consegue fazer isto? Encontrou os endereços IP e físicos do remetente no pacote.
- O remetente recebe assim o endereço físico que procurava. Armazena-o na memória para poder utilizá-lo mais tarde, caso seja necessário enviar outros pacotes para o mesmo destinatário.
O endereço IP de uma máquina é normalmente armazenado num dos seus ficheiros de configuração, que pode ser consultado para o recuperar. Este endereço pode ser alterado: basta editar o ficheiro. O endereço físico, no entanto, é armazenado na memória da placa de rede e não pode ser alterado.
Quando um administrador deseja reorganizar a rede, pode ser necessário alterar os endereços IP de todos os nós e, assim, editar os vários ficheiros de configuração de cada nó. Isto pode ser tedioso e propenso a erros se houver muitas máquinas. Um método envolve não atribuir um endereço IP às máquinas: em vez disso, é inserido um código especial no ficheiro onde a máquina normalmente encontraria o seu endereço IP. Ao descobrir que não tem endereço IP, a máquina solicita um usando um protocolo chamado RARP (Reverse Address Resolution Protocol). Em seguida, envia um pacote especial chamado pacote RARP pela rede — semelhante ao pacote ARP anterior — contendo o seu endereço físico. Este pacote é enviado a todos os nós que reconhecem um pacote RARP. Um deles, chamado servidor RARP, mantém um ficheiro contendo os mapeamentos de endereço físico <--> endereço IP para todos os nós. Em seguida, responde ao remetente do pacote RARP, devolvendo o seu endereço IP. Um administrador que pretenda reconfigurar a sua rede precisa, portanto, apenas de editar o ficheiro de mapeamento do servidor RARP. O servidor RARP deve normalmente ter um endereço IP fixo que deve ser capaz de conhecer sem ter de utilizar o próprio protocolo RARP.
9.1.6. A camada de rede, conhecida como camada do Protocolo de Internet (IP)
O IP (Protocolo de Internet) define o formato que os pacotes devem assumir e como devem ser tratados durante a transmissão ou receção. Este tipo específico de pacote é chamado de datagrama IP. Já o discutimos:
![]() |
O ponto importante é que, além dos dados a serem transmitidos, o datagrama IP contém os endereços de Internet das máquinas de origem e de destino. Assim, a máquina receptora sabe quem lhe está a enviar uma mensagem.
Ao contrário de um quadro de rede, cujo comprimento é determinado pelas características físicas da rede pela qual transita, o comprimento do datagrama IP é fixado pelo software e, por isso, será o mesmo em diferentes redes físicas. Vimos que, à medida que descemos da camada de rede para a camada física, o datagrama IP é encapsulado num quadro físico. Apresentámos o exemplo do quadro físico de uma rede Ethernet:
![]() |
Os quadros físicos viajam de nó em nó em direção ao seu destino, que pode não estar na mesma rede física que a máquina emissora. O pacote IP pode, portanto, ser sucessivamente encapsulado em diferentes quadros físicos nos nós que ligam duas redes de tipos diferentes. Também é possível que o pacote IP seja demasiado grande para ser encapsulado num único quadro físico. O software IP no nó onde este problema ocorre divide então o pacote IP em fragmentos de acordo com regras específicas, sendo cada um deles enviado pela rede física. Só serão reagrupados no seu destino final.
9.1.6.1. Roteamento
O encaminhamento é o método de direcionar pacotes IP para o seu destino. Existem dois métodos: encaminhamento direto e encaminhamento indireto.
Roteamento direto
O encaminhamento direto refere-se à transmissão de um pacote IP diretamente do remetente para o destinatário dentro da mesma rede:
- A máquina que envia um datagrama IP possui o endereço IP do destinatário.
- Obtém o endereço físico do destinatário através do protocolo ARP ou a partir das suas tabelas, caso esse endereço já tenha sido obtido.
- Envia o pacote pela rede para esse endereço físico.
Roteamento indireto
O encaminhamento indireto refere-se à transmissão de um pacote IP para um destino localizado numa rede diferente daquela a que o remetente pertence. Neste caso, as partes do endereço de rede dos endereços IP das máquinas de origem e de destino são diferentes. A máquina de origem reconhece isso. Em seguida, envia o pacote para um nó especial chamado router, um nó que liga uma rede local a outras redes e cujo endereço IP encontra nas suas tabelas — um endereço inicialmente obtido a partir de um ficheiro, da memória permanente ou através de informações que circulam na rede.
Um router está ligado a duas redes e possui um endereço IP em ambas as redes.
![]() |
No nosso exemplo acima:
- A Rede n.º 1 tem o endereço de Internet 193.49.144.0 e a Rede n.º 2 tem o endereço 193.49.145.0.
- Dentro da rede n.º 1, o router tem o endereço 193.49.144.6 e, dentro da rede n.º 2, o endereço 193.49.145.3.
A função do router é pegar no pacote IP que recebe — que está contido num quadro físico típico da rede n.º 1 — e colocá-lo num quadro físico que possa viajar pela rede n.º 2. Se o endereço IP do destino do pacote estiver na rede n.º 2, o router enviará o pacote diretamente para lá; caso contrário, enviá-lo-á para outro router, ligando a rede n.º 2 à rede n.º 3, e assim sucessivamente.
9.1.6.2. Mensagens de erro e controlo
Também dentro da camada de rede — no mesmo nível que o protocolo IP — está o ICMP (Internet Control Message Protocol). É utilizado para enviar mensagens relativas ao funcionamento interno da rede: nós com falha, congestionamento num router, etc. As mensagens ICMP são encapsuladas em pacotes IP e enviadas pela rede. As camadas IP dos vários nós tomam as medidas adequadas com base nas mensagens ICMP que recebem. Assim, uma aplicação em si nunca vê estas questões específicas da rede. Um nó utilizará as informações ICMP para atualizar as suas tabelas de encaminhamento.
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 de dados não fiável entre dois pontos, o que significa que a entrega bem-sucedida de um pacote no seu destino não é garantida. A aplicação, se assim o desejar, pode lidar com isto por si própria, por exemplo, aguardando uma confirmação após enviar uma mensagem antes de enviar a seguinte.
Até agora, ao nível da rede, discutimos endereços IP de máquinas. No entanto, numa única máquina, podem coexistir simultaneamente diferentes processos, todos os quais podem comunicar. Por isso, ao enviar uma mensagem, é necessário especificar não só o endereço IP da máquina de destino, mas também o «nome» do processo de destino. Este nome é, na verdade, um número, chamado número de porta. Certos números estão reservados para aplicações padrão: a porta 69 para a aplicação TFTP (Trivial File Transfer Protocol), por exemplo. Os pacotes tratados pelo protocolo UDP também são chamados datagramas. Têm a seguinte forma:
![]() |
Estes datagramas serão encapsulados em pacotes IP e, posteriormente, em quadros físicos.
9.1.7.2. O Protocolo TCP: Protocolo de Controlo de Transmissão
Para comunicações seguras, o protocolo UDP é insuficiente: o programador da aplicação deve criar ele próprio um protocolo para garantir que os pacotes sejam encaminhados corretamente.
O TCP (Protocolo de Controlo de Transmissão) evita estes problemas. As suas características são as seguintes:
- O processo que pretende enviar dados estabelece primeiro uma ligação com o processo que irá receber os dados. Esta ligação é estabelecida entre uma porta na máquina de envio e uma porta na máquina de receção. É assim criado um caminho virtual entre as duas portas, que é reservado exclusivamente para os dois processos que estabeleceram a ligação.
- Todos os pacotes enviados pelo processo de origem seguem este caminho virtual e chegam na ordem em que foram enviados, o que não era garantido no protocolo UDP, uma vez que os pacotes podiam seguir caminhos diferentes.
- Os dados transmitidos parecem contínuos. O processo de envio envia dados ao seu próprio ritmo. Estes dados não são necessariamente enviados imediatamente: o protocolo TCP aguarda até ter dados suficientes para enviar. São armazenados numa estrutura denominada segmento TCP. Assim que este segmento estiver cheio, é transmitido para a camada IP, onde é encapsulado num pacote IP.
- Cada segmento enviado pelo protocolo TCP é numerado. O protocolo TCP de receção verifica se está a receber os segmentos em sequência. Por cada segmento recebido corretamente, envia um aviso de receção ao remetente.
- Quando o remetente recebe esta confirmação, notifica o processo de envio. O processo de envio pode assim confirmar que um segmento chegou em segurança, o que não era possível com o protocolo UDP.
- Se, após um determinado período de tempo, o protocolo TCP que enviou um segmento não receber um aviso de receção, retransmite o segmento em questão, garantindo assim a qualidade do serviço de entrega de informação.
- O circuito virtual estabelecido entre os dois processos em comunicação é full-duplex: isto significa que a informação pode circular em ambos os sentidos. Assim, o processo de destino pode enviar confirmações mesmo enquanto o processo de origem continua a enviar informação. Isto permite, por exemplo, que o protocolo TCP de origem envie vários segmentos sem esperar por uma confirmação. Se, após um determinado período de tempo, perceber que não recebeu um aviso de receção para um segmento específico n.º n, retomará o envio de segmentos a partir desse ponto.
9.1.8. A Camada de Aplicação
Acima dos protocolos UDP e TCP, existem vários protocolos padrão:
TELNET
Este protocolo permite que um utilizador na máquina A da rede se ligue à máquina B (frequentemente designada por máquina anfitriã). O TELNET emula um chamado terminal universal na máquina A. O utilizador comporta-se, assim, como se tivesse um terminal ligado à máquina B. O Telnet baseia-se no protocolo TCP.
FTP: (Protocolo de Transferência de Ficheiros)
Este protocolo permite a troca de ficheiros entre duas máquinas remotas, bem como operações com ficheiros, como a criação de diretórios, por exemplo. Baseia-se no protocolo TCP.
TFTP: (Protocolo Trivial de Transferência de Ficheiros)
Este protocolo é uma variante do FTP. Baseia-se no protocolo UDP e é menos sofisticado do que o FTP.
DNS: (Sistema de Nomes de Domínio)
Quando um utilizador pretende trocar ficheiros com uma máquina remota, por exemplo, através do FTP, tem de conhecer o endereço de Internet dessa máquina. Por exemplo, para utilizar o FTP na máquina Lagaffe da Universidade de Angers, teria de executar o FTP da seguinte forma: FTP 193.49.144.1
Isto requer um diretório que mapeie máquinas para endereços IP. Neste diretório, as máquinas seriam provavelmente designadas por nomes simbólicos, tais como:
Máquina DPX2/320 na Universidade de Angers
Máquina Sun no ISERPA em Angers
É evidente que seria mais conveniente referir-se a uma máquina por um nome do que pelo seu endereço IP. Isto levanta a questão da exclusividade dos nomes: existem milhões de máquinas interligadas. Poder-se-ia imaginar um organismo centralizado a atribuir os nomes. Isso seria, sem dúvida, bastante complicado. O controlo sobre os nomes foi, de facto, distribuído por domínios. Cada domínio é gerido por uma organização geralmente muito enxuta, que tem total liberdade na escolha dos nomes das máquinas. Assim, as máquinas em França pertencem ao domínio fr, que é gerido pelo Inria em Paris. Para simplificar ainda mais as coisas, o controlo é distribuído ainda mais: são criados domínios dentro do domínio fr. Assim, a Universidade de Angers pertence ao domínio univ-Angers. O departamento que gere este domínio tem total liberdade para nomear as máquinas na rede da Universidade de Angers. Por enquanto, este domínio não foi subdividido. Mas numa grande universidade com muitas máquinas em rede, isso poderia acontecer.
A máquina DPX2/320 da Universidade de Angers foi batizada de Lagaffe, enquanto um PC 486DX50 recebeu o nome de liny. Como se faz referência a estas máquinas a partir do exterior? Especificando a hierarquia de domínios a que pertencem. Assim, o nome completo da máquina Lagaffe será:
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 pode ser referenciada como
Lagaffe.univ-Angers
Por fim, dentro do domínio univ-Angers, pode ser referenciada simplesmente como
Lagaffe
Uma aplicação pode, portanto, referir-se a uma máquina pelo seu nome. No entanto, em última análise, ainda é necessário obter o endereço IP da máquina. Como é que isso se faz? Suponhamos que, a partir da máquina A, queremos comunicar com a máquina B.
- Se a máquina B pertencer ao mesmo domínio que a máquina A, o seu endereço IP provavelmente estará num ficheiro na máquina A.
- Caso contrário, a máquina A encontrará, noutro ficheiro ou no mesmo de antes, uma lista de vários servidores de nomes com os seus endereços IP. Um servidor de nomes é responsável por mapear o nome de uma máquina para o seu endereço IP. A máquina A enviará um pedido especial ao primeiro servidor de nomes da sua lista, conhecido como consulta DNS, que inclui o nome da máquina procurada. Se o servidor consultado tiver esse nome nos seus registos, enviará o endereço IP correspondente à máquina A. Caso contrário, o servidor também encontrará nos seus ficheiros uma lista de servidores de nomes que pode consultar. E assim o fará. Assim, serão consultados vários servidores de nomes, não de forma aleatória, mas de uma forma que minimize o número de pedidos. Se a máquina for finalmente encontrada, a resposta será enviada de volta à máquina A.
XDR: (eXternal Data Representation)
Criado pela Sun Microsystems, este protocolo especifica uma representação de dados padrão e independente da máquina.
RPC: (Chamada de Procedimento Remoto)
Também definido pela Sun, este é um protocolo de comunicação entre aplicações remotas, independente da camada de transporte. Este protocolo é importante: dispensa o programador de conhecer os detalhes da camada de transporte e torna as aplicações portáteis. Este protocolo baseia-se no protocolo XDR
NFS: Sistema de Ficheiros em Rede
Também definido pela Sun, este protocolo permite que uma máquina «veja» o sistema de ficheiros de outra máquina. Baseia-se no protocolo RPC mencionado acima.
9.1.9. Conclusão
Nesta introdução, apresentámos algumas das principais características dos protocolos da Internet. Para aprofundar este tema, os leitores podem consultar o excelente livro de Douglas Comer:
Título | TCP/IP: Arquitetura, Protocolos, Aplicações. |
Autor | Douglas COMER |
Editora | InterEditions |
9.2. Gestão de endereços de rede
Uma máquina na Internet é identificada de forma única por um endereço IP (Protocolo de Internet) no formato I1.I2.I3.I4, em que I1 é um número entre 1 e 254. Também pode ser identificada por um nome único. Este nome não é obrigatório, uma vez que as aplicações acabam sempre por utilizar os endereços IP das máquinas. Estes nomes existem para facilitar a vida aos utilizadores. Assim, é mais fácil utilizar um navegador para solicitar o URL http://www.ibm.com do que o URL http://129.42.17.99, embora ambos os métodos sejam possíveis. O mapeamento entre endereço IP e nome da máquina é tratado por um serviço de Internet distribuído chamado DNS (Sistema de Nomes de Domínio). A plataforma .NET fornece a classe Dns para gerir endereços de Internet:

A maioria dos métodos da classe é estática. Vejamos os que nos interessam:
Retorna um IPHostEntry a partir de um endereço IP no formato "I1.I2.I3.I4". Lança uma exceção se o endereço da máquina não for encontrado. | |
retorna um IPHostEntry a partir de um nome de máquina. Lança uma exceção se o nome da máquina não for encontrado. | |
retorna o nome da máquina na qual o programa que executa esta instrução está a ser executado |
Os endereços de rede IPHostEntry têm o seguinte formato:
![]() |
As propriedades que nos interessam:
Lista de endereços IP de uma máquina. Embora um endereço IP identifique uma e apenas uma máquina física, uma máquina física pode ter vários endereços IP. É o que acontece se tiver várias placas de rede que a ligam a redes diferentes. | |
Lista de aliases para uma máquina, que pode ser identificada por um nome principal e aliases | |
O nome da máquina, caso exista |
Da classe IPAddress, utilizaremos o seguinte construtor, propriedades e métodos:

Um objeto [IPAddress] pode ser convertido para a cadeia de caracteres I1.I2.I3.I4 utilizando o método ToString(). Por outro lado, um objeto IPAddress pode ser obtido a partir da cadeia de caracteres I1.I2.I3.I4 utilizando o método estático IPAddress.Parse("I1.I2.I3.I4"). Considere o seguinte programa, que apresenta o nome da máquina na qual está a ser executado e, em seguida, fornece de forma interativa as correspondências entre endereços IP e nomes de máquinas:
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:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
Imports System.Net
Imports System.Text.RegularExpressions
' test module
Public Module adresses
Sub Main()
' displays the name of the local machine
' then interactively provides information on network machines
' identified by name or address IP
' local machine
Dim localHost As String = Dns.GetHostName()
Console.Out.WriteLine(("Machine Locale=" + localHost))
' interactive Q&A
Dim machine As String
Dim adresseMachine As IPHostEntry
While True
' enter the name of the machine you are looking for
Console.Out.Write("Machine recherchée (fin pour arrêter) : ")
machine = Console.In.ReadLine().Trim().ToLower()
' finished?
If machine = "fin" Then
Exit While
End If
' address I1.I2.I3.I4 or machine name?
Dim isIPV4 As Boolean = Regex.IsMatch(machine, "^\s*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\s*$")
' management exception
Try
If isIPV4 Then
adresseMachine = Dns.GetHostByAddress(machine)
Else
adresseMachine = Dns.GetHostByName(machine)
End If
' the name
Console.Out.WriteLine(("Machine : " + adresseMachine.HostName))
' IP addresses
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()
' 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
' the machine doesn't exist
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. Informações gerais
Considere a comunicação entre duas máquinas remotas A e B:
![]() |
Quando uma aplicação AppA na máquina A pretende comunicar com uma aplicação AppB na máquina B através da Internet, tem de saber várias coisas:
- o endereço IP ou nome de host da máquina B
- o número da porta utilizada pela aplicação AppB. Com efeito, a máquina B pode hospedar muitas aplicações em execução na Internet. Quando recebe informações da rede, deve saber a que aplicação se destinam essas informações. As aplicações na máquina B acedem à rede através de interfaces também conhecidas como portas de comunicação. Esta informação está contida no pacote recebido pela máquina B, para que possa ser entregue à aplicação correta.
- Os protocolos de comunicação compreendidos pela máquina B. No nosso estudo, utilizaremos apenas protocolos TCP-IP.
- O protocolo de comunicação aceite pela aplicação AppB. Com efeito, as máquinas A e B irão «comunicar» entre si. O que dizem será encapsulado nos protocolos TCP/IP. No entanto, quando, no final da cadeia, a aplicação AppB recebe a informação enviada pela aplicação AppA, tem de ser capaz de a interpretar. Isto é análogo à situação em que duas pessoas, A e B, comunicam por telefone: a sua conversa é transportada pelo telefone. A fala é codificada como sinais pelo telefone A, transmitida através das linhas telefónicas e chega ao telefone B para ser descodificada. A pessoa B ouve então as palavras. É aqui que entra em jogo o conceito de protocolo de comunicação: se A falar francês e B não compreender essa língua, A e B não conseguirão comunicar eficazmente.
Por conseguinte, as duas aplicações em comunicação devem chegar a acordo quanto ao tipo de protocolo de comunicação que irão utilizar. Por exemplo, a comunicação com um serviço FTP não é a mesma que com um serviço POP: estes dois serviços não aceitam os mesmos comandos. Têm protocolos de comunicação diferentes.
9.3.2. Características do Protocolo TCP
Aqui, iremos examinar apenas as comunicações de rede que utilizam o protocolo de transporte TCP. Vamos rever as suas características:
- O processo que pretende transmitir dados estabelece primeiro uma ligação com o processo que irá receber a informação que está prestes a transmitir. Esta ligação é estabelecida entre uma porta na máquina emissora e uma porta na máquina recetora. É assim criado um caminho virtual entre as duas portas, que será reservado exclusivamente para os dois processos que estabeleceram a ligação.
- Todos os pacotes enviados pelo processo de origem seguem este caminho virtual e chegam na ordem em que foram enviados
- Os dados transmitidos parecem contínuos. O processo de envio envia dados ao seu próprio ritmo. Estes dados não são necessariamente enviados imediatamente: o protocolo TCP aguarda até ter dados suficientes para enviar. São armazenados numa estrutura denominada segmento TCP. Assim que este segmento estiver cheio, é transmitido para a camada IP, onde é encapsulado num pacote IP.
- Cada segmento enviado pelo protocolo TCP é numerado. O protocolo TCP de receção verifica se está a receber os segmentos em sequência. Por cada segmento recebido corretamente, envia um aviso de receção ao remetente.
- Quando o remetente recebe esta confirmação, notifica o processo de envio. O processo de envio pode assim confirmar que um segmento chegou em segurança.
- Se, após um determinado período de tempo, o protocolo TCP que enviou um segmento não receber um aviso de receção, retransmite o segmento em questão, garantindo assim a qualidade do serviço de entrega de informação.
- O circuito virtual estabelecido entre os dois processos em comunicação é full-duplex: isto significa que a informação pode circular em ambos os sentidos. Assim, o processo de destino pode enviar confirmações mesmo enquanto o processo de origem continua a enviar informação. Isto permite, por exemplo, que o protocolo TCP de origem envie vários segmentos sem esperar por uma confirmação. Se, após um determinado período de tempo, perceber que não recebeu um aviso de receção para um segmento específico n.º n, retomará o envio de segmentos a partir desse ponto.
9.3.3. A relação cliente-servidor
A comunicação pela Internet é frequentemente assimétrica: a máquina A inicia uma ligação para solicitar um serviço à máquina B, especificando que pretende abrir uma ligação com o serviço SB1 na máquina B. A máquina B aceita ou recusa. Se aceitar, a máquina A pode enviar os seus pedidos ao serviço SB1. Estes pedidos devem estar em conformidade com o protocolo de comunicação compreendido pelo serviço SB1. Estabelece-se assim um diálogo de pedido-resposta entre a máquina A, conhecida como máquina cliente, e a máquina B, conhecida como máquina servidor. Um dos dois parceiros encerrará a ligação.
9.3.4. Arquitetura do Cliente
A arquitetura de um programa de rede que solicita os serviços de uma aplicação de servidor será a seguinte:
ouvrir la connexion avec le service SB1 de la machine B
si réussite alors
tant que ce n'est pas fini
préparer une demande
l'émettre vers la machine B
attendre et récupérer la réponse
la traiter
fin tant que
finsi
9.3.5. Arquitetura do servidor
A arquitetura de um programa que oferece serviços será a seguinte:
ouvrir le service sur la machine locale
tant que le service est ouvert
se mettre à l'écoute des demandes de connexion sur un port dit port d'écoute
lorsqu'il y a une demande, la faire traiter par une autre tâche sur un autre port dit port de service
fin tant que
O programa servidor trata o pedido de ligação inicial de um cliente de forma diferente dos seus pedidos de serviço subsequentes. O programa não presta o serviço propriamente dito. Se o fizesse, deixaria de estar à escuta de pedidos de ligação enquanto o serviço estivesse em curso, e os clientes não seriam atendidos. Por isso, procede de forma diferente: assim que um pedido de ligação é recebido na porta de escuta e depois aceite, o servidor cria uma tarefa responsável por prestar o serviço solicitado pelo cliente. Este serviço é prestado noutra porta da máquina do servidor, denominada porta de serviço. Isto permite que vários clientes sejam atendidos ao mesmo tempo. Uma tarefa de serviço terá a seguinte estrutura:
tant que le service n'a pas été rendu totalement
attendre une demande sur le port de service
lorsqu'il y en a une, élaborer la réponse
transmettre la réponse via le port de service
fin tant que
libérer le port de service
9.3.6. A classe TcpClient
A classe TcpClient é a classe adequada para representar um cliente de serviço TCP. É 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 em execução na porta especificada (port) da máquina especificada (hostname). Por exemplo, new TcpClient("istia.univ-angers.fr", 80) para se ligar à porta 80 da máquina istia.univ-angers.fr | |
fecha a ligação ao servidor TCP | |
Obtém um NetworkStream para leitura e gravação no servidor. Este fluxo permite a comunicação cliente-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, é útil utilizar objetos StreamReader e StreamWriter para ler e escrever essas linhas no fluxo de rede. Quando duas máquinas comunicam entre si, existe um objeto TcpClient em cada extremidade da ligação. O método GetStream deste objeto fornece acesso 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 utilizando um objeto TcpClient client1 e estiverem a trocar linhas de texto, pode 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 do cliente1 não passará por um buffer intermédio, mas irá diretamente para a rede. Este ponto é importante. Geralmente, quando o cliente1 envia uma linha de texto ao seu parceiro, espera uma resposta. Esta resposta nunca chegará se a linha tiver sido efetivamente armazenada em buffer na máquina M1 e nunca enviada. Para enviar uma linha de texto à máquina M2, escrevemos:
Para ler a resposta da M2, escrevemos:
9.3.8. Arquitetura básica de um cliente de Internet
Temos agora os elementos para escrever a arquitetura básica de um cliente de Internet:
Dim client As TcpClient = Nothing ' the customer
Dim [IN] As StreamReader = Nothing ' the customer's reading flow
Dim OUT As StreamWriter = Nothing ' the customer's writing flow
Dim demande As String = Nothing ' customer request
Dim réponse As String = Nothing ' server response
Try
' connect to the service running on port P of machine M
client = New TcpClient(nomServeur, port)
' create customer input/output flows TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' request-response loop
While True
' preparing the application
demande = ...
' we send it to the server
OUT.WriteLine(demande)
' we read the server response
réponse = [IN].ReadLine()
' the answer is processed
...
End While
' it's over
client.Close()
Catch ex As Exception
' we handle the exception
...
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á escutar pedidos de clientes numa porta passada como parâmetro (port), conhecida como a porta de escuta da máquina local com endereço IP localaddr. | |
aceita um pedido de cliente. Devolve um objeto TcpClient associado a outra porta, denominada porta de serviço. | |
Inicia a escuta de pedidos do cliente | |
Interrompe a escuta de pedidos do cliente |
9.3.10. Arquitetura básica de um servidor de Internet
A partir do que vimos anteriormente, podemos deduzir a estrutura básica de um servidor:
' we create the listening service
Dim ecoute As TcpListener = Nothing
Dim port As Integer = ...
Try
' create the service
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
' launch it
ecoute.Start()
' service loop
Dim liaisonClient As TcpClient = Nothing
While not fini
' waiting for a customer
liaisonClient = ecoute.AcceptTcpClient()
' the service is provided by another task
Dim tache As Thread = New Thread(New ThreadStart(AddressOf [méthode]))
tache.Start()
End While
Catch ex As Exception
' we report the error
....
End Try
' end of service
ecoute.Stop()
A classe Service é um segmento de execução que pode ter o seguinte aspeto:
Public Class Service
Private liaisonClient As TcpClient ' customer liaison
Private [IN] As StreamReader ' iNPUTS
Private OUT As StreamWriter ' output flow
' manufacturer
Public Sub New(ByVal liaisonClient As TcpClient, ...)
Me.liaisonClient = liaisonClient
...
End Sub
' run method
Public Sub Run()
' renders service to the customer
Try
' iNPUTS
[IN] = New StreamReader(liaisonClient.GetStream())
' output flow
OUT = New StreamWriter(liaisonClient.GetStream())
OUT.AutoFlush = True
' loop read request/write response
Dim demande As String = Nothing
Dim reponse As String = Nothing
demande = [IN].ReadLine
While Not (demande Is Nothing)
' we process demand
...
' we send the answer
reponse = "[" + demande + "]"
OUT.WriteLine(reponse)
' following request
demande = [IN].ReadLine
End While
' end link
liaisonClient.Close()
Catch e As Exception
...
End Try
' end of service
End Sub
9.4. Exemplos
9.4.1. Servidor Echo
Vamos escrever um servidor de eco que será iniciado a partir de uma janela do DOS utilizando o comando:
O servidor funciona na porta passada como parâmetro. Ele simplesmente reenvia ao cliente o pedido que este lhe enviou. O programa é o seguinte:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System.Net.Sockets
Imports System.Net
Imports System
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
' call: serveurEcho port
' echo server
' returns the line sent to the customer
Public Class serveurEcho
Private Shared syntaxe As String = "Syntaxe : serveurEcho port"
' main program
Public Shared Sub Main(ByVal args() As String)
' is there an argument
If args.Length <> 1 Then
erreur(syntaxe, 1)
End If
' this argument must be integer >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
' we create the listening service
Dim ecoute As TcpListener = Nothing
Dim nbClients As Integer = 0 ' of customers handled
Try
' create the service
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
' launch it
ecoute.Start()
' follow-up
Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & port))
Console.Out.WriteLine(ecoute.LocalEndpoint)
' service loop
Dim liaisonClient As TcpClient = Nothing
While True
' infinite loop - will be stopped by Ctrl-C
' waiting for a customer
liaisonClient = ecoute.AcceptTcpClient()
' the service is provided by another task
nbClients += 1
Dim tache As Thread = New Thread(New ThreadStart(AddressOf New traiteClientEcho(liaisonClient, nbClients).Run))
tache.Start()
End While
' back to listening to requests
Catch ex As Exception
' we report the error
erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
End Try
' end of service
ecoute.Stop()
End Sub
' error display
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
End Class
' -------------------------------------------------------
' provides service to an echo server client
Public Class traiteClientEcho
Private liaisonClient As TcpClient ' customer liaison
Private numClient As Integer ' customer no
Private [IN] As StreamReader ' iNPUTS
Private OUT As StreamWriter ' output flow
' manufacturer
Public Sub New(ByVal liaisonClient As TcpClient, ByVal numClient As Integer)
Me.liaisonClient = liaisonClient
Me.numClient = numClient
End Sub
' run method
Public Sub Run()
' renders service to the customer
Console.Out.WriteLine(("Début de service au client " & numClient))
Try
' iNPUTS
[IN] = New StreamReader(liaisonClient.GetStream())
' output flow
OUT = New StreamWriter(liaisonClient.GetStream())
OUT.AutoFlush = True
' loop read request/write response
Dim demande As String = Nothing
Dim reponse As String = Nothing
demande = [IN].ReadLine
While Not (demande Is Nothing)
' follow-up
Console.Out.WriteLine(("Client " & numClient & " : " & demande))
' the service stops when the client sends an end-of-file marker
reponse = "[" + demande + "]"
OUT.WriteLine(reponse)
' service stops when client sends "end
If demande.Trim().ToLower() = "fin" Then
Exit While
End If
' following request
demande = [IN].ReadLine
End While
' end link
liaisonClient.Close()
Catch e As Exception
erreur("Erreur lors de la fermeture de la liaison client (" + e.ToString + ")", 2)
End Try
' end of service
Console.Out.WriteLine(("Fin de service au client " & numClient))
End Sub
' error display
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
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:
Ele liga-se à máquina servername na porta port e, em seguida, envia linhas de texto para o servidor, que as repete de volta.
' options
Option Explicit On
Option Strict On
' namespaces
Imports System.Net.Sockets
Imports System.Net
Imports System
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
Public Class clientEcho
' connects to an echo server
' any line typed on the keyboard is then received as an echo
Public Shared Sub Main(ByVal args() As String)
' syntax
Const syntaxe As String = "pg machine port"
' number of arguments
If args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' note the server name
Dim nomServeur As String = args(0)
' port must be integer >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
' we can work
Dim client As TcpClient = Nothing ' the customer
Dim [IN] As StreamReader = Nothing ' the customer's reading flow
Dim OUT As StreamWriter = Nothing ' the customer's writing flow
Dim demande As String = Nothing ' customer request
Dim réponse As String = Nothing ' server response
Try
' connect to the service running on port P of machine M
client = New TcpClient(nomServeur, port)
' create customer input/output flows TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' request-response loop
While True
' demand comes from the keyboard
Console.Out.Write("demande (fin pour arrêter) : ")
demande = Console.In.ReadLine()
' we send it to the server
OUT.WriteLine(demande)
' we read the server response
réponse = [IN].ReadLine()
' the answer is processed
Console.Out.WriteLine(("Réponse : " + réponse))
' finished?
If demande.Trim().ToLower() = "fin" Then
Exit While
End If
End While
' it's over
client.Close()
Catch ex As Exception
' we handle the exception
erreur(ex.Message, 3)
End Try
End Sub
' error display
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
End Class
A estrutura deste cliente está em conformidade com a arquitetura geral dos clientes TCP. Aqui estão os resultados obtidos com a seguinte configuração:
- O servidor está a ser executado 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, obtemos 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]
No 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]
No lado 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 simultaneamente.
9.4.3. Um cliente TCP genérico
Muitos serviços criados nos primórdios da Internet funcionam de acordo com o modelo de servidor de eco discutido anteriormente: as trocas cliente-servidor consistem na 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 no servidor server. Uma vez ligado, irá criar duas threads:
- uma thread responsável por ler os comandos digitados no teclado e enviá-los para o servidor
- uma thread responsável por ler as respostas do servidor e exibi-las no ecrã
Porquê duas threads quando isso não era necessário na aplicação anterior? Nessa aplicação, o protocolo de comunicação era fixo: o cliente enviava uma única linha e o servidor respondia com uma única linha. Cada serviço tem o seu próprio protocolo específico, e também nos deparamos com as seguintes situações:
- o cliente deve enviar várias linhas de texto antes de receber uma resposta
- a resposta de um servidor pode conter várias linhas de texto
Por isso, o ciclo que envia uma única linha para o servidor e recebe uma única linha do servidor nem sempre é adequado. Vamos, portanto, criar dois ciclos separados:
- um loop para ler os comandos digitados no teclado a serem enviados ao servidor. O utilizador sinalizará o fim dos comandos com a palavra-chave «fin».
- um ciclo para receber e exibir as respostas do servidor. Este será um ciclo infinito que só será interrompido pelo servidor ao fechar a ligação de rede ou pelo utilizador ao digitar o comando «end» no teclado.
Para ter estes dois loops separados, precisamos de dois threads independentes. Vejamos um exemplo de execução em que o nosso cliente TCP genérico se liga a um serviço SMTP (Simple Mail Transfer Protocol). Este serviço é responsável pelo encaminhamento de e-mails aos destinatários. Opera na porta 25 e utiliza um protocolo de troca baseado em 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 cliente-servidor:
- O serviço SMTP envia uma mensagem de boas-vindas quando um cliente se liga a ele:
- Alguns serviços têm um comando «help» que fornece informações sobre os comandos disponíveis para esse serviço. Não é esse o caso aqui. Os comandos SMTP utilizados no exemplo são os seguintes:
- mail from: remetente, para especificar o endereço de e-mail do remetente
- rcpt to: destinatário, para especificar o endereço de e-mail do destinatário da mensagem. Se houver vários destinatários, o comando rcpt to: é repetido tantas vezes quantas forem necessárias para cada destinatário.
- data que sinaliza ao servidor SMTP que uma mensagem está prestes a ser enviada. Conforme indicado na resposta do servidor, estes dados consistem numa série de linhas que terminam com uma linha contendo apenas um ponto. Uma mensagem pode ter cabeçalhos separados do corpo da mensagem por uma linha em branco. No nosso exemplo, incluímos um assunto utilizando a palavra-chave Subject:
- Assim que a mensagem for enviada, pode indicar ao servidor que terminou utilizando o comando quit. O servidor encerra então a ligação de rede. O segmento de leitura pode detetar este evento e parar.
- O utilizador digita então «end» no teclado para também parar a thread que lê os comandos digitados no teclado.
Se verificarmos o e-mail recebido, vemos o seguinte (Outlook):

Note que o serviço SMTP não consegue detetar se um remetente é válido ou não. Por isso, nunca se pode confiar no campo «De» de uma mensagem. Aqui, o remetente machin@univ-angers.fr não existia. Este cliente TCP genérico permite-nos descobrir o protocolo de comunicação dos serviços da Internet e, a partir daí, criar classes especializadas para clientes desses serviços. Vamos explorar o protocolo de comunicação do serviço POP (Post Office Protocol), que permite aos utilizadores recuperar os seus 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:
- login do utilizador, onde introduz o seu nome de utilizador na máquina que aloja os seus e-mails
- pass password, onde introduz a palavra-passe associada ao login anterior
- list, para obter uma lista de mensagens no formato número, tamanho em bytes
- retr i, para ler a mensagem número i
- sair, para encerrar a sessão.
Agora vamos explorar o protocolo de comunicação entre um cliente e um servidor web, que normalmente funciona 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 para o servidor de acordo com o seguinte padrão:
O servidor web só responde após receber a linha vazia. Neste exemplo, utilizámos apenas um comando:
que solicita a URL /index.html ao servidor e indica que está a utilizar a versão 1.0 do HTTP. A versão mais recente deste protocolo é a 1.1. O exemplo mostra que o servidor respondeu enviando o conteúdo do ficheiro index.html e, em seguida, encerrou a ligação, como podemos ver na thread de leitura da resposta a terminar. Antes de enviar o conteúdo do ficheiro index.html, o servidor web enviou uma série de cabeçalhos seguidos de uma linha vazia:
<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
A linha <html> é a primeira linha do ficheiro /index.html. O texto que a precede denomina-se cabeçalhos HTTP (HyperText Transfer Protocol). Não vamos entrar em pormenores sobre estes cabeçalhos aqui, mas tenha em mente que o nosso cliente genérico permite aceder aos mesmos, o que pode ser útil para os compreender. Por exemplo, a primeira linha:
indica que o servidor web contactado suporta o protocolo HTTP/1.1 e que encontrou com sucesso o ficheiro solicitado (200 OK), sendo que 200 é um código de resposta HTTP. As linhas
informa ao cliente que receberá 11 251 bytes de texto HTML (HyperText Markup Language) e que a ligação será encerrada assim que os dados forem enviados. Portanto, temos aqui um cliente TCP muito útil. Na verdade, este cliente já existe em máquinas onde é chamado de telnet, mas foi interessante escrevê-lo nós próprios. O programa genérico do cliente TCP é o seguinte:
' namespaces
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
' the class
Public Class clientTcpGénérique
' receives the characteristics of a service as a parameter in the form
' server port
' connects to the service
' creates a thread to read keyboard commands
' these will be sent to the
' creates a thread to read server responses
' these will be displayed on the screen
' the whole thing ends with the command end typed on the keyboard
Public Shared Sub Main(ByVal args() As String)
' syntax
Const syntaxe As String = "pg serveur port"
' number of arguments
If args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' note the server name
Dim serveur As String = args(0)
' port must be integer >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
' there may be problems
Try
' connect to the service
client = New TcpClient(serveur, port)
Catch ex As Exception
' error
Console.Error.WriteLine(("Impossible de se connecter au service (" & serveur & "," & port & "), erreur : " & ex.Message))
' end
Return
End Try
' create read/write threads
Dim thReceive As New Thread(New ThreadStart(AddressOf New clientReceive(client).Run))
Dim thSend As New Thread(New ThreadStart(AddressOf New clientSend(client).Run))
' start execution of both threads
thSend.Start()
thReceive.Start()
' end of main thread
Return
End Sub
' error display
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
End Class
Public Class clientSend
' class for reading keyboard commands
' and send them to a server via a tcp client passed to the
Private client As TcpClient ' tcp client
' manufacturer
Public Sub New(ByVal client As TcpClient)
' we note the tcp client
Me.client = client
End Sub
' thread Run method
Public Sub Run()
' local data
Dim OUT As StreamWriter = Nothing ' network write streams
Dim commande As String = Nothing ' command read from keyboard
' error management
Try
' create network write stream
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' order entry-send loop
Console.Out.WriteLine("Commandes : ")
While True
' read command typed on keyboard
commande = Console.In.ReadLine().Trim()
' finished?
If commande.ToLower() = "fin" Then
Exit While
End If
' send order to server
OUT.WriteLine(commande)
End While
Catch ex As Exception
' error
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' end - we close the feeds
Try
OUT.Close()
client.Close()
Catch
End Try
' signals the end of the thread
Console.Out.WriteLine("[fin du thread d'envoi des commandes au serveur]")
End Sub
End Class
Public Class clientReceive
' class responsible for reading lines of text intended for a
' tcp client passed to builder
Private client As TcpClient ' tcp client
' manufacturer
Public Sub New(ByVal client As TcpClient)
' we note the tcp client
Me.client = client
End Sub
'manufacturer
' thread Run method
Public Sub Run()
' local data
Dim [IN] As StreamReader = Nothing ' network read stream
Dim réponse As String = Nothing ' server response
' error management
Try
' create network read stream
[IN] = New StreamReader(client.GetStream())
' loop read text lines from IN stream
While True
' network streaming
réponse = [IN].ReadLine()
' closed flow?
If réponse Is Nothing Then
Exit While
End If
' display
Console.Out.WriteLine(("<-- " + réponse))
End While
Catch ex As Exception
' error
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' end - close flows
Try
[IN].Close()
client.Close()
Catch
End Try
' signals the end of the 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 ver um servidor
- que exibe no ecrã os comandos enviados pelos seus clientes
- e lhes envia o texto introduzido por um utilizador através do teclado. O utilizador atua, portanto, como servidor.
O programa é iniciado por: srvgen listeningPort, onde listeningPort é a porta à qual os clientes devem ligar-se. O serviço ao cliente será tratado por duas threads:
- uma thread dedicada exclusivamente à leitura das linhas de texto enviadas pelo cliente
- uma thread dedicada exclusivamente à leitura das respostas digitadas pelo utilizador. Esta thread sinalizará, utilizando o comando fin, que está a encerrar a ligação com o cliente.
O servidor cria duas threads por cliente. Se houver n clientes, haverá 2n threads ativas ao mesmo tempo. O próprio servidor nunca pára, a menos que o utilizador pressione Ctrl-C no teclado. Vejamos alguns exemplos.
O servidor está a funcionar na porta 100 e usamos o cliente genérico para comunicar com ele. A janela do cliente tem este aspeto:
dos>cltgen localhost 100
Commandes :
commande 1 du client 1
<-- réponse 1 au client 1
commande 2 du client 1
<-- réponse 2 au client 1
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]
As linhas que começam com <-- são as enviadas do servidor para o cliente; as outras são do cliente para o servidor. A janela do servidor é a seguinte:
dos>srvgen 100
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
1 : Thread de lecture des demandes du client 1 lancé
<-- commande 1 du client 1
réponse 1 au client 1
1 : <-- commande 2 du client 1
réponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des réponses du serveur au client 1]
As linhas que começam com <-- são as enviadas do cliente para o servidor. As linhas N: são as enviadas do servidor para o cliente N. O servidor acima continua ativo, apesar de o cliente 1 ter terminado. Iniciamos um segundo cliente para o mesmo servidor:
dos>cltgen localhost 100
Commandes :
commande 3 du client 2
<-- réponse 3 au client 2
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]
A janela do servidor tem agora o seguinte aspeto:
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
Agora vamos simular um servidor web executando o nosso servidor genérico na porta 88:
Agora vamos abrir um navegador e aceder ao URL http://localhost:88/exemple.html. O navegador irá então ligar-se à porta 88 na máquina localhost e solicitar a página /example.html:

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

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

ou seja, exatamente o que foi enviado pelo servidor genérico. O código para o servidor TCP genérico é o seguinte:
' namespaces
Imports System
Imports System.Net
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
Public Class serveurTcpGénérique
' main program
Public Shared Sub Main(ByVal args() As String)
' receives the port of listening to customer requests
' creates a thread to read client requests
' these will be displayed on the screen
' creates a thread to read keyboard commands
' these will be sent as a reply to the customer
' the whole thing ends with the command end typed on the keyboard
Const syntaxe As String = "Syntaxe : pg port"
' is there an argument
If args.Length <> 1 Then
erreur(syntaxe, 1)
End If
' this argument must be integer >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
' we create the listening service
Dim ecoute As TcpListener = Nothing
Dim nbClients As Integer = 0 ' of customers handled
Try
' create the service
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
' launch it
ecoute.Start()
' follow-up
Console.Out.WriteLine(("Serveur générique lancé sur le port " & port))
' customer service loop
Dim client As TcpClient = Nothing
While True ' infinite loop - will be stopped by Ctrl-C
' waiting for a customer
client = ecoute.AcceptTcpClient()
' the service is provided by separate threads
nbClients += 1
' thread for reading customer requests
Dim thReceive As New Thread(New ThreadStart(AddressOf New serveurReceive(client, nbClients).Run))
' thread for reading responses typed on the keyboard by the user
Dim thSend As New Thread(New ThreadStart(AddressOf New serveurSend(client, nbClients).Run))
' start execution of both threads
thSend.Start()
thReceive.Start()
End While
' back to listening to requests
Catch ex As Exception
' we report the error
erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
End Try
End Sub
' error display
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
End Class
Public Class serveurSend
' class responsible for reading typed responses
' and send them to a client via a tcp client passed to the
Private client As TcpClient ' tcp client
Private numClient As Integer ' customer no
' manufacturer
Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)
' we note the tcp client
Me.client = client
' and its
Me.numClient = numClient
End Sub
' thread Run method
Public Sub Run()
' local data
Dim OUT As StreamWriter = Nothing ' network write streams
Dim réponse As String = Nothing ' answer read from keyboard
' follow-up
Console.Out.WriteLine(("Thread de lecture des réponses du serveur au client " & numClient & " lancé"))
' error management
Try
' network write stream creation
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' order entry-send loop
While True
' customer identification
Console.Out.Write((numClient & " : "))
' read response typed on keyboard
réponse = Console.In.ReadLine().Trim()
' finished?
If réponse.ToLower() = "fin" Then
Exit While
End If
' send response to server
OUT.WriteLine(réponse)
End While
' following response
Catch ex As Exception
' error
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' end - close flows
Try
OUT.Close()
client.Close()
Catch
End Try
' signals the end of the thread
Console.Out.WriteLine(("[fin du Thread de lecture des réponses du serveur au client " & numClient & "]"))
End Sub
End Class
Public Class serveurReceive
' class responsible for reading text lines sent to the server
' via a tcp client passed to the builder
Private client As TcpClient ' tcp client
Private numClient As Integer ' customer no
' manufacturer
Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)
' we note the tcp client
Me.client = client
' and its
Me.numClient = numClient
End Sub
' thread Run method
Public Sub Run()
' local data
Dim [IN] As StreamReader = Nothing ' network read stream
Dim réponse As String = Nothing ' server response
' follow-up
Console.Out.WriteLine(("Thread de lecture des demandes du client " & numClient & " lancé"))
' error management
Try
' create network read stream
[IN] = New StreamReader(client.GetStream())
' loop read text lines from IN stream
While True
' network streaming
réponse = [IN].ReadLine()
' closed flow?
If réponse Is Nothing Then
Exit While
End If
' display
Console.Out.WriteLine(("<-- " + réponse))
End While
Catch ex As Exception
' error
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' end - close flows
Try
[IN].Close()
client.Close()
Catch
End Try
' signals the end of the 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 enviados por um navegador:
<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, */*
<-- Accept-Language: fr
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.0.2
914)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--
Vamos escrever um cliente web que receba um URL como parâmetro e exiba no ecrã o texto enviado pelo servidor. Vamos assumir que o servidor suporta o protocolo HTTP 1.1. Dos cabeçalhos listados acima, vamos utilizar apenas os seguintes:
- O primeiro cabeçalho indica qual a página que pretendemos
- o segundo especifica qual o servidor que estamos a consultar
- O terceiro indica que queremos que o servidor feche a ligação depois de nos responder.
Se substituirmos GET por HEAD no exemplo acima, o servidor enviar-nos-á apenas os cabeçalhos HTTP e não a página HTML.
O nosso cliente web será chamado da seguinte forma: webclient URL cmd, onde URL é o URL pretendido e cmd é uma das duas palavras-chave GET ou HEAD para indicar se queremos apenas os cabeçalhos (HEAD) ou também o conteúdo da página (GET). Vejamos um primeiro exemplo. Iniciamos o servidor IIS e, em seguida, o cliente web na mesma máquina:
dos>clientweb http://localhost HEAD
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:23:37 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=HMFNCCMDECBJJBPPBHAOAJNP; path=/
Cache-control: private
A resposta
significa que a página solicitada foi movida (e, portanto, tem um novo URL). O novo URL é fornecido pelo cabeçalho Location:
Se utilizarmos GET em vez de HEAD na chamada para o cliente web:
dos>clientweb http://localhost GET
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:33:36 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=IMFNCCMDAKPNNGMGMFIHENFE; path=/
Cache-control: private
<head><title>L'objet a changé d'emplacement</title></head>
<body><h1>L'objet a changé d'emplacement</h1>Cet objet peut être trouvé <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>
Obtemos o mesmo resultado que com HEAD, além do corpo da página HTML. O programa é o seguinte:
' namespaces
Imports System
Imports System.Net.Sockets
Imports System.IO
Public Class clientWeb1
' requests a URL
' displays its contents on the screen
Public Shared Sub Main(ByVal args() As String)
' syntax
Const syntaxe As String = "pg URI GET/HEAD"
' number of arguments
If args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' note the URI required
Dim URIstring As String = args(0)
Dim commande As String = args(1).ToUpper()
' URI validity check
Dim uri As Uri = Nothing
Try
uri = New Uri(URIstring)
Catch ex As Exception
' URI incorrect
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try
' order verification
If commande <> "GET" And commande <> "HEAD" Then
' incorrect order
erreur("Le second paramètre doit être GET ou HEAD", 3)
End If
' we can work
Dim client As TcpClient = Nothing ' the customer
Dim [IN] As StreamReader = Nothing ' the customer's reading flow
Dim OUT As StreamWriter = Nothing ' the customer's writing flow
Dim réponse As String = Nothing ' server response
Try
' connect to the server
client = New TcpClient(uri.Host, uri.Port)
' create customer input/output flows TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' request URL - send HTTP headers
OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))
OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port))
OUT.WriteLine("Connection: close")
OUT.WriteLine()
' we read the answer
réponse = [IN].ReadLine()
While Not (réponse Is Nothing)
' the answer is processed
Console.Out.WriteLine(réponse)
' we read the answer
réponse = [IN].ReadLine()
End While
' it's over
client.Close()
Catch e As Exception
' we handle the exception
erreur(e.Message, 4)
End Try
End Sub
' error display
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
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) no formato http://serveur:port/cheminPageHTML?param1=val1;param2=val2;.... A classe Uri permite-nos decompor a cadeia de caracteres da URL nos seus vários componentes. Um objeto Uri é construído a partir da cadeia de caracteres URI recebida como parâmetro:
' URI validity check
Dim uri As Uri = Nothing
Try
uri = New Uri(URIstring)
Catch ex As Exception
' URI incorrect
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try
Se a cadeia de caracteres URI recebida como parâmetro não for um URI válido (falta de protocolo, servidor, etc.), é lançada uma exceção. Isto permite-nos verificar a validade do parâmetro recebido. Assim que o objeto URI é construído, temos acesso aos seus vários elementos. Assim, se o objeto URI do código anterior foi construído a partir da string http://serveur:port/cheminPageHTML?param1=val1;param2=val2;..., teremos:
uri.Host=server, uri.Port=port, uri.Path=HTMLPagePath, uri.Query=param1=val1;param2=val2;..., uri.pathAndQuery=HTMLPagePath?param1=val1;param2=val2;..., uri.Scheme=http.
9.4.6. Cliente web a lidar com redirecionamentos
O cliente web anterior não lida com qualquer possível redirecionamento do URL que solicitou. O cliente seguinte lida com isso.
- Ele lê a primeira linha dos cabeçalhos HTTP enviados pelo servidor para verificar se contém a string «302 Object moved», o que indica um redirecionamento
- O programa lê os seguintes cabeçalhos. Se houver um redirecionamento, procura a linha «Location: url», que fornece o novo URL da página solicitada, e regista esse URL.
- Exibe o resto da resposta do servidor. Se houver um redirecionamento, os passos 1 a 3 são repetidos com a nova URL. O programa não aceita mais do que um redirecionamento. Este limite é definido por uma constante que pode ser modificada.
Eis um exemplo:
dos>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:
' namespaces
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic
' web client class
Public Class clientWeb
' requests a URL and displays its contents on the screen
Public Shared Sub Main(ByVal args() As String)
' syntax
Const syntaxe As String = "pg URI GET/HEAD"
' number of arguments
If args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' note the URI required
Dim URIstring As String = args(0)
Dim commande As String = args(1).ToUpper()
' URI validity check
Dim uri As Uri = Nothing
Try
uri = New Uri(URIstring)
Catch ex As Exception
' URI incorrect
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try 'catch
' order verification
If commande <> "GET" And commande <> "HEAD" Then
' incorrect order
erreur("Le second paramètre doit être GET ou HEAD", 3)
End If
' we can work
Dim client As TcpClient = Nothing ' the customer
Dim [IN] As StreamReader = Nothing ' the customer's reading flow
Dim OUT As StreamWriter = Nothing ' the customer's writing flow
Dim réponse As String = Nothing ' server response
Const nbRedirsMax As Integer = 1 ' no more than one redirection accepted
Dim nbRedirs As Integer = 0 ' number of redirects in progress
Dim premièreLigne As String ' 1st line of the answer
Dim redir As Boolean = False ' indicates redirection or not
Dim locationString As String = "" ' the URI string of a possible redirection
' regular expression to find a URL redirect
Dim location As New Regex("^Location: (.+?)$") '
' error management
Try
' you may have several URL to request if there are redirections
While nbRedirs <= nbRedirsMax
' connect to the server
client = New TcpClient(uri.Host, uri.Port)
' create customer input/output flows TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' we send HTTP headers to request URL
OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))
OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port))
OUT.WriteLine("Connection: close")
OUT.WriteLine()
' read the first line of the answer
premièreLigne = [IN].ReadLine()
' screen echo
Console.Out.WriteLine(premièreLigne)
' redirection?
If Regex.IsMatch(premièreLigne, "302 Object moved$") Then
' there is a redirection
redir = True
nbRedirs += 1
End If
' next HTTP headers until you find the empty line signalling the end of the headers
Dim locationFound As Boolean = False
réponse = [IN].ReadLine()
While réponse <> ""
' the answer is displayed
Console.Out.WriteLine(réponse)
' if there is a redirection, we search for the Location header
If redir And Not locationFound Then
' compare the line with the relational expression location
Dim résultat As Match = location.Match(réponse)
If résultat.Success Then
' if found, note the URL of redirection
locationString = résultat.Groups(1).Value
' we note that we found
locationFound = True
End If
End If
' next line
réponse = [IN].ReadLine()
End While
' following lines of the answer
Console.Out.WriteLine(réponse)
réponse = [IN].ReadLine()
While Not (réponse Is Nothing)
' the answer is displayed
Console.Out.WriteLine(réponse)
' next line
réponse = [IN].ReadLine()
End While
' close the connection
client.Close()
' are we done?
If Not locationFound Or nbRedirs > nbRedirsMax Then
Exit While
End If
' there is a redirection to be made - we build the new Uri
URIstring = uri.Scheme + "://" & uri.Host & ":" & uri.Port & locationString
uri = New Uri(URIstring)
' follow-up
Console.Out.WriteLine((ControlChars.Lf + "<--Redirection vers l'URL " + URIstring + "-->" + ControlChars.Lf))
End While
Catch e As Exception
' we handle the exception
erreur(e.Message, 4)
End Try
End Sub
' error display
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
End Class
9.4.7. Servidor de Cálculo de Impostos
Estamos a revisitar o exercício TAXES, que já foi abordado de várias formas. Vamos rever a versão mais recente. Foi criada uma classe de impostos. Os seus atributos são três matrizes de números:
Public Class impôt
' les données nécessaires au calcul de l'impôt
' proviennent d'une source extérieure
Private limites(), coeffR(), coeffN() as double
A classe tem dois construtores:
- um construtor que recebe as três matrizes de dados necessárias para calcular o imposto
// constructeur 1
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' initializes the three limit arrays, coeffR, coeffN from
' parameters passed to the constructor
- um construtor ao qual passamos o nome DSN de uma base de dados ODBC
' builder 2
Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
' initializes the three limit arrays, coeffR, coeffN from
' the contents of the Timpots table in the ODBC DSNimpots database
' colLimites, colCoeffR, colCoeffN are the three columns of this table
' can throw an exception
Foi 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
Aqui, o programa de teste e o objeto fiscal estavam na mesma máquina. Propomos colocar o programa de teste e o objeto fiscal em máquinas diferentes. Teremos uma aplicação cliente-servidor em que o objeto fiscal remoto será o servidor. A nova classe chama-se TaxServer e deriva da classe tax:
Public Class ServeurImpots
Inherits impôt
' attributes
Private portEcoute As Integer ' the ability to listen to customer requests
Private actif As Boolean ' server status
' manufacturer
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)
' we note the listening port
Me.portEcoute = portEcoute
' currently inactive
actif = False
' creates and launches a thread for reading keyboard commands
' the server will be managed using these commands
Dim threadLecture As Thread = New Thread(New ThreadStart(AddressOf admin))
threadLecture.Start()
End Sub
O único parâmetro novo no construtor é a porta para ouvir os pedidos dos clientes. Os outros parâmetros são passados diretamente para a classe base do imposto. O servidor de impostos é controlado por comandos de teclado. Por isso, criamos um segmento de execução para ler esses comandos. Haverá dois comandos possíveis: «start» para iniciar o serviço e «stop» para o encerrar definitivamente. O método «admin» que trata destes comandos é o seguinte:
Public Sub admin()
' reads server administration commands typed from the keyboard
' in an endless loop
Dim commande As String = Nothing
While True
' invite
Console.Out.Write("Serveur d'impôts>")
' read command
commande = Console.In.ReadLine().Trim().ToLower()
' order execution
If commande = "start" Then
' active?
If actif Then
'error
Console.Out.WriteLine("Le serveur est déjà actif")
Else
' we launch the listening service
Dim threadEcoute As Thread = New Thread(New ThreadStart(AddressOf ecoute))
threadEcoute.Start()
End If
Else
If commande = "stop" Then
' end of all execution threads
Environment.Exit(0)
Else
' error
Console.Out.WriteLine("Commande incorrecte. Utilisez (start,stop)")
End If
End If
End While
End Sub
Se o comando introduzido através do teclado for «start», é lançada uma thread que escuta os pedidos do cliente. Se o comando introduzido for «stop», todas as threads são paradas. A thread de escuta executa o método «ecoute»:
Public Sub ecoute()
' thread for listening to customer requests
' we create the listening service
Dim ecoute As TcpListener = Nothing
Try
' create the service
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), portEcoute)
' launch it
ecoute.Start()
' follow-up
Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & portEcoute))
' service loop
Dim liaisonClient As TcpClient = Nothing
While True ' infinite loop
' waiting for a customer
liaisonClient = ecoute.AcceptTcpClient()
' the service is provided by another task
Dim threadClient As Thread = New Thread(New ThreadStart(AddressOf New traiteClientImpots(liaisonClient, Me).Run))
threadClient.Start()
End While
' back to listening to requests
Catch ex As Exception
' we report the error
erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
End Try
End Sub
' error display
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
Aqui temos um servidor TCP clássico a escutar na porta portEcoute. Os pedidos do cliente são tratados pelo método Run de um objeto ao qual são passados dois parâmetros:
- o objeto TcpClient, que permite o acesso ao cliente
- o objeto tax this, que fornece acesso ao método de cálculo de impostos this.calculate.
' -------------------------------------------------------
' provides service to a tax server client
Public Class traiteClientImpots
Private liaisonClient As TcpClient ' customer liaison
Private [IN] As StreamReader ' iNPUTS
Private OUT As StreamWriter ' output flow
Private objImpôt As impôt ' object Tax
' manufacturer
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:
- calculateMarried(y/n) numChildren annualSalary
- endCalculations
O Formulário 1 calcula um imposto, enquanto o Formulário 2 encerra a ligação cliente-servidor.
' run method
Public Sub Run()
' renders service to the customer
Try
' iNPUTS
[IN] = New StreamReader(liaisonClient.GetStream())
' output flow
OUT = New StreamWriter(liaisonClient.GetStream())
OUT.AutoFlush = True
' send a welcome message to the customer
OUT.WriteLine("Bienvenue sur le serveur d'impôts")
' loop read request/write response
Dim demande As String = Nothing
Dim champs As String() = Nothing ' elements of the request
Dim commande As String = Nothing ' customer order: calculation or fincalculs
demande = [IN].ReadLine()
While Not (demande Is Nothing)
' demand is broken down into fields
champs = Regex.Split(demande.Trim().ToLower(), "\s+")
' two successful applications: calcul and fincalculs
commande = champs(0)
Dim erreur As Boolean = False
If commande <> "calcul" And commande <> "fincalculs" Then
' customer error
OUT.WriteLine("Commande incorrecte. Utilisez (calcul,fincalculs).")
End If
If commande = "calcul" Then
calculerImpôt(champs)
End If
If commande = "fincalculs" Then
' good-bye message to customer
OUT.WriteLine("Au revoir...")
' freeing up resources
Try
OUT.Close()
[IN].Close()
liaisonClient.Close()
Catch
End Try
' end
Return
End If
' new request
demande = [IN].ReadLine()
End While
Catch e As Exception
erreur("L'erreur suivante s'est produite (" + e.ToString + ")", 2)
End Try
End Sub
O cálculo do imposto é realizado pelo método CalculateTax, que recebe como parâmetro a matriz de campos da solicitação do cliente. A validade da solicitação é verificada e, se for válida, o imposto é calculado e devolvido ao cliente.
' tax calculation
Public Sub calculerImpôt(ByVal champs() As String)
' processing the application: calculation married nbEnfants salaireAnnuel
' broken down into fields in the fields table
Dim marié As String = Nothing
Dim nbEnfants As Integer = 0
Dim salaireAnnuel As Integer = 0
' validity of arguments
Try
' at least 4 fields are required
If champs.Length <> 4 Then
Throw New Exception
End If
' married
marié = champs(1)
If marié <> "o" And marié <> "n" Then
Throw New Exception
End If
' children
nbEnfants = Integer.Parse(champs(2))
' salary
salaireAnnuel = Integer.Parse(champs(3))
Catch
OUT.WriteLine(" syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel")
' finish
Exit Sub
End Try
' tax can be calculated
Dim impot As Long = objImpôt.calculer(marié = "o", nbEnfants, salaireAnnuel)
' we send the response to the client
OUT.WriteLine(impot.ToString)
End Sub
' error display
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
Esta classe é compilada por
onde impots.dll contém o código para a classe impôt. Um programa de teste pode ter o seguinte aspeto:
' namespaces
Imports System
Imports System.IO
Imports Microsoft.VisualBasic
Public Class testServeurImpots
Public Shared syntaxe As String = "Syntaxe : pg port dsnImpots Timpots colLimites colCoeffR colCoeffN"
' main program
Public Shared Sub Main(ByVal args() As String)
' you need6 arguments
If args.Length <> 6 Then
erreur(syntaxe, 1)
End If
' port must be integer >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
' we create the tax server
Try
Dim srvimots As ServeurImpots = New ServeurImpots(port, args(1), args(2), args(3), args(4), args(5))
Catch ex As Exception
'error
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
End Sub
' error display
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
End Class
Passamos os dados necessários para construir um objeto ServeurImpots para o programa de teste, e a partir daí ele cria esse objeto. Este programa de teste é compilado por:
Eis um teste inicial:
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 TaxServer que ainda não está a escutar pedidos de clientes. É o comando start digitado no teclado que inicia este processo de escuta. O comando stop pára o servidor. Vamos agora utilizar um cliente. Utilizaremos o cliente genérico criado anteriormente. O servidor é iniciado:
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:
Podemos ver que o cliente recebeu com sucesso a mensagem de boas-vindas do servidor. Enviamos outros comandos:
x
<-- Commande incorrecte. Utilisez (calcul,fincalculs).
calcul
<-- syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel
calcul o 2 200000
<-- 22506
calcul n 2 200000
<-- 33388
fincalculs
<-- Au revoir...
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Voltamos à janela do servidor para o parar:















