Опубликован: 15.06.2004 | Доступ: свободный | Студентов: 2286 / 579 | Оценка: 4.35 / 3.96 | Длительность: 27:47:00
ISBN: 978-5-9556-0011-6
Лекция 11:

Сетевые средства

Сделаем теперь более существенные изменения, прежде всего на серверной стороне. Перейдем на использование надежного режима с установлением соединения (и, соответственно, сокетов типа 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. Пример программы, принимающей через сокеты целочисленные данные.
Антон Коновалов
Антон Коновалов

В настоящее время актуальный стандарт - это POSIX 2008 и его дополнение POSIX 1003.13
Планируется ли актуализация материалов данного очень полезного курса?

Владимир Крюков
Владимир Крюков
Казахстан