Boka "Linux API. Omfattande guide"


Boka "Linux API. Omfattande guide"

God eftermiddag Jag presenterar boken "Linux API. En omfattande guide" (översättning av boken Linux-programmeringsgränssnittet). Den kan beställas på förlagets webbplats och om du använder kampanjkoden LinuxAPI , får du 30 % rabatt.

Utdrag ur boken för referens:

Sockets: Serverarkitektur

I det här kapitlet kommer vi att diskutera grunderna för att designa iterativa och parallella servrar, och även titta på en speciell demon som heter inetd, som gör det lättare att skapa Internetserverapplikationer.

Iterativa och parallella servrar

Det finns två vanliga socket-baserade nätverksserverarkitekturer:

  • iterativ: servern betjänar klienter en i taget, bearbetar först en begäran (eller flera förfrågningar) från en klient och går sedan vidare till nästa;

  • parallell: servern är utformad för att betjäna flera klienter samtidigt.

Ett exempel på en iterativ server baserad på FIFO-köer presenterades redan i avsnitt 44.8.

Iterativa servrar är vanligtvis bara lämpliga i situationer där klientförfrågningar kan behandlas ganska snabbt, eftersom varje klient tvingas vänta tills andra klienter framför den har betjänats. Ett vanligt användningsfall för detta tillvägagångssätt är utbytet av enskilda förfrågningar och svar mellan en klient och server.

Parallella servrar är lämpliga i fall där varje begäran tar en betydande tid att bearbeta, eller där klienten och servern deltar i långa meddelandeutbyten. I det här kapitlet kommer vi främst att fokusera på det traditionella (och enklaste) sättet att designa parallella servrar, vilket är att skapa en separat underordnad process för varje ny klient. Denna process utför allt arbete för att betjäna kunden och avslutas sedan. Eftersom var och en av dessa processer fungerar oberoende, kan flera klienter betjänas samtidigt. Huvuduppgiften för huvudserverprocessen (förälder) är att skapa ett separat barn för varje ny klient (alternativt kan exekveringstrådar skapas istället för processer).

I de följande avsnitten kommer vi att titta på exempel på iterativa och parallella domänsocketservrar för internet. Dessa två servrar implementerar en förenklad version av ekotjänsten (RFC 862), som returnerar en kopia av alla meddelanden som skickas till den av en klient.

Iterativt UDP-servereko

I detta och nästa avsnitt kommer vi att presentera servrarna för ekotjänsten. Den finns på port nummer 7 och fungerar över både UDP och TCP (denna port är reserverad, och därför måste ekoservern köras med administratörsbehörighet).

Echo UDP-servern läser kontinuerligt datagram och returnerar kopior av dem till avsändaren. Eftersom servern bara behöver bearbeta ett meddelande i taget räcker det med en iterativ arkitektur. Rubrikfilen för servrarna visas i Listing 56.1.

Lista 56.1. Header-fil för programmen id_echo_sv.c och id_echo_cl.c

#include "inet_sockets.h" /* Deklarerar våra socketfunktioner */
#include "tlpi_hdr.h"

#define SERVICE "echo" /* UDP-tjänstens namn */

#define BUF_SIZE 500 /* Maximal storlek på datagram som
kan läsas av klient och server */
__________________________________________________________________sockets/id_echo.h

Lista 56.2 visar serverimplementeringen. Följande punkter är värda att notera:

  • för att sätta servern i demonläge använder vi funktionen becomeDaemon() från avsnitt 37.2;

  • för att göra programmet mer kompakt använder vi biblioteket för att arbeta med Internetdomänsockets, utvecklat i avsnitt 55.12;

  • om servern inte kan returnera ett svar till klienten, skriver den ett meddelande till loggen med anropet syslog().

I en riktig applikation skulle vi sannolikt införa en viss gräns för frekvensen av loggning av meddelanden med syslog(). Detta skulle eliminera möjligheten för en angripare att svämma över systemloggen. Dessutom, glöm inte att varje anrop till syslog() är ganska dyrt, eftersom det använder fsync() som standard.

Lista 56.2. Iterationsserver som implementerar UDP-ekotjänsten

_________________________________________________________________sockets/id_echo_sv.c
#omfatta
#inkludera "id_echo.h"
#include "become_daemon.h"

int
main(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 (bliDaemon(0) == -1)
errExit("bliDaemon");

sfd = inetBind(SERVICE, SOCK_DGRAM, NULL);
if (sfd == -1) {
syslog(LOG_ERR, "Kunde inte skapa serversocket (%s)",
strerror(errno));
exit(EXIT_FAILURE);

/* Ta emot datagram och returnera kopior av dem till avsändarna */
}
för (;;) {
len = sizeof(struct sockaddr_storage);
numRead = recvfrom(sfd, buf, BUF_SIZE, 0, (struct sockaddr *) &claddr, &len);

if (antalRead == -1)
errExit("recvfrom");
if (sendto(sfd, buf, numRead, 0, (struct sockaddr *) &claddr, len)
!= antalLäst)
syslog(LOG_WARNING, "Fel vid eko av svar till %s (%s)",
inetAddressStr((struct sockaddr *) &claddr, len,
addrStr, IS_ADDR_STR_LEN),
strerror(errno));
}
}
_________________________________________________________________sockets/id_echo_sv.c

För att testa serverns funktion använder vi programmet från Listing 56.3. Den använder också biblioteket för att arbeta med Internetdomänsockets, utvecklat i avsnitt 55.12. Som det första kommandoradsargumentet tar klientprogrammet namnet på nätverksnoden där servern finns. Klienten går in i en loop där den skickar vart och ett av de återstående argumenten till servern som separata datagram, och läser sedan och skriver ut datagrammen som den tar emot från servern som svar.

Lista 56.3. Klient för UDP-ekotjänst

#inkludera "id_echo.h"

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

if (argc < 2 || strcmp(argv[1], "--hjälp") == 0)
usageErr("%s host msg...n", argv[0]);

/* Forma serveradressen baserat på det första kommandoradsargumentet */
sfd = inetConnect(argv[1], SERVICE, SOCK_DGRAM);
if (sfd == -1)
fatal("Kunde inte ansluta till serversocket");

/* Skicka de återstående argumenten till servern i form av separata datagram */
för (j = 2; j < argc; j++) {
len = strlen(argv[j]);
if (skriv(sfd, argv[j], len) != len)
fatal("partiell/misslyckad skrivning");

numRead = read(sfd, buf, BUF_SIZE);
if (antalRead == -1)
errExit("läs");
printf("[%ld bytes] %.*sn", (lång) numRead, (int) numRead, buf);
}
exit(EXIT_SUCCESS);
}
_________________________________________________________________sockets/id_echo_cl.c

Nedan är ett exempel på vad vi kommer att se när vi kör servern och två klientinstanser:

$su // Privilegier krävs för att binda till en reserverad port
Lösenord:
# ./id_echo_sv // Servern går in i bakgrundsläge
# exit // Ge upp administratörsrättigheter
$ ./id_echo_cl localhost hello world // Denna klient skickar två datagram
[5 bytes] hej // Klienten visar svaret mottaget från servern
[5 bytes] värld
$ ./id_echo_cl localhost hejdå // Denna klient skickar ett datagram
[7 bytes] hejdå

Jag önskar dig trevlig läsning)

Källa: linux.org.ru