Оба параметра it_value и it_interval фактически являются структурами типа struct timespec — еще одного POSIX-объекта. Эта структура позволяет вам обеспечить разрешающую способность на уровне долей секунд. Первый ее элемент, tv_sec, — это число секунд, второй элемент, tv_nsec, — число наносекунд в текущей секунде. (Это означает, что никогда не следует устанавливать параметр tv_nsec в значение, превышающее 1 миллиард — это будет подразумевать смещение на более чем 1 секунду).
Несколько примеров:
it_value.tv_sec = 5;
it_value.tv_nsec = 500000000;
it_interval.tv_sec = 0;
it_interval.tv_nsec = 0;
Это сформирует однократный таймер, который сработает через 5,5 секунды. (5,5 секунд складывается из 5 секунд и 500,000,000 наносекунд.)
Мы предполагаем здесь, что этот таймер используется как относительный, потому что если бы это было не так, то его время срабатывания уже давно бы его истекло (5.5 секунд с момента 00:00 по Гринвичу, 1 января 1970).
Другой пример:
it_value.tv_sec = 987654321;
it_value.tv_nsec = 0;
it_interval.tv_sec = 0;
it_interval.tv_nsec = 0;
Данная комбинация параметров сформирует однократный таймер, который сработает в четверг, 19 апреля 2001 года в 00:25:21 по EDT. (Существует множество функций, которые помогут вам преобразовать воспринимаемый человеком интервал времени в «число секунд, истекшее с 00:00:00 по Гринвичу, 1 января 1970 года». См. функции time(), asctime(), ctime(), mktime(), strftime(), и т.д.).
В данном примере мы предполагаем, что это абсолютный таймер, поскольку в противном случае ждать пришлось бы достаточно долго (987654321 секунд — приблизительно 31.3 года).
Отметьте, что в двух приведенных выше примерах я говорил: «мы предполагаем». В коде функции timer_settime() нет никаких проверок на правильность аргументов! Вы должны самостоятельно доопределить, является таймер абсолютным или относительным. Что до ядра, то оно будет просто счастливо запланировать какое-нибудь событие на 31.3 года вперед.
И еще один пример:
it_value.tv_sec = 1;
it_value.tv_nsec = 0;
it_interval.tv_sec = 0;
it_interval.tv_nsec = 500000000;
Если предположить, что это относительный таймер, он сработает через одну секунду и далее каждые полсекунды. Не существует никаких требований какого бы то ни было подобия значений интервала перезагрузки значениям задержки однократного срабатывания.
Сервер с периодическими импульсами
Первое, что следует рассмотреть, — это сервер, который желает получать периодические сообщения. Типовыми применениями такой схемы являются:
• поддерживаемые сервером тайм-ауты клиентских запросов;
• внутренние периодические события серверов.
Конечно, есть и другие, специализированные, применения для таких вещей — например, периодические подтверждения готовности узлов сети («я жив»), запросы на повторную передачу, и т.п.
Поддерживаемые сервером тайм-ауты
В таком сценарии сервер предоставляет клиенту некоторую услугу, и клиент способен задать тайм-аут. Это может использоваться в самых разнообразных приложениях. Например, вы можете сказать серверу «выдай мне данные за 15 секунд» или «дай мне знать, когда истекут 10 секунд», или «жди прихода данных, но в течение не более чем 2 секунд».
Все это — примеры поддерживаемых сервером тайм-аутов. Клиент посылает сообщение серверу и блокируется. Сервер принимает периодические сообщения от таймера (раз в секунду, реже или чаще) и подсчитывает, сколько этих сообщений он получил. Когда число сообщений о тайм-аутах превышает время ожидания, указанное клиентом, сервер отвечает клиенту с сообщением о тайм-ауте или, возможно, с данными, которые он успел накопить на данный момент — это зависит от того, как структурированы отношения клиента и сервера.
Ниже приведен полный пример сервера, который принимает одно из двух сообщений от клиентуры и сообщения о тайм-ауте в виде импульса. Первое клиентское сообщение говорит серверу: «Дай мне знать, есть ли для меня данные, но не блокируй меня более чем на 5 секунд». Второе клиентское сообщение говорит: «Вот, возьми данные». Сервер должен позволить нескольким клиентам блокироваться на себе в ожидании данных, и поэтому обязан сопоставить клиентам тайм-ауты. Тут-то и нужен импульс; он информирует сервер: «Истекла одна секунда».
Чтобы программа не выглядела излишне громоздкой, перед каждым из основных разделов я прерываю исходный текст небольшими пояснениями. Скачать эту программу вы можете на FTP-сайте компании PARSE (ftp://ftp.parseftp.parse.com/pub/book_v3.tar.gz), файл называется time1.с.
ДекларацииВ первом разделе программы определяются различные именованные константы и структуры данных. В нем также подключаются все необходимые заголовочные файлы. Оставим это без комментариев. :-)
/*
* time1.c
*
* Пример сервера, получающего периодические сообщения
* от таймера
* и обычные сообщения от клиента.
*
* Иллюстрирует использование функций таймера с импульсами.
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/siginfo.h>
#include <sys/neutrino.h>
// Получаемые сообщения
// Сообщения
#define MT_WAIT_DATA 2 // Сообщение от клиента
#define MT_SEND_DATA 3 // Сообщение от клиента
// Импульсы
#define CODE_TIMER 1 // Импульс от таймера
// Отправляемые сообщения
#define MT_OK 0 // Сообщение клиенту
#define MT_TIMEDOUT 1 // Сообщение клиенту
// Структура сообщения
typedef struct {
int messageType; // Содержит сообщение от клиента и
// клиенту
int messageData; // Опциональные данные, зависят от
// сообщения
} ClientMessageT;
typedef union {
ClientMessageT msg; // Сообщение может быть
// либо обычным,
struct _pulse pulse; // либо импульсом
} MessageT;
// Таблица клиентов
#define MAX_CLIENT 16 // Максимум клиентов
// одновременно
struct {
int in_use; // Элемент используется?
int rcvid; // Идентификатор
// отправителя клиента
int timeout; // Оставшийся клиенту
//тайм-аут
} clients[MAX_CLIENT]; // Таблица клиентов
int chid; // Идентификатор канала
// (глобальный)
int debug = 1; // Режим отладки, 1 ==
// вкл, 0 == выкл
char *progname = "time1.c";
// Предопределенные прототипы
static void setupPulseAndTimer(void);
static void gotAPulse(void);
static void gotAMessage(int rcvid, ClientMessageT *msg);
main()Следующий раздел кода является основным и отвечает за следующее:
• создание канала с помощью функции ChannelCreate();
• вызов подпрограммы setupPulseAndTimer() (для настройки периодического таймера, срабатывающего раз в секунду и использующего импульс в качестве способа доставки события;
• и, наконец, бесконечный цикл ожидания импульсов и сообщений и их обработки.
Обратите внимание на проверку значения, возвращаемого MsgReceive() — нуль указывает, что был принят импульс (здесь мы не делаем никакой дополнительной проверки, наш ли это импульс), ненулевое значение говорит о том, что было принято сообщение.
Обработка импульсов и сообщений выполняется функциями gotAPulse() и gotAMessage().
int main void) // Игнорировать аргументы
// командной строки
{
int rcvid; // PID отправителя
MessageT msg; // Само сообщение
if ((chid = ChannelCreate(0)) == -1) {
fprintf(stderr, "%s: не удалось создать канал!n",
progname);
perror(NULL);
exit(EXIT_FAILURE);
}
// Настроить импульс и таймер
setupPulseAndTimer();
// Прием сообщений
for(;;) {
rcvid = MsgReceive(chid, &msg, sizeof(msg), NULL));
// Определить, от кого сообщение
if (rcvid == 0) {
// Здесь неплохо бы еще проверить поле «code»...
gotAPulse();
} else {