Книга: UNIX: разработка сетевых приложений
Интерфейс kqueue
Разделы на этой странице:
Интерфейс kqueue
Система FreeBSD версии 4.1 предложила сетевым программистам новый интерфейс, получивший название kqueue
. Этот интерфейс позволяет процессу зарегистрировать фильтр событий, описывающий интересующие данный процесс события kqueue
. К событиям этого типа относятся операции ввода-вывода с файлами и тайм-ауты, а также асинхронный ввод-вывод, уведомление об изменении файлов и обработка сигналов.
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
int kqueue(void);
int kevent(int kq, const struct kevent *changelist, int nchanges,
struct kevent *eventlist, int nevents, const struct timespec *timeout);
void EV_SET(struct kevent *kev, uintptr_t ident, short filter,
u_short flags, u_int fflags, intptr_t data, void *udata);
Функция kqueue
возвращает новый дескриптор kqueue
, который может использоваться в последующих вызовах kevent
. Функция kevent
применяется для регистрации интересующих событий, а также для получения уведомлений об этих событиях. Параметры changelist
и nchanges
описывают изменения в предыдущем варианте списка событий. Если nchanges
отлично от нуля, выполняются все запрошенные в структуре changelist
изменения. Функция kevent
возвращает количество событий или нуль, если произошел выход по тайм-ауту. В аргументе timeout
хранится значение тайм-аута, обрабатываемое подобно тому, как при вызове select(NULL
для блокирования, ненулевое значение для задания конкретного тайм- аута, а нулевое значение трактуется как необходимость неблокирующего вызова). Обратите внимание, что параметр timeout
имеет тип struct timespec
, отличающийся от struct timeval
в вызове select
тем, что первый имеет наносекундное разрешение, а второй — микросекундное.
Структура kevent
определяется в заголовочном файле <sys/event.h>
:
struct kevent {
uintptr_t ident; /* идентификатор (например, дескриптор файла) */
short filter; /* тип фильтра (например, EVFILT_READ) */
u_short flags; /* флаги действий (например, EV_ADD); */
u_int fflags; /* флаги, относящиеся к конкретным фильтрам */
intptr_t data; /* данные, относящиеся к конкретным фильтрам */
void uidata; /* непрозрачные пользовательские данные */
};
Действия по смене фильтра и флаговые возвращаемые значения приведены в табл. 14.5.
Таблица 14.5. Флаги для операций kevent
Значение flags | Описание | Изменяется | Возвращается |
---|---|---|---|
EV_ADD | Добавить новое событие, подразумевается по умолчанию, если не указан флаг EV_DISABLE | • | |
EV_CLEAR | Сброс состояния события после считывания его пользователем | • | |
EV_DELETE | Удаление события из фильтра | • | |
EV_DISABLE | Отключение события без удаления его из фильтра | • | |
EV_ENABLE | Включение отключенного перед этим события | • | |
EV_ONESHOT | Удаление события после его однократного срабатывания | • | |
EV_EOF | Достигнут конец файла | • | |
EV_ERROR | Произошла ошибка, код errno записан в поле data | • |
Типы фильтров приведены в табл. 14.6.
Таблица 14.6. Типы фильтров
Значение filter | Описание |
---|---|
EVFILT_AIO | События асинхронного ввода-вывода |
EVFILT_PROC | События exit, fork, exec для процесса |
EVFILT_READ | Дескриптор готов для чтения (аналогично select) |
EVFILT_SIGNAL | Описание сигнала |
EVFILT_TIMER | Периодические или одноразовые таймеры |
EVFILT_VNODE | Изменение и удаление файлов |
EVFILT_WRITE | Дескриптор готов для записи (аналогично select) |
Перепишем функцию str_cli
из листинга 6.2 так, чтобы она использовала kqueue
. Результат представлен в листинге 14.8.
Листинг 14.8. Функция str_cli, использующая kqueue
//advio/str_cli_kqueue04.c
1 #include "unp.h"
2 void
3 str_cli(FILE *fp, int sockfd)
4 {
5 int kq, i, n, nev, stdineof = 0, isfile;
6 char buf[MAXLINE];
7 struct kevent kev[2];
8 struct timespec ts;
9 struct stat st;
10 isfile = ((fstat(fileno(fp), &st) 0) &&
11 (st.st_mode & S_IFMT) == S_IFREG);
12 EV_SET(&kev[0], fileno(fp), EVFILT_READ, EV_ADD, 0, 0, NULL);
13 EV_SET(&kev[1], sockfd, EVFILT_READ, EV_ADD, 0, 0, NULL);
14 kq = Kqueue();
15 ts.tv_sec = ts.tv_nsec = 0;
16 Kevent(kq, kev, 2, NULL, 0, &ts);
17 for (;;) {
18 nev = Kevent(kq, NULL, 0, kev, 2, NULL);
19 for (i = 0; i < nev; i++) {
20 if (kev[i].ident == sockfd) { /* сокет готов для чтения */
21 if ((n = Read(sockfd, buf, MAXLINE)) == 0) {
22 if (stdineof == 1)
23 return; /* нормальное завершение*/
24 else
25 err_quit("str_cli: server terminated prematurely");
26 }
27 Write(fileno(stdout), buf, n);
28 }
29 if (kev[i].ident == fileno(fp)) { /* входной поток готов к чтению */
30 n = Read(fileno(fp), buf, MAXLINE);
31 if (n > 0)
32 Writen(sockfd, buf, n);
33 if (n == 0 || (isfile && n == kev[i].data)) {
34 stdineof = 1;
35 Shutdown(sockfd, SHUT_WR); /* отправка FIN */
36 kev[i].flags = EV_DELETE;
37 Kevent(kq, &kev[i], 1, NULL, 0, &ts); /* удаление
kevent */
38 continue;
39 }
40 }
41 }
42 }
43 }
Проверка, указывает ли дескриптор на файл
10-11
Поведение kqueue
при достижении конца файла зависит от того, связан ли данный дескриптор с файлом, каналом или терминалом, поэтому мы вызываем fstat
, чтобы убедиться, что мы работаем с файлом. Эти сведения понадобятся позже.
Настройка структур kevent для kqueue
12-13
При помощи макроса EV_SET
мы настраиваем две структуры kevent
. Обе содержат фильтр событий готовности к чтению (EVFILT_READ
) и запрос на добавление этого события к фильтру (EV_ADD
).
Создание kqueue и добавление фильтров
14-16
Мы вызываем kqueue
, чтобы получить дескриптор kqueue
, устанавливаем тайм- аут равным нулю, чтобы сделать вызов kevent
неблокируемым, и наконец, вызываем kevent
с массивом kevent
на месте соответствующего аргумента.
Бесконечный цикл с блокированием в kevent
17-18
Мы входим в бесконечный цикл и блокируемся в kevent
. Функции передается пустой список изменений, потому что все интересующие нас события уже зарегистрированы, и нулевой тайм-аут, что позволяет заблокироваться навечно.
Перебор возвращаемых событий в цикле
19
Мы проверяем все возвращаемые события и обрабатываем их последовательно.
Сокет готов для чтения
20-28
Эта часть кода ничем не отличается от листинга 6.2.
Вход готов для чтения
29-40
Код практически аналогичен листингу 6.2 за тем отличием, что нам приходится обрабатывать конец файла, возвращаемый kqueue
. Для каналов и терминалов kqueue
возвращает событие готовности дескриптора к чтению, подобно select
, так что мы можем считать из этого дескриптора символ конца файла. Для файлов kqueue
возвращает количество байтов, оставшихся в поле data
структуры struct kevent
и предполагает, что приложение само определит, когда оно доберется до конца этих данных. Поэтому мы переписываем цикл таким образом, чтобы отправлять данные по сети, если они были считаны из дескриптора. Затем проверяется достижение конца файла: если мы считали нуль байтов или если мы считали все оставшиеся байты из дескриптора файла, значит, это и есть EOF
. Еще одно изменение состоит в том, что вместо FD_CLR
для удаления дескриптора из набора файлов мы используем флаг EV_DELETE
и вызываем kevent
для удаления события из фильтра в ядре.
- 12. Лекция: Создание приложений с графическим интерфейсом пользователя.
- 5.21 IP-адреса, интерфейсы и множественное пребывание
- Множественные интерфейсы и имена методов
- 2.1 Интерфейс SCSI
- 2.2 Интерфейсы IDE, EIDE и АТА
- 7.2 Интерфейс WMI
- 7.5 Программные интерфейсы приложений для адаптеров шины
- Не допускайте того, чтобы поток пользовательского интерфейса блокировался на длительное время
- Абстрактные базы как двоичные интерфейсы
- Интерфейсы накопителей на жестких магнитных дисках
- Интерфейс SATA
- 8.4. Оформляем интерфейс проигрывателя