Libro “API Linux. Guida completa"


Libro “API Linux. Guida completa"

Buon pomeriggio Presento alla vostra attenzione il libro “Linux API. Una guida completa" (traduzione del libro L'interfaccia di programmazione Linux). Può essere ordinato sul sito web dell’editore e se si applica il codice promozionale API Linux , riceverai uno sconto del 30%.

Estratto dal libro per riferimento:

Socket: architettura del server

In questo capitolo discuteremo le basi della progettazione di server iterativi e paralleli e vedremo anche uno speciale demone chiamato inetd, che semplifica la creazione di applicazioni server Internet.

Server iterativi e paralleli

Esistono due architetture comuni di server di rete basate su socket:

  • iterativo: il server serve i client uno alla volta, elaborando prima una richiesta (o più richieste) da un client e poi passando a quello successivo;

  • parallelo: il server è progettato per servire più client contemporaneamente.

Un esempio di server iterativo basato su code FIFO è già stato presentato nella Sezione 44.8.

I server iterativi sono solitamente adatti solo in situazioni in cui le richieste dei client possono essere elaborate abbastanza rapidamente, poiché ogni client è costretto ad attendere fino a quando tutti gli altri client davanti a lui non sono stati serviti. Un caso d'uso comune per questo approccio è lo scambio di singole richieste e risposte tra un client e un server.

I server paralleli sono adatti nei casi in cui ciascuna richiesta richiede una notevole quantità di tempo per essere elaborata o in cui il client e il server effettuano lunghi scambi di messaggi. In questo capitolo ci concentreremo principalmente sul modo tradizionale (e più semplice) di progettare server paralleli, ovvero creare un processo figlio separato per ogni nuovo client. Questo processo esegue tutto il lavoro per servire il cliente e poi termina. Poiché ciascuno di questi processi opera in modo indipendente, è possibile servire più clienti contemporaneamente. Il compito principale del processo del server principale (padre) è creare un figlio separato per ogni nuovo client (in alternativa, è possibile creare thread di esecuzione anziché processi).

Nelle sezioni seguenti esamineremo esempi di server socket di dominio Internet iterativi e paralleli. Questi due server implementano una versione semplificata del servizio echo (RFC 862), che restituisce una copia di qualsiasi messaggio inviatogli da un client.

Eco iterativo del server UDP

In questa e nella prossima sezione introdurremo i server per il servizio echo. È disponibile sulla porta numero 7 e funziona sia su UDP che su TCP (questa porta è riservata e pertanto il server echo deve essere eseguito con privilegi di amministratore).

Il server echo UDP legge continuamente i datagrammi e ne restituisce copie al mittente. Poiché il server deve elaborare solo un messaggio alla volta, sarà sufficiente un'architettura iterativa. Il file header per i server è mostrato nel Listato 56.1.

Elenco 56.1. File di intestazione per i programmi id_echo_sv.c e id_echo_cl.c

#include "inet_sockets.h" /* Dichiara le funzioni del nostro socket */
#include "tlpi_hdr.h"

#define SERVICE "echo" /* Nome del servizio UDP */

#define BUF_SIZE 500 /* Dimensione massima dei datagrammi che
può essere letto dal client e dal server */
_____________________________________________________________________sockets/id_echo.h

Il Listato 56.2 mostra l'implementazione del server. Vale la pena notare i seguenti punti:

  • per mettere il server in modalità demone, utilizziamo la funzione diventaDaemon() della sezione 37.2;

  • per rendere il programma più compatto, utilizziamo la libreria per lavorare con i socket di dominio Internet, sviluppata nella sezione 55.12;

  • se il server non può restituire una risposta al client, scrive un messaggio nel log utilizzando la chiamata syslog().

In un'applicazione reale, probabilmente imporremmo qualche limite alla frequenza di registrazione dei messaggi utilizzando syslog(). Ciò eliminerebbe la possibilità che un utente malintenzionato trabocchi il registro di sistema. Inoltre, non dimenticare che ogni chiamata a syslog() è piuttosto costosa, poiché utilizza fsync() per impostazione predefinita.

Elenco 56.2. Server di iterazione che implementa il servizio echo UDP

_________________________________________________________________sockets/id_echo_sv.c
#includere
#include "id_echo.h"
#include "diventa_daemon.h"

int
main(int argc, char *argv[])
{
int sfd;
ssize_t numLeggi;
calzino_t lente;
struct sockaddr_storage claddr;
char bu[BUF_SIZE];
char addrStr[IS_ADDR_STR_LEN];

if (diventaDaemon(0) == -1)
errExit("diventaDaemon");

sfd = inetBind(SERVICE, SOCK_DGRAM, NULL);
se (sfd == -1) {
syslog(LOG_ERR, "Impossibile creare il socket del server (%s)",
strerror(errno));
uscita(EXIT_FAILURE);

/* Riceve i datagrammi e ne restituisce copie ai mittenti */
}
per (;;) {
len = sizeof(struct sockaddr_storage);
numRead = recvfrom(sfd, buf, BUF_SIZE, 0, (struct sockaddr *) &claddr, &len);

se (numLeggi == -1)
errExit("recvfrom");
if (sendto(sfd, buf, numRead, 0, (struct sockaddr *) &claddr, len)
!= numLeggi)
syslog(LOG_WARNING, "Errore durante la visualizzazione della risposta a %s (%s)",
inetAddressStr((struct sockaddr *) &claddr, len,
addrStr, IS_ADDR_STR_LEN),
strerror(errno));
}
}
_________________________________________________________________sockets/id_echo_sv.c

Per testare il funzionamento del server, utilizziamo il programma del Listato 56.3. Utilizza anche la libreria per lavorare con i socket di dominio Internet, sviluppata nella sezione 55.12. Come primo argomento della riga di comando, il programma client prende il nome del nodo di rete su cui si trova il server. Il client entra in un ciclo in cui invia ciascuno degli argomenti rimanenti al server come datagrammi separati, quindi legge e stampa i datagrammi che riceve dal server in risposta.

Elenco 56.3. Client per il servizio eco UDP

#include "id_echo.h"

int
main(int argc, char *argv[])
{
int sfd, j;
taglia_t lente;
ssize_t numLeggi;
char bu[BUF_SIZE];

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

/* Forma l'indirizzo del server in base al primo argomento della riga di comando */
sfd = inetConnect(argv[1], SERVIZIO, SOCK_DGRAM);
se (sfd == -1)
fatal("Impossibile connettersi al socket del server");

/* Invia gli argomenti rimanenti al server sotto forma di datagrammi separati */
for (j = 2; j < argc; j++) {
len = strlen(argv[j]);
if (scrivi(sfd, argv[j], len) != len)
fatal("scrittura parziale/non riuscita");

numLeggi = leggi(sfd, buf, BUF_SIZE);
se (numLeggi == -1)
errExit("leggi");
printf("[%ld byte] %.*sn", (long) numRead, (int) numRead, buf);
}
uscita(EXIT_SUCCESS);
}
_________________________________________________________________sockets/id_echo_cl.c

Di seguito è riportato un esempio di ciò che vedremo durante l'esecuzione del server e di due istanze client:

$su // Sono necessari privilegi per collegarsi a una porta riservata
Password:
# ./id_echo_sv // Il server entra in modalità background
# exit // Rinuncia ai diritti di amministratore
$ ./id_echo_cl localhost hello world // Questo client invia due datagrammi
[5 byte] ciao // Il client visualizza la risposta ricevuta dal server
[5 byte] mondo
$ ./id_echo_cl localhost arrivederci // Questo client invia un datagramma
[7 byte] arrivederci

Vi auguro una piacevole lettura)

Fonte: linux.org.ru