Литмир - Электронная Библиотека
Содержание  
A
A

Мы не уменьшаем значение переменной

maxi
, но могли бы проверять возможность сделать это каждый раз, когда клиент закрывает свое соединение.

Этот сервер сложнее, чем сервер, показанный в листингах 5.1 и 5.2, но он позволяет избежать затрат на создание нового процесса для каждого клиента, что является хорошим примером использования функции

select
. Тем не менее в разделе 15.6 мы опишем проблему, связанную с этим сервером, которая, однако, легко устраняется, если сделать прослушиваемый сокет неблокируемым, а затем проверить и проигнорировать несколько ошибок из функции
accept
.

Атака типа «отказ в обслуживании»

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

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

Дело в том, что обрабатывая множество клиентов, сервер никогда не должен блокироваться в вызове функции, относящейся к одному клиенту. В противном можно «подвесить» сервер, что приведет к отказу в обслуживании для всех остальных клиентов. Это называется атакой типа «отказ в обслуживании» (DoS attack — Denial of Service). Такая атака воздействует на сервер, делая невозможным обслуживание нормальных клиентов. Обезопасить себя от подобных атак позволяют следующие решения: использовать неблокируемый ввод-вывод (см. главу 16), предоставлять каждому клиенту обслуживание отдельным потоком (например, для каждого клиента порождать процесс или поток) или установить тайм-аут для ввода-вывода (см. раздел 14.2).

6.9. Функция pselect

Функция

pselect
была введена в POSIX и в настоящий момент поддерживается множеством версий Unix.

#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
имеет два отличия от обычной функции
select
:

1. Функция

pselect
использует структуру
timespec
, нововведение стандарта реального времени POSIX, вместо структуры
timeval
.

struct timespec {

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

 long tv_nsec;  /* наносекунды */

};

Эти структуры отличаются вторыми элементами: элемент

tv_nsec
новой структуры задает наносекунды, в то время как элемент
tv_usec
прежней структуры задает микросекунды.

2. В функции

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

В отношении второго пункта рассмотрим следующий пример (описанный на с. 308–309 [110]). Обработчик сигнала нашей программы для сигнала

SIGINT
просто устанавливает глобальную переменную
intr_flag
и возвращает управление. Если наш процесс блокирован в вызове функции select, возвращение из обработчика сигнала заставляет функцию завершить работу, присвоив
errno
значение
EINTR
. Код вызова
select
выглядит следующим образом:

if (intr_flag)

 handle_intr(); /* обработка этого сигнала */

if ((nready = select(...)) &lt; 0) {

 if (errno == EINTR) {

  if (intr_flag)

   handle_intr();

 }

 ...

}

Проблема заключается в том, что если сигнал придет в промежутке между проверкой переменной

intr_flag
и вызовом функции
select
, он будет потерян в том случае, если функция
select
заблокирует процесс навсегда. С помощью функции
pselect
мы можем переписать этот пример так, чтобы он работал более надежно:

sigset_t newmask, oldmask, zeromask;

sigemptyset(&amp;zeromask);

sigemptyset(&amp;newmask);

sigaddset(&amp;newmask, SIGINT);

sigprocmask(SIG_BLOCK, &amp;newmask, &amp;oldmask); /* блокирование сигнала SIGINT */

if (intr_flag)

 handle_intr(); /* обработка этого сигнала */

if ((nready = pselect(..., &amp;zeromask)) &lt; 0) {

 if (errno == EINTR) {

  if (intr_flag)

  handle_intr();

 }

 ...

}

Перед проверкой переменной

intr_flag
мы блокируем сигнал
SIGINT
. Когда вызывается функция
pselect
, она заменяет маску сигналов процесса пустым набором (
zeromask
), а затем проверяет дескрипторы, возможно, переходя в состояние ожидания. Но когда функция
pselect
возвращает управление, маске сигналов процесса присваивается то значение, которое предшествовало вызову функции
pselect
(то есть сигнал
SIGINT
блокируется).

71
{"b":"225366","o":1}