Ein PHP-Script als Windows-Dienst starten
Viele wichtige Dinge weltweit laufen permanent und tun endlos ihren Job. Webserver warten auf Besucher, Berechnungsprogramme rechnen Tag und Nacht, FTP-Server warten auf Datenübertragungen und SSH-Server warten auf Benutzer.
Auch mit PHP kann man all solche Dinge lösen. Es gibt bereits DNS-Server geschrieben in PHP, selbst ein Webserver in PHP ist verfügbar, ein Continuous Integration Server, einen FTP Server, und es gibt zig tausend Chat-Server (auf Socket-Basis, keine einfachen Webchats), die mit PHP erstellt wurden usw.
Wenn man plant, ein PHP-Script quasi permanent laufen zu lassen, gibt es 3 Möglichkeiten:
- Man startet das Script einmalig nach dem Rechnerstart und lässt es in einer Endlosschleife (while (true) { ) laufen
- Linux: Cron Eintrag @reboot
- Windows: Geplanter Task bei Rechnerstart
- Wenn das Script beispielsweise 2 Minuten für seine Arbeit braucht, startet man es alle 3 Minuten
- Linux: Cron Eintrag */3 * * * *
- Windows: Geplanter Task alle 3 Minuten
- Man installiert das Script als Dienst und lässt es darüber automatisch laufen und kann es starten/stoppen
- Linux: mit Hilfe der Runlevel-Scripte (rc.d / init.d etc)
- Windows: Windows-Dienste
Hier will ich besonders auf die Windows-Dienste eingehen. Unter Windows ist das die einzige Art, ein PHP-Script beim System-Shutdown kontrolliert beenden zu lassen, denn PHP beherrscht unter Windows keine Signalverarbeitung. Es kann also keine Betriebssystem-Signale empfangen wie zB SIGTERM oder SIGHUP, wohingehen es unter Linux die PHP-Funktionen PCNTL zur Prozesskontrolle gibt. Ohne diese Signale läuft das PHP-Script also solange, bis es vom Betriebssystem gekillt wird (hart, also mitten im Ablauf irgendwo im Code), was natürlich große Probleme bereiten kann bei komplizierten Scripten.
Ein Dienst löst also dieses Problem, denn wenn ein Betriebssystem herunterfährt, gibt es seinen Diensten die Möglichkeit, sich selbst innerhalb einiger Sekunden zu beenden. Dazu muß das PHP-Script natürlich ab und zu in einen definierten Zustand gelangen, wo es “aussteigen” kann.
Vielleicht sieht man es besser am Code:
while (WIN32_SERVICE_CONTROL_STOP != win32_get_last_control_message()) {
// hier kommt der Code, der ausgeführt werden soll
}
Es gibt also eine Funktion win32_get_last_control_message(), die dem Script sagt, ob es sich beenden soll oder nicht. Damit diese Funktion (und einige weitere) zur Verfügung stehen, benötigt man die extention win32service aus der PECL.
Aber wie installiere ich nun dieses PHP-Script als Dienst? Dazu gibt es auch eine Funktion. Hier der Grundaufbau eines jeden Services:
if ($argv[1] == 'install') {
$x = win32_create_service(array(
'service' => 'My_first_PHP_Service',
'display' => 'My PHP Service',
'params' => __FILE__ . ' run',
));
debug_zval_dump($x);
exit;
} else if ($argv[1] == 'uninstall') {
$x = win32_delete_service('My_first_PHP_Service');
debug_zval_dump($x);
exit;
} else if ($argv[1] != 'run') {
die("bogus args, please use install/uninstall/run");
}
$x = win32_start_service_ctrl_dispatcher('My_first_PHP_Service');
while (WIN32_SERVICE_CONTROL_STOP != win32_get_last_control_message()) {
// here comes the code which will be executed
// it should not last longer than 30sec if possible
$someCode = new SomeCode();
$someCode->start();
usleep(500000);
}
Wir haben oben erstmal einige Zeilen, um den Dienst installieren und deinstallieren zu können. Das geht sehr einfach, man benötigt nur einen eindeutigen internen Dienstnamen (hier My_first_PHP_Service) und im Installationsfall eine Zeichenkette, die dann später angezeigt wird.
Aufgerufen mit dem Parameter “install” wird der Dienst also installiert:
Dann können wir ihn starten, entweder von der Konsole oder aus der mmc:
Wie bereits als Kommentar geschrieben, sollte der Code in der Schleife nicht all zu lange laufen, damit der Dienst noch vernünftig gesteuert werden kann. Sollte der Code beispielsweise 5 Minuten laufen, und man versucht den Dienst zu beenden (“net stop My_First_PHP_Service” oder über die mmc), kommt nach ca. einer Minute die Nachricht:
Windows wartet also nicht ewig darauf, dass sich der Dienst beendet. Im Falle des System-Shutdowns wird der Prozess dann zwangsweise hart gekillt, was wieder zu unserem Grundproblem führt. Zur Not muss man einfach die Arbeit in kleine Häppchen unterteilen und nacheinander aufrufen.
Die Deinstallation, ihr ahnt es schon, ist genauso einfach wie die Installation:
Hier gibts noch einige Worte zur win32service extension vom Entwickler selbst:
Man kann natürlich nicht nur “normale” Scripte bauen und als Dienst laufen lassen, man kann auch feine Dinge machen, indem man einen wirklichen “Dienst” anbietet, der auf einem Port lauscht und zu dem man sich verbinden kann! Hier gibts Informationen zu Sockets unter PHP, und auch bald einen Artikel hier im Blog.
Falls ihr eure PHP-Scripte als Windows-Dienste laufen habt, würde mich interessieren, was diese Scripte so tun!



Sieht interessant aus und nach gefrickel; nur ich frag mich wozu man dies sinnvoll verwenden kann. Für mich ist PHP eine Script Sprache für dynamische Webseiten. Wenn ich einen Windowsdienst benötige, verwende ich C++ (z.B. im managed code ist das super einfach).
Würd mich interessieren wie win32service unter Vista läuft da dies ja unter ganz andere User ausgeführt wird. Und ist es damit auch möglich Nachrichten in der Ereignisanzeige einzutragen?
tjado
14 Aug 09 at 20:13
Kannst du das “Gefrickel” näher erläutern? Ich finde die 15 Zeilen, die es effektiv nur sind, durchaus einleuchtend und nicht zu kompliziert. Ich habe mit C++ noch nicht so viel gemacht, aber ich glaube, dass es damit komplizierter ist, einen Dienst zu installieren/zu deinstallieren oder laufen zu lassen mit Hilfe von Parametern (install, uninstall, run).
PHP ist eine Script-Sprache, aber warum ist sie deshalb nicht für Backend-Systeme oder Dienste zu gebrauchen, die keine Oberfläche haben und die kein HTML ausgeben? Das PHP-CLI hat durchaus seine Berechtigung und man kann sehr tolle Dinge damit machen.
Wo genau siehst du das Problem mit Vista? Wir lassen unsere Dienste auch mit anderen Usern (in der Firma Domain-User unter Windows 2003) laufen, das ist eine Sache die Windows erledigt, damit hat der eigentliche Dienst (das PHP-Script) nichts am Hut.
Mit dem Eventlog habe ich noch nicht gearbeitet in PHP, wir nutzen immer Logdateien, da sich diese besser archivieren, überwachen und rotaten lassen. Wenn man dem Eventlog eine maximale Größe gibt, werden alte Einträge ersatzlos verworfen, außerdem kann man es nicht archivieren. Ist keine Option für uns.
Michael Kliewe
14 Aug 09 at 20:54
Für mich persönlich ist PHP eine Scriptsprache um dynamische Webseiten zu schreiben deswegen sehe ich es als gefrickel wenn es in anderen Bereichen genutzt wird wo es wesentlich sauberere Möglichkeiten gibt.
Ich denke auch, dass dies viel mit dem Anwendungsgebiet zu tun hat aber mir fallen grad keine ein :/
Vielleicht liegt diese Meinung auch daran, dass ich mit *NIX im Serverbereich aufgewachsen bin
Jedenfalls ist es sehr einfach mit managed C++ ein Dienst zu schreiben (siehe z.B. hier: http://www.developer.com/net/cplus/article.php/3293351 ).
Wie du z.B. angesprochen hast, kann man damit Listen-Sockets erstellen. Nun ist die Frage welche Privilegien werden unter Vista gebraucht und welche Gefahren könnte dies bringen? Windows Dienste laufen ja oft unter hoch priviligierten Usern.
Mag die Eventlogs auch nicht besonders aber wäre ja trotzdem Interessant für bestimmte Ereignisse.
Welche genauen Anwendungsgebiete gibt es den nun für die win32service?
tjado
14 Aug 09 at 21:47
Hello,
For information my distribution (pinetd) also contains a FTP server and a Mail (POP3, SMTP and soon IMAP4) server.
Interesting article, I’ll try to add windows services support to my daemon framework.
MagicalTux
14 Aug 09 at 23:34
Das kommt drauf an, was “sauber” bedeutet. PHP-Code kann man genauso sauber schreiben wie C++.. Man kann auch in beiden Sprachen frickeln (sprich in C++ mit Pointern rumhampeln und memory-leaks erzeugen etc).
Bei managed C++ sind es also auch ~15 Zeilen, um einen Dienst zu erstellen, den man kontrolliert starten und anhalten kann. In C++ braucht man für die Installation des Dienstes dann das InstallUtil, in PHP kann es das Script selbst (wie im Artikel beschrieben einfach via Parameter). Ist also vergleichbar einfach. in beiden Sprachen gibt es dann eine “mainLoop”, worin der eigentliche Dienst läuft.
Genau, Socket-Server kann man genauso erstellen wie in C++. Der Prozess wartet auf einem TCP-Port auf Verbindungen. PHP war ursprünglich sicher nur für kleine dynamische Webseiten gedacht, aber in den letzten 10 Jahren (Seit Version 4 würd ich sagen) hat es diverse Quantensprünge gegeben, die es auch in anderen Bereichen sehr brauchbar gemacht haben. Zum Beispiel die CLI, also das Command-Line-Interface, wodurch man PHP-Scripte eben nicht nur durch einen Webserver aufrufen kann, sondern auch direkt aus der Konsole. Oder aber die volle Objektorientiertheit, Enterprisetauglichkeit (durch gute Debugging-Möglichkeiten, Caching und Clustering, stabile umfangreiche Frameworks etc), Unit-Testing, Code-Coverage-Analysen, mächtige IDEs usw. ist PHP keine Frickelsprache mehr wie vor 10 Jahren.
Natürlich KANN man nach wie vor unsichere kleine Miniprogramme schreiben, die in wenigen Zeilen ihren Zweck erfüllen, man kann aber auch riesige Projekte umsetzen.
Bezüglich der Privilegien: Das ist wie gesagt ein Problem auf Betriebssystemebene. Dort bestimmt man ja, unter welchem Account der Dienst laufen soll. Es ist dabei egal, ob der Dienst ein PHP-Programm oder ein C++-Programm ist, wenn es einen Socket öffnen möchte, braucht der Run-User eben bestimmte Rechte dafür. Mittels Lokaler Security-Policy etc. kann man aber diesen Run-User auch stark beschränken, sodass er ähnlich wie unter Linux nahezu nichts mehr darf (sich beispielsweise nicht mehr anmelden darf am System).
Welche Anwendungsgebiete gibt es für Windows-Dienste? Hmm, im Prinzip alles, was man so als Dienst auch unter Unix laufen lassen kann. Wie gesagt gibt es Webserver, FTP-Server, Telnet-Chat-Server in PHP geschrieben, die man natürlich als Dienst laufen lassen kann. Es gibt glaube ich keinen Dienst, den man nicht auch in PHP schreiben könnte.
Ein Beispiel habe ich noch: Wenn ich zB eine sehr umfangreiche Webseite programmieren würde, und da gäbe es bereits alle möglichen Klassen und Funktionen, womit man alles machen kann. Und nun benötige ich einen Dienst, der mir beispielsweise jede Stunde die Datenbank aufräumen soll oder bestimmte Berechnungen erledigen soll. Macht es da Sinn, diesen Dienst in C++ zu schreiben und alles neuschreiben zu müssen? Oder schreibe ich fix einen PHP-Dienst, wo ich dann auf die bereits vorhandenen PHP-Klassen zugreifen kann und in wenigen Zeilen den gewünschten Dienst laufen habe? 2 verschiedene Programmiersprachen würden da wenig Sinn machen, denn man müßte alles doppelt entwickeln und doppelt pflegen.
Michael Kliewe
15 Aug 09 at 03:37
Wirst du zeitnah noch ein solches HowTo für *NIX Systeme veröffentlichen? Bin gespannt darauf wie es andere anstellen.
Fitschi
15 Aug 09 at 05:10
Mit ‘sauber’ meinte ich nicht den Code an sich sondern was man damit umsetzt. PHP ist einfach eine Scriptsprache.
Und was es kann musst du mir nicht erklären
Das mit den Privilegien hab ich grad bisschen auf Workstations abgewälzt, bei denen die Services meist unter einen der 3 Service-Accounts laufen. Wie du schon sagtest werden hier wohl eher separate User benutzt.
Diese ganzen Beispiel Dienste untersützten aber kein Multithreading, sodass diese für die Praxis eher wenig zu gebrauchen sind.
Klar weiß ich was damit möglich ist, aber da mir keine richtigen Anwendungsgebiete einfallen die sinnvoll sind habe ich nachgefragt.
Dein letztes Beispiel ist auch nicht sehr überzeugend. Datenbanken haben Jobs/Scheduler in denen man auch alles berechnen kann. Und wenn man das nicht will ist trotzdem kein richtiger Dienst notwenig (Taskplaner).
“2 verschiedene Programmiersprachen würden da wenig Sinn machen, denn man müßte alles doppelt entwickeln und doppelt pflegen.”
Ich find win32services als Feature auch gut, aber mir fehlen trotzdem die praktischen Anwendungsgebite.
tjado
15 Aug 09 at 10:11
Achso, nur um das noch zu ergänzen: In das Windows Event Log kann man mit PHP auch schreiben:
http://www.php.net/manual/en/function.syslog.php
bzw mit dem Zend_Log_Writer_Syslog:
http://framework.zend.com/manual/en/zend.log.writers.html#zend.log.writers.syslog
Michael Kliewe
15 Aug 09 at 11:54
@Fitschi: Einen Eintrag ins den cron-daemon bekommt nun wirklich jeder hin. Und wenn es dann noch hakt, dann installierst du einfach php-cli.
Der einzige Unterschied ist die Übergabe der Variablen, die logischerweise bei einem Commanline Aufruf nicht in $_GET oder $_POST stehen.
@tjado: Den Chat will ich sehen der kein Multithreading unterstützt
Kevin
20 Aug 09 at 23:28
@Kevin:
jeder PHP Chat auf Basis von einem Windows Dienst unterstützt kein multithreading
tjado
21 Aug 09 at 19:26
[...] einem meiner letzten Artikel über Windows-Dienste habe ich ja bereits angesprochen, dass auch Dienste aller Art in PHP realisiert werden können. In [...]
Wie erstelle ich einen Socket-Server in PHP? at PHP Gangsta
24 Aug 09 at 21:05
Hallo,
Ich hab deinen Code mal kopiert und wollte ihn testen. Über die cmd hab ich dann “php service.php install” im entsprechenden Verzeichnis ausgeführt. Leider kommt bei mir immer die Meldung, dass der Befehl “php” falsch geschrieben oder nicht gefunden wurde!
Was muss ich noch machen, damit das funktioniert?
raphaelniederer
3 Sep 09 at 09:30
Hi,
das bedeutet, dass Windows deine php.exe nicht finden kann.
2 Lösungen gibts:
- Gib den vollen Pfad zur php.exe an
- Füge das PHP-Verzeichnis deiner PATH-Umgebungsvariablen hinzu.
Einfach mal googlen, oder zB hier gucken:
http://www.ssw.uni-linz.ac.at/Teaching/Lectures/GdP/2007/Environment.html
Danach noch die cmd neustarten (damit die Umgebungsvariablen neu geladen werden) und dann sollte es gehen.
Michael Kliewe
3 Sep 09 at 10:38
Hallo,
Vielen Dank für deine schnelle Antwort! Ich hab jetzt den absoluten Pfad angegeben. Leider kam die Meldung “Xdegub MUST be loaded as a Zend extension in Unknown on line 0″ sowie “Module xdebug already loaded in Unknown on line 0″. Zudem ein Fatal-Error, dass die Funktion “win32_create_service() nicht existieren soll. Vermutlich muss ich die da oben extension noch aktivieren…
Danke für die Hilfe…
raphaelniederer
3 Sep 09 at 11:11
Ja, die win32service Extension brauchst du.
Meld dich einfach wenn es noch nicht klappt.
Michael Kliewe
3 Sep 09 at 11:14
Danke, tu ich… es kommt immer noch folgende Meldung:
PHP Warning: Xdebug MUST be loaded as a Zend extension in Unknown on line 0
PHP Warning: Module ‘xdebug’ already loaded in Unknown on line 0
bool(true) refcount(2)
raphaelniederer
3 Sep 09 at 11:27
XDebug hat mit diesem Artikel nichts zu tun. Hast du das irgendwann schonmal eingebunden? Durchsuch mal deine php.ini nach “xdebug”. Wird die extension da irgendwo falsch geladen?
Der Dienst sollte installiert sein, schau mal in deinen Windows-Diensten nach ob er dort nun auftaucht.
Michael Kliewe
3 Sep 09 at 11:36
Ah, super! Der Dienst ist nun unter den Windows-Diensten ersichtlich.
xdebug wird geladen. Ob das ganze korrekt ist weiss ich nicht… aber was solls… probiert jetzt mal ein paar Dinge mit dem Service aus
Danke für die Hilfe
raphaelniederer
3 Sep 09 at 12:20
ich bekomme diese fehler meldung:
Notice: Undefined variable: argv in C:\Program Files\EasyPHP3.1\www\Windows_Service\windows_service.php on line 3
Notice: Undefined variable: argv in C:\Program Files\EasyPHP3.1\www\Windows_Service\windows_service.php on line 11
Notice: Undefined variable: argv in C:\Program Files\EasyPHP3.1\www\Windows_Service\windows_service.php on line 15
hab bei dir aber nicht gesehen das du irgend wo argv definiert hast.
sorry bin Informatiker Lehrling neu zu php :-S
Raoulius
27 Jul 10 at 16:32
@Raoulius: Die Variable $argv wird von PHP gesetzt wenn das Script aus der Kommandozeile mit Parametern ausgeführt wird. Hier gibts weitere Infos dazu:
http://www.php.net/manual/de/reserved.variables.argv.php
Du musst also “install”, “uninstall” oder “run” übergeben, dann sollte es klappen.
Michael Kliewe
27 Jul 10 at 16:50
Ja habs gesehen musste einfach die neue version von php nehmen *upps*
danke
Raoulius
28 Jul 10 at 08:56