Демонстрационное приложение-служба TimeService
октября 12 2009 by admin in СлужбыПример службы TimeService («03 TimeService.exe») содержит все компоненты, необходимые для построения службы. Исходный код и файлы ресурсов этого приложения см. в каталоге ОЗ-TimeService на прилагаемом компакт-диске. Эта простейшая служба возвращает дату и время серверной машины, когда к ней подключается клиент. Предполагается, что вы имеете представление об именованных каналах и портах завершения ввода-вывода .
Просмотрев функцию JWinMain, вы увидите, что эта служба может сама себя устанавливать или удалять из базы данных SCM в зависимости от того, какой из двух параметров передается ей в командной строке — «-install» или «-remove». Собрав службу, запустите ее первый раз из командной строки с параметром «-install». Когда эта служба вам больше будет не нужна, запустите ее из командной строки с аргументом «-remove». Я подробней расскажу о функциях для добавления и удаления службы из базы данных SCM в следующей главе.
Самое важное в jXVinMain — это заполнение двух элементов массива структур SERVICE_TABLE__ENTRY. один элемент для службы, а записи второго установлены в NULL, указывая на последнюю службу. Адрес этого массива таблиц служб передается функции StartSemiceCtrlDispatcher, которая создает поток для службы. Этот новый поток начинает выполнение с функции TimeServiceMain. Заметьте: StartServiceCtrlDispatcher не возвращает управление JWinMain, пока не отработает TimeServiceMain и не завершится ее поток.
Код, обрабатывающий клиентские запросы, реализован в TimeServiceMain. Он начинается с создания порта завершения ввода-вывода. Поток службы в цикле ожидает запросов, чтобы войти в порт завершения. Возможны запросы двух видов: клиент подключился к каналу и хочет получить сведения о дате и времени данной машины, или службе требуется обработать запрос некоторой операции, например, приостановки, продолжения или останова.
Посте создания порта завершения я инициализирую глобальный объект g_ssTime класса CServiccStatus (мое детище!). Этот класс C++ заметно облегчает оповещение об изменении состояния службы. Он является производным от структуры Windows SERVICE_STATUS и на более абстрактном уровне отражает изменение ее элементов. В качестве одного из членов он имеет объект 1сласса CGate, применяемый для синхронизации потоков TimeHandlerEx и TimeServiceMain, гарантируя, что поток ServiceMain в каждый отдельный момент обрабатывает лишь один запрос операции.
Внутри метода Initialize класса CServiccStatus производится обращение к RegisterServiceClrlHandlerEx, чтобы указать SCM функцию HandlerEx службы (она называется TimeHandlerEx). В качестве параметра pvContext функции HandlerEx указывается адрес объекта iocp, который представляет порт завершения ввода-вывода. Метод Initialize также устанавливает элемент dwServiceType, который никогда не изменяется в течение всего времени работы службы.
Затем я вызываю ueroj\AcceptControls, который устанавливает значение элемента diuConirolsAccepted. Этот метод может периодически вызываться при работе службы, чтобы принять или отклонить те или иные коды управления. TimeService принимает коды останова, приостановки, продолжения и выключения.
Моя функция TimeHandlerEx передает управляющие коды потоку службы с помощью метода PostStatus класса СЮСР, который в свою очередь вызывает Po&tQueuedCompletionSlatus, указывая описатель порта завершения ввода-вывода. Чтобы указать потоку ServiceMain, что он выводится из состояния ожидания для выполнения запроса операции службы, задается ключ завершения CK_SERVICECONTROL. Затем TimeHandlerEx как можно быстрее возвращает управление. Поток службы должен выйти из состояния ожидания, обработать полученный код и вновь перейти к ожиданию клиентских запросов (когда это имеет смысл).
В конце TimeSennceMain начинается цикл «do-while», внутри которого я проверяю переменную СотрКеу, чтобы определить, какую операцию служба должна выполнять следующей. Поскольку начальное значение этой переменной -CK_SERVICECONTROL, а переменной dwControl - SERVICE _CONTROL_CON-TINUE, первое, что запрашивается у службы, — создать именованный канал, который будет использоваться клиентским приложением для запроса службы. Далее этот канал ассоциируется с портом завершения с помощью ключа СК_Р1РЕ, и производятся асинхронные вызовы ConnectNamedPipe. Теперь служба оповещает SCM, что она запущена и работает, с помощью метода Report-UltimateState объекта g_ssTime, в котором для выдачи кода SERVICE_RUNNING вызывается функция SetServiceStatus.
Служба вызывает метод GetStatus объекта iocp (который в свою очередь вызывает GetQueuedCompletionStatus). Это заставляет поток службы перейти в состояние ожидания события в порту завершения. Если там появляется управляющий код (поскольку TimeHandlerEx вызывает PostQueuedCompletionStatus), поток службы выходит из ожидания, обрабатывает управляющий код (соответствующим образом) и вновь оповещает SCM о завершении операции. Кстати, за оповещение о текущем состоянии операции отвечает TimeHandlerEx, тогда как за оповещение о конечном состоянии службы — TimeServiceMain. (Здесь используется третий метод оповещения — см. выше раздел «Вопросы, связанные с взаимодействием потоков».)
Когда поток службы выходит из состояния ожидания, поскольку GetQueuedCompletionStatus возвращает ключ завершения СК_Р1РЕ, это значит, что подключился клиент. Теперь служба узнает системное время и вызывает WriteFile, выдавая его клиенту. Затем служба отсоединяет клиента и производит следующий асинхронный вызов ConnectNamedPipe, чтобы мог подключиться другой клиент.
Когда поток службы выходит из состояния ожидания, получая управляющие коды SERVICE_CONTROL_STOP или SERVICE_CONTROL_SHUTDOWN, он закрывает канал и прекращает выполнение. Это приводит к закрытию порта завершения, выходу из TimeServiceMain и уничтожению потока службы. Теперь StartServiceCtriDispatcher возвращает управление jWinMain, которая также возвращает управление, и соответствующий процесс уничтожается.
Собранную службу нужно запустить из командной строки с параметром «-install», чтобы служба зарегистрировалась в базе данных SCM. Не пропустите кавычки, ограничивающие имя службы, которое включает пробел («03 Time-Service. ехе<>). Кроме того, можете использовать модульный компонент Services для запуска и администрирования службы «Programming Server-Side Applications Time».