Выбрать главу

/*

 * gotAMessage

 *

 * Эта подпрограмма вызывается при каждом приеме

 * сообщения. Проверяем тип

 * сообщения (либо «жду данных», либо «вот данные»),

 * и действуем

 * соответственно. Для простоты предположим, что данные

 * никогда не ждут.

 * Более подробно об этом см. в тексте.

*/

void gotAMessage(int rcvid, ClientMessageT *msg) {

 int i;

 // Определить тип сообщения

 switch (msg->messageType) {

 // Клиент хочет ждать данных

 case MT_WAIT_DATA:

  // Посмотрим, есть ли пустое место в таблице клиентов

  for (i = 0; i < MAX_CLIENT; i++) {

   if (!clients[i].in_use) {

    // Нашли место - пометить как занятое,

    // сохранить rcvid

    // и установить тайм-аут

    clients[i].in_use = 1;

    clients[i].rcvid = rcvid;

    clients[i].timeout = 5;

    return;

   }

  }

  fprintf(stderr,

   "Таблица переполнена, сообщение от rcvid %d"

   " игнорировано, клиент заблокирован\n", rcvid);

  break;

  // Клиент с данными

 case MT_SEND_DATA:

  // Посмотрим, есть ли другой клиент, которому можно ответить

  // данными от этого клиента

  for (i = 0; i < MAX CLIENT; i++) {

   if (clients[i].in_use) {

    // Нашли - использовать полученное сообщение

    // в качестве ответного

    msg->messageType = MT_OK;

    // Ответить ОБОИМ КЛИЕНТАМ!

    MsgReply(clients[i].rcvid, EOK, msg, sizeof(*msg));

    MsgReply(rcvid, EOK, msg, sizeof(*msg));

    clients[i].in_use = 0;

    return;

   }

  }

  fprintf(stderr,

   "Таблица пуста, сообщение от rcvid %d игнорировано,"

   " клиент заблокирован\n", rcvid);

  break;

 }

}

Примечания

Несколько общих замечаний по тексту программы:

• Если сообщение с данными прибывает, когда либо никто не ждет, либо список ожидающих клиентов переполнен, в стандартный поток ошибок выводится сообщение, но клиенту при этом мы не отвечаем ничего. Это означает, что ряд клиентов может оказаться в REPLY-блокированном состоянии навсегда — идентификаторы отправителей мы потеряли, а значит, и ответ им дать не можем.

Это сделано намеренно. Вы можете изменить это, добавив соответственно сообщения MT_NO_WAITERS и MT_NO_SPACE, которыми можно было бы отвечать всякий раз при обнаружении ошибок данного типа.

• Когда клиент-обработчик ждет, а клиент-поставщик пересылает ему данные, мы отвечаем обоим клиентам. Это критично, поскольку мы должны разблокировать обоих клиентов.

• Мы повторно использовали буфер клиента-поставщика для обоих ответов. Этот прием программирования — опять же, вопрос стиля: в большом приложении у вас, вероятно, было бы много типов возвращаемых значений, и вы могли бы и не захотеть повторно использовать одни и те же буферы.

• В приведенном примере используется «щербатый» массив фиксированной длины с флагом «элемент задействован» (clients[i].in_use). Поскольку моей целью здесь является отнюдь не демонстрация хитростей программирования односвязных списков, я использовал простейший для понимания вариант. В конечном же программном продукте, разумеется, имело бы смысл использовать динамический список.

• Когда функция MsgReceive() получает импульс, наше решение относительно того, действительно ли это «наш» импульс, фактически является весьма слабо аргументированным — мы просто предполагаем (согласно комментариям), что все входящие импульсы имеют тип CODE_TIMER. Опять же, в конечном продукте следовало бы проверять значение кода импульса и сообщать о наличии каких-либо аномалий.

Отметим, что в приведенном примере демонстрируется только один способ реализации тайм-аутов клиентуры. Позже, в этой же главе (в разделе «Тайм-ауты ядра») мы поговорим о тайм-аутах ядра. Это еще один способ делать почти то же самое, только управление на этот раз осуществляется клиентом, а не таймером.

Внутренние периодические события серверов

Здесь мы имеем несколько другое применение для периодических сообщений о тайм-аутах, когда эти сообщения предназначены сервером исключительно для внутреннего использования и не имеют никакого отношения к клиенту вообще.

Например, некоторые аппаратные средства могут требовать, чтобы сервер опрашивал их периодически — например, такое может быть в случае сетевого соединения: сервер должен периодически проверять, является ли данное подключение доступным, и это не зависит от команд клиентуры.

Другой вариант — если, например, в аппаратных средствах предусмотрен таймер «выключения по неактивности». Например, если длительное пребывание какого-то аппаратного модуля во включенном состоянии может приводить к неоправданным затратам электроэнергии, то если его никто не использует в течение, скажем, 10 секунд, его можно было бы выключить (или переключить в режим низкого энергопотребления — прим. ред.). Опять же, к клиенту это не имеет никакого отношения (за исключением того, что запрос от клиента отменит режим ожидания) — это просто функция, которую сервер должен уметь предоставлять «своим» аппаратным средствам.

Код в этом случае сильно бы напоминал приведенный выше пример, за исключением того, что вместо списка ожидающих клиентов у вас была бы только одна переменная тайм-аута. С каждым событием от таймера ее значение уменьшалось бы, но пока оно больше нуля, ничего бы не происходило. Когда оно стало бы равным нулю, это вызывало бы отключение аппаратных средств (или какое-либо другое соответствующее действие).

Единственный трюк здесь заключается в том, что всякий раз, когда поступает сообщение от клиента, использующего данные аппаратные средства, вы должны восстановить первоначальное значение этой переменной, поскольку обращение к ресурсу должно сбрасывать «обратный отсчет». И наоборот, аппаратным средствам может потребоваться определенный промежуток времени «на разогрев» после включения. В этом случае после выключения аппаратных средств вам придется при поступлении запроса от клиента организовать еще один таймер, чтобы «придержать» запрос до того момента, пока аппаратные средства не станут готовы.

Таймеры, посылающие сигналы

На настоящий момент мы уже рассмотрели практически все, что относится к таймерам, за исключением одного небольшого момента. Мы обеспечивали отправку импульса, но у нас также есть возможность посылать POSIX-сигналы. Давайте посмотрим, как это делается:

timer_create(CLOCK_REALTIME, NULL, &timerid);

Это простейший способ создать таймер, который будет посылать вам сигнал. Он обеспечивает выдачу сигнала SIGALRM при срабатывании таймера. Если бы мы предоставили struct sigevent, мы могли бы определить, какой именно сигнал мы хотим получить: