Livro "API do Linux. Guia completo »


Livro "API do Linux. Guia completo »

Boa tarde Apresento a sua atenção o livro “Linux API. Um guia completo" (tradução do livro A interface de programação Linux). Pode ser encomendado no site da editora e se você aplicar o código promocional LinuxAPI , você receberá um desconto de 30%.

Trecho do livro para referência:

Soquetes: Arquitetura de Servidor

Neste capítulo, discutiremos os fundamentos do projeto de servidores iterativos e paralelos e também veremos um daemon especial chamado inetd, que facilita a criação de aplicativos de servidor de Internet.

Servidores iterativos e paralelos

Existem duas arquiteturas comuns de servidor de rede baseadas em soquete:

  • iterativo: o servidor atende os clientes um de cada vez, primeiro processando uma solicitação (ou várias solicitações) de um cliente e depois passando para o próximo;

  • paralelo: o servidor é projetado para atender vários clientes simultaneamente.

Um exemplo de servidor iterativo baseado em filas FIFO já foi apresentado na Seção 44.8.

Os servidores iterativos geralmente são adequados apenas em situações em que as solicitações dos clientes podem ser processadas com bastante rapidez, uma vez que cada cliente é forçado a esperar até que qualquer outro cliente à sua frente tenha sido atendido. Um caso de uso comum para esta abordagem é a troca de solicitações e respostas únicas entre um cliente e um servidor.

Os servidores paralelos são adequados nos casos em que cada solicitação leva um tempo significativo para ser processada ou onde o cliente e o servidor se envolvem em longas trocas de mensagens. Neste capítulo, focaremos principalmente na maneira tradicional (e mais simples) de projetar servidores paralelos, que consiste em criar um processo filho separado para cada novo cliente. Esse processo realiza todo o trabalho para atender o cliente e depois termina. Como cada um desses processos opera de forma independente, é possível atender vários clientes simultaneamente. A principal tarefa do processo principal do servidor (pai) é criar um filho separado para cada novo cliente (alternativamente, threads de execução podem ser criados em vez de processos).

Nas seções a seguir, veremos exemplos de servidores de soquete de domínio de Internet iterativos e paralelos. Esses dois servidores implementam uma versão simplificada do serviço de eco (RFC 862), que retorna uma cópia de qualquer mensagem enviada por um cliente.

Eco iterativo do servidor UDP

Nesta e na próxima seção apresentaremos os servidores para o serviço echo. Está disponível na porta número 7 e funciona tanto em UDP quanto em TCP (esta porta é reservada e, portanto, o servidor de eco deve ser executado com privilégios de administrador).

O servidor echo UDP lê continuamente datagramas e retorna cópias deles ao remetente. Como o servidor só precisa processar uma mensagem por vez, uma arquitetura iterativa será suficiente. O arquivo de cabeçalho dos servidores é mostrado na Listagem 56.1.

Listagem 56.1. Arquivo de cabeçalho para programas id_echo_sv.c e id_echo_cl.c

#include "inet_sockets.h" /* Declara nossas funções de soquete */
#include "tlpi_hdr.h"

#define SERVICE "echo" /* Nome do serviço UDP */

#define BUF_SIZE 500 /* Tamanho máximo dos datagramas que
pode ser lido por cliente e servidor */
_____________________________________________________________________sockets/id_echo.h

A Listagem 56.2 mostra a implementação do servidor. Os seguintes pontos são dignos de nota:

  • para colocar o servidor em modo daemon, usamos a função BecomeDaemon() da seção 37.2;

  • para tornar o programa mais compacto, utilizamos a biblioteca para trabalhar com soquetes de domínio da Internet, desenvolvida na seção 55.12;

  • se o servidor não puder retornar uma resposta ao cliente, ele grava uma mensagem no log usando a chamada syslog().

Em uma aplicação real, provavelmente imporíamos algum limite na frequência de registro de mensagens usando syslog(). Isso eliminaria a possibilidade de um invasor transbordar o log do sistema. Além disso, não esqueça que cada chamada para syslog() é bastante cara, pois usa fsync() por padrão.

Listagem 56.2. Servidor de iteração que implementa o serviço de eco UDP

_________________________________________________________________soquetes/id_echo_sv.c
#incluir
#include "id_echo.h"
#include "become_daemon.h"

int
main (int argc, char * argv [])
{
interno sfd;
ssize_t numRead;
socklen_t len;
struct sockaddr_storage claddr;
char buf[BUF_SIZE];
char addrStr[IS_ADDR_STR_LEN];

if (tornar-seDaemon(0) == -1)
errExit("tornar-seDaemon");

sfd = inetBind(SERVICE, SOCK_DGRAM, NULL);
se (sfd == -1) {
syslog(LOG_ERR, "Não foi possível criar o soquete do servidor (%s)",
strerror(errno));
saída(EXIT_FAILURE);

/* Recebe datagramas e devolve cópias deles aos remetentes */
}
para (;;) {
len = sizeof(struct sockaddr_storage);
numRead = recvfrom(sfd, buf, BUF_SIZE, 0, (struct sockaddr *) &claddr, &len);

if (numLeitura == -1)
errExit("recebidode");
if (sendto(sfd, buf, numRead, 0, (struct sockaddr *) &claddr, len)
!= numLeitura)
syslog(LOG_WARNING, "Erro ao ecoar resposta para %s (%s)",
inetAddressStr((struct sockaddr *) &claddr, len,
addStr, IS_ADDR_STR_LEN),
strerror(errno));
}
}
_________________________________________________________________soquetes/id_echo_sv.c

Para testar o funcionamento do servidor, utilizamos o programa da Listagem 56.3. Também utiliza a biblioteca para trabalhar com soquetes de domínio da Internet, desenvolvida na seção 55.12. Como primeiro argumento da linha de comando, o programa cliente assume o nome do nó da rede no qual o servidor está localizado. O cliente entra em um loop onde envia cada um dos argumentos restantes ao servidor como datagramas separados e, em seguida, lê e imprime os datagramas que recebe do servidor em resposta.

Listagem 56.3. Cliente para serviço de eco UDP

#include "id_echo.h"

int
main (int argc, char * argv [])
{
int sfd, j;
tamanho_t comprimento;
ssize_t numRead;
char buf[BUF_SIZE];

if (argc < 2 || strcmp(argv[1], "--help") == 0)
useErr("%s mensagem do host…n", argv[0]);

/* Forme o endereço do servidor com base no primeiro argumento da linha de comando */
sfd = inetConnect(argv[1], SERVIÇO, SOCK_DGRAM);
se (sfd == -1)
fatal("Não foi possível conectar ao soquete do servidor");

/* Envia os argumentos restantes para o servidor na forma de datagramas separados */
para (j = 2; j < argc; j++) {
len = strlen(argv[j]);
if (write(sfd, argv[j], len) != len)
fatal("gravação parcial/falha");

numRead = leitura(sfd, buf, BUF_SIZE);
if (numLeitura == -1)
errExit("ler");
printf("[%ld bytes] %.*sn", (long) numRead, (int) numRead, buf);
}
sair (EXIT_SUCCESS);
}
_________________________________________________________________soquetes/id_echo_cl.c

Abaixo está um exemplo do que veremos ao executar o servidor e duas instâncias do cliente:

$su // Privilégios são necessários para vincular a uma porta reservada
Senha:
# ./id_echo_sv // Servidor entra em modo de segundo plano
# exit // Desistir dos direitos de administrador
$ ./id_echo_cl localhost hello world // Este cliente envia dois datagramas
[5 bytes] hello // O cliente exibe a resposta recebida do servidor
[5 bytes] mundo
$ ./id_echo_cl localhost adeus // Este cliente envia um datagrama
[7 bytes] adeus

Desejo-lhe uma boa leitura)

Fonte: linux.org.ru