Windows service v C#
V nasledujúcom článku si popíšeme, ako fungujú servisy v systéme Windows a ukážeme si, ako jednoducho si ho vieme vytvoriť pomocou C#.
V tomto článku si popíšeme, ako fungujú servisy v systéme Windows a ukážeme si, ako jednoducho si ho vieme vytvoriť pomocou Visual Studia a C#.
Zoznam aktuálne nainštalovaných servisov vo vašom systéme (bavíme sa o OS Windows) viete nájsť v aplikácii Services (Nástroje na správu systému Windows, prípade ju vieme spustiť z príkazového riadka ako services.msc).Servis môže byť spúšťaný manuálne (užívateľom, prípadne iným programom) alebo automaticky (po spustení operačného systému). Pri automatickom spúštaní existuje aj podvoľba oneskorený štart (Delayed Start), ktorý štart servisu nevykoná ihneď po štarte systému, ale chvíľu neskôr.Tak, ako každá iná aplikácia beží aj servis pod nejakým kontom, ktoré jej okrem iného vyhradzuje aj prístupové práva. Použiť je možné aj zabudované kontá (Local Service, Network Service a pod), alebo nejaké lokálne, či doménové užívateľské konto. Postačí aby konkrétne konto malo oprávnenie Log on as a service (viď. > link <) a poznáme jeho heslo.Toľko k teórii, je čas sa pustiť do práce.
- Microsoft Visual Studio Installer Projects Extension,
Ktorý pre jednotlivé verzie VS nájdete:
VS 2013: > link <
VS 2015: > link <
VS 2017-2019 > link <
Diskusia
Servis všeobecne
Pod pojmom servis si môžeme predstaviť program / skript bežiaci na pozadí v našom zariadení (či už v počítači alebo aj mobilnom telefóne). Servis môže kontrolovať a vyhodnocovať aktivitu v zariadení (napr. realtime ochrana antivírusu), vyhľadávať aktualizácie (operačného systému, či konkrétnej aplikácie), sprostredkovávať výmenu dát (WEB server), vytvárať zálohy súborov a mnoho ďalšieho.Aplikácia Services
Zoznam aktuálne nainštalovaných servisov vo vašom systéme (bavíme sa o OS Windows) viete nájsť v aplikácii Services (Nástroje na správu systému Windows, prípade ju vieme spustiť z príkazového riadka ako services.msc).Servis môže byť spúšťaný manuálne (užívateľom, prípadne iným programom) alebo automaticky (po spustení operačného systému). Pri automatickom spúštaní existuje aj podvoľba oneskorený štart (Delayed Start), ktorý štart servisu nevykoná ihneď po štarte systému, ale chvíľu neskôr.Tak, ako každá iná aplikácia beží aj servis pod nejakým kontom, ktoré jej okrem iného vyhradzuje aj prístupové práva. Použiť je možné aj zabudované kontá (Local Service, Network Service a pod), alebo nejaké lokálne, či doménové užívateľské konto. Postačí aby konkrétne konto malo oprávnenie Log on as a service (viď. > link <) a poznáme jeho heslo.Toľko k teórii, je čas sa pustiť do práce.
Čo budeme potrebovať?
- Visual Studio- Microsoft Visual Studio Installer Projects Extension,
Ktorý pre jednotlivé verzie VS nájdete:
VS 2013: > link <
VS 2015: > link <
VS 2017-2019 > link <
Tvorba servisu
Otvoríme si Visual Studio a cez menu (FILE -> New -> Project) si vytvoríme si nový projekt typu "Windows Service" (Templates -> Visual C# -> Windows Desktop -> Windows Service) a pomenujeme ho CODEBlogService.V okne Solution Explorer premenujeme súbor Service1.cs na MainService.cs. Dvojklikneme na premenovaný súbor a v okne Properties nastavíme vlastnosť ServiceName na "CODEBlogService".Súbor MainService.cs si otvoríme v móde editácie kódu a upravíme ho nasledovne:Zdrojový kód:
...
using System.Timers;
...namespace CODEBlogService
{
public partial class MainService : ServiceBase
{
public const string ServiceLogName = "CODEBlogService";
private EventLog EvLog = new System.Diagnostics.EventLog();
private Timer WorkTimer = new Timer(); public MainService()
{
InitializeComponent();
} protected override void OnStart(string[] args)
{
EvLog.Source = MainService.ServiceLogName;
EvLog.Log = "Application";
EvLog.WriteEntry("Start servisu"); WorkTimer.Elapsed += WorkTimer_Elapsed;
WorkTimer.Interval = 5000;
WorkTimer.Enabled = true;
WorkTimer.Start();
} protected override void OnStop()
{
EvLog.WriteEntry("Ukoncenie servisu");
WorkTimer.Stop();
} void WorkTimer_Elapsed(object sender, ElapsedEventArgs e)
{
EvLog.WriteEntry("Servis bezi");
}
}
}
V kóde sme si zadefinovali konštantu ServiceLogName, ktorá nám poslúži pri práci s logmi aplikácie, ktoré bude možné následne prezrieť v apikácii EventLogger, ktorá je súčasťou Windows-u.Hneď pod ňou sme definovali premennú EvLog, ktorá bude slúžiť na samotný zápis záznamov do logu. Treticu premenných uzatvára WorkTimer, ktorý bude vytvárať slučku výkonného kódu našeho servisu.Ďalej nasledujú metódy OnStart a OnStop. Ako už ich názov napovedá, metóda OnStart je volaná pri požiadavke na štart servisu a metóda OnStop pri pokuse o jeho zastavenie. Pri metóde OnStart je dôležité, aby neobsahovala samotný výkonný kód servisu, nakoľko systém pri spúštaní servisu čaká na jeho "odpoveď", ktorá sa neodošle skôr, ako sa vykoná metóda OnStart. Preto je v metóde okrem nastavení vlastností premennej EvLog aj nastavenie a aktivácia nášho WorkTimer-u. V prípade, ak by sme potrebovali aby bola funkčnosť servisu implementovaná v samostatnom vlákne (nie v Timer-i), tu je to správne miesto, kde ho môžeme spustiť. V metóde OnStop náš WorkTimer zastavíme.Posledná v poradí je metóda WorkTimer_Elapsed, v ktorej môže byť výkonný kód servisu, v našom prípade len informatívny zápis do logu.Ak by sme náš servis aktivovali v tejto chvíli, hneď pri jeho štarte by došlo k jeho pádu, nakoľko sa v premennej EvLog snažíme použiť zdroj (vlastnosť Source), ktorý nie je v našom systéme registrovaný. Túto akciu pre nás vykoná inštalátor servisu, ktorý si v nasledujúcich riadkoch vytvoríme. Pomenovanie "inštalátor servisu" môže byť trochu zavádzajúce, nakoľko sa nejedná a inštalačný program, ale technicky ide o inštrukcie ako servis nainštalovať.Otvoríme si súbor MainService.cs v design móde a cez right click menu aktivujeme voľbu Add Installer. Táto funkcia nám vytvorí v Solution Explorer-i súbor ProjectInstaller.cs. Po dvojklik-u naň sa nám zobrazia dve jeho komponenty serviceProcessInstaller1 a serviceInstaller, ktoré si podľa potreby môžeme aj premenovať.Vo vlastnostiach komponenty serviceProcessInstaller1 upravíme vlastnosť Account, ktorá hovorí o tom, pod akým kontom bude náš servis štandardne bežať. Ak necháme pôvodnú hodnotu User, bude inštalátor vyžadovať zadanie prihlasovacích údajov pri inštalácii. V našom prípade môžeme zvoliť LocalService. V eventoch komponenty si vytvoríme handlery na udalosti AfterInstall a BeforeUninstall.Kód týchto metód bude vyzerať nasledovne:using System.Timers;
...namespace CODEBlogService
{
public partial class MainService : ServiceBase
{
public const string ServiceLogName = "CODEBlogService";
private EventLog EvLog = new System.Diagnostics.EventLog();
private Timer WorkTimer = new Timer(); public MainService()
{
InitializeComponent();
} protected override void OnStart(string[] args)
{
EvLog.Source = MainService.ServiceLogName;
EvLog.Log = "Application";
EvLog.WriteEntry("Start servisu"); WorkTimer.Elapsed += WorkTimer_Elapsed;
WorkTimer.Interval = 5000;
WorkTimer.Enabled = true;
WorkTimer.Start();
} protected override void OnStop()
{
EvLog.WriteEntry("Ukoncenie servisu");
WorkTimer.Stop();
} void WorkTimer_Elapsed(object sender, ElapsedEventArgs e)
{
EvLog.WriteEntry("Servis bezi");
}
}
}
Zdrojový kód:
...
using System.Diagnostics;
...
private void serviceProcessInstaller1_AfterInstall(object sender, InstallEventArgs e)
{
if (!EventLog.SourceExists(MainService.ServiceLogName))
{
EventLog.CreateEventSource(MainService.ServiceLogName, "Application");
}
} private void serviceProcessInstaller1_BeforeUninstall(object sender, InstallEventArgs e)
{
if (EventLog.SourceExists(MainService.ServiceLogName))
{
EventLog.DeleteEventSource(MainService.ServiceLogName);
}
}
Kód metód netreba nejako extra popisovať, metóda serviceProcessInstaller1_AfterInstall vytvára zdroj pre EventLog a serviceProcessInstaller1_BeforeUninstall ho odstráni.Ďalej sa pozrieme na vlastnosti komponenty serviceInstaller1, kde si vyplníme DisplayName na "CODEBlog Service", Description na "CODEBlog.sk testovací servis" a StartType necháme "Manual".using System.Diagnostics;
...
private void serviceProcessInstaller1_AfterInstall(object sender, InstallEventArgs e)
{
if (!EventLog.SourceExists(MainService.ServiceLogName))
{
EventLog.CreateEventSource(MainService.ServiceLogName, "Application");
}
} private void serviceProcessInstaller1_BeforeUninstall(object sender, InstallEventArgs e)
{
if (EventLog.SourceExists(MainService.ServiceLogName))
{
EventLog.DeleteEventSource(MainService.ServiceLogName);
}
}
Inštalátor projektu
Servis máme pripravený a ostáva nám už len vytvoriť inštalátor nášho projektu. Tento inštalátor nakopíruje súbory projektu do cieľového priečinka, zaregistruje ho v zozname nainštalovaných aplikácii a pomocou vyššie definovaných inštalačných tried zaregistruje náš servis v systéme a v logoch systému.V tomto bode využijeme už hore spomenuté rozšírenie Microsoft Visual Studio Installer Projects Extension, ktoré tento typ projektu obsahuje.V okne Solution Explorer nad našou solution v right click menu zvolíme Add-> New project. V okne vyhľadáme Other Project Types -> Visual Studio Installer a zvolíme voľbu Setup Wizard a projekt pomenujeme CODEBlogServiceInstaller. Otvorí sa nám Wizard v ktorom postupne zvolíme voľby Create a setup for a Windows application, Primary output from CODEBlogService a ostatné necháme nevyplnené.Dvojklikom na novo vytvorený projekt v okne Solution Explorer zobrazíme jeho vlastnosti, kde nastavíme požadované vlastnosti, v našom prípade Author, Description, Manufacturer, ProductName a Title.Pokračujeme right clickom na projekt a v menu zvolíme View -> Custom Actions. (V menu View je toho viac čo stojí za pozretie, avšak v našom prípade je to nepotrebné. V prípade záujmu môže o inštalátoroch vzniknúť samostatný článok.). V novo otvorenom okne Custom Action right clickom na akciu Install vyvoláme funkciu Add Custom Action..., kde cez voľbu Application Folder zvolíme možnosť Primary output from CODEBlogService (Active). Rovnaký postup zvolíme aj pre akciu Uninstall. Týmto sme si zabezpečili zavolanie inštalačných tried našeho servisu pri inštalácii a odinštalovaní.Zmeníme konfiguráciu solution z Debug na Release a vyvoláme Rebuild hlavného projektu CODEBlogService a následne aj našeho inštalátora CODEBlogInstaller.Visual Studio nám vytvorilo inštalačný balíček v priečinku projektu, ktorý môžeme zobraziť cez right click menu nad projektom inštalátora cez voľbu Open Folder in File Explorer. Inštalovať a odinštalovať môžeme aj cez voľby Install a Uninstall v rovnakom menu.Po inštalácii našeho servisu a jeho následným spustením cez aplikáciu Services, môžeme pomocou aplikácie Event Viewer otestovať, či náš servis beží.Debug servisu
Ako postupne narastá komplexnosť aplikácie, zvyšuje sa aj riziko chýb v nej. Aj keď náš servis nemá nejakú skutočnú funkčnosť a ladiť ho pravdepodobne nebudeme, musíme myslieť aj na tento prípad. Ak sa pokúsime spustit servis (ako aplikáciu) cez Visual Studio, dostaneme hlásenie o tom, že servis nemôže byť spustení z príkazového riadku alebo debuggera.Jedným z možných riešení je využitie funkčnosti Attach to Process vo Visual Studio-u (či už priamo alebo cez Remote Debugger) a pripojiť sa na už bežiaci servis. Toto má však svoje obmedzenia a samotné ladenie vie byť veľmi zdĺhavé.Ďalším riešením je upraviť si servis tak, aby vedel bežať aj ako klasická aplikácia a následne ho môžeme priamo spúštať a ladiť cez VS. Samotná úprava aplikácie je jednoduchá. Ako prvé je nutné pridať do projektu referenciu na System.Windows.Forms (V Solution explorer-i, right click na References pod projektom CODEBlogService a voľba Add Reference.) a následne sa môžeme použit do úprav kódu.Začneme súborom MainService.cs, kde pridáme nasledujúci kód:Zdrojový kód:
...
#if DEBUG
public void Debug(string[] args)
{
OnStart(args);
System.Windows.Forms.Application.Run();
}
#endif
...
V kóde sme zadefinovali metódu Debug, ktorá sa postará o zavolanie metódy OnStart a následne pomocou System.Windows.Forms.Application.Run(); sa postará o ďaľší beh aplikácie. Táto metóda bude v programe len v Debug konfigurácii.Pokračujeme súborom Program.cs v ktorom upravíme metódu Main:#if DEBUG
public void Debug(string[] args)
{
OnStart(args);
System.Windows.Forms.Application.Run();
}
#endif
...
Zdrojový kód:
static void Main()
{
#if DEBUG
(new MainService()).Debug(null);
#else
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new MainService()
};
ServiceBase.Run(ServicesToRun);
#endif
}
Táto úprava sa postará o to, aby sa v Debug konfigurácii spustila nami definovaná metóda MainService.Debug() namiesto pôvodného kódu, čo nám umožný ladiť náš servis ako bežnú aplikáciu.{
#if DEBUG
(new MainService()).Debug(null);
#else
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new MainService()
};
ServiceBase.Run(ServicesToRun);
#endif
}
Záverom
Kompletné zdrojové kódy nášho projektu je možné sťahovať z > link <. V prípade nejakých nejastností alebo otázok, prosím využite komentáre pod článkom (po registrácii).Vytvorenie nového servisu
Add installer
Vlastnosti Service Installer
Vlastnosti Service process Installer
Nový projekt inštalátora
Wizard 1
Wizard 2
Wizard 3
Custom actions
Inštalátor našeho projektu
Náš výpis v aplikácii Event Viewer
Pokus o štart servisu cez VS
Žiadne príspevky v diskusii.
Na prispievanie do diskusie musíte byť prihlásený.