|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Глава 24Службы Windows В главе 22 рассматривается работа в сети, глава 23 охватывает работу с серверами с помощью .NET Remoting. Описанные серверные процессы запускаются вручную. Однако программы должны начинать работать автоматически во время запуска машины. Здесь на помощь приходят службы Windows. В этой главе мы рассмотрим следующие вопросы: □ Архитектура служб Windows, функциональность служебной программы, служебная управляющая программа, и служебная конфигурационная программа. □ Реализация службы с помощью классов, находящихся в пространстве имен System.ServiceProcess. □ Программы установки для конфигурирования службы в реестре. □ Написание программы для управления службой с помощью класса ServiceController. □ Реализация обработки событий. Так как службы обычно не имеют интерфейса пользователя, ошибки не могут выводиться в окне сообщений. Обработка событий является хорошим способом сообщения об ошибках. □ Производительность службы. Мониторинг производительности может использоваться для получения информации о нормальном выполнении службы. Вначале мы рассмотрим, что же такое службы. Понятие службыСлужбы Windows являются приложениями, которые начинают работать автоматически при запуске операционной системы. Они могут выполняться без интерактивного взаимодействия с пользователем системы. Можно сконфигурировать службу под требования специально сконфигурированным пользователем или таким пользователем System, который имеет больше привилегий, чем системный администратор.
Вот несколько примеров таких служб: □ Простая служба 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.
После нажатия OK для создания приложения Windows Service появится окно проектировщика, как в приложениях Windows Forms, но здесь нельзя вставлять компоненты Windows Forms. Окно проектировщика будет использоваться для добавления других компонентов, таких как счетчики производительности и регистрации событий: Выбор свойств этой службы открывает окно редактора свойств: Сконфигурируем свойства службы: □ AutoLog означает, что события автоматически регистрируются для запуска и остановки службы. □ CanPauseAndContinue, CanShutdown и CanStop означают, что служба может обрабатывать специальные запросы pause, continue, shutdown и stop. □ ServiceName является именем службы, которое записывается в реестр и используется для управления службой. □ CanHandlePowerEvent является допустимым параметром для служб, работающих на системе Windows 2000. Мы поговорим о параметрах power позже.
Изменение этих свойств с помощью редактора свойств задает значения нашего класса, производного из 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имеет свойства для всего процесса и для всех служб внутри процесса:
ServiceInstallerявляется классом, необходимым для каждой службы. Он имеет свойства, уникальные для каждой службы внутри процесса: StartType, DisplayName, ServiceNameи ServiceDependedOn:
ServiceInstallerDialogДругим классом установки в пространстве имен System.ServiceProcess.Designявляется ServiceInstallerDialog. Если желательно, чтобы системный администратор вводил имя пользователя и пароль во время установки, может использоваться этот класс. Если задать свойства Usernameи Passwordкласса ServiceProcessInstallerкак null, это диалоговое окно будет автоматически выводиться во время установки. Также можно в это время отменить установку: InstallUtilПосле добавления в проект классов для установки можно воспользоваться утилитой installutil.exeдля установки и удаления cлужбы. Ввод командной строки для этих действий выглядит соответственно: installutil quoteservice.exe installutil /u quoteservice.exe
КлиентПосле успешной установки службы и запуска ее вручную (см. следующий раздел для дальнейших указаний) можно запустить клиент, используя службы ММС со следующими настройками. Мониторинг и управление службойДля мониторинга и управления службой имеется несколько утилит. Они относятся к службам консоли ММС, которая, в свою очередь, является частью административной утилиты управления компьютером. Для каждой оконной системы мы получаем также утилиту командной строки 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имеет следующие свойства для данных о службе:
В рассматриваемом приложении используются свойства 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можно также посылать службе управляющие запросы.
Код для управления службой следует далее. Так как код для запуска, останова, приостановки и временной остановки аналогичен, то используется только одна программа обработки для четырех кнопок: 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");
Создание приемника событийТеперь было бы полезно создать приложение, которое получает событие, когда в службе происходит что-то плохое. Мы создадим простое оконное приложение, отслеживающее события службы 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. В таблице показаны все счетчики производительности нашей службы:
Построитель счетчика производительности записывает конфигурацию в базу данных производительности. Это может также делаться динамически с помощью метода 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 2000Windows 95, 98 и ME не поддерживают службы Windows. Они поддерживаются в Windows NT, Windows 2000 и Windows ХР. Windows 2000 имеет несколько больше свойств для служб по сравнению с Windows NT. Рассмотрим свойства служб в Windows 2000. Изменения сетевого соединения и события электропитанияВ Windows 2000 не требуется, чтобы система перезагружалась так часто, как это было необходимо в Windows NT; например, не нужно перезагружать систему, когда изменяется адрес IP, — служба получает события при смене адреса и действует соответственно. Windows 2000 посылает следующие управляющие коды службам, когда изменяется сетевое соединение:
Если служба использует соединение, необходимо заново прочитать информацию соединения и удалить соединения, которые стали недоступными. Служба реагирует на сетевые изменения, поэтому перезагрузка не требуется. 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, перечислены ниже в таблице:
ВосстановлениеАвтоматическое восстановление является вопросом конфигурации, оно используется для всех свойств выполняющихся в системе 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 | Добавить материал | Нашёл ошибку | Наверх | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|