UNIX: разработка сетевых приложений
Мы не уменьшаем значение переменной
, но могли бы проверять возможность сделать это каждый раз, когда клиент закрывает свое соединение.maxiЭтот сервер сложнее, чем сервер, показанный в листингах 5.1 и 5.2, но он позволяет избежать затрат на создание нового процесса для каждого клиента, что является хорошим примером использования функции
. Тем не менее в разделе 15.6 мы опишем проблему, связанную с этим сервером, которая, однако, легко устраняется, если сделать прослушиваемый сокет неблокируемым, а затем проверить и проигнорировать несколько ошибок из функцииselect.acceptАтака типа «отказ в обслуживании»
К сожалению, функционирование только что описанного сервера вызывает проблемы. Посмотрим, что произойдет, если некий клиент-злоумышленник соединится с сервером, отправит 1 байт данных (отличный от разделителя строк) и войдет в состояние ожидания. Сервер вызовет функцию
, которая прочитает одиночный байт данных от клиента и заблокируется в следующем вызове функцииreadline, ожидая следующих данных от клиента. Сервер блокируется (вернее, «подвешивается») этим клиентом и не может предоставить обслуживание никаким другим клиентам (ни новым клиентским соединениям, ни данным существующих клиентов), пока упомянутый клиент-злоумышленник не отправит символ перевода строки или не завершит свой процесс.readДело в том, что обрабатывая множество клиентов, сервер никогда не должен блокироваться в вызове функции, относящейся к одному клиенту. В противном можно «подвесить» сервер, что приведет к отказу в обслуживании для всех остальных клиентов. Это называется атакой типа «отказ в обслуживании» (DoS attack — Denial of Service). Такая атака воздействует на сервер, делая невозможным обслуживание нормальных клиентов. Обезопасить себя от подобных атак позволяют следующие решения: использовать неблокируемый ввод-вывод (см. главу 16), предоставлять каждому клиенту обслуживание отдельным потоком (например, для каждого клиента порождать процесс или поток) или установить тайм-аут для ввода-вывода (см. раздел 14.2).
6.9. Функция pselect
Функция
была введена в POSIX и в настоящий момент поддерживается множеством версий Unix.pselect#include <sys/select.h>#include <signal.h>#include <time.h>int pselect(int <i>maxfdp1</i>, fd_set *<i>readset</i>, fd_set *<i>writeset</i>, fd_set *<i>exceptset</i>,const struct timespec *<i>timeout</i>, const sigset_t *<i>sigmask</i>);<i>Возвращает: количество готовых дескрипторов, 0 в случае тайм-аута, -1 в случае ошибки</i>Функция
имеет два отличия от обычной функцииpselect:select1. Функция
использует структуруpselect, нововведение стандарта реального времени POSIX, вместо структурыtimespec.timevalstruct timespec {time_t tv_sec; /* секунды */long tv_nsec; /* наносекунды */};Эти структуры отличаются вторыми элементами: элемент
новой структуры задает наносекунды, в то время как элементtv_nsecпрежней структуры задает микросекунды.tv_usec2. В функции
добавляется шестой аргумент — указатель на маску сигналов. Это позволяет программе отключить доставку ряда сигналов, проверить какие-либо глобальные переменные, установленные обработчиками этих отключенных сигналов, а затем вызвать функциюpselect, сообщив ей, что нужно переустановить маску сигналов.pselectВ отношении второго пункта рассмотрим следующий пример (описанный на с. 308–309 [110]). Обработчик сигнала нашей программы для сигнала
просто устанавливает глобальную переменнуюSIGINTи возвращает управление. Если наш процесс блокирован в вызове функции select, возвращение из обработчика сигнала заставляет функцию завершить работу, присвоивintr_flagзначениеerrno. Код вызоваEINTRвыглядит следующим образом:selectif (intr_flag)handle_intr(); /* обработка этого сигнала */if ((nready = select(...)) < 0) {if (errno == EINTR) {if (intr_flag)handle_intr();}...}Проблема заключается в том, что если сигнал придет в промежутке между проверкой переменной
и вызовом функцииintr_flag, он будет потерян в том случае, если функцияselectзаблокирует процесс навсегда. С помощью функцииselectмы можем переписать этот пример так, чтобы он работал более надежно:pselectsigset_t newmask, oldmask, zeromask;sigemptyset(&zeromask);sigemptyset(&newmask);sigaddset(&newmask, SIGINT);sigprocmask(SIG_BLOCK, &newmask, &oldmask); /* блокирование сигнала SIGINT */if (intr_flag)handle_intr(); /* обработка этого сигнала */if ((nready = pselect(..., &zeromask)) < 0) {if (errno == EINTR) {if (intr_flag)handle_intr();}...}Перед проверкой переменной
мы блокируем сигналintr_flag. Когда вызывается функцияSIGINT, она заменяет маску сигналов процесса пустым набором (pselect), а затем проверяет дескрипторы, возможно, переходя в состояние ожидания. Но когда функцияzeromaskвозвращает управление, маске сигналов процесса присваивается то значение, которое предшествовало вызову функцииpselect(то есть сигналpselectблокируется).SIGINT