KnigaRead.com/

Нейл Мэтью - Основы программирования в Linux

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Нейл Мэтью, "Основы программирования в Linux" бесплатно, без регистрации.
Перейти на страницу:

Поскольку вы создаете дочерние процессы, но не ждете их завершения, следует сделать так, чтобы сервер игнорировал сигналы SIGCHLD, препятствуя возникновению процессов-зомби.

Упражнение 15.7. Сервер для многочисленных клиентов

1. Программа server4.c начинается так же, как последний рассмотренный сервер с важным добавлением директивы include для заголовочного файла signal.h. Переменные и процедуры создания и именования сокета остались прежними: 

#include <sys/types.h>

#include <sys/socket.h>

#include <stdio.h>

#include <netinet/in.h>

#include <signal.h>

#include <unistd.h>

#include <stdlib.h>


int main() {

 int server_sockfd, client_sockfd;

 int server_len, client_len;

 struct sockaddr_in server_address;

 struct sockaddr_in client_address;

 server_sockfd = socket(AF_INET, SOCK_STREAM, 0);

 server_address.sin_family = AF_INET;

 server_address.sin_addr.s_addr = htonl(INADDR_ANY);

 server_address.sin_port = htons(9734);

 server_len = sizeof(server_address);

 bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

2. Создайте очередь соединений, игнорируйте подробности завершения дочернего процесса и ждите запросов клиентов:

 listen(server_sockfd, 5);

 signal(SIGCHLD, SIG_IGN);

 while(1) {

  char ch;

  printf("server waitingn");

3. Примите запрос на соединение:

  client_len = sizeof(client_address);

  client_sockfd = accept(server_sockfd,

   (struct_sockaddr*)&client_address, &client_len);

4. Вызовите fork с целью создания процесса для данного клиента и выполните проверку, чтобы определить, родитель вы или потомок:

  if (fork() == 0) {

5. Если вы потомок, то можете читать/писать в программе-клиенте на сокете client_sockfd. Пятисекундная задержка нужна для того, чтобы это продемонстрировать:

   read(client_sockfd, &ch, 1);

   sleep(5);

   ch++;

   write(client_sockfd, &ch, 1);

   close(client_sockfd);

   exit(0);

  }

6. В противном случае вы должны быть родителем и ваша работа с данным клиентом закончена:

  else {

   close(client_socket);

  }

 }

}

Код включает пятисекундную задержку при обработке запроса клиента для имитации вычислений сервера или обращения к базе данных. Если бы вы проделали это в предыдущем сервере, каждое выполнение программы client3 заняло бы пять секунд. С новым сервером вы сможете обрабатывать множественные клиентские программы client3 параллельно с общим затраченным временем, чуть превышающим пять секунд.

$ ./server4 &

[1] 26566 server waiting

$ ./client3 & ./client3 & ./client3 & ps x

[2] 26581

[3] 26582

[4] 26583

server waiting

server waiting

server waiting

PID   TTY   STAT TIME COMMAND

26566 pts/1 S    0:00 ./server4

26581 pts/1 S    0:00 ./client3

26582 pts/1 S    0:00 ./client3

26583 pts/1 S    0:00 ./client3

26584 pts/1 R+   0:00 ps x

26585 pts/1 S    0:00 ./server4

26586 pts/1 S    0:00 ./server4

26587 pts/1 S    0:00 ./server4

$ char from server = В

char from server = В

char from server = В

ps x

PID  TTY    STAT TIME COMMAND

26566 pts/1 S    0:00 ./server4

26590 pts/1 R+   0:00 ps x

[2] Done   ./client3

[3]- Done  ./client3

[4]+ Done  ./client3

$

Как это работает

Теперь серверная программа создает новый дочерний процесс для обработки каждого клиента, поэтому вы можете видеть несколько сообщений об ожидании сервера, поскольку основная программа продолжает ждать новые запросы на подключения. В выводе команды ps (отредактированном) показан главный процесс server4 с PID, равным 26 566, который ожидает новых клиентов, в то время, как три клиентских процесса client3 обслуживаются тремя потомками сервера. После пятисекундной паузы все клиенты получают свои результаты и завершаются. Дочерние серверные процессы тоже завершаются, оставляя только один главный серверный процесс.

Серверная программа применяет вызов fork для обработки множественных клиентов. В приложении для работы с базой данных это может быть не самым удачным решением, т.к. серверная программа может быть довольно большой, и, кроме того, существует проблема координации обращений к базе данных множественных копий сервера. На самом деле, все, что вам нужно, — это способ обработки множественных клиентов единственным сервером без блокировки и ожидания доставки клиентских запросов. Решение этой задачи включает одновременную обработку множественных открытых файловых дескрипторов и не ограничено только приложениями с применением сокетов. Рассмотрим функцию select.

select

Очень часто при разработке приложений Linux вам может понадобиться проверка состояния ряда вводов для того, чтобы определить следующее предпринимаемое действие. Например, программа обмена данными, такая как эмулятор терминала, нуждается в эффективном способе одновременного чтения с клавиатуры и с последовательного порта. В однопользовательской системе подойдет цикл "активного ожидания", многократно просматривающий ввод в поиске данных и читающий их, как только они появятся. Такое поведение очень расточительно в отношении времени ЦП.

Системный вызов select позволяет программе ждать прибытия данных (или завершения вывода) одновременно на нескольких низкоуровневых файловых дескрипторах. Это означает, что программа эмулятора терминала может блокироваться до тех пор, пока у нее не появится работа. Аналогичным образом сервер может иметь дело с многочисленными клиентами, ожидая запросы одновременно на многих открытых сокетах.

Функция select оперирует структурами данных fd_set, представляющими собой множества открытых файловых дескрипторов. Для обработки этих множеств определен набор макросов:

#include <sys/types.h> #include <sys/time.h>

void FD_ZERO(fd_set *fdset);

void FD_CLR(int fd, fd_set *fdset);

void FD_SET(int fd, fd_set *fdset);

int FD_ISSET(int fd, fd_set *fdset);

Как и предполагается в соответствии с их именами, макрос FD_ZERO инициализирует структуру fd_set пустым множеством, FD_SET и FD_CLR задают и очищают элементы множества, соответствующего файловому дескриптору, переданному как параметр fd, а макрос FD_ISSET возвращает ненулевое значение, если файловый дескриптор, на который ссылается fd, является элементом структуры fd_set, на которую указывает параметр fdset. Максимальное количество файловых дескрипторов в структуре типа fd_set задается константой FD_SETDIZE.

Функция select может также использовать значение для времени ожидания, чтобы помешать бесконечной блокировке. Это значение задается с помощью структуры struct timeval. Она определена в файле sys/time.h и содержит следующие элементы:

struct timeval {

 time_t tv_sec; /* Секунды */

 long tv_usec;  /* Микросекунды */

}

Тип time_t, определенный в файле sys/types.h, — целочисленный. Системный вызов select объявляется следующим образом:

#include <sys/types.h>

#include <sys/time.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,

 fd_set *errorfds, struct timeval *timeout);

Вызов select позволяет проверить, не готов ли хотя бы один из множества файловых дескрипторов к чтению или записи, или находится ли в ожидании из-за состояния ошибки и может быть заблокирован до момента готовности одного из дескрипторов.

Аргумент nfds задает количество проверяемых файловых дескрипторов, имеются в виду дескрипторы от 0 до nfds-1. Каждое из трех множеств дескрипторов может оказаться пустым указателем, тогда связанный с ним тест не выполняется.

Функция select вернет управление, если какой-либо из дескрипторов в множестве readfds готов к чтению, какой-нибудь дескриптор из множества writefds готов к записи или у одного из дескрипторов множества errorfd есть состояние ошибки. Если ни одно из условий не соблюдается, select вернет управление после промежутка времени, заданного timeout. Если параметр timeout — пустой указатель и нет активности на сокетах, вызов может быть заблокирован на неопределенное время.

Когда select возвращает управление программе, множества дескрипторов будут модифицированы для того, чтобы указать на готовые к чтению или записи или имеющие ошибки дескрипторы. Для их проверки следует использовать макрос FD_ISSET, позволяющий определить, какие дескрипторы требуют внимания. Можно изменить значение timeout для того, чтобы показать время, остающееся до следующего превышения времени ожидания, но такое поведение не задано стандартом X/Open. При превышении времени ожидания все множества дескрипторов будут очищены.

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*