Co je to Windows Service?
Windows service (dále jen service, či česky služba) je druh aplikace, která na rozdíl od klasických programů běží na pozadí systému. Důležitý je fakt, že služba nevyžaduje pro svůj běh, aby byl přihlášený uživatel. Z toho důvodu nemají žádné grafické rozhraní a běží bez přímé interakce s uživatelem. Příkladem může být webový server, který očekává příchozí HTTP spojení a obsluhuje požadavky.
Některé služby se spouští se startem systému a zastavují s jeho ukončením. Jiné se spouštějí až na vyžádání nebo jejich start není povolen vůbec, protože jejich běh není aktuálně v systému nutný.
Nezanedbatelné množství služeb využívá sám systém k obsluze zcela základních funkcí. Příkladem může být služba pro zpracování audio výstupu, cache DNS záznamů, firewall, kryptografické služby, plánovač úloh a celá řada dalších. Při doinstalování různých komponent samozřejmě počet služeb může narůstat – například databázový nebo webový server.
Seznam instalovaných služeb
Rychlý přehled služeb nainstalovaných v počítači lze zobrazit pomocí aplikace Windows Task Manager (po stisknutí Ctrl+Alt+Delete nebo spuštěním taskmgr.exe):
Zde podle sloupce “Status” vidíme, zda konkrétní služby běží (Running) nebo jsou zastaveny (Stopped).
Pokud však chcete provádět i konfiguraci, využijete konzoli pro správu služeb přístupnou přes:
Ovládací panely > Nástroje pro správu > Služby
Nebo spuštěním:
services.msc
Konzole obsahuje seznam služeb s možností jejich zastavování, spouštění, restartování, či konfigurací:
Poznámka: Za určitých okolností lze zvýšit výkon (či snížit paměťovou náročnost) systému vypínáním nepotřebných služeb. Toto doporučuji pouze v případech, kdy přesně víte, k čemu služba slouží a máte jistotu, že tím neovlivníte jinou část systému.
Co to je služba?
Windows služba je fyzicky exe soubor, který však není možné přímo spustit. Obvykle po manuálním spuštění vypíše hlášku, že se jedná o službu a nelze jej spustit přímo, či se ihned ukončí.
Vnitřně je služba postavená tak, aby s ní dokázal komunikovat systém. Konkrétně, aby dokázal službě předávat příkazy. Těmi nejdůležitějšími jsou “Start” a “Stop” pro spuštění a zastavení. Případně “Pause” a “Continue”, pokud služba podporuje dočasné pozastavení.
Velkým rozdílem oproti běžným aplikacím je způsob vývoje. Ve službě neběží hlavní vlákno, pouze dostáváme signály "spustit službu”, “zastavit službu” a podobné další. Je jen na nás jak při těchto událostech budeme reagovat a jaké části našeho systému spustíme.
Aby bylo službu možné vidět v seznamu služeb, měnit jí konfiguraci a samozřejmě spouštět, je potřeba provést její registraci do systému. O tom budu psát dále.
Co jsou to procesy “svchost.exe”?
Při zobrazení běžících procesů si můžete všimnout, že v systému žije řada instancí služby “svchost.exe” – zkratka pro Service host. Tato aplikace se stará o hostování služeb, které nejsou ve formátu exe, ale dll a nelze je tak zaregistrovat jako službu přímo. Jinými slovy je “svchost.exe” pouze běhové prostředí pro určitý typ služeb ve formátu dll.
Pokud tedy máme s tímto procesem problémy (padá, je na 100% vytížený atp.), většinou se nejedná o problém se “svchost.exe” ale se službou, kterou hostuje.
Těchto pár vět o “svchost.exe” je pouze informativních. V tomto článku se totiž budu věnovat psaní běžné služby (ve formátu exe) a nebudeme proto nijak tento proces potřebovat.
Windows Service v C#
Windows služby lze velmi pohodlně psát v prostředí .NET Framework. Následující řádky popisují, jak službu naprogramovat a zaregistrovat do systému.
Založení projektu
Založte si nový projekt typu Windows Service. Využít můžete jak C#, tak Visual Basic .NET. Používám Visual Studio 2010. Téměř identicky však vše probíhá i pod Visual Studio 2008. Projekt jsem si pojmenovat “Jecha.Samples.MyWindowsService”. Výstupem tedy bude ”Jecha.Samples.MyWindowsService.exe”.
V projektu se založí 2 důležité soubory:
- Program – řeší spouštění služby
- Service1 – implementace chování služby
Pokud aplikaci spustíme, zobrazí se hláška o tom, že nelze službu spustit přímo – je potřeba ji nejprve zaregistrovat:
Cannot start service from the command line or a debugger. A Windows Service must first be installed (using installutil.exe) and then started with the ServerExplorer, Windows Services Administrative tool or the NET START command.
Toto zajistí kód v třídě Program. Díky tomu dokáže systém Windows zajistit komunikaci s třídou Service1 obsahující konkrétní chování služby při instalaci a jejím spuštění. Pokud však tento kód odstraníme a nahradíme ho například za kód pro zobrazení formuláře, stane se ze služby běžná Windows aplikace.
Nyní se podívejme na kód služby do souboru Service1. Po rozkliknutí se zobrazí návrhář s touto hláškou:
To add componets to your class, drag them from the Toolbox and use the Properties window to set their properties. To create methods and events for your class, click here to switch to code view.
My však návrhář nebudeme nijak potřebovat a proto zvolíme “Click here to switch to code view”. Existuje malý trik, kterým zamezíte zobrazování návrháře, který je v případě služeb zcela zbytečný. Vložte nad třídu služby tento atribut:
[System.ComponentModel.DesignerCategory("")]
Návrhář s neužitečnou hláškou se pak při otevřením služby již nebude zobrazovat. Někdy však chvilku trvá, než Visual Studio atribut vezme v potaz. Občas je potřeba celé Visual Studio zavřít a zase otevřít.
Celý kód třídy pak bude:
[System.ComponentModel.DesignerCategory("")]
public partial class Service1 : ServiceBase
{
public Service1()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
}
protected override void OnStop()
{
}
}
V této třídě nás zajímají především metody “OnStart”, která se vyvolá při spuštění služby a “OnStop”, která se vyvolá při zastavování.
Akce při spouštění a zastavování služby
Délka spouštění a zastavování služby odpovídá době, kterou se stráví prováděním OnStart a OnStop metod. Jinak řečeno, pokud bude metoda OnStart trvat nekonečnou dobu, služba se nikdy nespustí. Účel těchto metod je tedy službu inicializovat a nastartovat a měl by trvat co nejkratší dobu. Zároveň úspěšné provedení těchto metod značí úspěšnost startu služby. Například pokud vyvoláte při spouštění v metodě OnStart výjimku, systém oznámí chybu při spouštění a proces služby se ihned ukončuje.
Pro demonstraci implementujeme jednoduchý časovač, který se spustí se startem služby a ukončí jejím uzavřením. Každou vteřinu přečteme vytížení procesoru a zapíšeme jej do souboru. V kódu jsem použil následující pevnou cestu:
Poznámka: Vytvořte si složku “c:\temp” nebo změňte cestu k souboru. Pokud tato složka nebude existovat, výsledek se nebude nikam zapisovat.
Zde je kód celé služby:
[System.ComponentModel.DesignerCategory("")]
public partial class Service1 : ServiceBase
{
public Service1()
{
InitializeComponent();
}
PerformanceCounter counter;
System.Timers.Timer timer = new System.Timers.Timer();
string fileName;
bool isStopping;
protected override void OnStart(string[] args)
{
// vytvořit performance counter pro zjištění zatížení procesoru
counter = new PerformanceCounter();
counter.CategoryName = "Processor";
counter.CounterName = "% Processor Time";
counter.InstanceName = "_Total";
// cesta, kam se budou data ukládat
fileName = @"c:\temp\cpu.txt";
// vytvořit timer, který bude každou vteřinu data zapisovat
timer.Interval = 1000;
timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
timer.AutoReset = false;
timer.Start();
}
protected override void OnStop()
{
// oznámit zastavení služby
isStopping = true;
}
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// zastavuje se služba? pak neprovádět další krok
if (isStopping)
return;
try
{
// naformátovat hlášku, která bude do logu zapsána
string message =
string.Format("{0:HH:mm:ss} - CPU usage {1}%",
DateTime.Now,
counter.NextValue());
// zapsat hlášku
System.IO.File.AppendAllText(fileName, message + Environment.NewLine);
}
finally
{
// po provedení kroku znovu spustit časovač
timer.Start();
}
}
}
Poznámka: Můžete si všimnout nastavené vlastnosti AutoReset = false u časovače. To zajištuje, že se po vykonání události Elapsed časovač zastaví. Já jej znovu spouštím až po dokončení této události v bloku finally. Tím docílíme, že se časovač bude spouštět až po ukončení předchozího zápisu a nikdy se nemůže vykonávat více zápisů paralelně. Časovač by bez této úpravy totiž po uplynutí intervalu vytvářel nové vlákno i pokud to předchozí ještě nedoběhlo.
Nyní máme připravenou službu a čeká nás její registrace do systému.
Registrace služby do systému
Při registraci potřebujeme definovat primárně následující nastavení:
- Jméno služby – ideálně bez diakritiky (diakritika může být problematické na starších systémech) – unikátní název služby
- Zobrazované jméno služby – jméno služby, které se zobrazí v seznamu služeb (může být s diakritikou), nepoužívá se k identifikaci služby, je pouze informativní
- Popis služby – informativní popis služby (může být delší text) – zobrazuje se v konzoli služeb při označení konkrétní služby v levém panelu nebo při zobrazení vlastností služby
- Účet, pod kterým se služba spouští – ačkoliv služby nevyžadují pro svůj běh přihlášeného uživatele (plochu), je nutné je spustit s určitou identitou – pod určitým uživatelem.
- Způsob spouštění – volíme, zda se služba spustí ihned po spuštění systému (Automatic), na vyžádání (Manual) a nebo je spouštění dočasně zakázáno (Disabled).
Dále lze nadefinovat:
- Závislosti – volitelně lze předat pole obsahující seznam jmen služeb, které jsou vyžadované pro spuštění
- Odložený start – služba se bude spouštět až po spuštění všech služeb, které odložený start nemají nastavený. Tato volba je defaultně vypnuta.
Registraci můžete provést několika způsoby. Já osobně preferuji vytvoření tzv. instalační třídy. Do ukázkového projektu přidejte třídu Service1Installer:
[RunInstaller(true)]
[System.ComponentModel.DesignerCategory("")]
public class Service1Installer: Installer
{
public Service1Installer()
{
ServiceProcessInstaller serviceProcessInstaller =
new ServiceProcessInstaller();
ServiceInstaller serviceInstaller = new ServiceInstaller();
// spouštění pod účtem LocalSystem
serviceProcessInstaller.Account = ServiceAccount.LocalSystem;
serviceProcessInstaller.Username = null;
serviceProcessInstaller.Password = null;
// unikátní název, název pro zobrazení a popis
serviceInstaller.ServiceName = "MyWindowsService";
serviceInstaller.DisplayName = "My service";
serviceInstaller.Description = "Popis služby";
// způsob spouštění
serviceInstaller.StartType = ServiceStartMode.Automatic;
this.Installers.Add(serviceProcessInstaller);
this.Installers.Add(serviceInstaller);
}
}
Pravděpodobně bude potřeba přidat referenci na assembly: System.Configuration.Install
K registraci služby z naší assembly využijeme utilitu “installutil.exe” (dodaná s .NET Frameworkem). Ta po spuštění vyhledá třídy dědící z bázové třídy Installer a dekorované atributem [RunInstaller(true)] a provede jejich vykonání. V našem případě to bude právě Service1Installer třída. V té najde popis pro registraci služby do systému.
Všimněte si, že nikde v instalační třídě neuvádíme odkaž na konkrétní třídu služby (Service1) – registruje se totiž celá assembly. O spuštění konkrétní služby se pak postará spouštěcí kód uvnitř třídy Program.
Ještě než provedeme samotnou registraci, rád bych se věnoval nastavení účtu, pod kterým se služba bude spouštět.
Účet služby
Při volbě účtu (vlastnost Account), pod kterým služba poběží si volíte buď jeden ze 3 pevně definovaných systémových účtů nebo libovolný účet pomocí jména a hesla:
- LocalSystem – účet s plným oprávněním – de facto administrátor. Není potřeba vyplňovat jméno a heslo. Toto je defaultní nastavení.
- NetworkService – účet s omezeným oprávněním. Není potřeba vyplňovat jméno a heslo.
- LocalService – účet s omezeným oprávněním podobný jako NetworkService. Rozdíl je v komunikaci s ostatními servery, kde využívá anonymní přihlašovací údaje. Není potřeba vyplňovat jméno a heslo.
- User – libovolný účet podle vyplněného jména a hesla. Je potřeba vyplnit vlastnosti Username a Password.
Registrace do systému pomocí InstallUtil.exe
K registraci do systému budeme potřebovat administrátorská oprávnění. Spusťte si proto v administrátorském režimu příkazový řádek Visual Studia – “Visual Studio Command Prompt (2010)”.
Poznámka: Můžete využít i klasickou příkazovou řádku, pouze se budete muset odkazovat na utilitu “installutil.exe” plnou cestou. Například v mém případě: “C:\Windows\Microsoft.NET\Framework\v4.0.30319\installutil.exe”.
Nyní spusťte příkaz pro registraci služby (patřičně upravte cestu k projektu):
installutil.exe c:\Dev\MyWindowsService\bin\Debug\Jecha.Samples.MyWindowsService.exe
Pro případné odebrání služby pak přidáme navíc parametr “/u”. Zatím ji ale neodebírejte.
installutil.exe /u c:\Dev\MyWindowsService\bin\Debug\Jecha.Samples.MyWindowsService.exe
Pokud se vše povedlo správně, služba by se měla zobrazit v seznamu konzole služeb systému Windows:
Nejčastější problémy
- Chybová hláška při instalaci služby: “System.Security.SecurityException: The source was not found, but some or all event logs could not be searched. Inaccessible logs: Security.” – příkazová řádka nebyla spuštěna s administrátorským oprávněním.
- Chybová hláška při instalaci služby: “System.BadImageFormatException: Could not load file or assembly XYZ” – používáte pravděpodobně verzi “installutil.exe” pro starší verzi .NET Frameworku.
- Služba běží, ale nezapisuje výsledek do souboru. Pravděpodobně služba nemá oprávnění zapisovat do složky, případně složka vůbec neexistuje. Zkontrolujte cestu, která v kódu služby určuje, kam se bude soubor zapisovat.
Spuštění služby
Službu se pokuste pomocí konzole spustit. Nechte ji 15 vteřin běžen a následně ji ukončete.
Zastavování služby:
Pokud jste vše provedli správně, soubor s výsledky bude obsahovat výpis zatížení procesoru za dobu, kdy služba byla v provozu. Například:
13:29:08 - CPU usage 6,447178%
13:29:09 - CPU usage 12,12361%
13:29:10 - CPU usage 11,93132%
13:29:11 - CPU usage 0%
13:29:12 - CPU usage 0%
13:29:13 - CPU usage 21,66084%
13:29:14 - CPU usage 0%
13:29:15 - CPU usage 1,547676%
13:29:16 - CPU usage 6,639277%
13:29:17 - CPU usage 10,30455%
13:29:18 - CPU usage 11,24968%
13:29:19 - CPU usage 5,585763%
13:29:20 - CPU usage 0%
13:29:21 - CPU usage 5,877517%
13:29:22 - CPU usage 4,334079%
13:29:23 - CPU usage 3,47057%
Nezapomeňte na konci testování službu zase odinstalovat. Pokud to neuděláte, služba se automaticky po restartu počítače spustí a bude každou vteřinu zapisovat na disk do příslušného souboru.
Tipy při vývoji
Pozastavení služby
Službu je možné zastavovat a spouštět. Po ukončení služby se instance procesu zcela ukončuje. Pokud chcete dovolit možnost dočasného pozastavování služby, stačí v konstruktoru služby (Service1) pozastavování povolit:
this.CanPauseAndContinue = true;
Dále naimplementujete metody “OnPause” a “OnContinue” a je čistě na vás, jak se v případě vyvolání těchto událostí zachováte:
protected override void OnPause()
{
// příkaz při pozastavení
}
protected override void OnContinue()
{
// příkaz při opětovném spuštění
}
Služba, která je takto nastavená bude mít v konzoli služeb navíc možnost “Pause” pro pozastavení. Většinou ale tuto možnost implementovat nemusíte a je, až na speciální případy, poměrně zbytečná.
Dodatečné nastavování
V konzoli služeb můžete z kontextového menu na příslušnou službou zvolit “Properties”. Zobrazí se dialog s konfigurací služby:
Na tomto dialogu můžete službu spouštět, zastavovat, měnit způsob spouštění, doplnit parametry spuštění, prohlédnout si její závislosti na jiných službách, změnit účet spouštění a případně nastavit možnosti zotavení (pokud se nepovede službu spustit, vyzkoušej to za chvilku znovu atp.). Přes tento dialog nelze službu odregistrovat ze systému, lze ji maximálně zakázat.
Logování
Logování je u služeb nesmírně důležité. Pokud vznikne jakákoliv výjimka, logování ji pomůže odhalit. Bez logování se o chybě při spouštění služby dozvíte tak maximálně, že vznikla. A o chybě v průběhu se nedozvíte dokonce vůbec. Zkuste si například v ukázkovém projektu vyvolat výjimku uvnitř události časovače. Chyba vznikne, ale služba se kvůli ní neukončí a “spolkne ji”. Logování je však nad rámec tohoto článku.
Vyžádání delšího času na spuštění
Vzhledem k tomu, že systém čeká na spuštění a ukončení služby, snažíme se o co nejrychlejší nastartování a zastavení. Pokud nejsme schopni start/stop zaručit bezpečně do 20ti vteřin (orientační hodnota), můžeme si na spouštění vyžádat více času příkazem:
// vyžádání dalších 60 vteřin na spouštění / zastavování
RequestAdditionalTime(60 * 1000);
Debugování služby
Na rozdíl od běžné aplikace je ladění služeb trochu komplikovanější. Službu totiž nespouštíte přímo vy, ale systém. Jaké máme tedy možnosti?
První možností je připojit debugger do procesu běžící služby. Toto je postup:
- Ujistěte se, že služba běží.
- Spusťte Visual Studio s projektem služby v režimu adminsitrátora (pokud to neuděláte, k službě se nepřipojíte).
- Proveďte volbu Debug > Attach to process…
- V zobrazeném seznamu zaškrtněte možnosti Show processes from all users i Show processes in all sessions a zvolte Refresh. Vyhledejte instanci služby:
- Pokud máte označenou instanci běžící služby, zvolte Attach.
- Nyní můžete plně debugovat. Vložte do příslušné části kódu breakpointy a vyčkejte na jejich zasažení.
Tento způsob má jasnou výhodu – ladíte přímo službu hostovanou systémem a pracujete tak přímo v prostředí, kde služba následně poběží. Existují ale i nevýhody – hlavní problém je v tom, že se špatně ladí kód při spouštění služby. Než stihnete debugger připojit, pravděpodobně služba již bude spuštěna. To lze však vyřešit malým trikem – vložte do metody OnStart příkaz pro čekání Thread.Sleep(čas) (například minutu) a to vám dá dostatek času na připojení debuggeru a nastavení breakpointu. Další nevýhodou je nutnost spouštění pod administrátorským oprávněním a potřeba při každém spouštění znovu provést připojení debuggeru.
Druhou možností je přímo zavolat kódu startu služby, pokud spustíte službu přímo z příkazové řádky. Například já používám jako odlišení testovacího spuštění parametr “debug”. Kód v třídě Program pak může vypadat takto:
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main(params string[] args)
{
if (string.Equals(args.FirstOrDefault(), "debug", StringComparison.OrdinalIgnoreCase))
{
var service = new Service1();
service.StartDebug();
System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
}
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Service1()
};
ServiceBase.Run(ServicesToRun);
}
}
Tento kód kontroluje, zda spouštíme aplikaci s parametrem “debug” a pokud ano, vytvoří instanci služby a vyvolá metodu na spuštění. Následně čeká nekonečnou dobu, díky které se aplikace ihned neukončí a my můžeme ladit. Bez uvedení parametru se služba chová jako doposud.
Aby mohlo toto řešení fungovat, je ještě potřeba vystavit metodu StartDebug pro třídu služby Service1. Metoda OnStart, kterou chceme volat, totiž není veřejná a nemůžeme ji zavolat přímo ze spouštěcí třídy Program.
internal void StartDebug()
{
this.OnStart(null);
}
Nyní stačí pouze nastavit při ladění spouštění s parametrem “debug” ve vlastnostech projektu:
Toto řešení je výhodné ve snadnosti použití. Program prostě spustíte přímo z Visual Studia s parametrem a můžete ladit. Nevýhodou je, že spouštění služby jen simulujeme. Přitom reálné spouštění služby systémem je trochu jiné a běží v jiném prostředí. Například systém ji přiděluje jiná oprávnění a nedává k dispozici použití uživatelského prostředí.
Závěr
Psaní Windows služeb patří mezi základní techniky programování pro systém Windows, ačkoliv k této možnosti saháme většinou až při náročnějších úkolech. Hodí se například jako způsob hostování WCF služeb, vlastního plánovače úloh, či modulu komunikujícím s hardwarem. Doufám, že tento článek dokázal osvětlit alespoň základy programování a práce s Windows Services.