Libro "Linux API. Guía completa"


Libro "Linux API. Guía completa"

Boas tardes Presento á súa atención o libro “Linux API. Unha guía completa" (tradución do libro Interfaz de programación de Linux). Pódese pedir no sitio web da editorial e se aplicas o código promocional LinuxAPI , recibirás un desconto do 30%.

Extracto do libro para referencia:

Sockets: Arquitectura do servidor

Neste capítulo, discutiremos os conceptos básicos para deseñar servidores iterativos e paralelos, e tamén analizaremos un daemon especial chamado inetd, que facilita a creación de aplicacións de servidor de Internet.

Servidores iterativos e paralelos

Hai dúas arquitecturas comúns de servidor de rede baseadas en sockets:

  • iterativo: o servidor atende aos clientes un a un, primeiro procesando unha solicitude (ou varias) dun cliente e despois pasando ao seguinte;

  • paralelo: o servidor está deseñado para servir a varios clientes simultaneamente.

Na Sección 44.8 xa se presentou un exemplo de servidor iterativo baseado en colas FIFO.

Os servidores iterativos adoitan ser axeitados só en situacións nas que as solicitudes dos clientes poden procesarse con bastante rapidez, xa que cada cliente está obrigado a esperar ata que se atendan os outros clientes que teñen diante. Un caso de uso común para este enfoque é o intercambio de solicitudes e respostas únicas entre un cliente e un servidor.

Os servidores paralelos son axeitados nos casos nos que cada solicitude leva un tempo significativo en procesarse ou nos que o cliente e o servidor realizan intercambios de mensaxes longos. Neste capítulo, centrarémonos principalmente na forma tradicional (e máis sinxela) de deseñar servidores paralelos, que consiste en crear un proceso fillo separado para cada novo cliente. Este proceso realiza todo o traballo para servir ao cliente e despois remata. Debido a que cada un destes procesos funciona de forma independente, é posible atender varios clientes á vez. A tarefa principal do proceso do servidor principal (pai) é crear un fillo separado para cada novo cliente (alternativamente, pódense crear fíos de execución en lugar de procesos).

Nas seguintes seccións, veremos exemplos de servidores de socket de dominio de Internet iterativos e paralelos. Estes dous servidores implementan unha versión simplificada do servizo de eco (RFC 862), que devolve unha copia de calquera mensaxe que lle envíe un cliente.

Eco iterativo do servidor UDP

Nesta e na seguinte sección presentaremos os servidores para o servizo echo. Está dispoñible no porto número 7 e funciona tanto en UDP como en TCP (este porto está reservado e, polo tanto, o servidor de eco debe executarse con privilexios de administrador).

O servidor echo UDP le continuamente datagramas e devolve copias deles ao remitente. Dado que o servidor só precisa procesar unha mensaxe á vez, unha arquitectura iterativa será suficiente. O ficheiro de cabeceira dos servidores móstrase no Listado 56.1.

Listaxe 56.1. Ficheiro de cabeceira dos programas id_echo_sv.c e id_echo_cl.c

#include "inet_sockets.h" /* Declara as funcións do noso socket */
#include "tlpi_hdr.h"

#define SERVICE "echo" /* Nome do servizo UDP */

#define BUF_SIZE 500 /* Tamaño máximo dos datagramas que
pódese ler polo cliente e o servidor */
___________________________________________________________________________sockets/id_echo.h

O Listado 56.2 mostra a implementación do servidor. Paga a pena destacar os seguintes puntos:

  • para poñer o servidor en modo daemon, usamos a función converteDaemon() da sección 37.2;

  • para facer o programa máis compacto, empregamos a biblioteca para traballar con sockets de dominio de Internet, desenvolvida na sección 55.12;

  • se o servidor non pode devolver unha resposta ao cliente, escribe unha mensaxe no rexistro usando a chamada syslog().

Nunha aplicación real, probablemente imporíamos algún límite na frecuencia de rexistro de mensaxes usando syslog(). Isto eliminaría a posibilidade de que un atacante desbordase o rexistro do sistema. Ademais, non esquezas que cada chamada a syslog() é bastante cara, xa que usa fsync() por defecto.

Listaxe 56.2. Servidor de iteración que implementa o servizo de eco UDP

_________________________________________________________________sockets/id_echo_sv.c
#incluír
#include "id_echo.h"
#include "become_daemon.h"

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

se (convértese enDaemon(0) == -1)
errExit("convértese enDaemon");

sfd = inetBind (SERVIZO, SOCK_DGRAM, NULL);
se (sfd == -1) {
syslog(LOG_ERR, "Non foi posíbel crear o socket do servidor (%s)",
strerror(errno));
saír(EXIT_FAILURE);

/* Recibir datagramas e devolver copias deles aos remitentes */
}
para (;;) {
len = sizeof(struct sockaddr_storage);
numRead = recvfrom(sfd, buf, BUF_SIZE, 0, (struct sockaddr *) &claddr, &len);

se (numRead == -1)
errExit("recvfrom");
if (sendto(sfd, buf, numRead, 0, (struct sockaddr *) &claddr, len)
!= numRead)
syslog(LOG_WARNING, "Erro ao facer eco da resposta a %s (%s)",
inetAddressStr((struct sockaddr *) &claddr, len,
addrStr, IS_ADDR_STR_LEN),
strerror(errno));
}
}
_________________________________________________________________sockets/id_echo_sv.c

Para probar o funcionamento do servidor, usamos o programa do Listado 56.3. Tamén utiliza a biblioteca para traballar con sockets de dominio de Internet, desenvolvida na sección 55.12. Como primeiro argumento da liña de comandos, o programa cliente toma o nome do nodo de rede no que se atopa o servidor. O cliente entra nun bucle onde envía cada un dos argumentos restantes ao servidor como datagramas separados, e despois le e imprime os datagramas que recibe do servidor como resposta.

Listaxe 56.3. Cliente para o servizo de eco UDP

#include "id_echo.h"

int
main(int argc, char *argv[])
{
int sfd, j;
size_t len;
ssiize_t numRead;
char buf[BUF_SIZE];

se (argc < 2 || strcmp(argv[1], "--help") == 0)
usageErr("%s mensaxe de host...n", argv[0]);

/* Forme o enderezo do servidor baseándose no primeiro argumento da liña de comandos */
sfd = inetConnect(argv[1], SERVIZO, SOCK_DGRAM);
se (sfd == -1)
fatal("Non se puido conectar ao socket do servidor");

/* Enviar os argumentos restantes ao servidor en forma de datagramas separados */
para (j = 2; j < argc; j++) {
len = strlen(argv[j]);
if (escribir (sfd, argv[j], len) != len)
fatal("escritura parcial/fallada");

numRead = lectura (sfd, buf, BUF_SIZE);
se (numRead == -1)
errSaír("ler");
printf("[%ld bytes] %.*sn", (long) numRead, (int) numRead, buf);
}
saír(EXIT_SUCCESS);
}
_________________________________________________________________sockets/id_echo_cl.c

A continuación móstrase un exemplo do que veremos ao executar o servidor e dúas instancias de cliente:

$su // Requírense privilexios para vincularse a un porto reservado
password:
# ./id_echo_sv // O servidor pasa ao modo de fondo
# saír // Ceder os dereitos de administrador
$ ./id_echo_cl localhost ola mundo // Este cliente envía dous datagramas
[5 bytes] ola // O cliente mostra a resposta recibida do servidor
[5 bytes] mundo
$ ./id_echo_cl localhost goodbye // Este cliente envía un datagrama
[7 bytes] adeus

Deséxoche unha boa lectura)

Fonte: linux.org.ru