Diferenças

Aqui você vê as diferenças entre duas revisões dessa página.

Link para esta página de comparações

Ambos lados da revisão anterior Revisão anterior
Próxima revisão
Revisão anterior
pua:comunicacao_em_rede [2008/08/13 20:51] mazieropua:comunicacao_em_rede [2008/08/20 15: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 //sockets//. Um //socket// é uma interface de comunicação bidirecional entre processos. Sockets são representados como descritores de arquivos e permitem a comunicação entre processos distintos na mesma máquina ou em máquinas distintas, através de uma rede. Os sockets são a base da comunicação em redes TCP/IP e também são muito usados em comunicações entre processos no interior de um mesmo computador.
 +
 +===== Estilos, nomes e protocolos =====
 +
 +Ao criar um //socket//, deve ser especificada a forma de comunicação (//communication style//) a ser usada e que protocolo deverá realizar a comunicação nos níveis mais baixos do sistema. Em UNIX, três formas de comunicação são normalmente usadas:
 +
 +  * **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 //best-effort//. Além disso, como os pacotes são tratados de forma independente, podem haver inversões de ordem.
 +  * **raw**  : esta forma de comunicação provê acesso direto aos protocolos e interfaces de comunicação de baixo nível. Geralmente é usada por processos que gerenciam ou monitoram a infra-estrutura de rede.
 +
 +Essas formas de comunicação são definidas através das constantes inteiras ''SOCK_STREAM'', ''SOCK_DGRAM'' e ''SOCK_RAW'', no arquivo ''sys/socket.h''.
 +
 +Além da forma de comunicação, também deve ser escolhido um espaço de nomes para nomear ou endereçar o socket criado. O nome de um socket sempre está relacionado a um espaço de nomes, também chamado de domínio (//socket domain//). Cada espaço de nomes é definido por uma macro na forma ''PF_*'' (que vem do termo //Protocol Family//). Os principais espaços de nomes em uso no UNIX são:
 +
 +  * ''PF_LOCAL'' : indica o espaço de nomes local, no qual os nomes de sockets são válidos somente no escopo do computador local. As macros ''PF_UNIX'' e ''PF_FILE'' são sinônimos desse espaço de nomes.
 +  * ''PF_INET'' : indica o espaço de nomes IPv4 e seus protocolos associados.
 +  * ''PF_INET6'' : indica o espaço de nomes IPv6 e seus protocolos associados.
 +
 +Além dos acima, outros espaços de nome estão disponíveis, embora sejam de uso menos freqüente: ''PF_NS'' (protocolos Xerox NS), ''PF_ISO'' (protocolos OSI/ISO), ''PF_CCITT'' (protocolos do CCITT), ''PF_IMPLINK'' (Internet Message Processors), ''PF_ROUTE'' (protocolos de roteamento), etc. Para cada espaço de nomes, uma macro correspondente ''AF_*'' define o formato dos endereços para aquele espaço.
 +
 +Por fim, deve ser escolhido o protocolo que irá efetuar a comunicação, ou seja, o mecanismo usado para a transferência dos dados. Cada protocolo é aplicável a um dado espaço de nomes e uma forma de comunicação. Algumas regras são importantes na escolha de um protocolo:
 +
 +  * Para ocorrer comunicação, os sockets de ambos os lados devem usar o mesmo protocolos e seus nomes devem estar no mesmo espaço de nomes.
 +  * 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 ''SOCK_STREAM'' e no espaço de nomes da Internet.
 +  * 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/servidor =====
 +
 +Geralmente a interação entre processos usa o modelo de comunicação Cliente/Servidor. Algumas características importantes desse modelo:
 +
 +  * 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, que caracterizam o serviço, devem ser respeitadas.
 +
 +Como servidor e cliente têm comportamentos distintos face à comunicação, suas implementações seguem também padrões distintos. Ambos criam sockets para se comunicar, mas operam de forma diferente.
 +
 +Um cliente executa normalmente os seguintes passos para estabelecer uma comunicação com um servidor:
 +
 +  - Cria um socket, usando a chamada de sistema ''socket''
 +  - Conecta seu socket ao endereço do servidor, usando a chamada de sistema ''connect''
 +  - Envia e recebe dados através do socket, usando as chamadas de sistema ''read'' e ''write''
 +  - Encerra a comunicação, fechando o socket através da chamada ''close''.
 +
 +Um servidor normalmente executa os seguintes passos para oferecer serviço a seus clientes:
 +
 +  - Cria um socket, usando a chamada de sistema ''socket''
 +  - Associa um endereço a seu socket, usando a chamada de sistema ''bind''.
 +  - Coloca o socket em modo de escuta, através da chamada de sistema ''listen''.
 +  - Aguarda um pedido de conexão, através da chamada ''accept'' (que gera um descritor específico para a conexão recebida).
 +  - Envia e recebe dados através do socket, usando as chamadas de sistema ''read'' e ''write''.
 +  - Encerra a comunicação com aquele cliente, fecha o descritor da conexão (chamada ''close'').
 +  - Volta ao passo 4, ou encerra suas atividades fechando seu socket (chamada ''close'').
 +
 +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 <sys/socket.h>
 +int socket (int namespace, int style, int protocol)
 +</code>
 +
 +Cria um socket no ''namespace'' indicado, definindo o ''style'' e o ''protocol'' a ser usado. Retorna o descritor de arquivo do socket criado, ou -1 em caso de erro. Alguns exemplos:
 +
 +<code c>
 +fd = socket (PF_INET,  SOCK_DGRAM,  0) ; /* socket UDP */
 +fd = socket (PF_INET,  SOCK_STREAM, 0) ; /* socket TCP */
 +fd = socket (PF_LOCAL, SOCK_STREAM, 0) ; /* socket stream local */
 +</code>
 +
 +<code c>
 +#include <sys/socket.h>
 +int bind (int socket, struct sockaddr *addr, socklen_t length)
 +</code>
 +
 +Associa um endereço ao ''socket''. Os parâmetros ''addr'' e ''length'' especificam o endereço a usar; seu formato depende do espaço de nomes em uso.
 +
 +<code c>
 +#include <sys/socket.h.>
 +int connect (int socket, struct sockaddr *addr, socklen_t length)
 +</code>
 +
 +Inicia uma conexão entre o socket local ''socket'' (lado cliente) e o socket cujo endereço está especificado nos campos ''addr'' and ''length'' (lado servidor). Bloqueia até obter uma resposta do servidor ou erro. Esta operação só é usada em sockets orientados a conexão (''SOCK_STREAM'').
 +
 +<code c>
 +#include <sys/socket.h>
 +int listen (int socket, unsigned int n)
 +</code>
 +
 +Habilita o ''socket'' a aceitar pedidos de conexão, tornando-o um socket de servidor. O parâmetro ''n'' indica o tamanho da fila de pedidos de conexão pendentes (clientes excedendo a capacidade da fila terão seus pedidos de conexão recusados).
 +
 +<code c>
 +#include <sys/socket.h>
 +int accept (int socket, struct sockaddr *addr, socklen_t *length_ptr)
 +</code>
 +
 +Recebe um pedido de conexão pendente (espera enquanto a fila estiver vazia). Os valores ''addr'' e ''length_ptr'' retornam informações sobre o cliente que efetuou o pedido de conexão.
 +
 +Ao aceitar o pedido de conexão, a chamada ''accept'' cria um novo socket local para manter a conexão e retorna seu descritor (ou -1 em caso de erro). O socket original permanece disponível para atender novos pedidos de conexão, até ser fechado.
 +
 +<code c>
 +#include <sys/socket.h>
 +int getsockname (int socket, struct sockaddr *addr, socklen_t *length_ptr)
 +</code>
 +
 +Retorna o endereço de ''socket'', nos parâmetros ''addr'' e ''length_ptr''. (observe que ''lenght_ptr'' é um ponteiro)
 +
 +<code c>
 +#include <sys/socket.h>
 +int getpeername (int socket, struct sockaddr *addr, socklen_t *length_ptr)
 +</code>
 +
 +Retorna o endereço do socket ao qual ''socket'' está conectado, nos parâmetros ''addr'' e ''length_ptr''.
 +
 +<code c>
 +#include <sys/socket.h>
 +int send (int socket, void *buffer, size_t size, int flags)
 +</code>
 +
 +Envia os dados contidos no ''buffer'' de tamanho ''size'' através do ''socket''. Alguns flags permitem modificar parâmetros do envio; se os flags forem 0 (zero), a chamada ''write'' pode ser usada. Retorna o número de bytes enviados, ou -1 (erro).
 +
 +<code c>
 +#include <sys/socket.h>
 +int recv (int socket, void *buffer, size_t size, int flags)
 +</code>
 +
 +Recebe dados de ''socket'', depositando-os no ''buffer'' de tamanho ''size''. Alguns flags permitem modificar parâmetros do envio; se os flags forem 0 (zero), a chamada ''read'' pode ser usada. Retorna o número de bytes recebidos, ou -1 (erro).
 +
 +<code c>
 +#include <unistd.h>
 +int close (int filedes)
 +</code>
 +
 +Fecha o socket representado por ''filedes''. Antes de fechar, o sistema tenta enviar os dados pendentes (ainda não enviados).
 +
 +<code c>
 +#include <sys/socket.h>
 +int shutdown (int socket, int how)
 +</code>
 +
 +Permite encerrar seletivamente um socket. O parâmetro ''how'' indica a ação a ser efetuada:
 +
 +  * 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 ''struct sockaddr'', definida da seguinte forma:
 +
 +<code c>
 +#include <sys/socket.h>
 +
 +struct sockaddr {
 +   unsigned short    sa_family;    // address family, AF_xxx
 +   char              sa_data[14];  // 14 bytes of protocol address
 +}; 
 +</code>
 +
 +O campo ''sa_family'' indica o tipo de endereço a ser considerado (por exemplo, ''AF_LOCAL'' ou ''AF_INET'').O campo ''sa_data'' contém o endereço do socket, e sua estrutura interna depende do espaço de nomes usado.
 +
 +No caso da Internet (espaço de nomes ''PF_INET''), o endereço de um socket é composto pelo endereço IP da interface de rede à qual ele está associado e do número da porta onde ele está conectado. Para definir esses parâmetros uma estrutura adicional struct ''sockaddr_in'' está definida:
 +
 +<code c>
 +#include <netinet/in.h>
 +
 +struct sockaddr_in {
 +   short int          sin_family;  // Address family
 +   unsigned short int sin_port;    // Port number (in network byte order)
 +   struct in_addr     sin_addr;    // Internet address (in network byte order)
 +   unsigned char      sin_zero[8]; // just fill this with zeros
 +}; 
 +</code>
 +
 +Como essa estrutura tem o mesmo tamanho de ''sockaddr'', pode-se fazer //casting// de ponteiros de uma para outra, nos dois sentidos.
 +
 +É importante observar que somente processos com ''UID'' = 0 (pertencentes ao usuário root) podem criar sockets ''AF_INET'' com número de porta inferior à constante ''IPPORT_RESERVED'' (geralmente igual a 1024).
 +
 +A estrutura interna ''in_addr'' (Internet address) tem o seguinte formato em ''AF_INET'':
 +
 +<code c>
 +#include <netinet/in.h>
 +
 +struct in_addr {
 +   unsigned long s_addr; // 32-bit long (4 bytes)
 +};
 +</code>
 +
 +O campo ''s_addr'' contém um endereço IP armazenado como 4 bytes (em //network byte order//). Para preenchê-lo pode ser usada a  função ''inet_aton'' (vide abaixo).
 +
 +===== Conversões de formatos =====
 +
 +Para evitar problemas de incompatibilidade entre sistemas //big-endian// e sistemas //little-endian//, alguns campos da estrutura de endereço são armazenados em um formato padronizado conhecido como //Network Byte Order//, ou formato de rede. Quatro funções estão disponíveis para converter dados entre o formato local da máquina e o de rede:
 +
 +  * ''short htons (short)'': converte //Host// => //Network Short//
 +  * ''short ntohs (short)'': converte //Network// => //Host Short//
 +  * ''long  htonl (long) '': converte //Host// => //Network Long//
 +  * ''long  ntohl (long) '': converte //Network// => //Host Long//
 +
 +Além destas, as funções abaixo também são usadas:
 +
 +<code c>
 +#include <arpa/inet.h>
 +int inet_aton (const char *name, struct in_addr *addr)
 +</code>
 +
 +Esta função converte um endereço IP no formato "''nnn.nnn.nnn.nnn''" para o formato binário exigido pela estrutura ''in_addr'' e o deposita no endereço indicado por ''addr''. Além disso, algumas constantes pré-definidas podem ser usadas no campo ''s_addr'':
 +
 +  * ''INADDR_LOOPBACK'': //localhost//, ou endereço de //loopback//
 +  * ''INADDR_ANY'': //any incoming address//, usada em sockets de servidores
 +  * ''INADDR_BROADCAST'': endereço de broadcast da rede local
 +  * ''INADDR_NONE'': nenhum endereço, valor retornado como erro em algumas funções
 +
 +<code c>
 +#include <arpa/inet.h>
 +char *inet_ntoa (struct in_addr addr)
 +</code>
 +
 +Efetua a conversão de um endereço no formato binário interno para seu equivalente no formato "''nnn.nnn.nnn.nnn''".
 +
 +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 (''listen'') nem aguardar conexões (''accept'').
 +  * Ao invés de usar as chamadas ''send''/''write'' e ''recv''/''read'', normalmente são usadas as chamadas ''sendto'' e ''recvfrom'', que especificam os parceiros de comunicação na hora do envio/recepção.
 +  * 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 ''sendto'' e ''recvfrom'' têm a seguinte sintaxe:
 +
 +<code c>
 +#include <sys/socket.h>
 +int sendto (int socket, void *buffer, size_t size, int flags,
 +            struct sockaddr *addr, socklen_t length)
 +</code>
 +
 +Esta chamada envia a mensagem presente em ''buffer'' através do ''socket'' para o destino especificado em ''addr'' e ''length''. O parâmetro ''size'' indica o número de bytes da mensagem. Os flags permitem alterar parâmetros do envio (default em 0). O retorno é similar ao de ''send'', mas somente erros locais são reportados.
 +
 +É possível executar a chamada ''connect'' em um socket UDP, mas essa ação apenas define um destinatário //default// para todas as mensagens enviadas por aquele socket. Nesse caso, pode-se usar a chamada ''send'' ou mesmo ''write'' para enviar mensagens através desse socket UDP "conectado".
 +
 +<code c>
 +#include <sys/socket.h>
 +int recvfrom (int socket, void *buffer, size_t size, int flags,
 +              struct sockaddr *addr, socklen_t *length-ptr)
 +</code>
 +
 +Esta chamada lê uma mensagem de ''socket'' e a deposita em ''buffer''. O parâmetro ''size'' indica o tamanho do buffer (pacotes maiores que o buffer serão truncados). A identidade do remetente é retornada através dos parâmetros ''addr'' e ''lenght_ptr''. Os erros possíveis são similares aos de ''recv''.
 +
 +Pode-se usar ''recv'' ou mesmo ''read'' caso não haja interesse em identificar o remetente da mensagem.
 +
 +Exemplos: [[cliente UDP]] e [[servidor UDP]].
 +
 +===== Sockets no domínio UNIX =====
 +
 +A criação de sockets no domínio ''AF_LOCAL'', também conhecidos como sockets UNIX, é bastante similar à construção de sockets TCP ou UDP. As únicas diferenças perceptíveis estão na estrutura de dados que representa o endereço do socket, pois o endereço de um socket local é definido como um nome de arquivo. Nesse caso, uma estrutura adicional ''struct sockaddr_un'' (Unix Name) está definida:
 +
 +<code c>
 +#include <sys/un.h>
 +
 +struct sockaddr_un {
 +   short int  sun_family;     // Address family
 +   char       sun_path[108];  // socket path in the filesystem
 +}; 
 +</code>
 +
 +O campo ''sun_path'' deverá indicar o caminho completo para o socket no sistema de arquivos (por exemplo, "''/tmp/socket-423142''"). Isso significa que cliente e servidor devem estar acessando o mesmo sistema de arquivos local. Uma vez fechado, o socket permanece no sistema de arquivos e deverá ser explicitamente removido pelo servidor que o criou (através da chamada ''unlink'').
 +
 +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, em inglês). As principais características de um daemon são:
 +
 +  * 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 //background// e não estão associados a um terminal;
 +  * oferecem serviços de rede ou executam tarefas administrativas (//backup//, //logging//, etc);
 +  * são de propriedade do usuário //root// ou de um usuário administrativo interno (//nobody//, //daemon//, etc).
 +
 +Os //daemons// nascem como processos normais, que são em seguida convertidos em //daemons//. Os passos necessários para a transformação de um processo ordinário em um //daemon// são:
 +
 +  - Duplicar o processo através de uma chamada ''fork()'' e encerrar o processo pai, para liberar o shell que lançou aquele processo.
 +  - Invocar a chamada ''setsid()'' para criar uma nova sessão de processos; isso faz com que o filho saia da sessão de seu pai (e do shell), sendo desvinculado do terminal e dos sinais porventura enviados ao shell.
 +  - Trocar o diretório atual pelo diretório de trabalho, que pode ser o diretório raiz (''/'') ou um diretório específico do daemon (por exemplo, o daemon ''httpd'' usa ''/etc/httpd'' como diretório de trabalho).
 +  - 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, instalar um tratador para o sinal ''SIGHUP'', que releia as configurações do daemon.
 +
 +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 ''/dev/log'' e também em um socket UDP (porta 514). A partir das configurações descritas no arquivo ''/etc/syslog.conf'' ele decide o que fazer com cada mensagem recebida: enviar a um terminal, avisar o administrador, armazenar em um arquivo, ativar um script externo ou enviar a outro host são as principais possibilidades. As funcionalidades oferecidas pelo sistema //syslog// e sua forma de acesso estão definidas no manual da GLibC.
 +
 +===== 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, as funções ''inet_aton'' e ''inet_ntoa'' permitem converter endereços IP em sua representação interna, ou vice-versa. Mas, como converter nomes simbólicos para endereços IP, ou vice-versa?
 +
 +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 <netdb.h>
 +struct hostent * gethostbyname (const char *name)
 +</code>
 +
 +Esta função retorna informações sobre o computador cujo nome é ''name'', ou //null// se houver erro. Ver também ''Gethostbyname2''.
 +
 +<code c>
 +#include <netdb.h>
 +struct hostent * gethostbyaddr (const char *addr, size_t length, int format)
 +</code>
 +
 +Esta função retorna informações sobre o computador a partir de seu endereço. O campo ''addr'' é um ponteiro para um endereço em formato interno, com tamanho ''size''. O campo ''format'' especifica o tipo de endereço (''AF_INET'' para IPv4 ou ''AF_INET6'' para IPv6).
 +
 +Essas funções podem falhar com um dos seguintes erros, indicados na variável ''h_errno'':
 +
 +  * ''HOST_NOT_FOUND'': o nome ou endereço não foi encontrado.
 +  * ''TRY_AGAIN'': um erro temporário (o servidor de nomes não pôde ser contactado, por exemplo).
 +  * ''NO_RECOVERY'': um erro não recuperável.
 +  * ''NO_ADDRESS'': existe uma entrada para o nome, mas não há endereço associado a ele.
 +
 +As funções acima retornam um ponteiro para uma estrutura do tipo ''hostent'', estaticamente alocada, que contém os seguintes campos:
 +
 +  * ''char *h_name'': nome principal do host.
 +  * ''char %%**%%h_aliases'': aliases (nomes alternativos) do computador, na forma de um vetor de strings.
 +  * ''int h_addrtype'': tipo de endereço retornado (''AF_INET'', ''AF_INET6'', etc).
 +  * ''int h_length'': tamanho em bytes dos endereços retornados (depende do tipo de endereço).
 +  * ''char %%**%%h_addr_list'': vetor de endereços associados ao host, terminado por //null//. Os endereços sempre estão no formato interno, em //Network Byte Order//.
 +  * char *h_addr : sinônimo de h_addr_list[0], o primeiro endereço do host.
 +
 +Como essa estrutura é alocada estaticamente, deve ser usada com muito cuidado em programas multi-thread.
 +
 +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, liberando-o das seguintes tarefas:
 +
 +  * 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, o cliente vê o servidor como uma interface constituída por uma ou mais funções e as estruturas de dados necessárias para definir os parâmetros das mesmas. Ao chamar uma função dessa interface, código de comunicação gerado automaticamente estabelece a comunicação com o servidor. O diagrama a seguir ilustra esse mecanismo:
 +
 +^ Passo ^ cliente ^ biliotecas e runtime RPC ^ rede ^ bibliotecas e runtime RPC ^ servidor ^
 +| 1 | ''cliente()''\\ ''{''\\ ''  x = 5 ;''\\ ''  y = fatorial(x);'' | | | | |
 +| 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 | | | | | ''int fatorial(int n)''\\ ''{''\\ '' ...''\\ '' return(fat) ;''\\ ''}'' |
 +| 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 | ''printf("%d\n",y);''\\ ''}'' | | | | | 
 +
 +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//), que provê a codificação de todos os dados em um formato independente de plataforma.
 +  * //daemon portmapper//, que gerencia os serviços ativos e informa aos clientes os serviços (interfaces) disponíveis e suas respectivas versões e portas.
 +  * ''rpcgen'', o compilador de descrições de interface e gerador de código de comunicação.
 +
 +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:
 +
 +<code>
 +GET /path/file HTTP/1.0
 +(linha vazia)
 +</code>
 +
 +Caso o arquivo seja encontrado, o servidor envia uma resposta com a seguinte forma e encerra a conexão:
 +
 +<code>
 +HTTP/1.0 200 ok
 +Content-type: text/html
 +(linha vazia)
 +... (conteúdo do arquivo solicitado)
 +</code>
 +
 +  - Construa um servidor Web que aceite requisições de arquivos HTML (''text/html'') e GIF (''image/gif''). Os arquivos oferecidos aos clientes devem estar em um sub-diretório ''webfiles'' no diretório corrente do servidor. Para mais informações sobre as mensagens do protocolo HTTP consulte [[espec:Serviço HTTP|esta página]].
 +
 +  - Construa um servidor TCP (e seu respectivo cliente) de operações matemáticas básicas. Em cada interação, o servidor recebe dois inteiros A e B e a definição de uma operação a ser realizada sobre eles (+, - * ou /), devolvendo ao cliente o resultado da operaçã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
 +</code>
 +
 +  - 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 ''gethostbyaddr'' para encontrar o nome de cada cliente conectado.
 +  - Transformar o servidor Web construído em um //daemon//. Ele deve armazenar os registros de sua operação em um arquivo ''web.log'' localizado seu diretório de trabalho.