46: читать из него снова */
47: fds[i].events = 0;
48: } else {
49: buf[rc] = '\0';
50: printf("чтение : %s", buf);
51: }
52: }
53: }
54: }
55:
56: return 0;
57: }
13.1.3. Мультиплексирование с помощью select()
Системный вызов poll()
был изначально представлен как часть Unix-дерева System V. Усилиями разработчиков BSD та же основная проблема была решена похожим способом — предоставлением системного вызова select()
.
#include <sys/select.h>
int select(int numfds, fd_set * readfds, fd_set * writefds,
fd_set * exceptfds, struct timeval * timeout);
Три промежуточных параметра — readfds
, writefds
и exceptfds
— определяют, за какими файловыми дескрипторами необходимо следить. Каждый параметр — это указатель на fd_set
, структуру данных, позволяющую процессу определить произвольное количество файловых дескрипторов[74]. Ею манипулируют с помощью перечисленных ниже макросов.
FD_ZERO(fd_set * fds); |
Очищает fds — в наборе не содержатся файловые дескрипторы. Этот макрос используется для инициализации структур fd_set . |
FD_SET(intfd, fd_set * fds); |
Добавляет fd к fd_set . |
FD_CLR(intfd, fd_set * fds); |
Удаляет fd из fd_set . |
FD_ISSET(int fd, fd_set * fds); |
Возвращает true , если fd содержится в установленном fds . |
Первый набор файловых дескрипторов select()
, readfds
, содержит перечень файловых дескрипторов, вызывающих возврат вызова select()
, когда они готовы для чтения[75] или (для каналов и сокетов) когда процесс на другом конце файла закрыл его. Когда любой файловый дескриптор в writefds
готов к записи, select()
возвращается, exceptfds
содержит файловые дескрипторы для слежения за исключительными условиями. В Linux (так же, как и в Unix) это происходит только при поступлении внешних данных в сетевое подключение. В качестве любого из них можно указать NULL
, если тот или иной тип события вас не интересует.
Окончательный параметр, timeout
, определяет, насколько долго (в миллисекундах) вызову select()
необходимо ожидать какого-либо события. Это указывает на struct timeval
, которая выглядит следующим образом.
#include <sys/time.h>
struct timeval {
int tv_sec; /* секунды */
int tv_usec; /* микросекунды */
};
Первый элемент — tv_sec
— это количество оставшихся секунд, a tv_usec
— это количество оставшихся микросекунд. Если значением timeout
является NULL
, select()
блокируется до следующего события. Если он указывает на struct timeval
, содержащую 0 в обоих элементах, вызов select()
не блокируется. Он обновляет наборы файловых дескрипторов, чтобы определить, какой файловый дескриптор в настоящее время готов для чтения или записи, а затем немедленно возвращается.
Первый параметр, numfds
, вызывает наибольшие трудности. Он задает количество файловых дескрипторов (начиная с файлового дескриптора 0), которое может быть определено с помощью fd_sets
. Еще один (и, возможно, более легкий) способ поведения numfds
намного лучше максимального файлового дескриптора select()
[76].
Поскольку Linux обычно позволяет каждому процессу иметь до 1024 файловых дескрипторов, numfds
избавляет ядро от необходимости просмотра всех 1024 файловых дескрипторов, которые может содержать каждая структура fd_set
, что улучшает показатели производительности.
После возврата три структуры fd_set
содержат файловые дескрипторы с задержкой входных данных, на которые можно произвести запись или которые находятся в исключительном состоянии. Вызов select()
в Linux возвращает общее количество элементов, установленных в трех структурах fd_set
, 0
в случае тайм-аута вызова либо -1
в случае ошибки. Однако многие системы Unix считают определенные файловые дескрипторы в возвращаемом значении только один раз, даже если они находятся как в readfds
, так и в writefds
, поэтому в целях переносимости лучше совершать проверку только тогда, когда возвращаемое значение больше 0
. Если возвращаемое значение равно -1
, не думайте, что структуры fd_set
остаются незатронутыми. Linux обновляет их только в случае, если select()
возвращает значение больше 0, однако некоторые системы Unix демонстрируют иное поведение.
Еще одним параметром, связанным с переносимостью, является timeout
. Ядра Linux[77] обновляют его, чтобы отобразить количество времени, оставшегося до тайм-аута вызова select()
, но большинство других систем Unix его не обновляют[78]. Однако другие системы не обновляют тайм-аут с целью соответствия более привычной реализации.
Для переносимости устраните зависимость от поведения и явно настройте структуру timeout
перед вызовом select()
.
Теперь рассмотрим несколько примеров применения select()
. Для начала используем select()
без связи с файлами, создав вторичный вызов sleep()
.
#include <sys/select.h>
#include <sys/stdlib.h>
int usecsleep(int usees) {
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = useсs;
return select(0, NULL, NULL, NULL, &tv);
}
Этот код разрешает переносимые паузы длительностью менее секунды (это обеспечивает также библиотечная функция BSD usleep()
, но select()
намного более переносима). Например, usecsleep(500000)
вызывает паузу минимум на полсекунды.
Вызов select()
также используется для решения примера мультиплексирования каналов, с которым мы работали. Решение очень похоже на решение при использовании poll()
.
1: /* mpx-select.c */
2:
3: #include <fcntl.h>
4: #include <stdio.h>
5: #include <sys/select.h>
6: #include <unistd.h>
7:
8: int main(void) {
9: int fds[2];
10: char buf[4096];
11: int i, rc, maxfd;
75
Когда сетевой сокет прослушивается (listen()
) и готов к приему (accept()
), считается, что он готов к считыванию для целей select()
; информацию о сокетах можно найти в главе 17.
76
Если сравнить это с параметром numfds
для poll()
, то можно понять, почему возникают затруднения.
78
Когда Линус Торвальдс впервые реализовал select()
, неспособность ядра BSD обновлять timeout
была отмечена как ошибка на man-странице для select()
. Вместо написания ошибочного кода Линус решил "исправить" эту ошибку. К сожалению, комитеты по стандартам одобрили поведение BSD.