# eventhandler fuer browsergames z.B opensource ugamela programmierung und erklärung



## apraxas (8. Februar 2008)

Hallo, ich bin einer der  Programmierer des Browsergames namens gigra game http://www.gigra-game.de

Ich habe lange Zeit das halbe Web nach einem anständigem Tutorial gesucht, und keines gefunden. Deswegen möchte ich hier einmal Präsentierenm, was ein Eventhandler ist, wofür man ihn nuzt und vor allem wie man ihn nuzt und wie man ihn programmiert.

In 6 Kapiteln beschreibe ich wie man einen einfachen Eventhandler erstellt, startet, dann einen Eventhandler mit Prozessprogrammierung ausstattet und erkläre pcntl_fork.

Erst einmal die Theorie:


*

1.Was ist überhaupt ein Eventhandler?*

Dies ist die erste wichtige Frage, die man klären muss. Ein Eventhandler ist eine Ansammlung von selbstständigen Funktionen, die zusammenfassend geschrieben wurden und verschiedenseitig einsetzbar sind.
Im Thema Browsergame spielt der EH(kurzform) eine sehr wichtige Rolle.
Hier vertseht man einen eigenen Prozess des Spieles, welches im Hintergrund des Games läuft.

Um das zu erklären nehmen wir ein praktisches Beispiel: Wir haben ein Spiel wie Ogame(mit Raumschiffen und PLaneten) und schießen einem anderen Spieler die Schiffflotte runter. Dadurch resultiert ein Trümmerfeld. Damit dieses Trümmerfeld in die Hände des Spielers kommt, muss es abgebaut werden mit "Recyclern" Angenommen ein Recycler benötigt für seinen Flug 2 Stunden, und nach dem man ihn gesendet hat, geht man offline.

Nach 6 Stunden kommt man zurück und krieg eine Nachricht, jedoch bemerkt man, dass die Rohstoffe des Trümmerfelds nicht abgebaut wurden. Reintheoretisch sollte das aber passiert sein, denn zu der Zeit, wo die 2 Stunden um waren war es noch da(leider aer auch dannach)

In den meisten SPielen ist das Problem das folgedne: der spieler logt sich ein und lädt die Flotten aktionen neu: da dies nach 6 stunden passier wird jetzt, nach 6 stunden versucht das TF abzubauen und dann am planeten wieder zu erscheinen, da ja nach zeitplan die flotte auch schon lkange hätte zurück sein sollen.

Solange der spieler die flotten ncith neugeladen hat, blieb der Stand des TFs unversehrt und ein anderer Spieler hat sich das TF geneommen.

Desweiteren ist das die GRundlage des Save bugs, die flotte wird gestartet und ist nru dann wieder angreifbar, wenn der Spieler den planeten neu lädt, bis dahin ist die Flotte im Raum

Um diese Fehlzeit u verhindern, braucht meinen einen Prozess, der das regelt, unabhängig ob der Spieler zurzeit da ist oder nicht. Das selbe gilt auch für Bauauftäge in wahrscheinlich jedem anderen SPiel auch.

Der Prozess, der das nun regelt, heisst Eventhandler!




*
2.Wie Funktioniert ein Eventhandler*



Von vorne Weg, wir arbeiten in der Regel mit einer Linux Dist. um einen Eventhandler zu betreiben.

Der Eventhandler ist ein Prozess der durch den Shell gestartet werden muss und in den Hintergrund geschoben wird.

Kleine erklärung


```
$gigra@unix: /home/gigra/testprogramm
```


Dieser Befehl Startet das Test Programm im Terminal, alles was es nun tut wird sichtbar, sollte man aber das Terminal schließen, so wrid auch das Programm getermt(ausgeschaltet)




```
$gigra@unix: /home/gigra/textprgoramm & disown
```


Damit schieben wir das Programm in den Hintergrund und es läuft durchgängig, bis es sich sebst beendet oder beendet wird, unabhängig ob der Terminal läuft oder nicht.

Das ganze geht auch mit PHP, in der ersten Lektion arbeiten wir mit einer gewöhnlichen php Datei. Dabei muss die Linux Version php-cli haben. Das braucht man um das hier zu starten



```
$gigra@unix: php /var/www/test.php
```



*3.Unser erste Eventhandler, Aufbau*



Die erste Aufgabe des Eventhandlers ist durchgängig zu funktionieren, das heisst es muss "unendlich" laufen.
Dieser script ist ziemlich einfach:

```
<?php while(1) {
   //Hier kommt unser Script später rein
}
```
while(1) gibt immer den boolischen Wert TRUE zurück, das heisst WHILE wird nicht beendet....nennt sich Endlos schleife.
wenn wir diesen Prozess so starten, würde er in wenigen Minuten überhitzen(ich geh mal von aus das der eh später viele aufgaben bewältigen muss, meiner hat mehr als 400 Zeilen script)
Der eh braucht anz klar eine Pause in dem er sich erholen kann. Dafür gibt es den befehl 

```
<?php while(1) {
   //Hier kommt unser Script später rein
   sleep(1);//Der Script sezt ab hier eine Sekunde aus, dann geht weiter
}
```

Als spätere Alternative verwenden wir einen besseren efehl namens Usleep. Das ist das Selbe wie sleep, bloß sleep benuzt sekunden und usleep mikrosekunden. So fern ich mcih nicht täusche sind 1 000 000 mSec genau eine Sekunde.

Im Durchschnitt reichen 10 000 mSec völlig aus um sich zu erholen, aber für den anfang nehmen wir sleep(1)

So nun Arbeiten wir mal an unserem Script weiter, wir icludeieren erstmal alle nötigen Dateien, zB die DB Klassen

```
<?php include "db.php";
include "funcs.php";
while(1) {
   //script
   sleep(1);//Pause
}
```


Nun kommen wir zu den globalen Zeiten. Wir braucghen auf jeden fall die Variable $now = time();
Nun führen wir eine Aktion durch, wir wollen ein Gebäude auf einem Planeten bauen lassen.
Wir richten uns Primär nach der Zeit, wann es fertig wird. Denn brauchen wir noch die ID des Planeten und des Gebäude. Wie genau ein Gebäude fertiggestellt wird, haben wir bereits in der "funcs.php" definiert und die funcktion ist eine public uns heisst build(pid, buildid)
Wir haben klassen
Dafür müssen wir im vorfeld die Klassen der functionen in ein Objekt rufen
Folgedner scriot entsteht nun

```
<?php include "db.php";
include "funcs.php";
$func = new funcs();//Hier haben wir alle nötigen funtikionen zum bauen
d_base::db_connect();//Klasse d_base steht in db.php und db_connect macht uns die MYSQL klar
while(1) {
   $now = time();//Niemals vor der While, der script läuft durchgängign und die Zeit wächst
    $res = mysql_query("SELECT pid, bid FROM builds WHERE endtime >= {$now}");//MySQL query, nur das nötige abfragen
   while($row = mysql_fetch_assoc($res)) { //Array unter stüzt intigers, assoc nciht $row[0] ist nicht möglich, sicherheit
      $pid = $row['pid'];
      $buildid = $row['bid'];
      $game->build($pid,$buildid); //Gebäude wird nun Gebaut

sleep(1);//Pause
}
d_base::disconnect(); //DB-Schließen nciht vergessen!
```

Hiermit haben wir bereits unseren ersten kleinen Eventhandler!


```
<?php $gigra@unix: php /var/www/game/eventh.php & disown ?>
```


*4.Weitere Methoden*



In der Form wie dieser Eventhandler Programmiert wurde, geht es noch nciht sauber. Der Prozess wird ab etwa 12 Stunden abkratzen wegen entweder Timeouts pder verwedung von zuviel ram.

Meine erste Methode, dieses Problem zu beseitigen war folgende.
Ich nutze den Befehl shell_exec() um den eventhandler exterm zu prüfen und gegebenfalls zu starten.
die idee: wir machen einen 2ten eventhandler der weiß wie der eventhandler heisst und kann den abfragen.

```
<?php while(1) {
   $ausgabe = shell_exec("ps aux | grep /var/www/game/eventh.php");//ps aux | grep - fragt einen prozess ab
   if(strpos($ausgabe, "/var/www/game/eventh.php") === false)//=== ist nötig, der rest sucht in der ausgabe den string
      {
         shell_exec("php /var/www/game/eventh.php ? disown");
      }
   sleep(1);//Pause
}
```


Dieser EH macht keine Ausgaben und ist daher unendlich ausführbar, sollte man denncoh daran zweifeln, so kann man einen 3ten bauen bloß ohne while(1) und slleep, stattdessen als simpler script in ein crontab setzten

damit haben wir 1 Vor und Nachteil. Der Vorteil ist, der EH bekommt eine maximale stabiltät, wenn er aufhört zu funktionieren, so wird er sofort reaktivert.

Der Nachteil ist aber hierbei auch gravierend. Der Eventhandelr wird zu einem nahe zu unzerstörbaren Przess und verliert sofort an flexibiltät, da man im schlimmsten fall, den cron deaktiveren, den prüfer abstellen und dann erst den eh selbst auschalten müsste, um den Prozess entgültig zu beenden

Das selbe gilt nun wieder anders rum, wenn wir den wieder an haben wollen.

Nachteil nummer 2 liegt aber nciht in der Prüf Methode sondern generel am gesammten Eventhandler. Dieser Tritt ein, wenn der eventhandelr komplizierte Aufgaben zu erledigen hat, die manchmal Fehler hervorufen. Der EH beendet sich wegen dem Fehler und wird von dem Prüf handler neu gestartet, wobei der Fehler wieder auftritt. In dem Moment krazt euch der EH wieder ab.

Somit sezt der EH enrom aus und belastet die Game Performence und ist dabei auch schwer beendbar!

Die nächte Methode die ich euch Vorstellen möchte ist die Prozzesprogramierte Idee. Es si hinzukriegen hat mich lange Forschungen gekostet


*5.Die Idee der Prozessprogrammierung*




Um dieses Thema zu verstehen beschäftigen wir uns Primär mit der zentralen Frage der Prozessprogrammierung. Diese werden wir in folgendem Beispiel bilden.

Wir haben beispielsweise unseren guten alten geliebten Firefox. Wir surfen täglich darin, plötzlich schickt der Firefox server die nachricht, das wir eine neuere Versionm haben. Die meisten von euch kennen das, Firefox bietet euch an, sich neu zustarten

Was heisst eigentlich neustarten?
Neustarten heisst, dass der Prozess einmal komplett beendet wird und dann sich neustartet.

Um das hier nochmal zusammenzufassen....wir haben einen Prozess, der sich selbst beendet und sich dann wieder startet....hmmm

Wie soll das gehen, wie kann ein Prozess der sich komplett beendet hat sich doch wieder neu starten?

Das ist die Zentrale FRage der Prozessprogrammierung. Die Antwort sieht so aus:
Firefox startet einen Childprozess, der mit dem Beenden vom Parentprozess(also dem Firefox) nicht beendet wird.
Dieser startet einen neuen Parentprozess...also einen neuen Firefox, un beendet sich dannach. Damit haben wir das was wir erreichen wollten, Firefox hat sich beendet und neugestartet.

Dies ist so in der Form auch in PHP möglich.

Nun gestallten wir den EH völlig neu. Der EH ist nun ein Parent Prozess, der die Aufgaben in Prozesse schiebt. Wenn also das Kind sich beendet(egal ob Fehler oder erfolgreich), läuft der Hauptprozess weiter. Dabei werden die Vom EH verwendeten Speicehr sofort geleert, was beim anderen EH ncith Passiert.

Und auch die Theorie mit dem Neustart wollen wir in den neuen EH ein bauen.
6.Programmierung des neuen Eventhandlers
In diesem Kapitel machen wir nun einen neuen Eventhandler.

Der EH soll selbstständig ohne Prüfhandler areiten können und als ein UNIX Programm erkannt und gegebenfalls gekillt werden können!

Wir speichern den eh nun in einer Endlugnslosen datei, einfach "eventh"
Damit Linux auch erkennt, dass es sich hierbei um eine PHP Betriebene Datwei handelt füge man folgenden Code ein:

```
<?php #!/usr/bin/php
```

Beachtet bitte, einige PHP Tools Kodieren das Dokument vor dem Speichern und setzten am ender jeder zeile ein ^. Um das nachzuprüfen muss das Programm mit einem VI oder mcedit geöffnet werden.

Nun weiter gehts
Das programm benötigt eine tick definition.
Wenn wir es ganz professionell engehen wollen, brauchen wir einen signal_handler der organisiert die programme beendet.
Aber das lass ich hier mal weg sonst kann ich gleich n buch schreiben Smile

Um eine Erfolgreiche Teilung der Prozesse durchzuführen haben wir den befehl "pcntl_fork()"

Ich möchte von vornerein genau erklären was diese Funktion macht, damit man es versteht.
pcntl_fork verdoppelt den aktuellen Prozess und gibt eine ausgabe.

```
<?php $pid = pcntl_fork();
if($pid == -1)
   //pcntl_fork ist nciht installiert
else if($pid)
  //Das ist die ID des Hauptprozesses, also ist das heir der Vater
else 
   //Das ist das Kind
```
Wir wollen , das die Prozesse jeweils das tun was sie machen was sie sollen!
Der Vater soll warten bis das Kind beendet wird. dann soll es weiter gehen
wichtig ist, das das Kind sich Kontrolliert mit "exit()" beendet

```
<?php $pid = pcntl_fork();
if($pid == -1)
   //pcntl_fork ist nciht installiert
else if($pid) {
   pcntl_waitpid($pid, $unwichtig); //$unwichtig muss nciht vorher defniert worden sein, und muss auch nicht beachtet werden, sollte aber da sein
else {
   //scriipt
   exit();
}
```

Damit haben wir nun den scriptaufbau fast durch, nun soll der script sich noch 1. neustarten sollen, 2. kontrilliert

bisher haben wir das mit der while(1) schleife geregelt. nun wollen wir das kontrollierter
hierzu folgender beispielscript

```
<?php $actual = time();
$endtime = time()+300;
while($endtime>$actual) {
   //script
   $actual = time();
   usleep(10000);
}
```

nun habe ich usleep verwendet.

Dieser script funktioniert genau 5 Minuten lang, bis dann das while false steht und sich beendet.
Um einen erfolgreichen Neustart durchzuführen müssen wir warten bis der Prozess insgesammt beendet wird um ihn dann, ja endos, wieder neu zustarten, bestmöglich wieder in einem einzelnem Prozess.
Das heisst wir müssen unseren gesammten EH noch einmal forken, und dann in einem Kind Prozess earbeiten der noch mehr Kinder(enkel also ^^) kriegt.

Das jedoch funktioniert fehler frei wenn man weiß wie.

Damit wären alle Bedinugnen erfüllt und nun der Zusammenfassende scriot:

```
<?php #!/usr/bin/php
include "db.php";
include "funcs.php";
$funcs = new funcs();
d_base::db_connect();
declare(ticks = 1);//Ticks werden definiert
while(1) { //Beginne Hauptfunktion
   $main = pcntl_fork();
   if($pid == -1)
      die("error");
   else if($pid) {
      pcntl_waitpid($main, $bla1);
   }
   else {
      $actual = time();
      $endtime = time()+300;
      while($endtime > $actual) { // hier kommt der EH
         $now = time();
         $pid = pcntl_fork();
         if($pid == -1)
            die();
         else if($pid) {
            pcntl_waitpid($pid, $bla2);
         }
         else {
            $res = mysql_query("SELECT pid, bid FROM builds WHERE endtime>={$now}");
            while($row = mysql_fetch_assoc($res)) {
               $pid = $row['pid'];
               $buildid = $row['bid'];
               $funcs->build($pid,$buildid);
            }
            mysql_free_result($res);//Speicher leeren
            exit();//Beenden des Kindes
         }
      $actual = time();
      usleep(10000);
      }
      exit();//Ende Handler
   }
}//Ende While(1) SChleife
d_base::disconnect();//Zumachen bitte
```

Zum starten braucht man das "php" in der Console nciht mehr

Des weiteren haben wir ein Programm, falls es stresst: killall -9 eventh
Schon isse weg
Viel Spaß und Erfolg, ich hoffe ich konnte dem einen oder anderen Helfen
http://www.gigra-game.de


----------



## ne0hype (8. Februar 2008)

Hi, nettes Tutorial,

aber warum nimmst du nicht ein Cronjob und lässt diesen immer wieder ein Shell-Script ausführen welches in php geschrieben ist? Ist das nicht besser als eine while(1) mit sleep und unsleep?

Gruß
ne0hype


----------



## Michael Engel (8. Februar 2008)

das while(1) stört mich auch irgendwie ein wenig, wenn das script doch mal crasht ists doof. ggf. Da du ohnehin schon fork verwendest kann man ja gleich einen Deamon schreiben der eben im Hintergrund weiterläuft und per cron neugestartet wird falls er doch sterben sollte. Und ihm per pcntl_setid() ihm eine eigene Group ID zu geben und dann den parent prozess beenden per exit; Und um die einzigartigkeit zu Garantieren per flock() und fopen() verwenden. So etwas währ mir glaub an so ner Stelle noch ein wenig lieber.

Aber sonst muss ich dir recht geben, das es viele nicht gut Programmierte Browsergams und alles gibt die nicht die möglichkeiten von PHP ausnutzen.


----------

