50 if ((n = Read(sockfd, buf, MAXLINE)) == 0) {
51 /* соединение закрыто клиентом */
52 Close(sockfd);
53 FD_CLR(sockfd, &allset);
54 client[i] = -1;
55 } else
56 Writen(sockfd, line, n);
57 if (--nready <= 0)
58 break; /* больше нет дескрипторов, готовых для чтения */
59 }
60 }
61 }
62 }
26-27
Функция
select
ждет, пока не будет установлено новое клиентское соединение или на существующем соединении не прибудут данные, сегмент FIN или сегмент RST.
28-45
Если прослушиваемый сокет готов для чтения, новое соединение установлено. Мы вызываем функцию
accept
и соответствующим образом обновляем наши структуры данных. Для записи присоединенного сокета мы используем первый незадействованный элемент массива
client
. Число готовых дескрипторов уменьшается, и если оно равно нулю, мы можем не выполнять следующий цикл
for
. Это позволяет нам использовать значение, возвращаемое функцией
select
, чтобы избежать проверки не готовых дескрипторов.
46-60
Каждое существующее клиентское соединение проверяется на предмет того, содержится ли его дескриптор в наборе дескрипторов, возвращаемом функцией
select
. Если да, то из этого дескриптора считывается строка, присланная клиентом, и отражается обратно клиенту. Если клиент закрывает соединение, функция read возвращает нуль и мы обновляем структуры соответствующим образом.
Мы не уменьшаем значение переменной
maxi
, но могли бы проверять возможность сделать это каждый раз, когда клиент закрывает свое соединение.
Этот сервер сложнее, чем сервер, показанный в листингах 5.1 и 5.2, но он позволяет избежать затрат на создание нового процесса для каждого клиента, что является хорошим примером использования функции
select
. Тем не менее в разделе 15.6 мы опишем проблему, связанную с этим сервером, которая, однако, легко устраняется, если сделать прослушиваемый сокет неблокируемым, а затем проверить и проигнорировать несколько ошибок из функции
accept
.
Атака типа «отказ в обслуживании»
К сожалению, функционирование только что описанного сервера вызывает проблемы. Посмотрим, что произойдет, если некий клиент-злоумышленник соединится с сервером, отправит 1 байт данных (отличный от разделителя строк) и войдет в состояние ожидания. Сервер вызовет функцию
readline
, которая прочитает одиночный байт данных от клиента и заблокируется в следующем вызове функции
read
, ожидая следующих данных от клиента. Сервер блокируется (вернее, «подвешивается») этим клиентом и не может предоставить обслуживание никаким другим клиентам (ни новым клиентским соединениям, ни данным существующих клиентов), пока упомянутый клиент-злоумышленник не отправит символ перевода строки или не завершит свой процесс.
Дело в том, что обрабатывая множество клиентов, сервер
6.9. Функция pselect
Функция
pselect
была введена в POSIX и в настоящий момент поддерживается множеством версий Unix.
#include
#include
#include
int pselect(int
const struct timespec *
Функция
pselect
имеет два отличия от обычной функции
select
:
1. Функция
pselect
использует структуру
timespec
, нововведение стандарта реального времени POSIX, вместо структуры
timeval
.
struct timespec {
time_t tv_sec; /* секунды */
long tv_nsec; /* наносекунды */
};