Cartea „Linux API. Ghid cuprinzător"


Cartea „Linux API. Ghid cuprinzător"

Bună ziua Vă prezint atenției cartea „Linux API. Un ghid cuprinzător” (traducerea cărții Interfața de programare Linux). Poate fi comandat pe site-ul editorului și dacă aplicați codul promoțional LinuxAPI , vei primi o reducere de 30%.

Extras din carte pentru referință:

Sockets: Arhitectura serverului

În acest capitol, vom discuta elementele de bază ale proiectării serverelor iterative și paralele și vom analiza, de asemenea, un demon special numit inetd, care facilitează crearea de aplicații de server de Internet.

Servere iterative și paralele

Există două arhitecturi comune de server de rețea bazate pe socket:

  • iterativ: serverul servește clienții pe rând, mai întâi procesând o cerere (sau mai multe cereri) de la un client și apoi trecând la următorul;

  • paralel: serverul este proiectat să deservească mai mulți clienți simultan.

Un exemplu de server iterativ bazat pe cozi FIFO a fost deja prezentat în Secțiunea 44.8.

Serverele iterative sunt de obicei potrivite numai în situațiile în care cererile clienților pot fi procesate destul de rapid, deoarece fiecare client este forțat să aștepte până când orice alți clienți din fața lui au fost serviți. Un caz comun de utilizare pentru această abordare este schimbul de cereri și răspunsuri unice între un client și un server.

Serverele paralele sunt potrivite în cazurile în care procesarea fiecărei cereri necesită o perioadă semnificativă de timp sau în care clientul și serverul se angajează în schimburi lungi de mesaje. În acest capitol, ne vom concentra în principal asupra modului tradițional (și cel mai simplu) de proiectare a serverelor paralele, care este de a crea un proces copil separat pentru fiecare client nou. Acest proces realizează toată munca pentru a servi clientul și apoi se încheie. Deoarece fiecare dintre aceste procese funcționează independent, este posibil să deserviți mai mulți clienți simultan. Sarcina principală a procesului serverului principal (părinte) este de a crea un copil separat pentru fiecare client nou (în mod alternativ, firele de execuție pot fi create în loc de procese).

În secțiunile următoare, ne vom uita la exemple de servere iterative și paralele pentru domeniul internetului. Aceste două servere implementează o versiune simplificată a serviciului echo (RFC 862), care returnează o copie a oricărui mesaj trimis acestuia de către un client.

Ecou iterativ al serverului UDP

În această secțiune și în următoarea secțiune vom introduce serverele pentru serviciul echo. Este disponibil pe portul numărul 7 și funcționează atât pe UDP, cât și pe TCP (acest port este rezervat și, prin urmare, serverul echo trebuie rulat cu privilegii de administrator).

Serverul echo UDP citește continuu datagramele și returnează copii ale acestora expeditorului. Deoarece serverul trebuie să proceseze doar un mesaj la un moment dat, o arhitectură iterativă va fi suficientă. Fișierul antet pentru servere este afișat în Lista 56.1.

Lista 56.1. Fișier antet pentru programele id_echo_sv.c și id_echo_cl.c

#include "inet_sockets.h" /* Declara funcțiile socket-ului nostru */
#include „tlpi_hdr.h”

#define SERVICE "echo" /* Nume serviciu UDP */

#define BUF_SIZE 500 /* Dimensiunea maximă a datagramelor care
poate fi citit de client și server */
__________________________________________________________________________sockets/id_echo.h

Lista 56.2 arată implementarea serverului. Următoarele puncte merită remarcate:

  • pentru a pune serverul în modul daemon, folosim funcția becomeDaemon() din secțiunea 37.2;

  • pentru a face programul mai compact, folosim biblioteca pentru lucrul cu prize de domeniu Internet, dezvoltată în secțiunea 55.12;

  • dacă serverul nu poate returna un răspuns clientului, scrie un mesaj în jurnal folosind apelul syslog().

Într-o aplicație reală, probabil că am impune o anumită limită asupra frecvenței de înregistrare a mesajelor folosind syslog(). Acest lucru ar elimina posibilitatea ca un atacator să depășească jurnalul de sistem. În plus, nu uitați că fiecare apel la syslog() este destul de costisitor, deoarece folosește fsync() în mod implicit.

Lista 56.2. Server de iterație care implementează serviciul de eco UDP

_________________________________________________________________sockets/id_echo_sv.c
#include
#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];

dacă (devineDaemon(0) == -1)
errExit("devineDaemon");

sfd = inetBind(SERVICE, SOCK_DGRAM, NULL);
dacă (sfd == -1) {
syslog(LOG_ERR, „Nu s-a putut crea soclul serverului (%s)”,
strerror(errno));
ieșire(EXIT_FAILURE);

/* Primește datagrame și returnează copii ale acestora expeditorilor */
}
pentru (;;) {
len = sizeof(struct sockaddr_storage);
numRead = recvfrom(sfd, buf, BUF_SIZE, 0, (struct sockaddr *) &claddr, &len);

dacă (numRead == -1)
errExit("recvfrom");
if (sendto(sfd, buf, numRead, 0, (struct sockaddr *) &claddr, len)
!= numRead)
syslog(LOG_WARNING, „Eroare la ecou răspuns la %s (%s)”,
inetAddressStr((struct sockaddr *) &claddr, len,
addrStr, IS_ADDR_STR_LEN),
strerror(errno));
}
}
_________________________________________________________________sockets/id_echo_sv.c

Pentru a testa funcționarea serverului, folosim programul din Listatul 56.3. De asemenea, folosește biblioteca pentru lucrul cu prizele de domeniu Internet, dezvoltată în secțiunea 55.12. Ca prim argument de linie de comandă, programul client ia numele nodului de rețea pe care se află serverul. Clientul intră într-o buclă în care trimite fiecare dintre argumentele rămase către server ca datagrame separate, apoi citește și tipărește datagramele pe care le primește de la server ca răspuns.

Lista 56.3. Client pentru serviciul 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];

dacă (argc < 2 || strcmp(argv[1], "--help") == 0)
usageErr(„%s msg gazdă…n”, argv[0]);

/* Formează adresa serverului pe baza primului argument din linia de comandă */
sfd = inetConnect(argv[1], SERVICE, SOCK_DGRAM);
dacă (sfd == -1)
fatal("Nu s-a putut conecta la soclul serverului");

/* Trimiteți argumentele rămase către server sub formă de datagrame separate */
pentru (j = 2; j < argc; j++) {
len = strlen(argv[j]);
dacă (scrie(sfd, argv[j], len) != len)
fatal(„scriere parțială/eșuată”);

numRead = read(sfd, buf, BUF_SIZE);
dacă (numRead == -1)
errExit("citește");
printf("[%ld octeți] %.*sn", (long) numRead, (int) numRead, buf);
}
ieșire(EXIT_SUCCESS);
}
_________________________________________________________________sockets/id_echo_cl.c

Mai jos este un exemplu de ceea ce vom vedea când rulăm serverul și două instanțe client:

$su // Sunt necesare privilegii pentru a se lega la un port rezervat
Parola:
# ./id_echo_sv // Serverul intră în modul de fundal
# exit // Renunțați la drepturile de administrator
$ ./id_echo_cl localhost hello world // Acest client trimite două datagrame
[5 octeți] salut // Clientul afișează răspunsul primit de la server
[5 octeți] lume
$ ./id_echo_cl localhost la revedere // Acest client trimite o datagramă
[7 octeți] la revedere

va doresc o lectura placuta)

Sursa: linux.org.ru