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/14 00:01] – 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 '' | ||