• Понятие службы
  • Архитектура
  • Служебная программа
  • Управляющий менеджер служб
  • Служебная управляющая программа
  • Конфигурационная программа службы
  • Пространство имен System.ServiceProcess
  • Создание службы
  • Библиотека классов, использующая сокеты
  • Пример TcpClient
  • Проект Windows Service
  • Класс ServiceBase
  • Потоки выполнения и службы
  • Установка службы
  • Программы установки
  • Класс Installer
  • Классы ServiceProcessInstaller и ServiceInstaller
  • ServiceInstallerDialog
  • InstallUtil
  • Клиент 
  • Мониторинг и управление службой
  • Консоль управления Microsoft (ММС)
  • net.exe
  • sc.exe
  • Server Explorer
  • Класс ServiceController
  • Управление службой
  • Поиск неисправностей
  • Интерактивные службы
  • Регистрация событий
  • Архитектура регистрации событий
  • Классы регистрации событий
  • Добавление регистрации событий
  • Трассировка
  • Создание приемника событий
  • Мониторинг производительности
  • Классы мониторинга производительности
  • Построитель счетчиков производительности 
  • Добавление счетчиков производительности
  • perfmon.exe
  • Служба счетчика производительности
  • Свойства служб Windows 2000
  • Изменения сетевого соединения и события электропитания
  • Восстановление
  • Приложения COM+ в роли служб
  • Заключение
  • Глава 24

    Службы Windows

    В главе 22 рассматривается работа в сети, глава 23 охватывает работу с серверами с помощью .NET Remoting. Описанные серверные процессы запускаются вручную. Однако программы должны начинать работать автоматически во время запуска машины. Здесь на помощь приходят службы Windows.

    В этой главе мы рассмотрим следующие вопросы:

    □ Архитектура служб Windows, функциональность служебной программы, служебная управляющая программа, и служебная конфигурационная программа.

    □ Реализация службы с помощью классов, находящихся в пространстве имен

    System.ServiceProcess
    .

    □ Программы установки для конфигурирования службы в реестре.

    □ Написание программы для управления службой с помощью класса

    ServiceController
    .

    □ Реализация обработки событий. Так как службы обычно не имеют интерфейса пользователя, ошибки не могут выводиться в окне сообщений. Обработка событий является хорошим способом сообщения об ошибках.

    □ Производительность службы. Мониторинг производительности может использоваться для получения информации о нормальном выполнении службы.

    Вначале мы рассмотрим, что же такое службы.

    Понятие службы

    Службы Windows являются приложениями, которые начинают работать автоматически при запуске операционной системы. Они могут выполняться без интерактивного взаимодействия с пользователем системы. Можно сконфигурировать службу под требования специально сконфигурированным пользователем или таким пользователем

    System
    , который имеет больше привилегий, чем системный администратор.

    Службы не выполняются на Windows 98 или Windows ME. Для них требуется ядро NT. Службы Windows работают в Windows NT 4, Windows 2000 и Windows ХР.

    Вот несколько примеров таких служб:

    □ Простая служба TCP/IP является служебной программой, которая содержит несколько небольших серверов TCP/IP:

    echo
    ,
    daytime
    ,
    quote
    и других.

    □ Служба публикации в Web является службой Информационного сервера Интернета (IIS).

    □ Журнал событий является службой для регистрации сообщений в системе регистрации событий.

    □ Microsoft Search является службой, которая создает индексы данных на диске.

    Можно использовать административную утилиту Component Services (Службы компонентов) для просмотра всех служб в системе. В Windows 2000 Server эта программа доступна через Start|Programs|Administrative Tools|Services:

    Архитектура

    Три типа программ требуются для работы службы. Служебная программа предназначена для решения реальной проблемы, в ней необходимо закодировать реальную функциональность. С помощью служебной управляющей программы можно посылать службе управляющие запросы, такие как запуск, останов, пауза и продолжение. И последнее, но не менее важное — требуется конфигурационная программа службы, с помощью которой можно установить службу. Это означает, что она не только копируется в файловую систему, но записывается также в реестр и конфигурируется как служба. В то время как компоненты .NET можно устанавливать, делая хсору, так как им не требуется реестр, установке служб необходима конфигурация в реестре. Программа конфигурации службы может также использоваться для последующего изменения конфигурации службы.

    Служебная программа

    Прежде чем рассматривать реализацию службы в .NET, давайте выясним, на что похожа архитектура служб в Windows и какова внутренняя функциональность службы.

    Служебная программа реализует функциональность службы. Ей требуются три части: основная функция (точка входа программы), основная служебная функция и обработчик. Service Control Manager (SCM) играет очень важную роль для служб, он посылает запросы службе для ее запуска и останова. В служебной программе необходимо регистрировать точки входа службы в SCM, чтобы SCM мог вызывать эти точки входа в службе.

    В основной функции служебной программы точки входа для основных служебных функций должны регистрироваться в SCM. SCM требуется информация об основных служебных функциях, чтобы эти функции можно было вызывать в SCM при запуске службы.

    Основная функция может зарегистрировать более одной основной служебной функции. Она должна регистрировать основную служебную функцию для каждой предоставляемой службы. Служебная программа может предоставить множество служб в одной программе. Например,

    C:\winnt\system32\services.exe
    является служебной программой, которая включает Alerter, Application Management, Computer Browser, DHCP Client, Distributed Link Tracking Client and Server, DNS Client, Event Log, и некоторые другие службы. 

    Второй частью служебной программы является основная служебная функция, которая содержит функциональность службы. Эта функция вызывается SCM, когда служба должна запускаться. Служба World Wide Publishing запускает поток выполнения, который слушает обычно порт 80 и ожидает запросы HTTP. Клиент DHCP запрашивает, освобождает и обновляет динамически присвоенные адреса IP. Основная функциональность службы находится внутри основной служебной функции. У основной служебной функции существует еще одна обязанность в отношении регистрации другой точки входа в SCM: эта функция должна регистрировать функцию обработки в SCM. 

    Функция обработки является третьей частью служебной программы. Обработчик должен отвечать на события из SCM. Службы могут останавливаться, приостанавливаться, продолжаться. Обработчик должен реагировать на эти события.

    Управляющий менеджер служб

    Управляющий менеджер служб (SCM — Service Control Manager) является частью операционной системы, которая взаимодействует со службой. Давайте посмотрим, как работает эта коммуникация на диаграмме последовательностей UML:

    Во время начальной загрузки системы начинает работу каждый процесс, для которого задан автоматический запуск службы, и поэтому вызывается основная функция этого процесса. Служба должна зарегистрировать основную функцию для каждой из своих служб, затем SCM вызывает основную служебную функцию. Основная служебная функция несет на себе, как ранее сообщалось, основную функциональность службы.

    Одной из важных задач, которую имеет основная служебная функция, является регистрация обработчика в SCM. Служебная управляющая программа посылает запросы SCM для остановки, приостановки и возобновления работы службы. Служебная управляющая программа независима от SCM и самой службы. Мы получаем вместе с операционной системой множество служебных управляющих программ, одна из них — ММС Services Snap-in, которую мы видели ранее. Можно написать также свою собственную служебную управляющую программу. Хорошей служебной управляющей программой является часть установки SQL Server. Она выводит цветные кнопки для управления службами SQL Server:

    Служебная управляющая программа

    Как предполагает название, служебная управляющая программа управляет службой. Для остановки, приостановки и возобновления работы службы ей посылаются управляющие коды, а обработчик должен реагировать на эти события. Также можно запрашивать службу о реальном статусе, при этом реализуется специальный обработчик, который отвечает на специальные управляющие коды.

    Конфигурационная программа службы

    Мы не можем использовать для установки службы хсору, так как службы должны конфигурироваться в реестре. Можно задать тип запуска как автоматический, ручной или отключенный. Необходимо сконфигурировать пользователя служебной программы и зависимости службы, например службы, которые должны начать работу перед тем, как запускается эта служба. Все конфигурации производятся внутри конфигурационной программа службы. Установка программы использует программу для конфигурирования службы, но эта программа может также использоваться позже для изменения параметров конфигурации службы.

    Пространство имен System.ServiceProcess

    В .NET Framework находятся классы служб в пространстве имен

    System.ServiceProcess
    , которые запускают три части службы:

    □ Для реализации службы мы наследуем из класса

    ServiceBase
    который используется для регистрации служб и отвечает на запросы запуска и останова.

    □ Класс

    ServiceController
    используется для реализации служебной управляющей программы. С помощью этого класса можно посылать запросы службам.

    □ Классы

    ServiceProcessInstaller
    и
    ServiceInstaller
    являются, как предполагают их имена, классами для установки и конфигурирования служебных программ.

    Давайте посмотрим, как создается новая служба

    Создание службы

    Создаваемая служба будет содержать сервер цитирования (quote server). Для каждого сделанного клиентом запроса сервер цитирования возвращает случайную цитату файла цитат. Первая часть решения будет сделана с помощью трех сборок: одна для клиента и две — для сервера. Сборка

    QuoteServer
    содержит реальную функциональность. Мы прочитаем файл цитат в кэш памяти и будем отвечать на запросы цитат с помощью сервера сокета.

    QuoteСlient
    является клиентским приложением Windows Forms. Это приложение создает клиентский сокет для коммуникации с
    QuoteServer
    . Третья сборка является реальной службой.
    QuoteService
    запускает и останавливает
    QuoteServer
    , служба будет управлять сервером:

    Прежде чем разработать служебную часть программы, создадим простой сервер сокета в дополнительной библиотеке классов C#, которая будет использоваться из процесса службы.

    Библиотека классов, использующая сокеты

    В системе Windows 2000 Server можно установить службы простого TCP/IP как компоненты Windows. Частью служб простого TCP/IP является сервер TCP/IP "цитата дня" ("quote of the day", кратко называемая "qotd"). Эта простая служба слушает порт 17 и отвечает на каждый запрос случайным сообщением из файла

    c:\winnt\system32\drivers\etc\quotes
    . Здесь будет создан аналогичный сервер. Только этот сервер возвращает строку Unicode, в отличие от старого qotd, который возвращает код ASCII.

    Постепенно начнем создавать исходный код класса

    QuoteServer
    в файле
    QuoteServer.cs
    :

    using System;

    using System.IO;

    using System.Threading;

    using System.Net.Sockets;

    using System.Text;

    using System.Collections.Specialized;


    namespace Wrox.ProfessionalCSharp {

     /// <summary>

     /// Пример сервера сокета.

     /// </summary>

     public class QuoteServer {

      private TcpListener listener;

      private int port;

      private string filename;

      private StringCollection quotes;

      private Random random;

      private Thread listenerThread;

    Конструктор

    QuoteServer()
    является перезагруженным, чтобы имя файла и порт можно было передать в вызове. Конструктор, где передается только имя файла, использует для сервера по умолчанию порт 7890. Конструктор по умолчанию определяет имя файла по умолчанию для цитат как
    quotes.txt
    :

      public QuoteServer() : this("quotes.txt") {}

      public QuoteServer(string filename) : this(filename, 7890) {}

      public QuoteServer(string filename, int port) {

       this.filename = filename;

       this.Port = port;

      }

    ReadQuotes()
    является вспомогательным методом, который читает все цитаты из файла, который был определен в конструкторе. Все цитаты добавляются в
    StringCollection quotes
    . Кроме того создается экземпляр класса
    Random
    , который будет использоваться для возврата случайных цитат:

      protected void ReadQuotes() {

       quotes = new StringCollection();

       Stream stream = File.OpenRead(filename);

       StreamReader StreamReader = new StreamReader(stream);

       string quote;

       while ((quote = streamReader.ReadLine()) != null) {

        quotes.Add(quote);

       }

       streamReader.Close(); stream.Close();

       random = new Random();

      }

    Другим вспомогательным методом является

    GetRandomQuoteOfTheDay()
    . Этот метод возвращает случайную цитату из цитат
    StringCollection quotes
    :

      protected string GetRandomQuoteOfTheDay() {

       int index = random.Next(0, quotes.Count); return quotes[index];

      }

    В методе

    Start()
    весь файл, содержащий цитаты, в
    StringCollection quotes
    считывается с помощью вспомогательной функции
    ReadQuotes()
    . После этого запускается новый поток выполнения, который незамедлительно вызывает метод
    Listener()
    . Мы используем поток выполнения, так как метод
    Start()
    не может блокироваться и ждать клиента, он должен сразу же вернуть управление вызывающей стороне (SCM). SCM предположит, что запуск отказал, если метод не вернет своевременно управление вызывающей стороне.

      public void Start() {

       ReadQuotes();

       listenerThread = new Thread(new ThreadStart(this.Listener));

       listenerThread.Start();

      }

    Функция потока выполнения

    Listener()
    создает экземпляр
    TCPListener
    . В методе
    AcceptSocket()
    ожидается соединение клиента. Как только клиент соединяется,
    AcceptSocket()
    возвращает управление с сокетом, связанным с клиентом. Метод
    GetRandomQuoteOfTheDay()
    вызывается для отправки возвращаемой случайной цитаты клиенту с помощью
    socket.Send()
    :

      protected void Listener() {

       listener = new TcpListener(port);

       listener.Start();

       while (true); {

        Socket socket = listener.AcceptSocket();

        if (socket == null) {

         return;

        }

        string message = GetRandomQuoteOfTheDay();

        UnicodeEncoding encoder = new UnicodeEncoding();

        byte [] buffer = encoder.GetBytes(message);

        socket.Send(buffer, buffer.Length, 0);

        socket.Close();

       }

      }

    Помимо

    Start()
    существуют другие методы для управления службой:
    Stop()
    ,
    Suspend()
    и
    Resume()
    :

      public void Stop() {

       listener.Stop();

      }

      public void Suspend() {

       listenerThread.Suspend();

      }

      public void Resume() {

       listenerThread.Resume();

      }

    Методом, который будет открыто доступным, является

    RefreshQuotes()
    . Если содержащий цитаты файл изменяется, то запускается повторное чтение данного файла с помощью этого метода:

      public void RefreshQuotes() {

       ReadQuotes();

      }

     }

    }

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

    QuoteServer
    и вызывает
    Start()
    . Таким образом можно протестировать функциональность без необходимости обрабатывать специфические для службы вопросы. Можно сконцентрироваться на создании требуемой функциональности. Этот тестовый сервер должен запускаться вручную и код легко просматривается с помощью отладчика.

    Тестовая программа является консольным приложением C#. Мы ссылаемся на сборку класса

    QuoteServer
    . Содержащий цитаты класс копируется в каталог
    с:\wrox
    (или нужно изменить аргумент конструктора для определения, куда копируется файл). После вызова конструктора мы обращаемся к методу
    Start()
    экземпляра
    QuoteServer.Start()
    возвращает управление сразу после создания потока выполнения, поэтому консольное приложение продолжает выполняться до тех пор, пока не будет нажата клавиша
    Return
    :

    static void Main(string[] args) {

     QuoteServer qs = new QuoteServer(@"c:\wrox\quotes.txt", 4567);

     qs.Start();

     Console.WriteLine("Hit return to exit");

     Console.ReadLine();

     qs.Stop();

    }

    Отметим, что

    QuoteServer
    с помощью этой программы будет выполняться на порте 4567 на
    localhost
    и необходимо использовать эти настройки позже на клиенте.

    Пример TcpClient

    Клиент является простым приложением Windows, где можно вводить имя хоста и номер порта сервера. Это приложение использует класс

    TCPClient
    для соединения с функционирующим сервером и получения возвращаемого сообщения для вывода его в текстовом поле. Внизу формы выводится статусная строка:

    В этом коде используются инструкции

    using
    :

    using System;

    using System.Drawing;

    using System.Collections;

    using System.ComponentModel;

    using System.Windows.Forms;

    using System.Data;

    using System.Net;

    using System.Net.Sockets;

    using System.Text;

    Мы также включаем ссылку на файл

    QuoteServer.dll
    . Оставшаяся часть кода автоматически создается в IDL, поэтому он здесь не будет рассматриваться подробно. Основная функциональность клиента находится в обработчике нажатия кнопки Get Quote:

    protected void buttonQuote_Click(object sender, System.EventArgs e) {

     statusBar.Text = "";

     string server = textBoxHostname.Text;

     try {

      int port = Convert.ToInt32(textBoxPortNumber.Text);

     } catch (FormatException ex) {

      statusBar.Text = ex.Message; return;

     }

     TcpClient client = new TcpClient();

     try {

      client.Connect(

       textBoxHostname.Text, Convert.ToInt32(textBoxPortNumber.Text));

      NetworkStream stream = client.GetStream();

      byte[] buffer = new Byte[1024];

      int received = stream.Read(buffer, 0, 1024);

      if {received <= 0) {

       statusBar.Text = "Read failed"; return;

      }

      texBoxQuote.Text = Encoding.Unicode.GetString(buffer);

     } catch (SocketException ex) {

      statusBar.Text = ex.Message;

     } finally {

      client.close();

     }

    }

    Запустив тестовый сервер и клиент этого оконного приложения, можно протестировать функциональность. Успешное выполнение может дать следующий результат при использовании указанных на экране настроек:

    Добавим серверу функциональность службы. Программа уже выполняется, что же нужно еще сделать? Необходимо, чтобы серверная программа автоматически запускалась во время начальной загрузки системы без какого-либо пользователя, зарегистрировавшегося в системе, и мы хотим управлять ею с помощью служебных управляющих программ.

    Проект Windows Service

    Используя новый мастер проектов для C# Windows Services, можно теперь начать создавать службу Windows. Будьте внимательны, чтобы не выбрать проект Web Service.

    Службы Web рассматриваются в главе 17.

    После нажатия OK для создания приложения Windows Service появится окно проектировщика, как в приложениях Windows Forms, но здесь нельзя вставлять компоненты Windows Forms. Окно проектировщика будет использоваться для добавления других компонентов, таких как счетчики производительности и регистрации событий: Выбор свойств этой службы открывает окно редактора свойств:

    Сконфигурируем свойства службы:

    □ AutoLog означает, что события автоматически регистрируются для запуска и остановки службы.

    □ CanPauseAndContinue, CanShutdown и CanStop означают, что служба может обрабатывать специальные запросы pause, continue, shutdown и stop.

    □ ServiceName является именем службы, которое записывается в реестр и используется для управления службой.

    □ CanHandlePowerEvent является допустимым параметром для служб, работающих на системе Windows 2000. Мы поговорим о параметрах power позже.

    По умолчанию используется имя службы WinService1 независимо от названия проекта. Можно установить только одну службу WinService1. Если возникает ошибка установки во время процесса тестирования, то причина может быть в этом. Проверьте, что имя службы изменено на более подходящее в начале разработки службы.

    Изменение этих свойств с помощью редактора свойств задает значения нашего класса, производного из

    ServiceBase
    , в методе
    InitializeComponent()
    . Мы уже знаем этот метод из приложений Windows Forms. Со службами он используется аналогичным образом.

    Мастер создаст код, но мы изменим имя файла на

    QuoteService.cs
    , имя пространства имен на
    Wrox.ProfessionalCSharp
    , а имя класса на
    QuoteService
    . Мы подробно рассмотрим этот код позже, а пока остановимся на классе
    ServiceBase
    .

    Класс ServiceBase

    Класс

    ServiceBase
    является базовым для всех служб .NET. Наш класс
    QuoteService
    выводится из
    ServiceBase
    . Этот класс общается со служебным управляющим менеджером при помощи недокументированного вспомогательного класса
    System.ServiceProcess.NativeMethods
    , который служит просто классом-оболочкой для вызовов API Win32. Этот класс закрытый, поэтому мы не можем его использовать.

    Следующая диаграмма последовательностей показывает взаимодействие SCM — класса

    QuoteService
    и классов из пространства имен
    System.ServiceProcess
    . На диаграмме последовательностей просматриваются вертикальные линии жизни объектов и коммуникация, проходящая в горизонтальном направлении. Коммуникация упорядочена по времени сверху вниз:

    SCM запускает процесс службы. Вначале вызывается метод

    Main()
    . В методе
    Main()
    нашей службы вызывается метод
    Run()
    базового класса
    ServiceBase
    .
    Run()
    регистрирует метод
    ServiceMainCallback()
    с помощью
    NativeMethods.StartServiceCtrlDispatcher()
    в SCM и записывает вход в журнал событий.

    Следующий шаг — SCM вызывает зарегистрированный метод

    ServiceMainCallback()
    в нашей программе службы.
    ServiceMainCallback
    сам регистрирует обработчик с помощью
    NativeMethods.RegisterServiceCtrlHandler[Ex]()
    и задает статус службы в SCM. Затем вызывается метод
    OnStart()
    , в котором мы должны реализовать код запуска. Если
    OnStart()
    выполнился успешно, то значение ресурса для
    StartSuccessful
    записывается в журнал событий.

    Обработчик реализуется в методе

    ServiceCommandCallback()
    . SCM вызывает этот метод, когда служба запрашивает изменения. Метод
    ServiceCommandCallback()
    направляет запросы далее в
    OnPause()
    ,
    OnContinue()
    ,
    OnStop()
    ,
    OnCustomCommand()
    и
    OnPowerEvent()
    .

    Основная функция

    Рассмотрим сгенерированную основную функцию служебного процесса. В ней объявляются массив

    ServiceToRun
    классов
    ServiceBase
    . Один экземпляр класса
    QuoteService
    создается и передается как первый элемент массива
    ServiceToRun
    . Если должно выполняться более одной службы внутри этого служебного процесса, то необходимо добавить в массив больше экземпляров специальных служебных классов. Этот массив передается затем в статический метод
    Run()
    класса
    ServiceBase
    . С помощью метода
    Run()
    из
    ServiceBase
    мы задаем ссылки SCM для точек входа служб. Основной поток выполнения служебного процесса теперь заблокирован и ожидает завершения службы.

    Вот автоматически сгенерированный код:

    // Основная точка входа для процесса

    static void Main() {

     System.ServiceProcess.ServiceBase[] ServicesToRun;

     // Более одной службы пользователя может выполняться в одном процессе.

     // Чтобы добавить другую службу в этот процесс, измените следующую

     // строку для создания второго служебного объекта. Например:

     // ServiceToRun = New System.ServiceProcess.ServiceBase[]

     // {

     // new WinService1(), new MySecondUserService()

     // };

     ServiceToRun = new System.ServiceProcess.ServiceBase[] {

      new QuoteService()

     };

     System.ServiceProcess.ServiceBase.Run(ServiceToRun);

    }

    При наличии только одной службы можно удалить в процессе массив. Метод

    Run()
    получает один объект, производный из класса ServiceBase, поэтому функцию Main можно сократить до приведенного здесь кода:

    System,ServiceProcess.ServiceBase.Run(new QuoteService());

    Если существует более одной службы и для них требуется некоторая общая инициализация, то эта общая инициализация должна делаться до метода

    Run()
    , так как основной поток выполнения блокируется, пока служебный процесс не будет остановлен. Последующие инструкции остаются недоступными до конца службы.

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

    Run()
    вовремя. Объект события может затем использоваться для сигнализации того, что поток выполнения закончил свою работу.

    Запуск службы

    При запуске службы вызывается метод

    OnStart()
    . Здесь может начать работу сервер сокета. Чтобы использовать
    QuoteServer
    , должна быть указана сборка
    Quote.Server.dll
    . Поток выполнения, вызывающий
    OnStart()
    , не может быть блокирован, этот метод возвращает управление вызывающей стороне, которая является методом
    ServiceMainCallback()
    класса
    ServiceBase
    . Класс
    ServiceBase
    регистрирует обработчик и информирует SCM после вызова
    OnStart()
    , что служба успешно запущена:

    /// <summary>

    /// Задает вещи по ходу, чтобы служба могла делать свою работу.

    /// </summary>

    protected override void OnStart(string[] args) {

     quoteServer = new QuoteServer(@"c:\Wrox\quotes.txt", 5678);

     quoteServer.Start();

    }

    Переменная

    quoteServer
    объявляется как закрытый член класса:

    namespace Wrox.ProfessionalCSharp {

     public class QuoteService : System.ServiceProcess.ServiceBase {

      /// <summary>

      /// Требуемые переменные проектировщика.

      /// </summary>

      private System.ComponentModel.Container components;

      private QuoteServer quoteServer;

    Методы обработки

    Когда служба заканчивает работу, вызывается метод

    OnStop()
    . Необходимо остановить функциональность службы в этом методе:

    /// <summary>

    /// Остановить эту службу. /// </summary>

    protected override void OnStop() {

     quoteServer.Stop();

    }

    В дополнение к

    OnStart()
    и
    OnStop()
    можно переопределить в классе следующие обработчики:

    □ 

    OnPause()
    вызывается, когда служба должна быть временно остановлена.

    □ 

    OnContinue()
    вызывается, когда служба возвращается к нормальной работе после временной остановки. Чтобы сделать возможным вызов перезагруженных методов
    OnPause()
    и
    OnContinue()
    , свойство
    CanPauseAndContinue
    должно быть задано как true.

    □ 

    OnShutdown()
    вызывается, когда Windows осуществляет выключение системы. Обычно поведение этого метода аналогично реализации
    OnStop()
    : если для выключения потребуется больше времени, то запрашивается дополнительное время. Аналогично
    OnPause()
    и
    OnContinue
    имеется свойство, чтобы включить такое поведение,—
    CanShutdown
    , которое должно быть задано как true.

    □ 

    OnCustomCommand()
    является обработчиком, который обслуживает специальные команды. С помощью специальной служебной управляющей программы службе посылаются особые команды. Как эти команды обрабатываются, определяет реализация
    OnCustomCommand()
    . Этот метод имеет аргумент типа
    int
    , где задается номер специальной команды. Значение может быть в диапазоне от 128 до 256, значения меньше 128 являются зарезервированными системой значениями. В нашей службе повторное чтение файла цитат выполняется с помощью специальной команды 128:

    protected override void OnPause() {

     quoteServer.Suspend();

    }

    protected override void OnContinue() {

     quoteServer.Resume();

    }

    protected override void OnShutDown() {

     OnStop();

    }

    public const int commandRefresh = 128;

    protected override void OnCustomCommand(int command) {

     switch(command) {

     case CommandRefresh:

      quoteServer.RefreshQuotes();

      break;

     default:

      break;

     }

    }

    Как и раньше, необходимо добавить ссылку на файл

    QuoteServer.dll
    .

    Потоки выполнения и службы

    При использовании служб мы имеем дело с потоками выполнения. Как мы говорили ранее, SCM предполагает, что служба отказала, если инициализация продолжается слишком долго. Чтобы справиться с этим, необходимо создать поток выполнения. Метод

    OnStart()
    в служебном классе должен вернуть управление вовремя. Для вызова заблокированного метода, такого как
    AcceptSocket()
    из класса
    TopListener
    , необходимо запустить поток выполнения. Если мы не находимся внутри
    AcceptSocket()
    , то следующий клиент, запрашивающий службу, должен ожидать, пока мы там не окажемся. Это означает, что если для клиента нужно сделать некоторую работу, то используется пул потоков выполнения.

    Установка службы

    Служба должна конфигурироваться в реестре. Все службы можно найти в

    HKEY_LOCAL_MACHINE\System\CurrentControlSetServices
    . Записи реестра можно увидеть с помощью
    regedit
    . Там находятся тип службы, выводимое имя, путь доступа к исполняемому файлу, конфигурация запуска и т.д.

    Эту конфигурацию можно сделать с помощью классов установки из пространства имен

    System.ServiceProcess
    .

    Программы установки

    Можно добавить программу установки в службу, переключаясь в представление конструктора в Visual Studio.NET и выбирая параметр Add Installer из контекстного меню. С помощью этого параметра создается новый класс

    ProjectInstaller
    и экземпляры
    ServiceProcessInstaller
    и
    ServiceInstaller
    :

    Диаграмма классов установки для служб должна помочь пониманию созданного мастером кода:

    Помня об этой диаграмме, пройдем через исходный код в файле

    ProjectInstaller.cs
    , созданный с помощью параметра Add Installer.

    Класс Installer

    Класс

    ProjectInstaller
    выводится из класса
    System.Configuration.Install.Installer
    . Класс
    Installer
    является базовым классом для всех специальных классов установки. С его помощью создается установка на основе транзакций, при которой можно вернуться в предыдущее состояние, если установка отказывает. При откате все изменения, сделанные при установке, будут отменены. Как можно видеть на диаграмме, класс
    Installer
    имеет методы
    Install()
    ,
    Commit()
    ,
    Rollback()
    и
    Uninstall()
    , вызываемые из программ установки.

    Атрибут

    RunInstaller(true)
    означает, что при установке сборки должен вызываться класс
    ProjectInstaller
    . Специальные программы установки действий, а также утилита
    installutil.exe
    (которая будет использоваться позднее) проверяют атрибут:

    using System;

    using System.Collections;

    using System.ComponentModel;

    using System.Configuration.Install;

    namespace Wrox.ProfessionalCSharp {

     /// <summary>

     /// Краткое описание ProjectInstaller

     /// </summary>

     [RunInstaller(true)]

     public class ProjectInstaller : System.Configuration.Install.Installer {

    Классы ServiceProcessInstaller и ServiceInstaller

    Аналогично приложениям Windows Forms метод

    InitializeComponent()
    вызывается внутри конструктора класса
    ProjectInstaller
    . В методе
    InitializeComponent()
    создается экземпляр класса
    ServiceProcessInstaller
    и класса
    ServiceInstaller
    . Оба эти класса выводятся из класса
    ComponentInstaller
    , который сам является
    Installer
    .

    Классы, производные из

    ComponentInstaller
    , используются как части процесса установки. Помните, что служебный процесс может включать более одной службы. Класс
    ServiceProcessInstaller
    применяется для части процесса установки, а класс
    ServiceInstaller
    для части службы, поэтому один экземпляр
    ServiceInstaller
    требуется для каждой службы. Если в процессе имеется три службы, то необходимо добавить дополнительные объекты
    ServiceInstaller
    , в таком случае понадобятся три экземпляра
    ServiceInstaller
    .

      private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1;

      private System.ServiceProcess.ServiceInstaller serviceInstaller1;


      /// <summary>

      /// требуемые переменные конструктора.

      /// </summary>

      private System.ComponentModel.Container components;

      public ProjectInstaller() {

       // Этот вызов затребован конструктором.

       InitializeComponent();

       // TODO: добавить инициализацию после вызова InitComponent

      }


      /// <summary>

      /// Требуемый метод для поддержки конструктора — не изменяйте

      /// содержимое этого метода с помощью редактора кода.

      /// </summary>

      private void InitializeComponent() {

       this.serviceProcessInstaller1 =

        new System.ServiceProcess.ServiceProcessInstaller();

       this.serviceInstaller1 =

        new System.ServiceProcess.ServiceInstaller();

       //

       // serviceProcessInstaller1

       //

       this.serviceProcessInstaller1.Password = null;

       this.serviceProcessInstaller1.UserName = null;

       //

       // serviceInstaller1

       //

       this.serviceInstaller1.ServiceName = "QuoteService";

       //

       // ProjectInstaller

       //

       this.Installers.AddRange(

        new System.Configuration.Install.Installer[] {

        this.serviceProcessInstaller1, this.serviceInstaller1});

      }

     }

    }

    ServiceProcessInstaller
    устанавливает исполняемый файл, который реализует класс
    ServiceBase
    .
    ServiceProcessInstaller
    имеет свойства для всего процесса и для всех служб внутри процесса:

    Свойства
    ServiceProcessInstaller
    Username
    ,
    Password
    Указывают учетную запись пользователя, с которой выполняется служба, если свойство
    RunUnderSystemAccount
    задано как false.
    Account
    С помощью этого свойства можно определить, будет ли служба выполняться с системной учетной записью.
    HelpText
    Свойство только для чтения, которое возвращает справочный текст для задания имени пользователя и пароля.

    ServiceInstaller
    является классом, необходимым для каждой службы. Он имеет свойства, уникальные для каждой службы внутри процесса:
    StartType
    ,
    DisplayName
    ,
    ServiceName
    и
    ServiceDependedOn
    :

    Свойства
    ServiceInstaller
    StartType
    Указывает, запускается ли служба автоматически или вручную. Возможные значения:
    ServiceStartMode.Automatic
    ,
    ServiceStartMode.Manual
    ,
    ServiceStartMode.Disabled
    .
    DisplayName
    Является именем службы, которое выводится пользователю. Это имя используется также многими утилитами управления для контроля и мониторинга службы.
    ServiceName
    Является именем службы. Это значение должно быть идентично свойству
    ServiceName
    класса
    ServiceBase
    в программе службы.
    ServicesDependentOn
    Определяет массив служб, которые должны запускаться, прежде чем можно будет запустить эту службу. Когда служба запускается, все подчиненные службы запускаются автоматически.

    Заметьте, что если изменяется имя службы в классе, производном от

    ServiceBase
    , то также необходимо изменить свойство
    ServiceName
    в объекта
    ServiceInstaller
    .

    Во время тестирования задавайте

    StartType
    как Manual (вручную). Если остановка службы откажет, этот процесс нельзя уничтожить, так как он будет сконфигурирован для выполнения в контексте учетной записи System. Эту конфигурацию можно будет изменить позднее, когда все будет работать правильно.

    ServiceInstallerDialog

    Другим классом установки в пространстве имен

    System.ServiceProcess.Design
    является
    ServiceInstallerDialog
    . Если желательно, чтобы системный администратор вводил имя пользователя и пароль во время установки, может использоваться этот класс.

    Если задать свойства

    Username
    и
    Password
    класса
    ServiceProcessInstaller
    как
    null
    , это диалоговое окно будет автоматически выводиться во время установки. Также можно в это время отменить установку:

    InstallUtil

    После добавления в проект классов для установки можно воспользоваться утилитой

    installutil.exe
    для установки и удаления cлужбы. Ввод командной строки для этих действий выглядит соответственно:

    installutil quoteservice.exe

    installutil /u quoteservice.exe

    Если установка отказывает, проверьте файлы регистрации установки

    InstallUtil.InstallLog
    и
    <имя_службы>.InstallLog
    . Там можно найти очень полезную информацию, такую как "Указанная служба уже существует".

    Клиент 

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

    Мониторинг и управление службой

    Для мониторинга и управления службой имеется несколько утилит. Они относятся к службам консоли ММС, которая, в свою очередь, является частью административной утилиты управления компьютером. Для каждой оконной системы мы получаем также утилиту командной строки

    net.exe
    , позволяющую управлять службами,
    sc.exe
    служит дополнительной утилитой командной строки, которая имеет значительно больше функций, чем команда
    net.exe
    , являющаяся частью Platform SDK. Мы создадим небольшое приложение Windows, использующее класс
    System.ServiceProcess.ServiceController
    для мониторинга и управления службами.

    Консоль управления Microsoft (ММС)

    Используя подключаемый модуль (snap-in) Services из консоли управления Microsoft (ММС), можно увидеть статус всех служб. Также можно послать службам управляющие запросы для останова, включения, выключения и изменения конфигурации. Подключаемый модуль Services является служебной управляющей, а также служебной конфигурационной программой:

    Двойной щелчок на

    QuoteService
    открывает следующее диалоговое окно. Мы видим имя службы, описание, путь доступа к исполняемому файлу, тип запуска и статус. Служба в данный момент запущена. С помощью вкладки Log On в этом диалоговом окне можно изменить учетную запись для процесса службы.

    net.exe

    Подключаемый модуль

    Services
    использовать легко, но системный администратор не может его автоматизировать, так как его нельзя применить внутри административного сценария. Системный администратор может написать программу для Windows Scripting Host, чтобы облегчить свою повседневную работу. Для этой задачи существует утилита командной строки
    net.exe
    , имеющаяся в любой установленной системе Windows. Эта утилита используется для управления службами,
    net start
    показывает все выполняющиеся службы,
    net start имя_службы
    запускает службу,
    net stop имя_службы
    посылает службе запрос останова. Можно также временно остановить и продолжить работу службы с помощью
    net pause
    и
    net continue
    (конечно, если служба это допускает).

    Результаты

    net start
    показаны в консольном окне:

    sc.exe

    Существует малоизвестная утилита

    sc.exe
    , которая является частью Microsoft Platform SDK. Необходимо установить Microsoft Platform SDK, чтобы получить доступ к этой утилите. Microsoft Platform SDK не является частью компакт-диска Visual Studio.NET. Этот дополнительный компакт-диск — часть MSDN, его можно загрузить из Интернета для подписчиков MSDN. Обычный путь доступа для установки такой утилиты —
    с:\Program Files\Microsoft Platform SDK\Bin\WinNT
    .

    sc.exe
    — хорошая утилита для работы со службами. С ней можно сделать значительно больше по сравнению с утилитой
    net.exe
    . С помощью
    sc
    проверяется реальный статус, конфигурируются, удаляются и добавляются службы. Эта утилита помогает в том случае, когда удаление службы не может производиться обычным образом.

    Server Explorer

    Управлять службами можно также с помощью Server Explorer из Visual Studio.NET. Если вы не находите Server Explorer в текущей конфигурации, можно сделать его видимым с помощью меню View|Server Explorer. Выбирая службу и открывая контекстное меню, запускают и останавливают службу. Это контекстное меню также используется для добавления в проект класса

    ServiceController
    . Если вы желаете управлять специфической службой в приложении, перетащите службу из Server Explorer в конструктор — экземпляр
    ServiceController
    добавится в приложение. Свойства этого объекта автоматически задаются для доступа к выбранной службе и создается ссылка на
    System.ServiceProcess.dll
    . Можно использовать этот экземпляр для управления службой таким же образом, как мы это делаем в следующем разделе в базовом приложении для управления всеми службами.

    Класс ServiceController

    Создадим небольшое оконное приложение с помощью класса

    ServiceController
    для мониторинга и управления оконными службами.

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

    Здесь используется класс

    System.ServiceProcess.ServiceController
    , поэтому необходимо иметь ссылку на
    System.ServiceProcess.dll
    .

    Мы реализуем метод

    RefreshServiceList()
    , который вызывается в конструкторе класса
    ServiceControlForm
    . Этот метод заполняет окно списка внешними именами всех служб.
    GetServices()
    является статическим методом класса
    ServiceController
    , и он возвращает массив
    ServiceController
    , представляющий все службы Windows. Класс
    ServiceController
    также имеет статический метод
    GetDevice()
    , который возвращает массив
    ServiceController
    , представляющий все драйверы устройств.

    Окно списка заполняется с помощью связывания данных:

    private System.ServiceProcess.ServiceController[] services;


    public ServiceControlForm() {

     //

     // Требуется для поддержки Windows Form Designer

     //

     InitializeComponent();

     RefreshServiceList();

    }


    protected void RefreshServiceList() {

     services = ServiceController.GetServices();

     listBoxServices.DisplayMember = "DisplayName";

     listBoxServices.DataSource = services;

    }

    Теперь все службы Windows выводятся в окне списка и можно получить данные о каждой службе. Класс

    ServiceController
    имеет следующие свойства для данных о службе:

    Свойства
    ServiceController
    CanPauseAndContinue
    Если службе можно послать запрос pause и continue, то возвращается
    true
    .
    CanShutdown
    true
    , если служба имеет программу обработки для выключения системы.
    CanStop
    true
    , если службу можно остановить.
    DependentServices
    Возвращает совокупность подчиненных служб. Если служба остановлена, то все подчиненные службы заранее останавливаются.
    ServicesDependentOn
    Возвращаем совокупность служб, которые зависят от этой службы.
    DisplayName
    Имя, которое должно выводиться для этой службы.
    MachineName
    Имя машины, на которой выполняется эта служба.
    ServiceName
    Имя службы.
    ServiceType
    Служба может выполняться внутри общего процесса, где более одной службы используют один и тот же процесс (
    Win32ShareProcess
    ), или выполняться так, что существует только одна служба внутри процесса (
    Win32OwnProcess
    ). Если служба может взаимодействовать с рабочим столом компьютера, то тип будет
    InteractiveProcess
    .
    Status
    Статус службы. Статус может быть running, stopped paused или в некотором промежуточном режиме, таком как start pending, stop pending и т.д.

    В рассматриваемом приложении используются свойства

    DisplayName
    ,
    ServiceName
    ,
    ServiceType
    и
    Status
    для вывода данных о службе, а также
    CanPauseAndContinue
    и
    CanStop
    для включения и отключения кнопок
    Pause
    ,
    Continue
    и
    Stop
    .

    Метод

    OnSelectedIndexChanged()
    является методом обработки для окна списка. Он вызывается, когда пользователь
    выбирает
    службу в окне списка. В методе
    OnSelectedIndexChanged()
    внешнее имя и имя свойства задаются непосредственно с помощью свойств класса ServiceController. Статус и тип не могут просто задаваться, так как должна выводиться строка вместо числа, которое возвращает класс
    ServiceController
    . Метод
    SetServiceStatus()
    является вспомогательной функцией, просматривающей перечисление свойств
    Status
    для выводa строки статуса, а также включает и отключает кнопки.
    GetServiceTypeName()
    создает имя типа службы.
    ServiceType
    мы получаем из
    ServiceController.ServiceType
    представляет множество флажков, которые могут комбинироваться с помощью побитового оператора
    ИЛИ
    . Бит
    InteractiveProcess
    может задаваться вместе с
    Win32OwnProcess
    и
    Win32ShareProcess
    . Необходимо проверить, задан ли бит
    InteractiveProcess
    прежде чем переходить к проверке других значений:

    protected string GetServiceTypeName(ServiceType type) {

     string serviceType = "";

     if ((type & ServiceType.InteractiveProcess) != 0) {

      serviceType = "Interactive ";

      type -= ServiceType.InteractiveProcess;

     }

     switch (type) {

     case ServiceType.Adapter:

      serviceType -= "Adapter";

      break;

     case ServiceType.FileSystemDriver:

     case ServiceType.KernelDriver:

     case ServiceType.RecognizerDriver:

      ServiceType += "Driver";

      break;

     case ServiceType.Win32OwnProcess:

      ServiceType += "Win32 Service Process";

      break;

     case ServiceType.Win32ShareProcess;

      ServiceType += "Win32 Shared Process";

      break;

     default:

      ServiceType += "unknown type " + type.ToString();

      break;

     }

     return ServiceType;

    }


    protected void SetServiceStatus(ServiceController controller) {

     buttonStart.Enabled = true;

     buttonStop.Enabled = true;

     buttonPause.Enabled = true;

     buttonContinue.Enabled = true;

     if (!controller.CanPauseAndContinue) {

      buttonPause.Enabled = false;

      buttonContinue.Enabled = false;

     }

     if (!controller.CanStop) {

      buttonStop.Enabled = false;

     }

     ServiceControllerStatus status = controller.Status;

     switch (status) {

     case ServiceControllerStatus.ContinuePending:

      textBoxServiceStatus.Text = "Continue Pending";

      buttonContinue.Enabled = false;

      break;

     case ServiceControllerStatus.Paused;

      textBoxServiceStatus.Text = "Paused";

      buttonPause.Enabled = false;

      buttonStart.Enabled = false;

      break;

     case ServiceControllerStatus.PausePending:

      textBoxServiceStatus.Text = "Pause Pending";

      buttonPause.Enabled = false;

      buttonStart.Enabled = false;

      break;

     case ServiceControllerStatus.StartPending:

      textBoxServiceStatus.Text = "Start Pending";

      buttonStart.Enabled = false;

      break;

     case ServiceControllerStatus.Running:

      textBoxServiceStatus.Text = "Running";

      buttonStart.Enabled = false;

      buttonContinue.Enabled = false;

      break;

     case ServiceControllerStatus.Stopped:

      textBoxServiceStatus.Text = "Stopped";

      buttonStop.Enabled = false;

      break;

     case ServiceControllerStatus.StopPending:

      textBoxServiceStatus.Text = "StopPending";

      buttonStop.Enabled = false;

      break;

     default:

      textBoxServiceStatus.Text = "Unknown status";

      break;

     }

    }


    protected void OnSelectedIndexChanged(object sender, System.EventArgs e) {

     ServiceController controller =

      (ServiceController)listBoxServices.SelectedItem;

     textBoxDisplayName.Text = controllerDisplayName;

     textBoxServiceType.Text =

      GetServiceTypeName(controller.ServiceType);

     textBoxServiceName.Text = controller.ServiceName;

     SetServiceStatus(controller);

    }

    Управление службой

    С помощью класса

    ServiceController
    можно также посылать службе управляющие запросы.

    Методы
    ServiceController
    Start()
    Start()
    сообщает SCM, что служба должна быть запущена. В нашей служебной программе вызывается
    OnStart()
    .
    Stop()
    Stop()
    вызывает
    OnStop()
    в нашей служебной программе с помощью SCM, если свойство
    CanStop
    задано как true в классе службы
    Pause()
    Pause()
    вызывает
    OnPause()
    , если свойство
    CanPauseAndContinue
    задано как true.
    Continue()
    Continue()
    вызывает
    OnContinue()
    , если свойство
    CanPauseAndContinue
    задано как true.
    ExecuteCommand()
    С помощью
    ExecuteCommand
    можно послать службе специальную команду.

    Код для управления службой следует далее. Так как код для запуска, останова, приостановки и временной остановки аналогичен, то используется только одна программа обработки для четырех кнопок:

    protected void buttonCommand_Click(object sender, System.EventArgs e) {

     Cursor Current = Cursors.WaitCursor;

     ServiceController controller =

     (ServiceController)listBoxServices.SelectedItem;

     if (sender == this.buttonStart) {

      controller.Start();

      controller.WaitForStatus(ServiceControllerStatus.Running);

     }  else if (sender == this.buttonStop) {

      controller.Stop();

      controller.WaitForStatus(ServiceControllerStatus.Stopped);

     } else if (sender == this.buttonPause) {

      controller.Pause();

      controller.WaitForStatus(ServiceControllerStatus.Paused);

     } else if (sender == this.buttonContinue) {

      controller.Continue();

      controller.WaitForStatus(ServiceControllerStatus.Running);

     }

     int index = listBoxService.SelectedIndex;

     RefreshServiceList();

     listBoxServices.SelectedIndex = index;

     Cursor.Current = Cursors.Default;

    }


    protected void buttonExit_Click(object sender, System.EventArgs e) {

     Application.Exit();

    }


    protected void buttonRefresh_Click(object sender, System.EventArgs e) {

     RefreshServiceList();

    }

    Это действие может потребовать некоторого времени, поэтому курсор в первой инструкции переключается в курсор ожидания. С помощью метода

    WaitForStatus()
    мы ожидаем максимум только 10 с, пока служба изменит статус на запрошенное значение. После этого времени информация в окне списка обновляется, и выбирается та же служба, чтобы выводился новый статус этой службы.

    Выполняющееся приложение выглядит так:

    Поиск неисправностей

    Поиск неисправностей работает для служб иначе, чем для обычных приложений. Лучший способ образовать службу — создание сначала требуемой функциональности и тестового клиента. В этом случае выполняется нормальная отладка и обработка ошибок. Как только приложение запустится, можно начинать создавать службу, используя эту сборку. Конечно, со службой по-прежнему возможны проблемы:

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

    □ Службу нельзя запустить из отладчика, но отладчик можно присоединить к выполняющемуся процессу службы. Откройте исходный код службы и задайте точки прерывания. В меню Visual Studio.NET Debug выберите Processes и присоедините выполняющийся процесс службы.

    □ Для мониторинга активности служб можно использовать монитор производительности. Добавьте к службе свои собственные объекты производительности, это даст некоторую полезную информацию для отладки. Можно задать объект для указания общего числа отправленных цитат, время, которое необходимо для инициализации и т.д.

    Интерактивные службы

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

    Никогда не открывайте диалоговые окна для служб, выполняющихся на серверной системе. Никто не ответит на это диалоговое окно.

    В тех случаях, где действительно желательно взаимодействие с пользователем, можно сконфигурировать интерактивную службу. Некоторыми примерами таких интерактивных служи являются Print Spooler, который выводит для пользователя сообщения на бумаге, и служба NetMeeting Remote Desktop Sharing.

    Чтобы сконфигурировать интерактивную службу, необходимо задать функцию Allow service to interact with desktop (Разрешить службе взаимодействовать с рабочим столом) в Computer Management. Это изменяет тип службы, добавляя к типу флажок

    SERVICE_INTERACTIVE_PROCESS
    .

    Регистрация событий

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

    ServiceBase
    , автоматически регистрирует события, когда свойство
    AutoLog
    задано как true. Класс
    ServiceBase
    проверяет это свойство и заносит запись в журнал при событиях запуска, остановки, паузы и продолжения. Вот пример журнальной записи, сделанной службой.

    Для регистрации специальных событий можно использовать классы из пространства имен

    System.Diagnostics
    .

    Архитектура регистрации событий

    По умолчанию Event Log (Журнал событий) хранится в трех файлах журналов: Application, Security и System. Просматривая конфигурацию реестра службы регистрации событий, можно увидеть три записи в

    HKLM\System\CurrentControlSet\Services\EventLog
    с конфигурациями, указывающими на определенные файлы. Файл журнала System используется из драйверов системы и устройств, приложения и службы записывают в журнал Application. Security является журналом только для чтения приложений. Свойство аудита операционной системы использует журнал Security.

    Можно прочитать эти события с помощью административной утилиты Event Viewer. Event Viewer запускается непосредственно из Server Explorer, входящего в Visual Studio.NET. Сделайте щелчок правой кнопкой мыши на пункте Event Logs и выберите запись Launch Event Viewer из контекстного меню:

    В журнале событий будет помещена следующая информация:

    □ Type может быть Information, Warning и Error. Information — это редкая успешная операция, Warning — проблема, которая не является немедленно значимой, и Error — основная проблема. Дополнительными типами являются FailureAudit и SuccessAudit, но эти типы используются только для журнала Security.

    □ Date и Time показывают время, когда происходит событие.

    □ Source — имя программного обеспечения, регистрирующего событие. Source для журнала Application конфигурируется в

    HKLM\System\CurrentControlSet\Services\EventLog\Application
    . Под этим ключом конфигурируется значение
    EventMessageFile
    для указания на DLL ресурса, который содержит сообщения об ошибках.

    □ Category можно определить так, чтобы журналы событий фильтровались при использовании Event View.

    □ Идентификатор события определяет сообщение об определенном событии.

    Классы регистрации событий

    Пространство имен

    System.Diagnostics
    имеет несколько классов для регистрации событий:

    □ С помощью класса

    EventLog
    можно прочитать и внести записи в журнал событий, а также определить приложения как источники событий.

    □ 

    EventLogEntry
    является единственным входом в журнал событий. С помощью
    EventLogEntryCollection
    можно просмотреть
    EventLogEntry
    .

     Класс

    EventLogInstaller
    предназначен для установки компонента
    EventLog
    .
    EventLogInstaller
    вызывает
    EventLog.CreateEventSource()
    для создания источника событий.

    С помощью

    EventLogTraceListener
    можно записать в журнал событий трассировки. Этот класс реализует абстрактный класс
    TraceListener
    .

    Добавление регистрации событий

    Если свойство

    AutoLog
    класса
    ServiceBase
    задано как true, то автоматически включается регистрация событий. Класс
    ServiceBase
    регистрирует информационное событие при запросах службы для запуска, остановки, паузы и продолжения. В классе
    ServiceInstaller
    создается экземпляр
    EventLogInstaller
    , чтобы сконфигурировать источник журнала событий. Этот источник журнала событий имеет такое же имя, как и служба. Для записи события используем статический метод
    WriteEntry()
    класса
    EventLog
    . Свойство
    Source
    было уже задано в классе
    ServiceBase
    :

    EventLog.WriteEntry("event log message");

    Этот метод регистрирует информационное событие. Если должно быть создано событие предупреждения или ошибки, то для определения этого типа используется перезагруженный метод

    WriteEvent()
    :

    EventLog.WriteEntry("event log message", EventLogEntryType.Warning);

    EventLog.WriteEntry("event log message", EventLogEntryType.Error);

    Добавление регистрации событий в другие типы приложений

    Для служб класс

    ServiceBase
    автоматически добавляет свойства регистрации событий. Если желательно использовать регистрацию событий в других типах приложений, это легко делается с помощью Visual Studio.NET.

     Используйте ToolBox для добавления компонента

    EventLog
    в конструктор.

     Задайте свойство

    Log
    компонента
    EventLog
    как
    Application
    , а свойство
    Source
    как выбранное имя. Обычно это бывает имя приложения, которое показано в Event View.

    □ Теперь можно записать журналы с помощью метода

    WriteEntry()
    экземпляра
    EventLog
    .

    □ Можно добавить программу установки из пункта контекстного меню Add Installer компонента

    EventLog
    . Это создает класс
    ProjectInstaller
    , который конфигурирует источник событий в реестре.

    □ С помощью команды

    installutil
    теперь можно зафиксировать приложение,
    installutil
    вызывает класс
    ProjectInstaller
    и регистрирует источник событий.

    Для установки типа хсору последние два шага на самом деле не нужны. Если задано свойство

    Source
    экземпляра
    EventLog
    , источник автоматически регистрируется, когда журнал событий заполняется в первый раз. Это действительно легко сделать, но для реального приложения предпочтительнее добавить программу установки: с помощью
    installutil /u
    конфигурация регистрации событий отменяется. Если приложение просто удаляется, этот ключ реестра остается, если не будет вызван метод
    EventLog.DeleteEventSource()
    .

    Трассировка

    Можно сделать так, чтобы все сообщения трассировки направлялись в журнал событий. На самом деле это не нужно, так как в нормально работающей системе журнал событий будет перегружен сообщениями трассировки. Системный администратор пропустит действительно важные записи, если это произойдет. Помните, что тип записи события — это ошибка, предупреждение и информационное. При этом информационные сообщения редко являются информацией об успехе. Включение трассировочных сообщений в журнал событий может быть полезным свойством для тестирования проблемных служб. Трассировка возможна как с отладочным, так и с окончательным кодом.

    Чтобы послать трассировочные сообщения в журнал событий, должен быть создан объект

    EventLogTraceListener
    и добавлен в список приемника класса
    Trace
    :

    EventLogTraceListener listener = new EventLogTraceListener(eventLog1);

    Trace.Listeners.Add(listener);

    Теперь все трассировочные сообщения посылаются в журнал событий:

    Trace.WriteLine("trace message");

    Дополнительная информация о методах трассировки находится в главе 6.

    Создание приемника событий

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

    Quote
    :

    Оконное приложение имеет только окно списка и кнопку выхода:

    Компонент

    EventLog
    добавляется в этот проект перетаскиванием его из панели инструментов. Свойство
    Log
    задается как
    Application
    , a
    Source
    как источник службы
    QuoteService
    . Класс
    EventLog
    также имеет свойство
    EnableRaisingEvents
    . До сих пор мы не говорили об этом свойстве. По умолчанию для него используется значение false, задание его как true означает, что событие создается каждый раз, когда происходит это событие, и можно написать обработчик событий для оконного события
    EntryWritten
    .

    В файле

    EventListener.cs
    свойства задаются в методе
    InitializeComponent()
    :

    private void InitializeComponent() {

     this.eventLogQuote = new System.Diagnostics.EventLog();

     this.buttonExit = new System.Windows.Forms.Button();

     this.listBoxEvents = new System.Windows.Forms.ListBox();

     ((System.ComponentModel.ISupportInitialize)

      (this.eventLogQuote)).BeginInit();

     this.SuspendLayout();

     //

     // eventLogQuote

     //

     this.eventLogQuote.EnableRaisingEvents = true;

     this.eventLogQuote.Log = "Application";

     this.eventLogQuote.Source = "QuoteService";

     this.eventLogQuote.SynchronizingObject = this;

     this.eventLogQuote.EntryWritten +=

      new System.Diagnostics.EntryWrittenEventHandler(this.OnEntryWritten);

     // ...

    Программа обработки

    OnEntryWritten()
    получает объект
    EntryWrittenEventArgs
    в качестве аргумента, где можно получить всю информацию из события. С помощью свойства
    Entry
    мы получаем объект
    EventLogEntry
    с информацией о времени, источнике события, типе, категории и т. д.:

    protected void OnEntryWritten(object sender, System.Diagnostics.EntryWrittenEventArgs e) {

     DateTime time = e.Entry.TimeGenerated;

     string message = e.Entry.Message;

     listBoxEvents.Items.Add(time + " " + message);

    }

    Выполняющееся приложение показывает все события для

    QuoteService
    :

    Мониторинг производительности

    Мониторинг производительности может использоваться для получения информации о нормальном выполнении службы. Это прекрасный инструмент, который помогает понять нагрузку системы и наблюдать изменения и тенденции.

    Windows 2000 имеет множество объектов производительности, таких как

    System
    ,
    Memory
    ,
    Objects
    ,
    Process
    ,
    Processor
    ,
    Thread
    ,
    Cache
    и т. д. Каждый из этих объектов имеет множество показателей для мониторинга. С помощью объекта Process для всех процессов или для определенных экземпляров процессов можно контролировать время пользователя, счетчик дескрипторов. Ошибки страниц, счетчик потоков выполнения и т. д. В некоторых приложениях также имеются специфические объекты, например SQL Server.

    Для нашей службы цитат может представлять интерес получение информации о числе клиентских запросов, размере данных, посылаемых по каналам связи, и т.д.

    Классы мониторинга производительности

    Пространство имен

    System.Diagnostics
    имеет следующие классы для мониторинга производительности:

    □ 

    PerformanceCounter
    используется как для мониторинга счетчиков, так и для записи счетчиков. С помощью этого класса можно создавать новые категории производительности.

    □ С помощью класса

    PerformanceCounterCategory
    можно пройти через все существующие категории, а также создать новые. Программным путем получаются все счетчики категории.

    □ Класс

    PerformanceCounterInstaller
    используется для установки счетчиков производительности, аналогично классу
    EventLogInstaller
    , о котором упоминалось ранее.

    Построитель счетчиков производительности 

    Можно создать новую категорию, выбирая счетчики производительности в Server Explorer. Категория называется

    Quote Service
    . В таблице показаны все счетчики производительности нашей службы:

    Имя Описание Тип
    # of Bytes sent Общее число байтов, посланных клиенту.
    NumberOfItems32
    # of Bytes sent/sec Число байтов, посылаемых клиенту в одну секунду.
    NumberOfItems32
    # of Requests Общее число запросов.
    NumberOfItems32
    # of Requests /sec Число запросов в одну секунду.
    NumberOfItems32

    Построитель счетчика производительности записывает конфигурацию в базу данных производительности. Это может также делаться динамически с помощью метода

    Create()
    класса
    PerformanceCategory
    в пространстве имен
    System.Diagnostics
    . Программу установки для других систем можно легко добавить в последующем с помощью Visual Studio.NET.

    Построитель счетчика производительности запускается из Server Explorer при выборе контекстного меню Performance Counters|Create New Category:

    Добавление счетчиков производительности

    Теперь мы хотим добавить счетчики производительности в сервер цитат. Класс

    QuoteServiсе
    не располагает информацией, необходимой для счетчиков производительности. Мы хотим получить число запросов, но после запуска службы
    QuoteService
    не получает запросов. Информация полностью содержится в классе
    QuoteServer
    , созданном ранее.

    Добавление поддержки Visual Studio.NET Designer в библиотеку классов

    Можно вручную добавить в код экземпляры класса

    PerformanceCounter
    либо использовать приложение Visual Studio.NET Designer. С его помощью перетаскиваются компоненты
    PerformanceCounter
    из панели инструментов на его рабочую поверхность. Поддержку легко добавить в библиотеку компонентов, выводя класс из
    System.ComponentModel.Component
    . Метод
    InitializeComponent()
    , который используется для задания свойств компонентов, будет исполняться автоматически, необходимо добавить лишь его вызов.

    Добавление компонентов PerformanceCounter

    Далее можно добавить компоненты

    PerformanceCounter
    из панели инструментов. Для нашей службы добавляется четыре экземпляра, где свойство
    CategoryName
    задается как Quote Service Count для всех объектов, а свойство
    CounterName
    задается одним из значений, доступным в выбранной категории. Свойство
    ReadOnly
    должно быть задано как
    False
    .

    Код, который был внесен в

    InitializeComponent()
    путем добавления
    Components
    в конструктор и заданием свойств, выглядит так:

    private void InitializeComponent() {

     // ...

     //

     // performanceCounterRequestsPerSec

     //

     this.performanceCounterRequestsPerSec.CategoryName =

      "Quote Service Counts";

     this.performanceCounterRequestsPerSec.CounterName =

      "# of Requests / sec";

     this.performanceCounterRequestsPerSec.ReadOnly = false;

     //

     // performanceCounterBytesSentTotal

     //

     this.performanceCounterBytesSentTotal.CategoryName =

      "Quote Service Counts";

     this.performanceCounterBytesSentTotal.CounterName =

      "# of Bytes sent";

     this.performanceCounterBytesSentTotal.ReadOnly = false;

     //

     // performanceCounterBytesSentPerSec

     //

     this.performanceCounterBytesSentPerSec.CategoryName =

      "Quote Service Counts";

     this.performanceCounterBytesSentPerSec.CounterName =

      "# of Bytes sent / sec";

     this.performanceCounterBytesSentPerSec.ReadOnly = false;

     //

     // performanceCounterRequestsTotal

     //

     this.performanceCounterRequestsTotal.CategoryName =

      "Quote Service Counts";

     this.performanceCounterRequestsTotal.CounterName =

      "# of Requests";

     this.performanceCounterRequestsTotal.Readonly = false;

     // ...

    Счетчики производительности, которые показывают общие значения, увеличиваются в методе

    Listener()
    класса
    QuoteServer
    . Метод
    Increment()
    увеличивает счетчик на 1, метод
    IncrementBy()
    увеличивает счетчик на значение аргумента.

    Для счетчиков производительности, которые показывают посекундные значения, в методе

    Listener()
    обновляются только две переменные —
    requestPerSec
    и
    bytessPerSec
    :

    void protected void Listener() {

     try {

      listener = new TCPListener(port);

      listener.Start();

      while (true) {

       Socket socket = listener.Accept();

       if (socket == null) {

        return;

       }

       string message = GetRandomQuoteOfTheDay();

       UnicodeEncoding encoder = new UnicodeEncoding();

       byte [] buffer = encoder.GetBytes(message);

       socket.Send(buffer, buffer.Length, 0);

       socket.Close();

       performanceCounterRequestsTotal.Increment();

       performanceCounterBytesSentTotal.IncrementBy(nBytes);

       requestsPerSec++;

       bytesPerSec += Bytes;

      }

     } catch (Exception e) {

      string message = "Quote Server failed in Listener: " + e.Message;

      eventLog.WriteEntry(message, EventLogEntryType.Error);

     }

    }

    Чтобы показывать обновленные значения каждую секунду, используется компонент

    Timer
    . Метод
    OnTimer()
    вызывается раз в секунду и задает счетчики производительности с помощью свойства
    RawValue
    класса
    PerformanceCounter
    :

    protected void OnTimer(object sender, system.EventArgs e) {

     performanceCounterBytesSentPerSec.RawValue = bytesPerSec;

     performanceCounterRequestsPerSec.RawValue = reguestsPerSec;

     bytesPerSec = 0;

     requestsPerSec = 0;

    }

    perfmon.exe

    Теперь можно контролировать нашу службу. Утилита Performance может запускаться из Administrative Tools|Performance. Нажимая кнопку + в панели инструментов, можно добавить счетчики производительности. Quote Service будет определяться как объект производительности. Все сконфигурированные счетчики показаны в списке счетчиков:

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

    Служба счетчика производительности

    Если не существует ссылки на объект счетчика производительности, и объект был удален сборщиком мусора, счетчик этого объекта теряется и запускается в следующий раз с 0. Чтобы решить эту проблему, состояние можно хранить в службе счетчиков производительности. С помощью .NET Framework служба счетчиков производительности устанавливается в системе. Необходимо только запустить службу; можно также сконфигурировать службу для автоматического запуска во время начальной загрузки системы.

    Свойства служб Windows 2000

    Windows 95, 98 и ME не поддерживают службы Windows. Они поддерживаются в Windows NT, Windows 2000 и Windows ХР. Windows 2000 имеет несколько больше свойств для служб по сравнению с Windows NT. Рассмотрим свойства служб в Windows 2000.

    Изменения сетевого соединения и события электропитания

    В Windows 2000 не требуется, чтобы система перезагружалась так часто, как это было необходимо в Windows NT; например, не нужно перезагружать систему, когда изменяется адрес IP, — служба получает события при смене адреса и действует соответственно. Windows 2000 посылает следующие управляющие коды службам, когда изменяется сетевое соединение:

    Управляющий код
    SERVICE_CONTROL_NETBINDADD
    Доступен новый компонент для соединения.
    SERVICE_CONTROL_NETBINDREMOVE
    Компонент для соединения был удален. Необходимо заново считать информацию соединения и отсоединиться от удаленного компонента.
    SERVICE_CONTROL_NFTBINDENABLED
    Ранее отключенное соединение снова включено.
    SERVICE_CONTROL_NETBINDDISABLE
    Ранее включенное соединение теперь отключено.

    Если служба использует соединение, необходимо заново прочитать информацию соединения и удалить соединения, которые стали недоступными. Служба реагирует на сетевые изменения, поэтому перезагрузка не требуется.

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

    Для всех событий электропитания служба получает управляющий код

    SERVICE_CONTROL_POWEREVENT
    с дополнительными параметрами. В параметре отражена причина события. Код причины может говорить о разряженности батареи, о том, что система переходит в приостановленное состояние, или об изменении статуса электропитания. В зависимости от кода причины служба должна замедлить скорость, приостановить фоновые потоки выполнения, закрыть сетевые соединения, закрыть файлы и т.д.

    Классы в пространстве имен

    System.ServiceProcess
    также имеют поддержку для этих свойств Windows 2000 Служба конфигурируется так, чтобы она реагировала на события паузы и продолжении с помощью свойства
    CanPauseAndContinue
    , и задается свойство для управления электропитанием:
    CanHandlePowerEvent
    . Службы Windows 2000, которые управляют электропитанием, регистрируются в SCM с помощью метода API Win32
    RegisterServiceCtrlHandlerEx()
    .

    Задавая значение

    CanHandlePowerEvent
    как
    True
    , метод

    protected virtual bool OnPowerEvent(PowerBroadcastStatus power Status);

    будет вызываться, как только изменится статус электропитания. Некоторые из значений, получаемые из

    PowerBroadcastStatus
    , перечислены ниже в таблице:

    Значение
    powerStatus
    Описание
    BatteryLow
    Слабый заряд батареи. Необходимо сократить функционирование службы до минимума.
    PowerStatusChange
    Произошло переключение электропитания от батареи на внешний источник, или мощность батареи опустилась ниже допустимого значения и т.д.
    QuerySuspend
    Полномочия системных запросов перешли в приостановленный режим. Можно отказаться от полномочий или приготовиться к переходу в приостановленный режим, закрывая файлы, разъединяя сетевые соединения и т.д.
    QuerySuspendFailed
    Переход в приостановленный режим был отвергнут системой. Можно продолжать с той же функциональностью.
    Suspend
    Никто не отменил запрос перехода в приостановленный режим. Система скоро будет приостановлена.

    Восстановление

    Автоматическое восстановление является вопросом конфигурации, оно используется для всех свойств выполняющихся в системе Windows 2000. Если процесс службы разрушается, то служба автоматически запускается снова или конфигурируется специальный файл, или автоматически перезагружается вся система. Обычно существует причина разрушения службы, и нежелательно автоматически непрерывно перезагружать систему, поэтому нужно разнообразить ответы на первую, вторую и последующие ошибки.

    Приложения COM+ в роли служб

    Начиная с Windows ХР (кодовое имя Whistler), приложение COM+ выполняется как служба. В Windows ХР служба имеет прямой доступ к таким службам COM+, как транзакции, пулы объектов, пулы потоков выполнения и т.д. Если желательно использовать службы COM+ в Windows 2000 как службы Windows, то создаются два отдельных приложения: одно имеет дело с функциями службы, а второе — со службами COM+. Это нам дает некоторые преимущества:

    □ Легче создать служебное приложение. Нам не нужно больше иметь дело со специальной установкой службы, так как это выполняется прямо из конфигурации COM+.

    □ Приложение COM+ может действовать как служба. Оно автоматически запускается во время начальной загрузки, имеет права учетной записи System и реагирует на управляющие коды службы, которые посылаются из управляющей программы службы.

    □ Служебное приложение, создаваемое как приложение COM+, имеет прямой доступ к таким службам COM+, как управление транзакциями, пулы объектов, пулы потоков выполнения и т.д.

    Больше о службах COM+ можно прочитать в главе 20.

    Заключение

    В этой главе было показано, что такое службы Windows и как они создаются с помощью .NET Framework. Приложения в службах Windows запускаются автоматически во время начальной загрузки, и мы можем использовать привилегированную учетную запись System в качестве пользователя службы. .NET Framework обладает хорошей поддержкой служб. Весь вспомогательный код, который требуется для создания, управления и установки служб, находится в классах .NET Framework в пространстве имен

    System.ServiceProcess
    . С помощью классов
    System.Diagnostic
    становятся легко доступными все технологии, необходимые для таких служб, как регистрация событий и мониторинг производительности.







     

    Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх