Вы здесь

Асинхронные операции - select

Для реализации клиент-серверной архитектуры на основе сокетов необходимо предоставить разработчику сервера инструмент для параллельной работы с несколькими клиентами. Возможные варианты:

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

Последний вариант является наиболее часто используемым в Unix и реализуется вызовами select() и poll().

Вызовы отличаются по формату параметров, но эквивалентны по своему назначению. Они приостанавливают выполнение процесса, до появления данных от клиента, появления возможности отправить данные клиенту, появления ошибки приёма-передачи или до истечения таймаута. Если точнее, то для операций чтения-записи проверяется, что они не будут заблокированы.

Реализация этих вызовов позволяет использовать их для отслеживания состояния любых файловых дескрипторов, а не только сокетов.

SELECT

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

Параметр nfds задает номер максимального выставленного флага и служит для оптимизации.

#include <sys/select.h>
int select(int nfds,
   fd_set *readfds,
   fd_set *writefds,
   fd_set *exceptfds,
   struct timeval *timeout);

Для манипуляции флагами используется следующие функции, которые позволяют очистить набор флагов, установить флаг, сбросить флаг, проверить состояние флага:

void FD_ZERO(fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);

При изменении состояния каких-либо интересующего нас файловых дескрипторов select() сбрасывает все флаги и выставляет те, которые обозначают, какие события и на каких файловых дескрипторах произошли. Возвращается значение, указывающее сколько флагов возвращено. Если событий не было и возврат из select() произошёл по таймауту, все наборы флагов обнуляются и возвращается ноль.

В случае ошибки возвращается -1. Значение флагов не определено.

Таймаут задаётся структурой timeval, содержащей секунды и микросекунды

struct timeval { 
    long    tv_sec;         /* seconds */
    long    tv_usec;        /* microseconds */
};

Поскольку вызов sleep() работает с точностью до секунды, то для приостановки процесса на более короткие промежутки времени часто используют select() с указателями NULL вместо указателей на флаги.

POLL

Вызов poll() функционально эквивалентен select. Его параметры как бы "вывернуты наизнанку" по сравнению с select(). Вместо трёх наборов битовых файлов в poll() массив интересующих файловых дескрипторов размером nfds. С каждым файловым дескриптором связаны две переменные: флаги интересующих событий и флаги случившихся событий. Время таймаута задаётся в миллисекундах.

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

Структура pollfd

struct pollfd { int fd; /* file descriptor / short events; / requested events / short revents; / returned events */ };

Битовые флаги в events определяются макросами:

#define POLLIN      0x0001    /* Можно считывать данные */
#define POLLPRI     0x0002    /* Есть срочные данные */
#define POLLOUT     0x0004    /* Запись не будет блокирована */
#define POLLERR     0x0008    /* Произошла ошибка */
#define POLLHUP     0x0010    /* Обрыв связи */
#define POLLNVAL    0x0020    /* Неверный запрос: fd не открыт */
Яндекс.Метрика