Livre « API Linux. Guide complet"


Livre « API Linux. Guide complet"

Bon après-midi Je présente à votre attention le livre « Linux API. Un guide complet" (traduction du livre L'interface de programmation Linux). Il peut être commandé sur le site de l’éditeur, et si vous appliquez le code promotionnel API Linux , vous bénéficierez d'une réduction de 30%.

Extrait du livre pour référence :

Sockets : architecture de serveur

Dans ce chapitre, nous aborderons les bases de la conception de serveurs itératifs et parallèles, et examinerons également un démon spécial appelé inetd, qui facilite la création d'applications de serveur Internet.

Serveurs itératifs et parallèles

Il existe deux architectures de serveur réseau basées sur socket courantes :

  • itératif : le serveur sert les clients un par un, traitant d'abord une requête (ou plusieurs requêtes) d'un client puis passant au suivant ;

  • parallèle : le serveur est conçu pour servir plusieurs clients simultanément.

Un exemple de serveur itératif basé sur des files d'attente FIFO a déjà été présenté dans la section 44.8.

Les serveurs itératifs ne conviennent généralement que dans les situations où les demandes des clients peuvent être traitées assez rapidement, puisque chaque client est obligé d'attendre que tous les autres clients devant lui aient été servis. Un cas d'utilisation courant de cette approche est l'échange de requêtes et de réponses uniques entre un client et un serveur.

Les serveurs parallèles conviennent dans les cas où le traitement de chaque requête prend beaucoup de temps ou lorsque le client et le serveur échangent de longs messages. Dans ce chapitre, nous nous concentrerons principalement sur la manière traditionnelle (et la plus simple) de concevoir des serveurs parallèles, qui consiste à créer un processus enfant distinct pour chaque nouveau client. Ce processus effectue tout le travail pour servir le client et se termine ensuite. Puisque chacun de ces processus fonctionne indépendamment, il est possible de servir plusieurs clients simultanément. La tâche principale du processus serveur principal (parent) est de créer un enfant distinct pour chaque nouveau client (vous pouvez également créer des threads d'exécution à la place des processus).

Dans les sections suivantes, nous examinerons des exemples de serveurs socket de domaine Internet itératifs et parallèles. Ces deux serveurs implémentent une version simplifiée du service echo (RFC 862), qui renvoie une copie de tout message qui lui est envoyé par un client.

Écho du serveur UDP itératif

Dans cette section et dans la suivante, nous présenterons les serveurs du service d'écho. Il est disponible sur le port numéro 7 et fonctionne à la fois sur UDP et TCP (ce port est réservé et le serveur d'écho doit donc être exécuté avec les privilèges d'administrateur).

Le serveur echo UDP lit en permanence les datagrammes et en renvoie des copies à l'expéditeur. Puisque le serveur n’a besoin de traiter qu’un seul message à la fois, une architecture itérative suffira. Le fichier d'en-tête des serveurs est présenté dans le listing 56.1.

Liste 56.1. Fichier d'en-tête pour les programmes id_echo_sv.c et id_echo_cl.c

#include "inet_sockets.h" /* Déclare les fonctions de notre socket */
#include "tlpi_hdr.h"

#define SERVICE "echo" /* Nom du service UDP */

#define BUF_SIZE 500 /* Taille maximale des datagrammes qui
peut être lu par le client et le serveur */
_____________________________________________________________________sockets/id_echo.h

Le listing 56.2 montre l'implémentation du serveur. Les points suivants méritent d’être soulignés :

  • pour mettre le serveur en mode démon, nous utilisons la fonction comeDaemon() de la section 37.2 ;

  • pour rendre le programme plus compact, nous utilisons la bibliothèque pour travailler avec les sockets de domaine Internet, développée dans la section 55.12 ;

  • si le serveur ne peut pas renvoyer de réponse au client, il écrit un message dans le journal à l'aide de l'appel syslog().

Dans une application réelle, nous imposerions probablement une certaine limite à la fréquence de journalisation des messages à l'aide de syslog(). Cela éliminerait la possibilité qu'un attaquant déborde le journal système. De plus, n'oubliez pas que chaque appel à syslog() est assez coûteux, puisqu'il utilise fsync() par défaut.

Liste 56.2. Serveur d'itération qui implémente le service d'écho UDP

_________________________________________________________________sockets/id_echo_sv.c
#inclure
#include "id_echo.h"
#include "devenir_daemon.h"

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

if (devenir Démon (0) == -1)
errExit("devenirDaemon");

sfd = inetBind(SERVICE, SOCK_DGRAM, NULL);
si (sfd == -1) {
syslog(LOG_ERR, "Impossible de créer le socket serveur (%s)",
strerror(errno));
quitter(EXIT_FAILURE);

/* Recevoir les datagrammes et en renvoyer des copies aux expéditeurs */
}
pour (;;) {
len = sizeof(struct sockaddr_storage);
numRead = recvfrom(sfd, buf, BUF_SIZE, 0, (struct sockaddr *) &claddr, &len);

si (numRead == -1)
errExit("recvfrom");
if (sendto(sfd, buf, numRead, 0, (struct sockaddr *) &claddr, len)
!= numLecture)
syslog(LOG_WARNING, "Erreur lors de l'écho de la réponse à %s (%s)",
inetAddressStr((struct sockaddr *) &claddr, len,
addrStr, IS_ADDR_STR_LEN),
strerror(errno));
}
}
_________________________________________________________________sockets/id_echo_sv.c

Pour tester le fonctionnement du serveur, nous utilisons le programme du listing 56.3. Il utilise également la bibliothèque pour travailler avec les sockets de domaine Internet, développée dans la section 55.12. Comme premier argument de ligne de commande, le programme client prend le nom du nœud de réseau sur lequel se trouve le serveur. Le client entre dans une boucle dans laquelle il envoie chacun des arguments restants au serveur sous forme de datagrammes distincts, puis lit et imprime les datagrammes qu'il reçoit du serveur en réponse.

Liste 56.3. Client pour le service d'écho UDP

#include "id_echo.h"

int
principal(int argc, char *argv[])
{
int sfd, j;
taille_t lentille ;
ssize_t numRead;
char buf[BUF_SIZE];

si (argc < 2 || strcmp(argv[1], "--help") == 0)
utilisationErr("%s message de l'hôte…n", argv[0]);

/* Former l'adresse du serveur en fonction du premier argument de ligne de commande */
sfd = inetConnect(argv[1], SERVICE, SOCK_DGRAM);
si (sfd == -1)
fatal("Impossible de se connecter au socket du serveur");

/* Envoie les arguments restants au serveur sous forme de datagrammes séparés */
pour (j = 2; j < argc; j++) {
len = strlen(argv[j]);
if (write(sfd, argv[j], len) != len)
fatal("écriture partielle/échec");

numRead = lire (sfd, buf, BUF_SIZE);
si (numRead == -1)
errExit("lire");
printf("[%ld octets] %.*sn", (long) numRead, (int) numRead, buf);
}
quitter(EXIT_SUCCESS);
}
_________________________________________________________________sockets/id_echo_cl.c

Vous trouverez ci-dessous un exemple de ce que nous verrons lors de l'exécution du serveur et de deux instances client :

$su // Des privilèges sont requis pour se lier à un port réservé
Mot de passe:
# ./id_echo_sv // Le serveur passe en mode arrière-plan
# exit // Abandonnez les droits d'administrateur
$ ./id_echo_cl localhost hello world // Ce client envoie deux datagrammes
[5 octets] bonjour // Le client affiche la réponse reçue du serveur
[5 octets] monde
$ ./id_echo_cl localhost au revoir // Ce client envoie un datagramme
[7 octets] au revoir

je vous souhaite une agréable lecture)

Source: linux.org.ru