Сетевые средства: основные понятия и объекты, ассоциированные с сетевыми средствами

Сеть как совокупность взаимосвязанных хостов. Аналог SOCK_DGRAM с дополнительной возможностью доступа к протокольным заголовкам и другой информации нижнего уровня. Функции для опроса данных о сети и для работы с сокетами. Мультиплексирующая программа.

Рубрика Программирование, компьютеры и кибернетика
Вид лекция
Язык русский
Дата добавления 27.11.2013
Размер файла 58,6 K

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

/* Структура - аргумент sendmsg */

struct msghdr msg = {NULL, 0, NULL, 0, NULL, 0, 0};

struct iovec iovbuf; /* Структура для сборки отправляемых данных */

/* Структура - входной аргумент getaddrinfo */

struct addrinfo hints = {0, AF_INET, SOCK_DGRAM, IPPROTO_UDP,

0, NULL, NULL, NULL};

/* Указатель - выходной аргумент getaddrinfo */

struct addrinfo *addr_res;

int res; /* Результат getaddrinfo */

int msg_len; /* Длина очередной введенной строки, */

/* включая завершающий нулевой байт */

if (argc != 2) {

fprintf (stderr, "Использование: %s имя_серверного_хоста\n", argv [0]);

return (1);

}

/* Создадим сокет, через который будем отправлять */

/* прочитанные строки */

if ((sd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 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);

/* Заполним структуру msghdr */

msg.msg_name = addr_res->ai_addr;

msg.msg_namelen = addr_res->ai_addrlen;

msg.msg_iov = &iovbuf;

msg.msg_iovlen = 1;

iovbuf.iov_base = line;

/* Цикл чтения строк со стандартного ввода */

/* и отправки их через сокет в виде датаграмм */

fputs (MY_PROMPT, stdout);

while (fgets (line, sizeof (line), stdin) != NULL) {

msg_len = strlen (line) + 1;

iovbuf.iov_len = msg_len;

if (sendmsg (sd, &msg, 0) != msg_len) {

perror ("SENDMSG");

break;

}

return (0);

}

Листинг 11.31. Пример программы, использующей сокеты адресного семейства AF_INET для отправки датаграмм.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* Программа процесса (будем называть его сервером), */

/* читающего сообщения (строки) из датаграммного сокета */

/* и выдающего их на стандартный вывод */

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <stdio.h>

#include <netdb.h>

#include <sys/socket.h>

#define MY_MSG "Вы ввели: "

int main (void) {

int sd; /* Дескриптор приемного сокета */

char line [LINE_MAX]; /* Буфер для копируемых строк */

/* Структура - аргумент recvmsg */

struct msghdr msg = {NULL, 0, NULL, 0, NULL, 0, 0};

struct iovec iovbuf; /* Структура для разнесения принимаемых данных */

/* Структура - входной аргумент getaddrinfo */

struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_DGRAM, IPPROTO_UDP,

0, NULL, NULL, NULL};

/* Указатель - выходной аргумент getaddrinfo */

struct addrinfo *addr_res;

int res; /* Результат getaddrinfo */

/* Создадим сокет, через который будем принимать */

/* прочитанные строки */

if ((sd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {

perror ("SOCKET");

return (1);

}

/* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */

if ((res = getaddrinfo (NULL, "spooler", &hints, &addr_res)) != 0) {

fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));

return (2);

}

if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) {

perror ("BIND");

return (3);

}

/* Можно освободить память, которую запрашивала функция getaddrinfo() */

freeaddrinfo (addr_res);

/* Заполним структуру msghdr */

msg.msg_iov = &iovbuf;

msg.msg_iovlen = 1;

iovbuf.iov_base = line;

iovbuf.iov_len = sizeof (line);

/* Цикл приема и выдачи строк */

while (1) {

if (recvmsg (sd, &msg, 0) < 0) {

perror ("RECVMSG");

break;

}

fputs (MY_MSG, stdout);

fputs (line, stdout);

}

return (0);

}

Листинг 11.32. Пример программы, использующей сокеты адресного семейства AF_INET для приема датаграмм.

Обратим внимание на несколько моментов. Адреса сокетов (целевого и приемного) получены при помощи функции getaddrinfo() с сервисом "spooler" в качестве аргумента. (Если на хосте этот сервис реально используется, для данного примера придется подыскать другой, свободный порт.) С практической точки зрения более правильным было бы пополнить базу данных сетевых сервисов новым элементом, специально предназначенным для представленного приложения, однако подобные административные действия находятся вне рамок стандарта POSIX и, следовательно, нами не рассматриваются. (Менее правильно, на наш взгляд, формировать адрес сокета покомпонентно, выбирая порт, по сути, случайным образом, поскольку это ведет к неявному, бессистемному, неконтролируемому пополнению базы сервисов.)

Для успешного вызова функции bind() процесс должен обладать соответствующими привилегиями. Это может оказаться существенным для запуска программы, показанной в пример 11.32.

Чтобы получить от getaddrinfo() адрес, пригодный для использования в качестве аргумента ориентированных на прием функций (listen(), bind() для приемного сокета), необходимо указать флаг AI_PASSIVE во входной для getaddrinfo() структуре addrinfo (аргумент hints в описании функции getaddrinfo()). В таком случае для IP-адреса будет выдано значение INADDR_ANY, которое трактуется в адресном семействе AF_INET как адрес локального хоста, успешно сопоставляющийся как со шлейфовым (127.0.0.1), так и с реальным сетевыми адресами. В результате через этот сокет можно будет принимать датаграммы, посланные и с локального, и с удаленного хостов.

Цикл приема данных сервером сделан бесконечным, поскольку у последовательности датаграмм нет естественного признака конца.

Для передающего сокета не выполнялась привязка к локальному адресу, а в серверном процессе не запрашивался исходный адрес поступающих датаграмм - подход, возможно, слишком экономный, но непротиворечивый. Заметим, однако, что у представленного приложения есть неиспользованный идейный запас, состоящий в том, что сервер способен принимать датаграммы не только из одного, но и из нескольких источников. Чтобы задействовать этот потенциал, внесем в приложения модификации двух видов.

В программе, посылающей датаграммы, выполним привязку сокета к свободному локальному адресу, воспользовавшись для этого функцией connect() и, для оправдания затраченных усилий, изменим способ отправки: вместо универсальной sendmsg() задействуем даже не более простую функцию send(), а ее вполне привычный аналог write(), скрытый под личиной fputs().

Какой режим буферизации выбрать для потока, сформированного по открытому файловому дескриптору сокета, - дело вкуса. Читателю рекомендуется попробовать разные варианты, каждый из которых имеет свои "за" и "против". Второй вид модификаций касается серверного процесса. Поскольку теперь у датаграмм появился исходный адрес, его можно выяснить и выдать, а заодно опросить выходной флаг MSG_TRUNC, чтобы убедиться, что при пересылке в виде датаграмм копируемые строки не были урезаны. Кроме того, изменение способа формирования отправляемых датаграмм привело к тому, что теперь их содержимое не завершается нулевым байтом; следовательно, способ вывода принимаемых данных также нуждается в модификации (в нашем случае - в дописывании нулевого байта).

Модифицированные варианты программ представлены в листингах пример 11.33 и пример 11.34.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* Программа процесса, читающего строки со стандартного ввода */

/* и посылающего их в виде датаграмм другому процессу */

/* (будем называть его сервером), */

/* который должно выдать их на свой стандартный вывод. */

/* Имя серверного хоста - аргумент командной строки */

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#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_DGRAM, IPPROTO_UDP,

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_DGRAM, IPPROTO_UDP)) < 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);

}

return (0);

}

Листинг 11.33. Модифицированный вариант программы, использующей сокеты адресного семейства AF_INET для отправки датаграмм.

/* * * * * * * * * * * * * * * * * * * * * * * * * * */

/* Программа процесса (будем называть его сервером), */

/* читающего сообщения из датаграммного сокета */

/* и копирующего их на стандартный вывод */

/* с указанием адреса, откуда они поступили */

/* * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <stdio.h>

#include <netdb.h>

#include <sys/socket.h>

int main (void) {

int sd; /* Дескриптор приемного сокета */

/* Буфер для принимаемых сообщений */

/* Оставлено место для вставки нулевого байта */

char lbuf [BUFSIZ + 1];

/* Структура - аргумент recvmsg */

struct msghdr msg = {NULL, 0, NULL, 0, NULL, 0, 0};

struct iovec iovbuf; /* Структура для разнесения принимаемых данных */

/* Структура - входной аргумент getaddrinfo */

struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_DGRAM, IPPROTO_UDP,

0, NULL, NULL, NULL};

/* Указатель - выходной аргумент getaddrinfo */

struct addrinfo *addr_res;

int res; /* Результат getaddrinfo */

/* Структура для исходного адреса датаграмм */

struct sockaddr_in sai;

ssize_t lmsg; /* Длина принятой датаграммы */

/* Создадим сокет, через который будем принимать */

/* прочитанные строки */

if ((sd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 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);

/* Заполним структуру msghdr */

msg.msg_name = &sai;

msg.msg_namelen = sizeof (struct sockaddr_in);

msg.msg_iov = &iovbuf;

msg.msg_iovlen = 1;

iovbuf.iov_base = lbuf;

/* Оставим место для вставки нулевого байта */

iovbuf.iov_len = sizeof (lbuf) - 1;

/* Цикл приема и выдачи строк */

while (1) {

if ((lmsg = recvmsg (sd, &msg, 0)) < 0) {

perror ("RECVMSG");

break;

}

printf ("Вы ввели и отправили с адреса %s, порт %d :",

inet_ntoa (((struct sockaddr_in *) msg.msg_name)->sin_addr),

ntohs (((struct sockaddr_in *) msg.msg_name)->sin_port));

if (msg.msg_flags & MSG_TRUNC) {

printf ("Датаграмма была урезана\n");

}

lbuf [lmsg] = 0;

fputs (lbuf, stdout);

}

return (0);

}

Листинг 11.34. Модифицированный вариант программы, использующей сокеты адресного семейства AF_INET для приема датаграмм.

Читателю предлагается оценить степень надежности (точнее, ненадежности) доставки датаграмм, перенеправив стандартный ввод читающего строки процесса, в какой-либо большой текстовый файл. Кроме того, любопытно параллельно запустить несколько таких процессов и проследить за очередностью датаграмм, поступающих серверу.

Сделаем теперь более существенные изменения, прежде всего на серверной стороне. Перейдем на использование надежного режима с установлением соединения (и, соответственно, сокетов типа SOCK_STREAM). Сервер будет получать запросы на установление соединения через порт сервиса "spooler" и порождать для каждого принятого запроса свой обслуживающий процесс.

Новый вариант программы процесса, читающего строки со стандартного ввода и отправляющего их через потоковый сокет, показан в пример 11.35; две программы серверной части - в листингах пример 11.35 и пример 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);

}

Размещено на Allbest.ru


Подобные документы

  • Управление доступом к передающей среде. Процедуры обмена данными между рабочими станциями абонентских систем сети, реализация методов доступа к передающей среде. Оценка максимального времени реакции на запрос абонента сети при различных методах доступа.

    курсовая работа [87,2 K], добавлен 13.09.2010

  • Характеристика и правила организации баз данных - совокупности взаимосвязанных данных, характеризующихся возможностью использования для большого количества приложений; возможностью быстрого получения и модификации необходимой информации. Выбор СУБД.

    реферат [1,4 M], добавлен 27.10.2010

  • Особенности организации передачи данных в компьютерной сети. Эталонная модель взаимодействия открытых систем. Методы передачи данных на нижнем уровне, доступа к передающей среде. Анализ протоколов передачи данных нижнего уровня на примере стека TCP/IP.

    курсовая работа [1,0 M], добавлен 07.08.2011

  • Что такое локальная сеть. Аппаратные средства компьютерных сетей, физические, логические топологии локальных вычислительных сетей. Передача информации по волоконно-оптическим кабелям, коммуникационная аппаратура. Концепции управления сетевыми ресурсами.

    курсовая работа [917,3 K], добавлен 22.09.2009

  • Основная цель и модели сети. Принцип построения ее соединений. Технология клиент-сервер. Характеристика сетевых архитектур Ethernet, Token Ring, ArcNet: метод доступа, среда передачи, топология. Способы защиты информации. Права доступа к ресурсам сети.

    презентация [269,0 K], добавлен 26.01.2015

  • Общие понятия компьютерных сетей. Протоколы и их взаимодействие. Базовые технологии канального уровня. Сетевые устройства физического и канального уровня. Характеристика уровней модели OSI. Глобальные компьютерные сети. Использование масок в IP-адресации.

    курс лекций [177,8 K], добавлен 16.12.2010

  • Классификация компьютерных сетей. Взаимодействие компьютеров в сети. Сетевые модели и архитектуры. Мосты и коммутаторы, сетевые протоколы. Правила назначения IP-адресов сетей и узлов. Сетевые службы, клиенты, серверы, ресурсы. Способы доступа в Интернет.

    курсовая работа [1,5 M], добавлен 11.05.2014

  • Всемирная тенденция к объединению компьютеров в сети. Компьютерные сети: основные типы и устройство. Глобальная сеть Интернет. Современные сетевые технологи в компьютерных сетях. Особенности технологии Wi-Fi, IP-телефония. Виртуальные частные сети.

    презентация [648,3 K], добавлен 14.02.2016

  • Современные средства связи и их характеристика. Разработка структуры сети передачи данных. Выбор типа доступа. Основные уровни модели OSI, технология доступа. Выбор оборудования, характеристики сервера. Расчет стоимостных показателей для прокладки сети.

    курсовая работа [411,7 K], добавлен 22.04.2013

  • Сетевые операционные системы, их характеристика и виды. Функции программного обеспечения локальной компьютерной сети. Структура и функции прокси-сервера и межсетевого экрана. Базы данных в локальных сетях, электронная почта, системы удаленного доступа.

    курсовая работа [43,9 K], добавлен 21.07.2012

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.