Книжка «Linux API. Вичерпне керівництво»


Книжка «Linux API. Вичерпне керівництво»

Добридень! Пропоную вашій увазі книгу «Linux API. Вичерпне керівництво» (переклад книги Інтерфейс програмування Linux). Її можна замовити на сайті видавництва, і якщо застосувати промокод LinuxAPI , то отримаєте знижку 30%.

Уривок із книги для ознайомлення:

Сокети: архітектура сервера

У цьому розділі ми обговоримо основи проектування ітераційних та паралельних серверів, а також розглянемо спеціальний демон inetd, який спрощує створення серверних інтернет-додатків.

Ітераційні та паралельні сервери

Існують дві поширені архітектури мережевих серверів на основі сокетів:

  • ітераційна: сервер обслуговує клієнтів по одному, спочатку опрацьовуючи запит (або кілька запитів) одного клієнта і потім переходячи до наступного;

  • паралельна: сервер спроектований обслуговування декількох клієнтів одночасно.

У розділі 44.8 вже було представлено приклад ітераційного сервера з урахуванням черг FIFO.

Ітераційні сервери зазвичай підходять лише у ситуаціях, коли клієнтські запити можна обробити досить швидко, оскільки кожен клієнт змушений чекати, доки обслужать будь-яких інших клієнтів, що є перед ним. Звичайним сценарієм використання цього підходу є обмін поодинокими запитами та відповідями між клієнтом та сервером.

Паралельні сервери підходять у випадках, коли на обробку кожного запиту витрачається значна кількість часу або клієнт і сервер виконують тривалий обмін повідомленнями. У цьому розділі ми зосередимося традиційному (і найпростішому) способі проектування паралельних серверів, який полягає у створенні окремого дочірнього процесу для кожного нового клієнта. Такий процес виконує всю роботу з обслуговування клієнта, після чого завершується. Оскільки кожен із цих процесів функціонує незалежно, можна обслуговувати кілька клієнтів одночасно. Основне завдання головного серверного процесу (батька) полягає у створенні окремого нащадка для кожного нового клієнта (як варіант замість процесів можна створювати потоки виконання).

У наступних розділах ми розглянемо приклади ітераційного та паралельного серверів на основі сокетів інтернет-домену. Ці два сервери реалізують спрощений варіант служби echo (RFC 862), яка повертає копію будь-якого повідомлення, надісланого їй клієнтом.

Ітераційний UDP-сервер echo

У цьому розділі ми представимо сервери для служби echo. Вона доступна на порті з номером 7 і працює як UDP, так і TCP (даний порт зарезервований, у зв'язку з чим сервер echo необхідно запускати з привілеями адміністратора).

UDP-сервер echo постійно зчитує датаграми та повертає відправнику їх копії. Оскільки серверу потрібно обробляти лише одне повідомлення за раз, тут буде достатньо архітектури ітерації. Заголовний файл для серверів показаний у лістингу 56.1.

Лістинг 56.1. Заголовковий файл для програм id_echo_sv.c та id_echo_cl.c

#include "inet_sockets.h" /* Оголошує функції нашого сокету */
#include "tlpi_hdr.h"

#define SERVICE "echo" /* Ім'я UDP-служби */

#define BUF_SIZE 500 /* Максимальний розмір датаграм, які
можуть бути прочитані клієнтом та сервером */
____________________________________________________________________sockets/id_echo.h

У лістингу 56.2 представлено реалізацію сервера. Варто зазначити такі моменти:

  • для переведення сервера в режим демону ми використовуємо функцію becomeDaemon() з розділу 37.2;

  • щоб зробити програму компактнішою, ми використовуємо бібліотеку для роботи з сокетами інтернет-домену, розроблену в розділі 55.12;

  • якщо сервер не може повернути відповідь клієнту, записує повідомлення в журнал, застосовуючи виклик syslog().

У реальному додатку ми, швидше за все, ввели певне обмеження на частоту запису повідомлень за допомогою syslog(). Це унеможливило б переповнення системного журналу зловмисником. До того ж не варто забувати, що кожен виклик syslog() досить затратний, оскільки за умовчанням використовує fsync().

Лістинг 56.2. Ітераційний сервер, який реалізує UDP-службу echo

_________________________________________________________________sockets/id_echo_sv.c
#include
#include "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 (becomeDaemon(0) == -1)
errExit("becomeDaemon");

sfd = inetBind(SERVICE, SOCK_DGRAM, NULL);
if (sfd == -1) {
syslog(LOG_ERR, "Не можна створити сервер ліхтаря (%s)",
strerror(errno));
exit(EXIT_FAILURE);

/* Отримуємо датаграми та повертаємо відправникам їх копії */
}
для (;;) {
len = sizeof(struct sockaddr_storage);
numRead = recvfrom(sfd, buf, BUF_SIZE, 0, (struct sockaddr *) &claddr, &len);

if (numRead == -1)
errExit("recvfrom");
if (sendto(sfd, buf, numRead, 0, (struct sockaddr *) &claddr, len)
! = numRead)
syslog(LOG_WARNING, "Error echoing response to %s (%s)",
inetAddressStr((struct sockaddr *) &claddr, len,
addrStr, IS_ADDR_STR_LEN),
strerror(errno));
}
}
_________________________________________________________________sockets/id_echo_sv.c

Для перевірки роботи сервера ми використовуємо програму з лістингу 56.3. У ній також застосовується бібліотека для роботи із сокетами інтернет-домену, розроблена у розділі 55.12. Як перший аргумент командного рядка клієнтська програма приймає ім'я мережного вузла, де знаходиться сервер. Клієнт входить у цикл, де відправляє серверу кожен із аргументів у вигляді окремих датаграм, а потім зчитує і виводить датаграми, отримані від сервера у відповідь.

Лістинг 56.3. Клієнт для UDP-служби echo

#include "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], "-help") == 0)
usageErr("%s host msg ... n", argv [0]);

/* Формуємо адресу сервера на основі першого аргументу командного рядка */
sfd = inetConnect(argv[1], SERVICE, SOCK_DGRAM);
if (sfd == -1)
fatal("Could not connect to server socket");

/* Посилаємо серверу інші аргументи як окремих датаграмм */
for (j = 2; j < argc; j++) {
len = strlen (argv [j]);
if (write(sfd, argv[j], len) != len)
fatal("partial/failed write");

numRead = read(sfd, buf, BUF_SIZE);
if (numRead == -1)
errExit("read");
printf("[%ld bytes] %.*sn", (long) numRead, (int) numRead, buf);
}
вихід (EXIT_SUCCESS);
}
_________________________________________________________________sockets/id_echo_cl.c

Нижче наведено приклад того, що ми побачимо при запуску сервера та двох екземплярів клієнта:

$ su // Для прив'язки до зарезервованого порту потрібні привілеї
пароль:
# ./id_echo_sv // Сервер перетворюється на фоновий режим
# exit // Відмовляємося від прав адміністратора
$ ./id_echo_cl localhost hello world // Цей клієнт надсилає дві датаграми
[5 bytes] hello // Клієнт виводить відповідь, отриману від сервера
[5 bytes] world
$ ./id_echo_cl localhost goodbye // Цей клієнт надсилає одну датаграму
[7 bytes] goodbye

Бажаю приємного читання)

Джерело: linux.org.ru