Andy: Zähler-Datei springt auf null!

Hallo!

Heute war es mal wieder so weit! Mein selbstgebastelter Zähler hatte sich verabschiedet und statt knappen 16000 Besuchern warens nur noch 9! Gut nur das zumindest immer eine Backupdatei für den Vortag vorhanden ist! ;-)

Hier der entsprechende Code:
$filename=$Logdir."count.txt";
$filehandler = fopen($filename,"r");
flock($filehandler,1);
$content = fread($filehandler, filesize($filename));
flock($filehandler,3);
fclose($filehandler);
$filehandler = fopen($filename,"w+");
flock($filehandler,2);
$content++;
fwrite($filehandler, $content);
flock($filehandler,3);
fclose($filehandler);

Wie man sieht sind die flocks drin!! Und am Anfang der PHP Datei hab ich auch noch ein ignore_user_abort(true);!

Ich weiss einfach nicht woran es liegt! Vermutlich natürlich zwei konkurierende Seitenzugriffe!
*.185.112.* - - [12/Feb/2004:11:06:54 +0100]
*.36.65.* - - [12/Feb/2004:11:06:55 +0100]
Der Ausschnitt aus der Logdatei zeigt es! (Unkenntlich gemacht aus rechtlichen Gründen!? ;-) )
Ich vermut mal das es der heute war!

Es ist halt zum k*tzen! Das einzige wirklich sichere erscheint mir der Einsatz von mySql, aber das ist für einen Counter wirklich mit Kanonen auf Spatzen geschossen!

Also, die Frage gibt es eine sichere Methode zum Benutzen von Textdateien?

Gruss,
Andy

  1. Hey,

    Wie man sieht sind die flocks drin!! Und am Anfang der PHP Datei hab ich auch noch ein ignore_user_abort(true);!

    Ich unterstelle mal, daß du die Werte für den zweiten flock() Parameter im Kopf hast, aber sehr leserlich ist es so nicht; und auch die Bedeutung von "w+" ist mir nicht ganz klar - war das nicht für Lesen+Schreiben über den selben Handle?, wozu dann zweimal fopen?

    Davon abgesehen, halte ich flock() auch für keine sichere Methode, schon weil es erst nach fopen() aufgerufen wird und PHP die Fkt. laut Manual auch nur simuliert.

    Also, die Frage gibt es eine sichere Methode zum Benutzen von Textdateien?

    Nicht machen. Für deinen Besucherzähler gibt es aber sicher eine bessere Methode. Du könntest z.B. einfach nur immer ein Zeichen an deine Textdatei anfügen, und schlicht die Dateigröße als den momentanen Wert verwenden.

    $counter = filesize($fn);
      $f = fopen($fn, "a");
      fwrite($f, "."); fclose($f);

    Dürfte nicht nur schneller sein, sondern ein echter Zugewinn, wenn das FTP-Programm den aktuellen Stand anzeigt, ohne daß die Datei erst heruntergeladen werden muß ;)
    Nachteil ist natürlich der knapper-werdende Webspace, wenn du zu viele Besucher hast.

    MsF,
    milky

    1. Hello Ihr zwei (und alle die noch dazukommen),

      Wie man sieht sind die flocks drin!! Und am Anfang der PHP Datei hab ich auch noch ein ignore_user_abort(true);!

      Das mit dem ignore_user_abort(true) muss ich mir doch tatsächlich nochmal zu Gemüte führen. Könnte doch tatsächlich sein, dass das in manchen Scripten notwendig ist.

      Zum lock() und zum fopen():

      flock() versucht, unter dem Handle der Datei einen Eintrag in der Sperrtabelle zu setzen. Dabei kann ein Exclusive Lock beantragt werden, dann bekommt niemand anderes mehr ein Lock zugeteilt oder es kann ein Shared Lock beantragt werden. Wenn ein Shared Lock erteilt ist, bekommt niemand mehr ein Exclusive Lock, aber jeder kann noch ein Shared Lock erhalten. Wenn dann z.B. der erste "Locker" sich entscheidet, aus seinem Shared Lock ein Exclusive Lock machen zu lassen, hat er Pech gehabt, da ja ein weiteres Shared Lock eingetragen ist.

      Nun zum Zeitverhalten:
      flock() in der Voreinstellung arbeitet im Blocking-Mode. das bedeutet, dass die Kontrolle solange nicht aus der Funktion flock() zurückkehrt, bis dieses erfolgreich abgeschlossen werden konnte oder ein Timeout (MaxExecutionTime) das Script sowieso abwürgt. MaxExecutionTime ist nun aber nur die reine verbrauchte Rechenzeit. Da die Funktion flock() aber zwischen ihren wiederholten Versuchen, die Datei zu sperren, immer in die idle-Funktion geht, können aus 30sec MaxExecutionTime schon mal 5 Minuten Wartezeit werden. Idle zählt nicht zur Arbeitszeit [und flock() hat keine Gewerkschaft *gg*].

      Ich unterstelle mal, daß du die Werte für den zweiten flock() Parameter im Kopf hast, aber sehr leserlich ist es so nicht; und auch die Bedeutung von "w+" ist mir nicht ganz klar - war das nicht für Lesen+Schreiben über den selben Handle?, wozu dann zweimal fopen?

      fopen(...,"w+") würde die Datei im zerstörenden Modus zum Schreiben und Lesen öffnen. Man beachte bitte die Reihenfolge! Erst schreiben, _dann_ lesen. Wenn die Datei nicht vorhanden ist, wird sie angelegt.

      fopen(...,"r+") würde die Datei im erhaltenden Modus zum Lesen und Schreiben öffnen. Wenn die Datei nicht vorhanden ist, gibts Gemecker.

      fopen(...,"a+") würde die Datei im nicht zerstörenden Modus zum Schreiben hinter dem Ende der Datei, also zum Anhängen, und zum Lesen öffnen. Dieser Öffnungsmodus ist also auf allen Systemen, die bei a+ auch die Bewegung des Dateizeigers zulassen, der geeignetste. Sowohl Linux als auch WinDOS lassen das zu. Man muss dann nur vor dem Auslesen den Zeiger mit rewind() oder fseek() auf das 0te (nullte) Byte, also auf den Anfang stellen

      $ok = fseek($fh,0,SEEK_SET);

      Davon abgesehen, halte ich flock() auch für keine sichere Methode, schon weil es erst nach fopen() aufgerufen wird und PHP die Fkt. laut Manual auch nur simuliert.

      Da wird nichts simuliert und auch nicht emuliert *g*

      Man kann ein advisory locking durchführen. Das macht flock(). Advisory bedeutet, dass Lock- und Dateioperationen voneinander entkoppelt sind. Die einzige Verbindung ist das Filehandle, dass man sich mit fopen() besorgt, um es dann als Kopie an flock() zu übergeben. Man kann aber auch auf ein inzwischen ungültiges Handle ein Lock setzen. Also Achtung!

      Wenn sich aber jedes Programm (jedes Script), dass die Datei nutzen will, an die Locking-Strategie hält, ist das Verfahren sicher.

      Hier der entsprechende Code:

      $filename = $Logdir."count.txt";

      $filehandler = fopen($filename,"r");
      flock($filehandler,1);
      $content = fread($filehandler, filesize($filename));
      flock($filehandler,3);
      fclose($filehandler);
      $filehandler = fopen($filename,"w+");
      flock($filehandler,2);
      $content++;
      fwrite($filehandler, $content);
      flock($filehandler,3);
      fclose($filehandler);

      function coutner($filename)
      {
        $uab = ignore_user_abort(true);   # damit das Script auch zuende läuft

      if ($fh = fopen($filename,"a+"))
        {
          if ($lock = flock($fh,LOCK_EX))   # wouldblock funktioniert noch nicht
          {                                 # wir Locken mit Wait und EXCLUSIV
            if($seek = fseek($fh,0,SEEK_SET); an den Anfang der Datei
            {
              $content = fread($filehandler, filesize($filename)); # Datei auslesen
              # Content in Variablen zerlegen
              # Variablen verarbeiten
              # neuen Content aus Variablen zusammenbauen
              # hier:

      $content++;

      if($seek = fseek($fh,0,SEEK_SET); an den Anfang der Datei
              {
                $write = fwrite($fh,$content);
                if($write)
                {
                  ## falls der neue Content kürzer ist als der alte
                  $trunc = ftruncate($fh,strlen($content));
                }
              }
            }
            fclose($fh);  ## Datei schließen und Lock aufheben.

      ignore_user_abort($uab); ## wieder auf alten Wert zurücksetzen
            return $seek and $write and $trunc;
          }
        }
        else
        {
          ignore_user_abort($uab); ## wieder auf alten Wert zurücksetzen
          return false;
        }
      }

      So müsste es funktionieren. habs noch nicht degugged.

      Die Datei bleibt die ganze Zeit des Vorganges gesperrt. So ist sichergestellt, das zwischen dem Read von Client A und dem Write von Client A der Client B warten muss.

      Da Auslesen, Verändern und Zurückschreiben in Sekundenbruchteilen ablaufen, ist das vertretbar.

      Bei langwierigeren Contentoperationen muss man sich ggf. ein anderes Verfahren ausdenken. Bei Satzoperationen, wenn also nicht die ganze Datei von der Änderung betroffen ist, benötigt man dann einen Conflict-Counter. Der muss _vor_ jedem Schreibvorgang nochmals gelesen und verglichen werden.

      Das Auslesen und Zurückschreiben von Daten muss aber insgesamt durch ein Lock_Ex geklammert bleiben!

      Liebe Grüße aus http://www.braunschweig.de

      Tom

      --
      Fortschritt entsteht nur durch die Auseinandersetzung der Kreativen
      1. Hi!
        Erstmal Danke für die Anworten!

        $filename = $Logdir."count.txt";

        $filehandler = fopen($filename,"r");
        flock($filehandler,1);
        $content = fread($filehandler, filesize($filename));
        flock($filehandler,3);
        fclose($filehandler);
        $filehandler = fopen($filename,"w+");
        flock($filehandler,2);
        $content++;
        fwrite($filehandler, $content);
        flock($filehandler,3);
        fclose($filehandler);

        Gut! Das mit dem w+ ist sicher trivaler Fehler, macht aber auch nichts, da ich ja neu schreiben möchte!
        Trotzdem kann ich mir nicht vorstellen, was schief läuft! Wenn A lesen will und B lesen will, muss halt B warten! Wenn dann A und B gleichzeitig schreiben wollen, ist IHMO das selbe, A erhält als erstes den Zuschlag und B muss warten! Das einige was passieren kann, ist das A und B den gleichen Zählerstand auslesen und der Zähler dann halt um 1 statt um 2 hochgeht!

        function coutner($filename)
        {
          $uab = ignore_user_abort(true);   # damit das Script auch zuende läuft

        if ($fh = fopen($filename,"a+"))
          {
            if ($lock = flock($fh,LOCK_EX))   # wouldblock funktioniert noch nicht
            {                                 # wir Locken mit Wait und EXCLUSIV
              if($seek = fseek($fh,0,SEEK_SET); an den Anfang der Datei
              {
                $content = fread($filehandler, filesize($filename)); # Datei auslesen
                # Content in Variablen zerlegen
                # Variablen verarbeiten
                # neuen Content aus Variablen zusammenbauen
                # hier:

        $content++;

        if($seek = fseek($fh,0,SEEK_SET); an den Anfang der Datei
                {
                  $write = fwrite($fh,$content);
                  if($write)
                  {
                    ## falls der neue Content kürzer ist als der alte
                    $trunc = ftruncate($fh,strlen($content));
                  }
                }
              }
              fclose($fh);  ## Datei schließen und Lock aufheben.

        ignore_user_abort($uab); ## wieder auf alten Wert zurücksetzen
              return $seek and $write and $trunc;
            }
          }
          else
          {
            ignore_user_abort($uab); ## wieder auf alten Wert zurücksetzen
            return false;
          }
        }

        So müsste es funktionieren. habs noch nicht degugged.

        Sieht schon nicht schlecht aus, vorallem der Zugriff mit einem Filehandler erscheint mir sinnvoll!
        Die Konstantennamen (LOCK_EX etc) kannte ich noch gar nicht! Musste mir gleich mal 'ne aktuelle PHP-Doc runterladen, meine war noch von 2001! ;-)

        Ich hab noch ein bisschen recherchiert: Auf http://www.php.net/manual/de/function.flock.php steht, dass wenn PHP als CGI-Modul läuft (meinen Provider leider ja), das es vorkommen kann das es nicht funzt!
        Ist halt die Frage ob das ignore_user_abort() was bringt bei CGI-Modul oder nicht?
        Mit meinen Code oben, wäre die Situation halt, das File wird zum schreiben geöffnet (und dabei inhaltlich gelöscht!) und bevor die Daten geschrieben werden, bricht das Skript ab! Und schon wieder ein Unsicherheitsfaktor! :-(

        Man ist halt von der Unwissenheit oder Böswilligkeit der Benutzer nie geschont!

        Danke nochmal und Danke für weitere Antworten,
        Andy

        1. Hello,

          Gut! Das mit dem w+ ist sicher trivaler Fehler, macht aber auch nichts, da ich ja neu schreiben möchte!

          Nein, das ist ein schwerer Fehler, da die Datei zum Zeitpunkt des Schreibens nicht gesperrt ist.

          Trotzdem kann ich mir nicht vorstellen, was schief läuft! Wenn A lesen will und B lesen will, muss halt B warten!

          Ja genau. Aber Du lässt B nicht warten, sondern für einen kurzen Moment machen was es will.

          Wenn dann A und B gleichzeitig schreiben wollen, ist IHMO das selbe, A erhält als erstes den Zuschlag und B muss warten! Das einige was passieren kann, ist das A und B den gleichen Zählerstand auslesen und der Zähler dann halt um 1 statt um 2 hochgeht!

          Und wenn 100 B's gelesen haben, dann geht der Zähler ben nur um eins hoch statt um 100.

          Mit Dir will ich nicht fliegen und Brot kauf ich auch nicht von Dir...

          Sieht schon nicht schlecht aus, vorallem der Zugriff mit einem Filehandler erscheint mir sinnvoll!

          Die Konstantennamen (LOCK_EX etc) kannte ich noch gar nicht! Musste mir gleich mal 'ne aktuelle PHP-Doc runterladen, meine war noch von 2001! ;-)

          Warum nutzt Du nicht die Online-Version? Sitzt Du in der Internet-Wüste?

          Ich hab noch ein bisschen recherchiert: Auf http://www.php.net/manual/de/function.flock.php steht, dass wenn PHP als CGI-Modul läuft (meinen Provider leider ja), das es vorkommen kann das es nicht funzt!

          Was soll da nicht funktionieren? (meintest Du "funktionieren?". Wenn Du mit mir postest, dann vermeide bitte diese Slang-Ausdrücke)
          Das Script wird aufgerufen und assoziiert sein Runtime. Das macht dann alles das, was ein PHP als Modul auch macht, nur dass kein direkter datenaustasuch zwischen Apache und PHP stattfinden kann. Der ist hier aber gar nicht norwendig, da das Filelocking im OS verankert ist.

          Ist halt die Frage ob das ignore_user_abort() was bringt bei CGI-Modul oder nicht?

          Auch das sollte was bringen, da es sich ja um die Verbindung des Clients mit der Runtime-Instanz des Scriptes handelt.

          Mit meinen Code oben, wäre die Situation halt, das File wird zum schreiben geöffnet (und dabei inhaltlich gelöscht!) und bevor die Daten geschrieben werden, bricht das Skript ab! Und schon wieder ein Unsicherheitsfaktor! :-(

          Ja. Für diesen Hinweis danke ich Dir auch. Man lernt immer 'was dazu, auch wenn man glaubt, schon gaaanz viel zu können. Aber alles ist halt relativ.

          Wenn ich Sven jetzt ein bisschen kitzele, dann hat der bestimmt noch drei Seiten Background dazu, über die ich noch nicht nachgedacht habe.

          Man ist halt von der Unwissenheit oder Böswilligkeit der Benutzer nie geschont!

          Das hat nichts mit Böswilligkeit zu tun, sondern nur mit zeitlich ungünstigen Konstellationen (hoher Auslastung). Da tauchen dann plötzlich die ganzen serialisierungs-Versäumnisse auf.

          Liebe Grüße aus http://www.braunschweig.de

          Tom

          --
          Fortschritt entsteht nur durch die Auseinandersetzung der Kreativen
          1. Hallo

            Gut! Das mit dem w+ ist sicher trivaler Fehler, macht aber auch nichts, da ich ja neu schreiben möchte!

            Nein, das ist ein schwerer Fehler, da die Datei zum Zeitpunkt des Schreibens nicht gesperrt ist.

            Jaja, ist schon gut! ;-)

            Und wenn 100 B's gelesen haben, dann geht der Zähler ben nur um eins hoch statt um 100.

            Mit Dir will ich nicht fliegen und Brot kauf ich auch nicht von Dir...

            Mein Gott, bist du ein Schwarzmaler! ;-) Ist ja gut! Aber das 100 (Besser 101 wir haben ja noch A) gleichzeitig zugreifen ist doch recht unverscheinlich. Ich hab' so um die 25 Hits durchschnittlich täglich.

            Warum nutzt Du nicht die Online-Version? Sitzt Du in der Internet-Wüste?

            Ja! Ich kann nicht immer online gehen, falls ich was nachschauen will! Ich bekomm ja leider kein DSL, obwohl ich seit 2000 regelmässig nachfrage! Auf den Speed könnt ich ruhig verzichten, aber ich bräuchte halt 'ne Flatrate! Da bleibt nur der XXL-Sonntag zum kostenlosen Surfen!
            Okay, ich hätte schon längst mal ein Dokumentation-Update machen können.

            Was soll da nicht funktionieren? (meintest Du "funktionieren?". Wenn Du mit mir postest, dann vermeide bitte diese Slang-Ausdrücke)

            Tut mir leid! :(

            Ja. Für diesen Hinweis danke ich Dir auch. Man lernt immer 'was dazu, auch wenn man glaubt, schon gaaanz viel zu können. Aber alles ist halt relativ.

            Gern geschehen! Aber das dürfte eigentlich ja wie schon geschrieben bei einen ignore_user_abort(true) nicht passieren! (Hoffentlich!)

            Man ist halt von der Unwissenheit oder Böswilligkeit der Benutzer nie geschont!
            Das hat nichts mit Böswilligkeit zu tun, sondern nur mit zeitlich ungünstigen Konstellationen (hoher Auslastung). Da tauchen dann plötzlich die ganzen serialisierungs-Versäumnisse auf.

            Unter Böswiligkeit verstehe ich das absichtliche Abschiessen durch viele gleichzeitige Anfragen (halt eine Art DoS-Angriff)!

            Grüße,
            Andreas