В настоящее время актуальный стандарт - это POSIX 2008 и его дополнение POSIX 1003.13 |
Сетевые средства
Сделаем теперь более существенные изменения, прежде всего на серверной стороне. Перейдем на использование надежного режима с установлением соединения (и, соответственно, сокетов типа SOCK_STREAM ). Сервер будет получать запросы на установление соединения через порт сервиса "spooler" и порождать для каждого принятого запроса свой обслуживающий процесс.
Новый вариант программы процесса, читающего строки со стандартного ввода и отправляющего их через потоковый сокет, показан в листинге 11.35; две программы серверной части - в листингах 11.36 и 11.37. Предполагается, что выполнимый файл с образом обслуживающего процесса находится в текущем каталоге и имеет имя gsce.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа процесса, читающего строки со стандартного ввода */ /* и посылающего их в виде потока другому процессу */ /* (будем называть его сервером), */ /* который должно выдать строки на свой стандартный вывод. */ /* Имя серверного хоста - аргумент командной строки */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include <stdio.h> #include <netdb.h> #include <sys/socket.h> #define MY_PROMPT "Вводите строки\n" int main (int argc, char *argv []) { int sd; /* Дескриптор передающего сокета */ FILE *ss; /* Поток, соответствующий передающему сокету */ char line [LINE_MAX]; /* Буфер для копируемых строк */ /* Структура - входной аргумент getaddrinfo */ struct addrinfo hints = {0, AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; /* Указатель - выходной аргумент getaddrinfo */ struct addrinfo *addr_res; int res; /* Результат getaddrinfo */ if (argc != 2) { fprintf (stderr, "Использование: %s имя_серверного_хоста\n", argv [0]); return (1); } /* Создадим сокет, через который будем отправлять */ /* прочитанные строки */ if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { perror ("SOCKET"); return (2); } /* Выясним целевой адрес для соединения */ /* Воспользуемся портом для сервиса spooler */ if ((res = getaddrinfo (argv [1], "spooler", &hints, &addr_res)) != 0) { fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); return (3); } /* Воспользуемся функцией connect() для достижения двух целей: */ /* установления соединения и привязки к некоему локальному адресу */ if (connect (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) { perror ("CONNECT"); return (4); } /* Сформируем поток по дескриптору сокета */ if ((ss = fdopen (sd, "w")) == NULL) { perror ("FDOPEN"); return (5); } /* Отменим буферизацию для этого потока */ setbuf (ss, NULL); /* Цикл чтения строк со стандартного ввода */ /* и отправки их через сокет в виде потока */ fputs (MY_PROMPT, stdout); while (fgets (line, sizeof (line), stdin) != NULL) { fputs (line, ss); } shutdown (sd, SHUT_WR); return (0); }Листинг 11.35. Пример программы, использующей режим с установлением соединения и сокеты типа SOCK_STREAM для пересылки строк.
/* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа процесса (будем называть его демоном), */ /* принимающего запросы на установления соединения */ /* и запускающего процессы для их обслуживания */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ #include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <netdb.h> #include <sys/socket.h> #include <sys/wait.h> int main (void) { int sd; /* Дескриптор слушающего сокета */ int ad; /* Дескриптор приемного сокета */ /* Буфер для принимаемых строк */ struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; /* Указатель - выходной аргумент getaddrinfo */ struct addrinfo *addr_res; int res; /* Результат getaddrinfo */ /* Создадим слушающий сокет */ if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { perror ("SOCKET"); return (1); } /* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */ if ((res = getaddrinfo (NULL, "spooler", &hints, &addr_res)) != 0) { fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); return (1); } if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) { perror ("BIND"); return (2); } /* Можно освободить память, которую запрашивала функция getaddrinfo() */ freeaddrinfo (addr_res); /* Пометим сокет как слушающий */ if (listen (sd, SOMAXCONN) < 0) { perror ("LISTEN"); return (3); } /* Цикл приема соединений и запуска обслуживающих процессов */ while (1) { /* Примем соединение. */ /* Адрес партнера по общению нас в данном случае не интересует */ if ((ad = accept (sd, NULL, NULL)) < 0) { perror ("ACCEPT"); return (4); } /* Запустим обслуживающий процесс */ switch (fork ()) { case -1: perror ("FORK"); return (5); case 0: /* Сделаем сокет ad стандартным вводом */ (void) close (STDIN_FILENO); (void) fcntl (ad, F_DUPFD, STDIN_FILENO); (void) close (ad); /* Сменим программу процесса на обслуживающую */ if (execl ("./gsce", "gsce", (char *) NULL) < 0) { perror ("EXECL"); return (6); } } /* В родительском процессе дескриптор принятого соединения нужно закрыть */ (void) close (ad); /* Обслужим завершившиеся порожденные процессы */ while (waitpid ((pid_t) (-1), NULL, WNOHANG) > 0) ; } return (0); }Листинг 11.36. Пример программы, принимающей запросы на установление соединения.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа обслуживающего процесса, */ /* выдающего принимаемые строки на стандартный вывод. */ /* Дескриптор приемного сокета передан */ /* в качестве стандартного ввода */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include <unistd.h> #include <stdio.h> #include <sys/socket.h> #include <arpa/inet.h> int main (void) { char line [LINE_MAX]; /* Буфер для принимаемых строк */ /* Структура для записи адреса */ struct sockaddr_in sai; /* Длина адреса */ socklen_t sai_len = sizeof (struct sockaddr_in); /* Опросим адрес партнера по общению (передающего сокета) */ if (getpeername (STDIN_FILENO, (struct sockaddr *) &sai, &sai_len) < 0) { perror ("GETPEERNAME"); return (1); } /* Цикл чтения строк из сокета */ /* и выдачи их на стандартный вывод */ while (fgets (line, sizeof (line), stdin) != NULL) { printf ("Вы ввели и отправили с адреса %s, порт %d :", inet_ntoa (sai.sin_addr), ntohs (sai.sin_port)); fputs (line, stdout); } /* Закрытие соединения */ shutdown (STDIN_FILENO, SHUT_RD); return (0); }Листинг 11.37. Пример программы, обрабатывающей данные, поступающие через сокет типа SOCK_STREAM.
Обратим внимание на два технических момента. Во-первых, дескриптор приемного сокета передается в обслуживающий процесс под видом стандартного ввода. В результате обслуживающая программа по сути свелась к циклу копирования строк со стандартного ввода на стандартный вывод. Мы вернулись к тому, с чего начинали рассмотрение средств буферизованного ввода/вывода, но на новом витке спирали. Во-вторых, в иллюстративных целях для обслуживания завершившихся порожденных процессов использована функция waitpid() со значением аргумента pid, равным -1 (выражающим готовность обслужить любой завершившийся процесс-потомок), и флагом WNOHANG, означающим отсутствие ожидания. В данном случае подобный прием не гарантирует отсутствия зомби-процессов, но препятствует их накоплению.
Чтобы проиллюстрировать еще один типичный прием, связанный с обработкой запросов на установление соединения, изменим способ обслуживания завершившихся порожденных процессов. Определим функцию обработки сигнала SIGCHLD и поместим в нее вызов waitpid(). Модифицированный вариант программы процесса-демона, аккуратно ликвидирующий все зомби-процессы, показан в листинге 11.38.
/* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа процесса (будем называть его демоном), */ /* принимающего запросы на установления соединения */ /* и запускающего процессы для их обслуживания */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ #include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <netdb.h> #include <sys/socket.h> #include <sys/wait.h> #include <signal.h> #include <errno.h> /* Функция обработки сигнала SIGCHLD */ static void chldied (int dummy) { /* Вдруг число завершившихся потомков отлично от единицы? */ while (waitpid ((pid_t) (-1), NULL, WNOHANG) > 0) ; } int main (void) { int sd; /* Дескриптор слушающего сокета */ int ad; /* Дескриптор приемного сокета */ /* Структура - входной аргумент getaddrinfo */ struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; /* Указатель - выходной аргумент getaddrinfo */ struct addrinfo *addr_res; int res; /* Результат getaddrinfo */ /* Структура для задания реакции на сигнал SIGCHLD */ struct sigaction sact; /* Создадим слушающий сокет */ if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { perror ("SOCKET"); return (1); } /* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */ if ((res = getaddrinfo (NULL, "spooler", &hints, &addr_res)) != 0) { fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); return (1); } if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) { perror ("BIND"); return (2); } /* Можно освободить память, которую запрашивала функция getaddrinfo() */ freeaddrinfo (addr_res); /* Пометим сокет как слушающий */ if (listen (sd, SOMAXCONN) < 0) { perror ("LISTEN"); return (3); } /* Установим обработку сигнала о завершении потомка */ sact.sa_handler = chldied; (void) sigemptyset (&sact.sa_mask); sact.sa_flags = 0; (void) sigaction (SIGCHLD, &sact, (struct sigaction *) NULL); /* Цикл приема соединений и запуска обслуживающих процессов */ while (1) { /* Примем соединение с учетом того, что ожидание может быть прервано */ /* доставкой обрабатываемого сигнала. */ /* Адрес партнера по общению в данном случае нас не интересует */ while ((ad = accept (sd, NULL, NULL)) < 0) { if (errno != EINTR) { perror ("ACCEPT"); return (4); } } /* Запустим обслуживающий процесс */ switch (fork ()) { case -1: perror ("FORK"); return (5); case 0: /* Сделаем сокет ad стандартным вводом */ (void) close (STDIN_FILENO); (void) fcntl (ad, F_DUPFD, STDIN_FILENO); (void) close (ad); /* Сменим программу процесса на обслуживающую */ if (execl ("./gsce", "gsce", (char *) NULL) < 0) { perror ("EXECL"); return (6); } } /* В родительском процессе дескриптор принятого соединения нужно закрыть */ (void) close (ad); } return (0); }Листинг 11.38. Пример программы, принимающей запросы на установление соединения с учетом того, что ожидание может быть прервано доставкой обрабатываемого сигнала.
Задание функции обработки сигнала о завершении потомка имеет нелокальный эффект. Оно повлияло на поведение функции accept(): ее вызов может быть прерван доставкой сигнала SIGCHLD. В такой ситуации прежний вариант процесса-демона завершился бы, выдав в стандартный протокол диагностическое сообщение вида "ACCEPT: Interrupted system call". Чтобы этого не случилось, вызов accept() пришлось заключить в цикл с проверкой (в случае неудачного завершения) значения переменной errno на совпадение с EINTR. Вообще говоря, по подобной схеме рекомендуется действовать для всех функций, выполнение которых может быть прервано доставкой сигнала, но допускающих и повторный вызов. Отметим в этой связи, что, например, функция connect() очень похожа, но все-таки отличается своими особенностями. После доставки сигнала ее вызов завершается неудачей, а установление соединения продолжается асинхронно, поэтому повторное обращение может завершиться неудачей со значением errno, равным EALREADY.
Рассмотрим теперь еще один вариант сервера, имеющего однопроцессную организацию, но мультиплексирующего ввод с помощью функции select() (см. листинг 11.39).
/* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа процесса (будем называть его сервером), */ /* принимающего запросы на установления соединения */ /* и обслуживающего их с использованием select() */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ #include <unistd.h> #include <stdio.h> #include <netdb.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/select.h> /* Структура для хранения дескрипторов приемных сокетов */ /* и ассоциированной информации */ #define MY_MSG_LN 128 struct sads { int ad; FILE *sd; char my_msg [MY_MSG_LN]; }; /* Максимальное число параллельно обслуживаемых запросов */ #define MY_MAX_QS FD_SETSIZE /* Функция для освобождения элемента массива */ /* дескрипторов приемных сокетов */ static void free_elem (struct sads *sadsp) { shutdown (sadsp->ad, SHUT_RD); close (sadsp->ad); sadsp->ad = -1; } int main (void) { int sd; /* Дескриптор слушающего сокета */ /* Структура - входной аргумент getaddrinfo */ struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; /* Указатель - выходной аргумент getaddrinfo */ struct addrinfo *addr_res; int res; /* Результат getaddrinfo */ fd_set rdset; /* Набор дескрипторов для чтения */ /* Структура для записи адреса */ struct sockaddr_in sai; socklen_t sai_len; /* Длина адреса */ char line [LINE_MAX]; /* Буфер для принимаемых строк */ /* Массов для хранения дескрипторов приемных сокетов */ /* и ассоциированной информации. */ /* Последний элемент - фиктивный, */ /* нужен для упрощения поиска свободного */ struct sads sadsarr [MY_MAX_QS + 1]; int sads_max; /* Верхняя граница дескрипторов, проверяемых select() */ int i; /* Создадим слушающий сокет */ if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { perror ("SOCKET"); return (1); } /* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */ if ((res = getaddrinfo (NULL, "spooler", &hints, &addr_res)) != 0) { fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); return (1); } if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) { perror ("BIND"); return (2); } /* Можно освободить память, которую запрашивала функция getaddrinfo() */ freeaddrinfo (addr_res); /* Пометим сокет как слушающий */ if (listen (sd, SOMAXCONN) < 0) { perror ("LISTEN"); return (3); } /* Инициализируем массив sadsarr. */ /* -1 в поле ad означает, что элемент свободен */ for (i = 0; i < (MY_MAX_QS + 1); i++) { sadsarr [i].ad = -1; } /* Цикл приема соединений и обслуживания запросов */ while (1) { /* Подготовим наборы дескрипторов для select() */ FD_ZERO (&rdset); FD_SET (sd, &rdset); sads_max = sd + 1; for (i = 0; i < MY_MAX_QS; i++) { if (sadsarr [i].ad >= 0) { FD_SET (sadsarr [i].ad, &rdset); if ((sadsarr [i].ad + 1) > sads_max) { sads_max = sadsarr [i].ad + 1; } } } /* Подождем запроса на установление соединения */ /* или готовности данных для чтения. */ /* Время ожидания зададим как бесконечное */ if (select (sads_max, &rdset, NULL, NULL, NULL) == -1) { perror ("PSELECT"); return (4); } /* Посмотрим, есть ли запросы, ждущие установления соединения */ if (FD_ISSET (sd, &rdset)) { /* Примем запрос на установление соединения, */ /* если есть свободный элемент массива дескрипторов. */ /* Последний элемент считаем фиктивным; он всегда свободен */ i = -1; while (sadsarr [++i].ad >= 0) ; if (i < MY_MAX_QS) { /* Свободный элемент нашелся */ sai_len = sizeof (struct sockaddr_in); if ((sadsarr [i].ad = accept (sd, (struct sockaddr *) &sai, &sai_len)) == -1) { perror ("ACCEPT"); continue; } /* Сформируем сообщение, выдаваемое перед принятой строкой */ (void) sprintf (sadsarr [i].my_msg, "Вы ввели и отправили с адреса %s, порт %d :", inet_ntoa (sai.sin_addr), ntohs (sai.sin_port)); /* Сформируем поток по дескриптору сокета */ /* и отменим буферизацию для этого потока */ if ((sadsarr [i].sd = fdopen (sadsarr [i].ad, "r")) == NULL) { perror ("FDOPEN"); free_elem (&sadsarr [i]); continue; } setbuf (sadsarr [i].sd, NULL); } } /* Посмотрим, есть ли данные, готовые для чтения */ for (i = 0; i < MY_MAX_QS; i++) { if ((sadsarr [i].ad >= 0) & FD_ISSET (sadsarr [i].ad, &rdset)) { /* Есть данные, готовые для чтения, */ /* или установлен признак конца файла */ if (fgets (line, sizeof (line), sadsarr [i].sd) != NULL) { /* Выведем полученную строку */ fputs (sadsarr [i].my_msg, stdout); fputs (line, stdout); } else { /* Отправитель закрыл соединение. */ /* Закроем его и мы */ fclose (sadsarr [i].sd); free_elem (&sadsarr [i]); } } } } return (0); }Листинг 11.39. Пример программы, мультиплексирующей ввод через сокеты с помощью функции select().
Обратим внимание на то, что функция select() позволяет мультиплексировать как поступление данных, так и прием запросов на установление соединения, а также на то, что конец поступления данных в результате закрытия соединения передающей стороной выявляется естественным образом: select() сообщает о наличии данных для чтения, а сама операция чтения возвращает признак конца файла.
В качестве следующего примера рассмотрим передачу целочисленных данных. Главная проблема здесь - аккуратное выполнение преобразований между хостовым и сетевым порядками байт. Для иллюстрации использовано вычисление и вывод строк треугольника Паскаля. Вычисляющая и передающая программа показана в листинге 11.40, принимающая и выводящая - в листинге 11.41.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа вычисляет несколько первых строк треугольника Паскаля */ /* и отправляет их через сокет сервису spooler. */ /* Имя целевого хоста - аргумент командной строки. */ /* Передаваемые данные снабжаются однобайтными маркерами типа */ /* и кратности и преобразуются к сетевому порядку байт */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include <unistd.h> #include <stdio.h> #include <netdb.h> #include <sys/socket.h> /* Количество вычисляемых строк треугольника Паскаля */ #define T_SIZE 16 /* Маркеры типов передаваемых данных */ #define T_UCHAR 1 #define T_UINT16 2 #define T_UINT32 4 #define T_HDR "\nТреугольник Паскаля:" int main (int argc, char *argv []) { uint32_t tp [T_SIZE]; /* Массив для хранения текущей строки треугольника */ unsigned char mtl; /* Переменная для хранения маркеров типа и кратности */ uint32_t ntelem; /* Текущий элемент строки в сетевом порядке байт */ int sd; /* Дескриптор передающего сокета */ /* Структура - входной аргумент getaddrinfo */ struct addrinfo hints = {0, AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; /* Указатель - выходной аргумент getaddrinfo */ struct addrinfo *addr_res; int res; /* Результат getaddrinfo */ int i, j; if (argc != 2) { fprintf (stderr, "Использование: %s имя_серверного_хоста\n", argv [0]); return (1); } /* Создадим передающий сокет и установим соединение */ if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { perror ("SOCKET"); return (2); } if ((res = getaddrinfo (argv [1], "spooler", &hints, &addr_res)) != 0) { fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); return (3); } if (connect (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) { perror ("CONNECT"); return (4); } /* Инициализируем массив для хранения текущей строки треугольника Паскаля, */ /* чтобы далее все элементы можно было считать и передавать единообразно */ tp [0] = 1; for (i = 1; i < T_SIZE; i++) { tp [i] = 0; } /* Передадим заголовок */ mtl = T_UCHAR; if (write (sd, &mtl, 1) != 1) { perror ("WRITE-1"); return (5); } mtl = sizeof (T_HDR) - 1; if (write (sd, &mtl, 1) != 1) { perror ("WRITE-2"); return (6); } if (write (sd, T_HDR, mtl) != mtl) { perror ("WRITE-3"); return (7); } /* Вычислим и передадим строки треугольника Паскаля */ for (i = 0; i < T_SIZE; i++) { /* Элементы очередной строки нужно считать от конца к началу */ /* Элемент tp [0] пересчитывать не нужно */ for (j = i; j > 0; j--) { tp [j] += tp [j - 1]; } /* Вывод строки треугольника в сокет */ mtl = T_UINT32; if (write (sd, &mtl, 1) != 1) { perror ("WRITE-4"); return (8); } mtl = i + 1; if (write (sd, &mtl, 1) != 1) { perror ("WRITE-5"); return (9); } /* Преобразование элементов строки к сетевому порядку байт и вывод */ for (j = 0; j <= i; j++) { ntelem = htonl (tp [j]); if (write (sd, &ntelem, sizeof (ntelem)) != sizeof (ntelem)) { perror ("WRITE-6"); return (10); } } } shutdown (sd, SHUT_WR); return (close (sd)); }Листинг 11.40. Пример программы, передающей через сокеты целочисленные данные.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа процесса (будем называть его наивным сервером), */ /* принимающего запросы на установления соединения */ /* и осуществляющего вывод поступающих числовых данных */ /* без порождения процессов и мультиплексирования */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include <unistd.h> #include <stdio.h> #include <netdb.h> #include <sys/socket.h> #include <arpa/inet.h> /* Маркеры типов передаваемых данных */ #define T_UCHAR 1 #define T_UINT16 2 #define T_UINT32 4 int main (void) { int sd; /* Дескриптор слушающего сокета */ int ad; /* Дескриптор приемного сокета */ /* Структура - входной аргумент getaddrinfo */ struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; /* Указатель - выходной аргумент getaddrinfo */ struct addrinfo *addr_res; int res; /* Результат getaddrinfo */ /* Структура для записи адреса */ struct sockaddr_in sai; socklen_t sai_len; /* Длина адреса */ char mt; /* Маркер типа поступающих данных */ char ml; /* Маркер кратности поступающих данных */ char bufc; /* Буфер для приема символьных данных */ uint16_t buf16; /* Буфер для приема 16-разрядных целых */ uint32_t buf32; /* Буфер для приема 16-разрядных целых */ /* Создадим слушающий сокет */ if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { perror ("SOCKET"); return (1); } /* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */ if ((res = getaddrinfo (NULL, "spooler", &hints, &addr_res)) != 0) { fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); return (1); } if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) { perror ("BIND"); return (2); } /* Можно освободить память, которую запрашивала функция getaddrinfo() */ freeaddrinfo (addr_res); /* Пометим сокет как слушающий */ if (listen (sd, SOMAXCONN) < 0) { perror ("LISTEN"); return (3); } /* Цикл приема соединений и обслуживания запросов на вывод */ while (1) { /* Примем соединение */ sai_len = sizeof (struct sockaddr_in); if ((ad = accept (sd, (struct sockaddr *) &sai, &sai_len)) < 0) { perror ("ACCEPT"); continue; } /* Цикл приема поступающих данных и их вывода */ printf ("Получено с адреса %s, порт %d :", inet_ntoa (sai.sin_addr), ntohs (sai.sin_port)); while (read (ad, &mt, 1) == 1) { /* Есть очередная порция данных, начинающаяся с типа. */ /* Прочитаем кратность, потом сами данные */ if (read (ad, &ml, 1) != 1) { perror ("READ-1"); return (4); } while (ml-- > 0) { switch (mt) { case T_UCHAR: if (read (ad, &bufc, sizeof (bufc)) != sizeof (bufc)) { perror ("READ-2"); return (5); } printf ("%c", bufc); continue; case T_UINT16: if (read (ad, &buf16, sizeof (buf16)) != sizeof (buf16)) { perror ("READ-3"); return (6); } printf (" %d", ntohs (buf16)); continue; case T_UINT32: if (read (ad, &buf32, sizeof (buf32)) != sizeof (buf32)) { perror ("READ-4"); return (7); } printf (" %d", ntohl (buf32)); continue; } } /* Вывод порции завершим переводом строки */ printf ("\n"); } /* Конец обслуживания соединения */ (void) close (ad); } return (0); }Листинг 11.41. Пример программы, принимающей через сокеты целочисленные данные.