Diferenças
Aqui você vê as diferenças entre duas revisões dessa página.
| Ambos lados da revisão anterior Revisão anterior Próxima revisão | Revisão anterior | ||
| pua:comunicacao_em_rede [2008/08/13 23:46] – maziero | pua:comunicacao_em_rede [2008/08/20 18:35] (atual) – maziero | ||
|---|---|---|---|
| Linha 1: | Linha 1: | ||
| + | ====== Comunicação em Rede ====== | ||
| + | |||
| + | A comunicação em rede no ambiente UNIX é feita através do conceito de // | ||
| + | |||
| + | ===== Estilos, nomes e protocolos ===== | ||
| + | |||
| + | Ao criar um //socket//, deve ser especificada a forma de comunicação (// | ||
| + | |||
| + | * **stream** : esta forma de comunicação provê canais de comunicação bidirecional ponto-a-ponto (entre dois processos pré-definidos) sobre os quais pode-se enviar seqüências de bytes de qualquer tamanho, de maneira confiável (sem perdas, duplicações ou inversões de ordem nos dados). | ||
| + | * **datagram** : forma que provê canais de comunicação bidirecionais sobre os quais pode-se enviar pacotes de dados a qualquer processo que tenha um canal equivalente definido. Não há garantia na entrega dos pacotes, pois o sistema implementa uma política de // | ||
| + | * **raw** | ||
| + | |||
| + | Essas formas de comunicação são definidas através das constantes inteiras '' | ||
| + | |||
| + | Além da forma de comunicação, | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | Além dos acima, outros espaços de nome estão disponíveis, | ||
| + | |||
| + | Por fim, deve ser escolhido o protocolo que irá efetuar a comunicação, | ||
| + | |||
| + | * Para ocorrer comunicação, | ||
| + | * Cada protocolo é aplicável a uma combinação específica de forma de comunicação e espaço de nomes, e não pode ser usado em combinações inadequadas. Por exemplo, o protocolo TCP somente pode ser usado na forma de comunicação '' | ||
| + | * Para cada combinação "forma de comunicação - espaço de nomes" existe um protocolo default, indicado pelo número 0 (zero), que é geralmente usado. | ||
| + | |||
| + | ===== O modelo cliente/ | ||
| + | |||
| + | Geralmente a interação entre processos usa o modelo de comunicação Cliente/ | ||
| + | |||
| + | * O cliente conhece o endereço e forma de acesso ao servidor e toma a iniciativa da comunicação | ||
| + | * O servidor é uma entidade passiva, apenas recebendo pedidos dos cliente e respondendo aos mesmos. | ||
| + | * O servidor oferece um serviço específico a seus clientes | ||
| + | * O cliente envia uma requisição de serviço e aguarda uma resposta do servidor. | ||
| + | * As implementações do cliente e do servidor são independentes e autônomas; apenas as seqüências de mensagens trocadas durante a comunicação, | ||
| + | |||
| + | Como servidor e cliente têm comportamentos distintos face à comunicação, | ||
| + | |||
| + | Um cliente executa normalmente os seguintes passos para estabelecer uma comunicação com um servidor: | ||
| + | |||
| + | - Cria um socket, usando a chamada de sistema '' | ||
| + | - Conecta seu socket ao endereço do servidor, usando a chamada de sistema '' | ||
| + | - Envia e recebe dados através do socket, usando as chamadas de sistema '' | ||
| + | - Encerra a comunicação, | ||
| + | |||
| + | Um servidor normalmente executa os seguintes passos para oferecer serviço a seus clientes: | ||
| + | |||
| + | - Cria um socket, usando a chamada de sistema '' | ||
| + | - Associa um endereço a seu socket, usando a chamada de sistema '' | ||
| + | - Coloca o socket em modo de escuta, através da chamada de sistema '' | ||
| + | - Aguarda um pedido de conexão, através da chamada '' | ||
| + | - Envia e recebe dados através do socket, usando as chamadas de sistema '' | ||
| + | - Encerra a comunicação com aquele cliente, fecha o descritor da conexão (chamada '' | ||
| + | - Volta ao passo 4, ou encerra suas atividades fechando seu socket (chamada '' | ||
| + | |||
| + | Diversas variantes são possíveis no esquema acima: por exemplo, o servidor pode lançar //threads// ou processos filhos para tratar as conexões recebidas. | ||
| + | |||
| + | ===== Chamadas de sistema ===== | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | int socket (int namespace, int style, int protocol) | ||
| + | </ | ||
| + | |||
| + | Cria um socket no '' | ||
| + | |||
| + | <code c> | ||
| + | fd = socket (PF_INET, | ||
| + | fd = socket (PF_INET, | ||
| + | fd = socket (PF_LOCAL, SOCK_STREAM, | ||
| + | </ | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | int bind (int socket, struct sockaddr *addr, socklen_t length) | ||
| + | </ | ||
| + | |||
| + | Associa um endereço ao '' | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | int connect (int socket, struct sockaddr *addr, socklen_t length) | ||
| + | </ | ||
| + | |||
| + | Inicia uma conexão entre o socket local '' | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | int listen (int socket, unsigned int n) | ||
| + | </ | ||
| + | |||
| + | Habilita o '' | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | int accept (int socket, struct sockaddr *addr, socklen_t *length_ptr) | ||
| + | </ | ||
| + | |||
| + | Recebe um pedido de conexão pendente (espera enquanto a fila estiver vazia). Os valores '' | ||
| + | |||
| + | Ao aceitar o pedido de conexão, a chamada '' | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | int getsockname (int socket, struct sockaddr *addr, socklen_t *length_ptr) | ||
| + | </ | ||
| + | |||
| + | Retorna o endereço de '' | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | int getpeername (int socket, struct sockaddr *addr, socklen_t *length_ptr) | ||
| + | </ | ||
| + | |||
| + | Retorna o endereço do socket ao qual '' | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | int send (int socket, void *buffer, size_t size, int flags) | ||
| + | </ | ||
| + | |||
| + | Envia os dados contidos no '' | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | int recv (int socket, void *buffer, size_t size, int flags) | ||
| + | </ | ||
| + | |||
| + | Recebe dados de '' | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | int close (int filedes) | ||
| + | </ | ||
| + | |||
| + | Fecha o socket representado por '' | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | int shutdown (int socket, int how) | ||
| + | </ | ||
| + | |||
| + | Permite encerrar seletivamente um socket. O parâmetro '' | ||
| + | |||
| + | * 0 : parar de receber dados no socket. Novos dados serão descartados. | ||
| + | * 1 : parar de transmitir dados; descartar dados sendo enviados, não retransmitir dados perdidos e não aguardar confirmações de envio. | ||
| + | * 2 : parar envio e recepção de dados. | ||
| + | |||
| + | ===== Estruturas de dados ===== | ||
| + | |||
| + | Várias das chamadas de sistema relacionadas a sockets usam como parâmetro uma estrutura do tipo '' | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | |||
| + | struct sockaddr { | ||
| + | | ||
| + | | ||
| + | }; | ||
| + | </ | ||
| + | |||
| + | O campo '' | ||
| + | |||
| + | No caso da Internet (espaço de nomes '' | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | |||
| + | struct sockaddr_in { | ||
| + | short int sin_family; | ||
| + | | ||
| + | | ||
| + | | ||
| + | }; | ||
| + | </ | ||
| + | |||
| + | Como essa estrutura tem o mesmo tamanho de '' | ||
| + | |||
| + | É importante observar que somente processos com '' | ||
| + | |||
| + | A estrutura interna '' | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | |||
| + | struct in_addr { | ||
| + | | ||
| + | }; | ||
| + | </ | ||
| + | |||
| + | O campo '' | ||
| + | |||
| + | ===== Conversões de formatos ===== | ||
| + | |||
| + | Para evitar problemas de incompatibilidade entre sistemas // | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | Além destas, as funções abaixo também são usadas: | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | int inet_aton (const char *name, struct in_addr *addr) | ||
| + | </ | ||
| + | |||
| + | Esta função converte um endereço IP no formato "'' | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | char *inet_ntoa (struct in_addr addr) | ||
| + | </ | ||
| + | |||
| + | Efetua a conversão de um endereço no formato binário interno para seu equivalente no formato "'' | ||
| + | |||
| + | Exemplos: [[cliente TCP]] e [[servidor TCP]]. | ||
| + | |||
| + | ===== Sockets UDP ===== | ||
| + | |||
| + | A construção de sistemas comunicando através de sockets UDP é similar à dos exemplos apresentados em TCP, com pequenas alterações: | ||
| + | |||
| + | * O servidor simplesmente cria um socket; não é necessário colocá-lo em modo de escuta ('' | ||
| + | * Ao invés de usar as chamadas '' | ||
| + | * Ao contrário do TCP, que transforma a comunicação em um fluxo contínuo de bytes, em UDP as fronteiras entre as mensagens são respeitadas. O receptor deve receber as mensagens com os mesmos tamanhos com que foram enviadas. | ||
| + | * Em UDP não há garantia de entrega de mensagens. Caso o servidor não esteja ativo, mensagens enviadas a ele são simplesmente descartadas. | ||
| + | |||
| + | As chamadas de sistema '' | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | int sendto (int socket, void *buffer, size_t size, int flags, | ||
| + | struct sockaddr *addr, socklen_t length) | ||
| + | </ | ||
| + | |||
| + | Esta chamada envia a mensagem presente em '' | ||
| + | |||
| + | É possível executar a chamada '' | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | int recvfrom (int socket, void *buffer, size_t size, int flags, | ||
| + | struct sockaddr *addr, socklen_t *length-ptr) | ||
| + | </ | ||
| + | |||
| + | Esta chamada lê uma mensagem de '' | ||
| + | |||
| + | Pode-se usar '' | ||
| + | |||
| + | Exemplos: [[cliente UDP]] e [[servidor UDP]]. | ||
| + | |||
| + | ===== Sockets no domínio UNIX ===== | ||
| + | |||
| + | A criação de sockets no domínio '' | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | |||
| + | struct sockaddr_un { | ||
| + | short int sun_family; | ||
| + | | ||
| + | }; | ||
| + | </ | ||
| + | |||
| + | O campo '' | ||
| + | |||
| + | Exemplos: [[cliente UNIX]] e [[servidor UNIX]]. | ||
| + | |||
| + | ===== Daemons ===== | ||
| + | |||
| + | A maior parte dos serviços de rede oferecidos por um computador é provida por processos com características especiais, denominados //daemons// (espíritos, | ||
| + | |||
| + | * têm um longo tempo de vida (podem ser relançados automaticamente caso terminem de forma inesperada); | ||
| + | * são lançados durante a inicialização do sistema operacional (//boot//); | ||
| + | * executam em // | ||
| + | * oferecem serviços de rede ou executam tarefas administrativas (// | ||
| + | * são de propriedade do usuário //root// ou de um usuário administrativo interno (// | ||
| + | |||
| + | Os //daemons// nascem como processos normais, que são em seguida convertidos em // | ||
| + | |||
| + | - Duplicar o processo através de uma chamada '' | ||
| + | - Invocar a chamada '' | ||
| + | - Trocar o diretório atual pelo diretório de trabalho, que pode ser o diretório raiz (''/'' | ||
| + | - Fechar todos os descritores de arquivos não usados, para evitar manter aberto algum descritor herdado de seu pai ou do shell. | ||
| + | - Por convenção, | ||
| + | |||
| + | Como os daemons não estão associados a terminais, eles não podem gerar mensagens em um terminal. Duas abordagens são possíveis para a geração de mensagens: | ||
| + | |||
| + | * escrita em em um arquivo de mensagens específico do daemon (como fazem o Apache e o Samba) | ||
| + | * envio de mensagens para o serviço //syslog// (como faz a maioria dos daemons) | ||
| + | |||
| + | O serviço //syslog// é oferecido por um daemon, que recebe mensagens em um socket local em ''/ | ||
| + | |||
| + | ===== Resolução de nomes ===== | ||
| + | |||
| + | Como visto acima, os programas que interagem através da rede usam nomes de sockets para localizar seus interlocutores. Na Internet, os sockets são nomeados através de um endereço IP e um número de porta, ambos definidos na estrutura sockaddr_in. O endereço IP de um socket é definido nessa estrutura por um inteiro long, e sua porta por um inteiro short. | ||
| + | |||
| + | Para os usuários, é muito mais fácil se referenciar a um servidor de rede através de um nome simbólico que usar seu endereço IP. Por isso, costumar ser associados nomes simbólicos aos endereços IP. Isso leva a várias representações para o mesmo endereço de socket, como mostra a tabela a seguir: | ||
| + | |||
| + | ^ nome simbólico\\ string ^endereço IP\\ string ^ representação interna\\ in_addr (long) ^ | ||
| + | | espec.ppgia.pucpr.br | 200.192.112.139 | 0xC8C0708B ou 0x8B70C0C8 | | ||
| + | |||
| + | Conforme visto anteriormente, | ||
| + | |||
| + | Diversos mecanismos de resolução de nomes podem ser usados por um sistema operacional para converter um nome simbólico em um endereço IP, ou o contrário. Os mecanismos mais usuais estão detalhados nesta página. Apesar da profusão de mecanismos, o sistema operacional oferece uma interface padronizada para resolver nomes, que é definida pela seguinte funções: | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | struct hostent * gethostbyname (const char *name) | ||
| + | </ | ||
| + | |||
| + | Esta função retorna informações sobre o computador cujo nome é '' | ||
| + | |||
| + | <code c> | ||
| + | #include < | ||
| + | struct hostent * gethostbyaddr (const char *addr, size_t length, int format) | ||
| + | </ | ||
| + | |||
| + | Esta função retorna informações sobre o computador a partir de seu endereço. O campo '' | ||
| + | |||
| + | Essas funções podem falhar com um dos seguintes erros, indicados na variável '' | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | As funções acima retornam um ponteiro para uma estrutura do tipo '' | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * char *h_addr : sinônimo de h_addr_list[0], | ||
| + | |||
| + | Como essa estrutura é alocada estaticamente, | ||
| + | |||
| + | Exemplos de [[resolução de nomes]] tanto direta quanto reversa. | ||
| + | |||
| + | ===== Chamada de procedimento remoto ===== | ||
| + | |||
| + | A chamada de procedimento remoto (RPC - //Remote Procedure Call//) é uma abstração construída sobre sockets. Ela simplifica a tarefa do programador, | ||
| + | |||
| + | * criação e manutenção de sockets e seus descritores e buffers associados | ||
| + | * formatos de endereços e resolução de nomes e de endereços | ||
| + | * definição de um protocolo de comunicação para formatar dados e comandos | ||
| + | * preocupação com a ordem dos bytes e formato dos dados nas arquiteturas envolvidas | ||
| + | |||
| + | Nessa abstração, | ||
| + | |||
| + | ^ Passo ^ cliente ^ biliotecas e runtime RPC ^ rede ^ bibliotecas e runtime RPC ^ servidor ^ | ||
| + | | 1 | '' | ||
| + | | 2 | | - recebe a chamada de função\\ - prepara os parâmetros para envio\\ - conecta com o servidor\\ - envia o pedido com seus parâmetros | | | | | ||
| + | | 3 | | | => | | | | ||
| + | | 4 | | | | - recebe o pedido e parâmetros\\ - extrai os parâmetros no formato local\\ - invoca a função solicitada | | | ||
| + | | 5 | | | | | '' | ||
| + | | 6 | | | | - recebe o valor de retorno\\ - prepara o retorno para envio\\ - devolve resposta ao cliente | | | ||
| + | | 7 | | | <= | | | | ||
| + | | 8 | | - recebe a resposta\\ - extrai os valores de retorno no formato local\\ - retorna a chamada da função | | | | | ||
| + | | 9 | '' | ||
| + | |||
| + | Somente o código do cliente e do servidor e a definição da interface devem ser escritos pelo programador da aplicação distribuída. Todo o código em rosa é gerado automaticamente pelas ferramentas que compõe o suporte a RPCs. | ||
| + | |||
| + | Em UNIX, o suporte a RPC é provido pelos seguintes componentes: | ||
| + | |||
| + | * biblioteca de RPC, que contém o suporte de comunicação e as funções de gerência de RPC. | ||
| + | * biblioteca XDR (//eXternal Data Representation// | ||
| + | * //daemon portmapper//, | ||
| + | * '' | ||
| + | |||
| + | Um [[exemplo de RPC]] está disponível nesta página. | ||
| + | |||
| + | RPC é uma abstração muito usada para implementar diversos serviços do mundo UNIX, entre os quais podem ser citados o NFS (//Network File System//) e o NIS (//Network Information System//). O suporte a RPC é padrão no mundo UNIX, e as diversas implementações são compatíveis entre si (mas incompatíveis com as RPCs DCE e Microsoft). | ||
| + | |||
| + | ===== Atividades ===== | ||
| + | |||
| + | - Compilar e executar os exemplos de clientes e servidores deste módulo, observando seus comportamentos e resultados. | ||
| + | - Quando deseja obter um arquivo de um servidor Web, o browser envia a seguinte requisição ao servidor e fica aguardando uma resposta: | ||
| + | |||
| + | < | ||
| + | GET /path/file HTTP/1.0 | ||
| + | (linha vazia) | ||
| + | </ | ||
| + | |||
| + | Caso o arquivo seja encontrado, o servidor envia uma resposta com a seguinte forma e encerra a conexão: | ||
| + | |||
| + | < | ||
| + | HTTP/1.0 200 ok | ||
| + | Content-type: | ||
| + | (linha vazia) | ||
| + | ... (conteúdo do arquivo solicitado) | ||
| + | </ | ||
| + | |||
| + | - Construa um servidor Web que aceite requisições de arquivos HTML ('' | ||
| + | |||
| + | - Construa um servidor TCP (e seu respectivo cliente) de operações matemáticas básicas. Em cada interação, | ||
| + | - Repita o exercício anterior usando sockets UDP. | ||
| + | - Idem, usando sockets UNIX. | ||
| + | - Vários servidores verificam a autenticidade de hosts através do DNS. O procedimento consiste em fazer duas consultas ao DNS e confrontar os resultados obtidos. Implemente uma ferramenta de verificação de nome que efetue esse procedimento. | ||
| + | |||
| + | <code c> | ||
| + | address = gethostbyname (name1) | ||
| + | name2 = gethostbyaddr (address) | ||
| + | if (name1 equal name2) | ||
| + | name1 is ok | ||
| + | else | ||
| + | name is false | ||
| + | endif | ||
| + | </ | ||
| + | |||
| + | - Modifique o cliente Web do exemplo para informar o nome do servidor a acessar através da linha de comando. | ||
| + | - Em seu servidor Web, use a chamada '' | ||
| + | - Transformar o servidor Web construído em um //daemon//. Ele deve armazenar os registros de sua operação em um arquivo '' | ||