Programmierung von Windows Services mit C#
In diesem kurzen Tutorial lernen Sie, wie sie ein Windows Service mittels der Programmiersprache C# und dem Microsoft.NET Framework erstellen. Ziel soll nicht sein, einen tiefen Einblick in die Gesamtarchitektur von Windows Services zu geben, sondern nur einen groben Überblick wie ein Grundlegendes Service implementiert wird.
Was sind Windows Services?
Ein Windows Service (im folgenden nur noch kurz "WS" genannt), ist prinzipiell nichts anderes, als eine Windows Applikation welche folgende Besonderheiten aufweist:
* Läuft auch dann, wenn kein Benutzer am System angemeldet ist.
* Interagiert im Normalfall nicht mit dem Desktop des Benutzers.
* Wird vom Windows Service Manager gestartet, angehalten und gestoppt.
Der Sinn eines WS liegt darin, dass ein System Dienste zur Verfügung stellen kann, ohne dass ein Benutzer dieses explizit starten muss.
Die Entwicklung eines WS
Wir werden in diesem Tutorial ein WS entwickeln, welches folgende Aufgaben übernimmt:
* An einem Netzwerkport horchen
* Für jede Anfrage auf diesem einen neuen Thread starten und das aktuelle Datum und Uhrzeit zurückschreiben
Der erste Schritt besteht darin, das Grundgestell unseres WS zu erstellen. Dies sieht in unserem Fall folgendermassen aus:
C#-Code: |
public class TestWinService : ServiceBase
{
private NetworkDateServer m_DateServer;
public static void Main( string[] args )
{
System.ServiceProcess.ServiceBase.Run( new TestWinService() );
}
protected override void OnStart( string[] args )
{
m_DateServer = new NetworkDateServer();
Thread dateServerThread = new Thread( new ThreadStart( m_DateServer.StartServer ));
dateServerThread.Start();
}
protected override void OnStop()
{
m_DateServer.StopServer();
}
}
|
Obige Klasse dient als Einsprungspunkt unseres WS und ist für folgende Aktionen zuständig:
* Starten des Service Prozesses
* Behandeln der Events "OnStart" und "OnStop"
Wird vom Service Manager das WS gestartet, so wird das Event "OnStart" ausgelöst, welches in unserem Fall dafür zuständig ist, die Klasse "NetworkDateServer" zu instanziieren und diesen Server zu starten. Analog wird beim beenden durch den Service Manager das Event "OnStop" ausgelöst, welches den Server beendet. Wie dieser Server aufgebaut ist, sehen wir untenstehend:
C#-Code: |
public class NetworkDateServer
{
private TcpListener m_TcpListener;
private bool m_StopServer;
public void StopServer()
{
m_StopServer = true;
}
public void StartServer()
{
m_StopServer = false;
m_TcpListener = new TcpListener( IPAddress.Any, 27200 );
m_TcpListener.Start();
while( ! m_StopServer )
{
TcpClient client = m_TcpListener.AcceptTcpClient();
Thread dateSender = new Thread( new ParameterizedThreadStart(( new DateSender()).SendDateToClient ));
dateSender.Start( client );
}
m_TcpListener.Stop();
}
internal class DateSender
{
public void SendDateToClient( object client )
{
TcpClient networkClient = ( TcpClient ) client;
NetworkStream networkStream = networkClient.GetStream();
byte[] dateBuffer = System.Text.Encoding.ASCII.GetBytes( DateTime.Now.ToString() );
networkStream.Write( dateBuffer, 0, dateBuffer.Length );
networkStream.Close();
networkClient.Close();
}
}
}
|
Im Endeffekt realisiert die Klasse "NetworkDateServer" einen Multi-Threaded Server, welcher am Port 27200 horcht und für jeden sich verbindenden Client einen neuen Thread startet, welcher anschliessen das aktuelle Datum und Uhrzeit an diesen schickt.
Da diese beiden Klassen schon unser gesamtes WS ausmachen, sehen wir abschliessend im nächsten Kapitel wie man die Installationsroutine programmatisch entwickeln kann.
Die Installation eines WS
Klassischerweise wird die Installation eines WS auf zwei verschiedene Arten durchgeführt:
* Per Hand durch erstellen der notwendigen Registry Einträge
* Vom Installer der Applikation
Wir werden den Schritt des Applikationsinstallers wählen und können dies mit .NET eigenen Bordmitteln relativ schnell und elegant realisieren. Dazu fügen wir zu unserem WS folgende Klasse hinzu:
C#-Code: |
[RunInstaller( true )]
public class TestWinInstaller : Installer
{
private ServiceInstaller m_ThisService;
private ServiceProcessInstaller m_ThisServiceProcess;
public TestWinInstaller()
{
m_ThisService = new ServiceInstaller();
m_ThisServiceProcess = new ServiceProcessInstaller();
m_ThisServiceProcess.Account = ServiceAccount.NetworkService;
m_ThisService.ServiceName = "Simple Test Service";
m_ThisService.StartType = ServiceStartMode.Manual;
Installers.Add( m_ThisService );
Installers.Add( m_ThisServiceProcess );
}
}
|
Wenn unser WS als kompilierte EXE Datei vorliegt können wir diese dann durch einen simplen aufruf von "installutil dateiname.exe" installieren. Dies funktioniert, weil wir das Attribut "RunInstaller" angegeben haben und das "installutil" mittels Reflection nachsieht welche Klasse dieses besitzt, diese dann lädt und ausführt.
Kompletter Sourcecode des WS inklusive Installers
C#-Code: |
using System;
using System.Configuration.Install;
using System.ComponentModel;
using System.ServiceProcess;
using System.IO;
using System.Net.Sockets;
using System.Net;
using System.Threading;
[RunInstaller( true )]
public class TestWinInstaller : Installer
{
private ServiceInstaller m_ThisService;
private ServiceProcessInstaller m_ThisServiceProcess;
public TestWinInstaller()
{
m_ThisService = new ServiceInstaller();
m_ThisServiceProcess = new ServiceProcessInstaller();
m_ThisServiceProcess.Account = ServiceAccount.NetworkService;
m_ThisService.ServiceName = "Simple Test Service";
m_ThisService.StartType = ServiceStartMode.Manual;
Installers.Add( m_ThisService );
Installers.Add( m_ThisServiceProcess );
}
}
public class TestWinService : ServiceBase
{
private NetworkDateServer m_DateServer;
public static void Main( string[] args )
{
System.ServiceProcess.ServiceBase.Run( new TestWinService() );
}
protected override void OnStart( string[] args )
{
m_DateServer = new NetworkDateServer();
Thread dateServerThread = new Thread( new ThreadStart( m_DateServer.StartServer ));
dateServerThread.Start();
}
protected override void OnStop()
{
m_DateServer.StopServer();
}
}
public class NetworkDateServer
{
private TcpListener m_TcpListener;
private bool m_StopServer;
public void StopServer()
{
m_StopServer = true;
}
public void StartServer()
{
m_StopServer = false;
m_TcpListener = new TcpListener( IPAddress.Any, 27200 );
m_TcpListener.Start();
while( ! m_StopServer )
{
TcpClient client = m_TcpListener.AcceptTcpClient();
Thread dateSender = new Thread( new ParameterizedThreadStart(( new DateSender()).SendDateToClient ));
dateSender.Start( client );
}
m_TcpListener.Stop();
}
internal class DateSender
{
public void SendDateToClient( object client )
{
TcpClient networkClient = ( TcpClient ) client;
NetworkStream networkStream = networkClient.GetStream();
byte[] dateBuffer = System.Text.Encoding.ASCII.GetBytes( DateTime.Now.ToString() );
networkStream.Write( dateBuffer, 0, dateBuffer.Length );
networkStream.Close();
networkClient.Close();
}
}
}
|
[EDIT=herbivore]Siehe auch
Debuggen von Windows-Dienstanwendungen[/EDIT]