Для реализации клиент-серверной архитектуры на основе сокетов необходимо предоставить разработчику сервера инструмент для параллельной работы с несколькими клиентами. Возможные варианты:
Последний вариант является наиболее часто используемым в Unix и реализуется вызовами select() и poll().
Вызовы отличаются по формату параметров, но эквивалентны по своему назначению. Они приостанавливают выполнение процесса, до появления данных от клиента, появления возможности отправить данные клиенту, появления ошибки приёма-передачи или до истечения таймаута. Если точнее, то для операций чтения-записи проверяется, что они не будут заблокированы.
Реализация этих вызовов позволяет использовать их для отслеживания состояния любых файловых дескрипторов, а не только сокетов.
Вызов 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() функционально эквивалентен 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 не открыт */