/* Формируем адрес сервера на основе первого аргумента командной строки */
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] %.*s\n", (long) numRead, (int) numRead, buf);
}
exit(EXIT_SUCCESS);
}
sockets/id_echo_cl.c
Ниже показан пример того, что мы увидим при запуске сервера и двух экземпляров клиента:
$ su
Password:
# ./id_echo_sv
# exit
$ ./id_echo_cl localhost hello world
[5 bytes] hello
[5 bytes] world
$ ./id_echo_cl localhost goodbye
[7 bytes] goodbye
TCP-служба echo тоже работает на порте под номером 7. Сервер принимает соединение и входит в бесконечный цикл, считывая все переданные ему данные и отправляя их обратно клиенту с помощью того же сокета. Сервер продолжает выполнять чтение, пока не обнаружит конец файла; после этого он закрывает свой сокет (чтобы клиент тоже получил символ конца файла, если все еще продолжает читать данные из своего сокета).
Клиент может отправить серверу данные неограниченного объема (следовательно, обслуживание клиента может занять неопределенное время), так что в подобном случае подходит архитектура параллельного сервера, которая позволяет работать с несколькими клиентами одновременно. Реализация серверного приложения показана в листинге 56.4 (реализация клиента для этой службы будет представлена в разделе 57.2). Стоит отметить следующие моменты.
• Чтобы стать демоном, сервер использует функцию becomeDaemon() из раздела 37.2.
• С целью сделать программу более компактной мы применяем библиотеку для работы с сокетами интернет-домена, разработанную в листинге 55.9.
• Поскольку сервер создает потомков для каждого клиентского соединения, мы должны следить за уничтожением зомби-процессов. Для этого используется обработчик SIGCHLD.
• В основе кода сервера лежит цикл for, который принимает клиентские соединения и выполняет вызов fork() в целях создать новый дочерний процесс, обслуживающий клиента с помощью функции handleRequest(). Тем временем родитель продолжает работу цикла for, чтобы принять следующее клиентское соединение.
В реальном приложении мы бы, вероятно, предусмотрели ограничение максимального количества дочерних процессов, которые может создать наш сервер; это помогло бы защититься от удаленной атаки, заключающейся в слишком интенсивном использовании службы и создании количества потомков, приводящего к зависанию системы. Данное ограничение можно ввести путем подсчета имеющихся дочерних процессов (счетчик инкрементируется после каждого успешного вызова fork() и декрементируется при уничтожении потомка с помощью обработчика SIGCHLD). В случае достижения ограничения мы могли бы временно приостановить прием соединений (или, как вариант, принимать соединения и тут же их закрывать).
• После каждого вызова fork() в дочернем процессе дублируется файловый дескриптор для слушающего и подключающегося сокетов (см. подраздел 24.2.1). То есть взаимодействовать с клиентским сокетом может как родитель, так и потомок. Однако заниматься этим нужно только потомку, так что сразу же после выполнения fork() родитель закрывает свой дескриптор подключенного сокета. (Если не совершить данное действие, то сокет так и будет оставаться открытым; кроме того, рано или поздно родитель исчерпает допустимое количество открытых файловых дескрипторов.) Так как потомок не принимает новые соединения, он закрывает свою копию файлового дескриптора для слушающего сокета.
• Обслужив клиента, дочерний процесс завершает работу.
Листинг 56.4. Параллельный сервер, реализующий TCP-службу echo
sockets/is_echo_sv.c
#include
#include
#include
#include "become_daemon.h"
#include "inet_sockets.h" /* Объявление функций сокета вида inet*() */
#include "tlpi_hdr.h"
#define SERVICE "echo" /* Имя TCP-службы */
#define BUF_SIZE 4096
static void /* Обработчик SIGCHLD, уничтожающий дочерние процессы */
grimReaper(int sig)
{
int savedErrno; /* Сохраняем значение 'errno' на случай,
если оно здесь изменится */
savedErrno = errno;
while (waitpid(-1, NULL, WNOHANG) > 0)
continue;
errno = savedErrno;
}