Книга: Разработка приложений в среде Linux. Второе издание
13.1.3. Мультиплексирование с помощью select()
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;
12: fd_set watchset; /* fds для чтения */
13: fd_set inset; /* обновляется select() */
14:
15: /* открыть оба канала */
16: if ((fds[0] = open("p1", O_RDONLY | O_NONBLOCK)) < 0) {
17: perror("open p1");
18: return 1;
19: }
20:
21: if ((fds[1] = open("p2", O_RDONLY | O_NONBLOCK)) < 0) {
22: perror("open p2");
23: return 1;
24: }
25:
26: /* начать чтение из обоих файловых дескрипторов */
27: FD_ZERO(&watchset);
28: FD_SET(fds[0], &watchset);
29: FD_SET(fds[1], &watchset);
30:
31: /* найти максимальный файловый дескриптор */
32: maxfd = fds[0] > fds[1] ? fds[0] : fds[1];
33:
34: /* пока наблюдаем за одним из fds[0] или fds[1] */
35: while (FD_ISSET(fds[0], &watchset) ||
36: FD_ISSET(fds[1], &watchset)) {
37: /* здесь копируем watchset, потому что select() обновляет его */
38: inset = watchset;
39: if (select(maxfd + 1, &inset, NULL, NULL, NULL) < 0) {
40: perror("select");
41: return 1;
42: }
43:
44: /* проверить, какой из файловых дескрипторов
45: готов для чтения из него */
46: for (i = 0; i < 2; i++) {
47: if (FD_ISSET(fds[i], &inset )) {
48: /* fds[i] готов для чтения, двигаться дальше... */
49: rc = read(fds[i], buf, sizeof (buf) - 1);
50: if (rc < 0) {
51: perror("read");
52: return 1;
53: } else if (!rc) {
54: /* этот канал закрыт, не пытаться
55: читать из него снова */
56: close(fds[i]);
57: FD_CLR(fds[i], &watchset);
58: } else {
59: buf[rc] = '';
60: printf("чтение: %s", buf);
61: }
62: }
63: }
64: }
65:
66: return 0;
67: }
- 13.1.5. Мультиплексирование с помощью epoll
- 13.1.6 Сравнение poll() и epoll
- 13.1. Мультиплексирование входных и выходных данных
- 13.1.2. Мультиплексирование с помощью poll()
- 13.1.4. Сравнение poll() и select()
- Повышение производительности приложений с помощью хранимых процедур
- Тестирование Web-сервиса XML с помощью WebDev.WebServer.exe
- Организация пользователей в группы с помощью ролей
- 1. Оператор Select – базовый оператор языка структурированных запросов
- 2.3.3 Selecting and Pasting
- Обработка запросов с помощью PHP
- Как с помощью компьютера подшутить над друзьями и коллегами?