retval = select(sock+1, &readv, &writev, 0, &to);
/* если select возвращает нулевое значение, значит тайм-аут */
if (retval == 0) {
err_ret('timeout');
continue;
}
/* в противном случае, ищем соответствующий дескриптор */
if ( (FD_ISSET(sock, &readv)) || (FD_ISSET(sock, &writev))) {
/* прием связи с сокета */
new_sock = accept(sock, (struct sockaddr *)0, (int *)0);
/* работа с сокетом new_sock */
...
/* закрытие текущей связи */
close(new_sock);
} else {
err_ret('Это не сокет! Проверьте все дескрипторы
');
}
}
}
Системный вызов select() принимает 5 аргументов:
int select(int fd, fd_set *input, fd_set *output,
fd_set *error, struct timeval *timeout);
Первый аргумент, fd, — это файловый дескриптор, который может быть сокетом. Следующие три аргумента задают множества файловых дескрипторов для ожидания условий ввода (input), вывода (output) и ошибок (error). Последний аргумент — это тайм-аут.
Множества файловых дескрипторов инициализируются с помощью трех макросов:
FD_ZERO(fd_set);
FD_SET(fd, fd_set);
FD_CLR(fd, fd_set);
Первый макрос полностью очищает множество, следующие два макроса, соответственно, добавляют и удаляют файловый дескриптор. Мы использовали два макроса для ввода и два для вывода. Сначала мы полностью очистили множество, а потом добавили в него соответствующие дескрипторы:
FD_ZERO(&readv);
FD_ZERO(&writev);
FD_SET(sock, &readv);
FD_SET(sock, &writev);
Особого разговора требует последний параметр — тайм-аут. Тайм-аут можно задавать в секундах и миллисекундах. Например, следующие операторы объявляют тайм-аут длительностью 2 секунды и 5 миллисекунд:
struct timeval tout; /* тайм-аут для select */
tout.tv_sec = 2; /* 2 секунды */
tout.tv_usec = 5; /* 5 миллисекунд */
Если вы хотите не использовать тайм-аут (то есть ждать бесконечно), укажите NULL в качестве последнего аргумента.
Функция select() возвращает число файловых дескрипторов, на которых выполнились ожидаемые условия (ввод/вывод/ошибка) или -1 при ошибке.
Вот еще один пример использования функции select(). Мы будем ожидать ввода из файла и из сокета. Если будет достигнут тайм-аут в 20 секунд, пользователь увидит соответствующее сообщение; в противном случае он увидит сообщение: «Получен ввод из файла/сокета».
Листинг 27.8. Еще один пример использования select()
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/select.h>
int k;
int sock;
int fd;
int max_fd;
fd_set input;
struct timeval timeout;
/* инициализация файла и сокета */
...
/* Инициализируем множество ввода */
FD_ZERO(input);
FD_SET(fd, input);
FD_SET(sock, input);
max_fd = (sock > fd ? sock : fd) + 1;
/* Задаем тайм-аут */
timeout.tv_sec = 20;
k = select(max_fd, &input, NULL, NULL, &timeout);
if (k < 0)
perror('Ошибка при вызове select');
else if (k == 0) puts('TIMEOUT');
else {
/* Получен ввод */
if (FD_ISSET(fd, input))
printf('Получен ввод из файла');
if (FD_ISSET(sock, input))
printf('Получен ввод из сокета');
}
Вроде бы код программы очень прост, но комментария заслуживает макрос FD_ISSET. С его помощью мы проверяем, есть ли во множестве ввода ввод из какого-либо источника.
27.3.12. Неблокирующие операции
Некоторые функции для работы с сокетами блокируют программу в случае, если удаленный процесс не осуществил требуемую операцию. Примеры таких функций:
♦ accept();
♦ connect();