Clanintern Clanintern Clanintern

Forum

Öffentliche Foren
FORUM: Spiele & Computer THEMA: [PHP] Kommunikation zwischen separaten Scripts
AUTOR BEITRAG
phoeniks

RANG Godlike

#1 - 01.09 06:14

Hallo zusammen,

über eine Webseite wird aus dem Server ein PHP-Script auf der Kommandozeile gestartet das länger läuft. Nun wäre es mir am liebsten wenn ich irgendwie auf Variablen (für den Status der Bearbeitung) in diesem separat laufenden Script zugreifen könnte.

Unter *nix Systemen scheint es da von PHP aus ja mit Semaphore eine Möglichkeit zu geben. Leider funktionert das alles nicht unter Windows....

Wie würdet ihr das Problem lösen? Zur Not würde ich in bestimmten Abständen die benötigten Werte in eine Datenbank schreiben und auf der Webseite dann wieder rausholen. Lieber wäre mir aber schon ein direkter Zugriff.

Wären die Kommunikation über SOAP, XML-RPC oder Sockets eine empfehlenswerte Lösung?

Danke&Gruß

phoeniks/Jürgen
*al!ve* - irgendwo zwischen Semester 4 und 7

RANG 0wn3r

#2 - 14.09 16:41

Was genau hast du denn damit vor?
Der klassische Weg wäre vermutlich, dass das längerlaufende Script nicht direkt geladen wird sondern per AJAX angestoßen.

1. Seite aufbauen ohne aufwändige Aktion.
2. Per Javascript nen ultra trendigen AJAX-Kringel irgend wo hin zeichnen, häufig einfach per CSS-Klasse eines DIVs das n entsprechendes Background-Image hat
3. Per AJAX nen separaten Aufruf erzeugen der deine lange Aktion startet
4. onSuccess des AJAX-Aufrufs den AJAX-Kringel ausblenden

Statuswerte zwischendrin gibt s normalerweise nicht -- was der Grund dafür ist dass im Web der Kringel deutlich verbreiteter ist als der Ladebalken.
*al!ve* - irgendwo zwischen Semester 4 und 7

RANG 0wn3r

#3 - 14.09 17:09

http://alive.no-ip.com/ajaxkringel/
Ich hab da mal was vorbereitet.

Starten über seite.html

Die Datei "schlaefer.php" hat nur <?PHP sleep(2); echo "Ich bin die tolle Antwort"; ?> zum Inhalt, soll nur ne zeitliche Verzögerung erzeugen.

Das "Fertig!" sowie die Optik liegt schon vor wenn die seite.html geladen wird, der Text "Ich bin die tolle Antwort" wird nach Ablauf der Wartezeit von zwei Sekunden vom Server zum Client übertragen.
phoeniks

RANG Godlike

#4 - 27.09 13:31

Vielleicht erkläre ich es nochmal genauer - sorry dass ich erst jetzt wieder hier rein schaue - hatte gedacht da kommt nix mehr.

Danke erst mal für die Mühe die du dir gemacht hast.

Also ich starte über passthru(psexec.exe php.exe berechundpdf.php) das Script auf der Kommandozeile - so bekomme ich die pid des php.exe-Prozesses zurück und dann prüfen ob er noch läuft.

ajax fällt glaube ich aus, da es auch möglich sein soll die Seite/den Browser zu schließen ohne dass das Script aufhört zu laufen. Es soll in jedem Fall fertig durchlaufen.

In diesem Script werden nun z.B. Berechnungen gemacht und PDFs erstellt - sagen wir so 500-1000 Stück. Das dauert natürlich eine Weile. Nun hätte ich gerne die Möglichkeit
dem Benutzer anzuzeigen wieviele PDFs schon erstellt wurden.
Im Script "berechundpdf.php" habe ich einen Zähler der pro PDF 1 hochzählt.

Das einfachsten wäre es jetzt, wenn ich auf die Variable in diesem laufenden Script zugreifen und sie anzeigen könnte. Das ist glaube ich aber nicht zu erreichen.
Deshalb hätte ich nun den Zähler in einer Datenbank hochgezählt und dann mit dem anderen Script mit dem der Benutzer den Fortschritt verfolgen kann z.B. jede Sekunde ausgelesen und angezeigt. Das ist aber wohl wegen der vielen DB-Zugriffe nicht sonderlich geschickt.
*al!ve* - irgendwo zwischen Semester 4 und 7

RANG 0wn3r

#5 - 27.09 22:39

Öhm ... ja. Nein.
Vergiss das, das führt zum Chaos.

Bau dir ne "Jobs"-Tabelle in deiner Datenbank die die Summe deiner Durchläufe kennt, n Array an Parametern besitzt das notwendig ist um den Job durchzuführen und die Anzahl der erledigten Iterationen. Dazu noch ne Spalte "errors" die die Anzahl der aufgetretenen Fehler zählt und ne Zeit-Spalte die den letzten erfolgten Zugriff zählt.

Dein Webseitenbenutzer erzeugt nur eine neue Zeile in dieser Tabelle, die Parameterliste wird gefüllt, die Ziel-Anzahl der Iterationen festgelegt (500, 1000, wie auch immer) und den aktuellen Iterationszähler sowie den aktuellen Fehlerzähler auf 0 gestellt.

Jetzt kommt n Cronjob (5 Sekunden oder sowas) und nimmt sich eine einzige Zeile deiner Jobs-Tabelle deren Zeit-Spalte älter als 2 Sekunden ist, deren Iterationszähler noch nicht der Anzahl der aktuellen Iteration entspricht und deren Fehlerzähler einen bestimmten Schwellenwert (3, vielleicht) noch nicht überschritten hat.
Zunächst wird die Zeitspalte auf die aktuelle Uhrzeit gesetzt damit sich nicht der nächste Cronjob die selbe Zeile nochmal schnappt.
Jetzt fängt der Job an, die PDF-Files anhand der Parameterliste aus der Datenbank zu erzeugen. Dabei weiß er anhand des Zählers der aktuellen Iteration welches das nächste PDF-File ist, muss also nicht zwingend bei 0 anfangen.
Nach jedem erfolgreich geschriebenen PDF aktualisiert der Job die Zeile in der Datenbank indem er die Zeit auf "jetzt" stellt und die aktuelle Iteration um eins erhöht.
Wenn ein PDF fehl schlägt sollte der Job die Zeile aktualisieren, indem er den Fehlerzähler um eins erhöht und anschließend stirbt.

Clientseitig kannst du jetzt per AJAX alle 3 bis 5 Sekunden einen Request schicken und das Verhältnis zwischen aktueller Iteration und Iteratinsziel erfragen. Wenn du nen Zähler von 25 bei nem Ziel von 1000 hast weißt du, dass du nen Statusbalken mit 2.5% anzeigen kannst.

Wenn dein Fehlerzähler irgend wann mal den Schwellenwert erreicht (auch den Fehlerzähler solltest du per AJAX an den Client übermitteln) kannst du dem Benutzer ein "Ops, das hätte nicht passieren sollen. Es ist ein Fehler bei PDF $i von $k aufgetreten" anzeigen.

Wenn du Lust hast kannst du den Cronjob auch so konfigurieren, dass er nach spätestens 120 Sekunden abbricht (Zeitprüfung bitte *vor* dem "nächstes PDF erzeugen" mit PHP-Timelimit etwa auf 200s). Dadurch vermeidest du, dass sich dein Cronjob über ne längere Zeit mit viel Speicher voll frisst und irgend wann unerwartet stirbt (was den Fehlerzähler erhöht) obwohl der Grund prinzipbedingt ist und nichts mit deinem konkreten PDF-Job zu tun hat.

Die Datenbankzugriffe könntest du reduzieren wenn du statt der Datenbank mit ner Sessionvariable arbeitest. Dann hat sich s aber fast mit dem Shellscript erleidgt, weil das die Session-ID mitgeteilt bekommen müsste um sich in die Session zu hängen. Allerdings würde ich dringend davon abraten, mehrere Prozessen über ne Session kommunizieren zu lassen von denen mindestens einer ne deutlich erhöhte Laufzeit als "nur für den Click" hat. Das schreit nach einer Race Condition.
Außerdem schreibt die PHP-Standardkonfiguration Sessionvariablen (genauer: das superglobale Array $_SESSION) serialisiert in Dateien (pro Session eine einzelne). Wenn der aktuelle PHP-Prozess die Session initialisiert, wird das Array aus der Datei deserialisiert, wenn der Request beendet wird wir das Array serailisiert und zurückgeschrieben. Dazwischen greift jeder Prozess auf die Variablen in seinem eigenen Scope zu, eine Synchronisation von Session unterschiedlicher Requests findet nicht statt. Den Speichervorgang kannst du per Hand auslösen (der PDF-Worker-Prozess kann also aktiv seine Zählung zurückschreiben), den Lesevorgang nicht.

Wemm du wirklich Angst um zu viele Datenbankrequests hast kannst du den von mir vorgeschlagenen Vorgang so gruppieren, dass nur dann die aktuellen Werte in die Datenbank geschrieben werden wenn der letzte Schreibvorgang länger als eine Sekunde her ist. Dann must du allerdings bedenken, dass dein Abbruchkriterium (Fehler oder Erfolg) nochmal die aktuellen Werte in die Datenbank schreibt, weil du sonst den letzen angefangenen Zeitblock verlierst.

Dein Problem ist aber bei allen erdachten Methoden, dass dein PHP-Script nun mal nicht streamt. Das weiß auch dein Client. Ajax schickt einen Request und es kommen daten über die Verbindung rein. So lange kriegt das aber dein Script nicht mit sondern das ist Sache des Browsers. Irgend wann schickt dre Server dann ein "so, jetzt bin ich mit den Daten fertig". Der Browser erkennt, dass sich der "Ready State" der Verbindung geändert hat, ruft deinen Ajax-Request und sagt ihm "hey, das waren die Daten und das ist der Ready-State". Sobald sich der Ready-State eines Ajax-Requests ein mal auf "fertig" oder "Fehler" geändert hat, wird er sich nie wieder ändern. Heißt: Sobald deine Ajax-Callback-Funktion einmal Daten bekommen hat, bekommt sie nie wieder welche sondern müsste dazu ne neue Anfrage stellen. Bevor sie aber alle Daten vollständig bekommen hat, bekommt deine Callback-Funktion aber überhaupt nichts mit.
Einen wirklichen streamenden Client kriegst du mit Nur-Javascript nicht hin sondern bräuchtest irgend was, das dir nen Stream teilweise auslesen kann. Mit Flash geht das irgend wie (weil der Flash-Videoplayer schon nach drei Sekunden mit dem Video anfängt wenn die 1GB Film noch gar nicht vollständig zu dir übertragen sind), aber wie du nen Flashclient schreibst der gestreamte Daten auswerten kann musst du jemand anderen fragen.
*al!ve* - irgendwo zwischen Semester 4 und 7

RANG 0wn3r

#6 - 27.09 22:49

Was mir grade noch einfällt: Wenn du die Requets vom client zum Server reduzieren willst kannst du ja aus der bisherigen Laufzeit und erreichtem Statusbalken die Gesamtzeit ermitteln, nach den ersten drei Requests mit 2s Differenz auf "10 Requets über die gesamte Laufzeit, mindestens alle 10 Sekunden, höchstens alle 2 Sekunden" wechseln und 5s vor dem errechneten Prozessende wieder auf "alle 2s" zurück wechseln.
phoeniks

RANG Godlike

#7 - 07.10 19:45

Nochmals vielen Dank für die ausführliche Antwort.
Es funktioniert jetzt so wie ich mir das vorgestellt habe.

Das Script läuft hier im Intranet und kann nur immer von einem Benutzer gleichzeitig genutzt werden. Eine Tabelle für die Jobs hatte ich ohnehin schon - diese habe ich noch um die Zähler und die Ziel-Anzahl der Iterationen erweitert.

Jetzt erhöhe ich im PDF-erzeugen-Script den Zähler in der DB für jedes erzeugte PDF. Es läuft jetzt natürlich dank der zusätzlichen DB-Zugriffe langsamer als vorher aber das macht nix - und wenn doch könnte ich es immer noch wie du vorgeschlagen hast gruppieren. Wenn der Job abgeschlossen ist wird die Endzeit in die Jobs-Tabelle geschrieben.

Für den Benutzer frage ich per xajax jede Sekunde den aktuellen Stand des Zählers in der DB ab.

Kurz und gut - es funktioniert - Vielen Dank - der Thread kann geschlossen werden.

Gruß

Jürgen / phoeniks