Książka „Linux API. Kompleksowy przewodnik”


Książka „Linux API. Kompleksowy przewodnik”

Dzień dobry Zwracam uwagę na książkę „Linux API. Obszerny przewodnik” (tłumaczenie książki Interfejs programistyczny Linuksa). Można ją zamówić na stronie wydawcy oraz po zastosowaniu kodu promocyjnego LinuxAPI , otrzymasz 30% rabatu.

Fragment książki dla przypomnienia:

Gniazda: Architektura serwerowa

W tym rozdziale omówimy podstawy projektowania serwerów iteracyjnych i równoległych, a także przyjrzymy się specjalnemu demonowi o nazwie inetd, który ułatwia tworzenie aplikacji serwerów internetowych.

Serwery iteracyjne i równoległe

Istnieją dwie popularne architektury serwerów sieciowych opartych na gniazdach:

  • iteracyjny: serwer obsługuje klientów pojedynczo, najpierw przetwarzając żądanie (lub kilka żądań) od jednego klienta, a następnie przechodząc do następnego;

  • równolegle: serwer jest przeznaczony do jednoczesnej obsługi wielu klientów.

Przykład serwera iteracyjnego opartego na kolejkach FIFO został już przedstawiony w rozdziale 44.8.

Serwery iteracyjne są zwykle odpowiednie tylko w sytuacjach, w których żądania klientów mogą zostać przetworzone dość szybko, ponieważ każdy klient jest zmuszony czekać, aż inni klienci przed nim zostaną obsłużeni. Typowym przypadkiem użycia tego podejścia jest wymiana pojedynczych żądań i odpowiedzi między klientem a serwerem.

Serwery równoległe są odpowiednie w przypadkach, gdy przetwarzanie każdego żądania zajmuje dużo czasu lub gdy klient i serwer wymieniają długie komunikaty. W tym rozdziale skupimy się głównie na tradycyjnym (i najprostszym) sposobie projektowania serwerów równoległych, który polega na utworzeniu osobnego procesu potomnego dla każdego nowego klienta. Proces ten wykonuje całą pracę związaną z obsługą klienta, po czym się kończy. Ponieważ każdy z tych procesów działa niezależnie, możliwa jest jednoczesna obsługa wielu klientów. Głównym zadaniem głównego procesu serwera (rodzica) jest utworzenie osobnego dziecka dla każdego nowego klienta (alternatywnie zamiast procesów można tworzyć wątki wykonawcze).

W kolejnych sekcjach przyjrzymy się przykładom iteracyjnych i równoległych serwerów gniazd domeny internetowej. Te dwa serwery implementują uproszczoną wersję usługi echo (RFC 862), która zwraca kopię dowolnej wiadomości wysłanej do nich przez klienta.

Iteracyjne echo serwera UDP

W tej i następnej sekcji przedstawimy serwery usługi echo. Jest dostępny na porcie numer 7 i działa zarówno przez UDP, jak i TCP (ten port jest zarezerwowany, dlatego serwer echo musi być uruchamiany z uprawnieniami administratora).

Serwer echo UDP w sposób ciągły odczytuje datagramy i zwraca ich kopie nadawcy. Ponieważ serwer musi przetwarzać tylko jedną wiadomość na raz, wystarczy architektura iteracyjna. Plik nagłówkowy serwerów pokazano na Listingu 56.1.

Lista 56.1. Plik nagłówkowy dla programów id_echo_sv.c i id_echo_cl.c

#include "inet_sockets.h" /* Deklaruje funkcje naszego gniazda */
#include „tlpi_hdr.h”

#define SERVICE "echo" /* nazwa usługi UDP */

#define BUF_SIZE 500 /* Maksymalny rozmiar datagramów
może być odczytany przez klienta i serwer */
_________________________________________________________sockets/id_echo.h

Listing 56.2 przedstawia implementację serwera. Warto zwrócić uwagę na następujące punkty:

  • aby przełączyć serwer w tryb demona, używamy funkcji bedeDaemon() z sekcji 37.2;

  • aby uczynić program bardziej zwartym, korzystamy z biblioteki do pracy z gniazdami domen internetowych, opracowanej w rozdziale 55.12;

  • jeśli serwer nie może zwrócić odpowiedzi klientowi, zapisuje komunikat w dzienniku za pomocą wywołania syslog().

W prawdziwej aplikacji prawdopodobnie nałożylibyśmy pewne ograniczenie na częstotliwość rejestrowania komunikatów za pomocą syslog(). Wyeliminowałoby to możliwość przepełnienia dziennika systemowego przez osobę atakującą. Ponadto nie zapominaj, że każde wywołanie syslog() jest dość kosztowne, ponieważ domyślnie używa fsync().

Lista 56.2. Serwer iteracyjny implementujący usługę echa UDP

________________________________________________________________________________sockets/id_echo_sv.c
#włączać
#include „id_echo.h”
#include „zostań się_demonem.h”

int
main(int argc, char *argv[])
{
int sfd;
ssize_t liczbaOdczyt;
socklen_t len;
struktura sockaddr_storage claddr;
char buf[BUF_SIZE];
char adresStr[IS_ADDR_STR_LEN];

if (zostańDaemonem(0) == -1)
errExit("zostań demonem");

sfd = inetBind(SERVICE, SOCK_DGRAM, NULL);
jeśli (sfd == -1) {
syslog(LOG_ERR, "Nie można utworzyć gniazda serwera (%s)",
strerror(numer błędu));
wyjście(EXIT_FAILURE);

/* Odbierz datagramy i zwróć ich kopie nadawcom */
}
dla (;;) {
len = rozmiar(struktura sockaddr_storage);
numRead = recvfrom(sfd, buf, BUF_SIZE, 0, (struct sockaddr *) &claddr, &len);

jeśli (numRead == -1)
errExit("odbiór");
if (sendto(sfd, buf, numRead, 0, (struct sockaddr *) &claddr, len)
!= liczbaPrzeczytanych)
syslog(LOG_WARNING, "Błąd podczas powtarzania odpowiedzi na %s (%s)",
inetAddressStr((struct sockaddr *) &claddr, len,
adresStr, IS_ADDR_STR_LEN),
strerror(numer błędu));
}
}
________________________________________________________________________________sockets/id_echo_sv.c

Do przetestowania działania serwera używamy programu z Listingu 56.3. Korzysta także z biblioteki do pracy z gniazdami domen internetowych, opracowanej w rozdziale 55.12. Jako pierwszy argument wiersza poleceń program kliencki przyjmuje nazwę węzła sieci, w którym znajduje się serwer. Klient wchodzi w pętlę, w której wysyła każdy z pozostałych argumentów do serwera jako osobne datagramy, a następnie odczytuje i drukuje datagramy otrzymane z serwera w odpowiedzi.

Lista 56.3. Klient usługi echa UDP

#include „id_echo.h”

int
main(int argc, char *argv[])
{
int sfd, j;
rozmiar_t dł.;
ssize_t liczbaOdczyt;
char buf[BUF_SIZE];

if (argc < 2 || strcmp(argv[1], "--help") == 0)
useErr("%s komunikat hosta…n", argv[0]);

/* Utwórz adres serwera na podstawie pierwszego argumentu wiersza poleceń */
sfd = inetConnect(argv[1], USŁUGA, SOCK_DGRAM);
jeśli (sfd == -1)
fatal("Nie można połączyć się z gniazdem serwera");

/* Wyślij pozostałe argumenty do serwera w postaci oddzielnych datagramów */
for (j = 2; j < argc; j++) {
len = strlen(argv[j]);
if (write(sfd, argv[j], len) != len)
fatal("częściowy/nieudany zapis");

numRead = odczyt (sfd, buf, BUF_SIZE);
jeśli (numRead == -1)
errExit("czytaj");
printf("[%ld bajtów] %.*sn", (long) numRead, (int) numRead, buf);
}
wyjście (EXIT_SUCCESS);
}
________________________________________________________________________________sockets/id_echo_cl.c

Poniżej znajduje się przykład tego, co zobaczymy podczas uruchamiania serwera i dwóch instancji klienta:

$su // Aby połączyć się z zarezerwowanym portem, wymagane są uprawnienia
Hasło:
# ./id_echo_sv // Serwer przechodzi w tryb tła
# wyjście // Zrezygnuj z praw administratora
$ ./id_echo_cl localhost hello world // Ten klient wysyła dwa datagramy
[5 bajtów] hello // Klient wyświetla odpowiedź otrzymaną od serwera
[5 bajtów] świat
$ ./id_echo_cl localhost do widzenia // Ten klient wysyła jeden datagram
[7 bajtów] do widzenia

Życzę miłej lektury)

Źródło: linux.org.ru