Christian Bliß: Counter mit Macken

Hallo,

ich habe folgendes Counter-Script in Perl geschrieben. Jedoch passiert es manchmal, dass sich der Counter "zurücksetzt" und von Null beginnt zu zählen. Das muss wohl irgendwie mit dem FLOCK zusammenhängen, denk ich mal. Aber wo ist der Fehler?

#!/usr/bin/perl -w
use CGI::Carp qw(fatalsToBrowser);

Standardvariablen

$root = -- zensiert für self-html-forum --
$ip = $ENV{'REMOTE_ADDR'};
$zeit = time;

andere Variablen

$reload = 3600; # entspricht 1 Stunde Reloadsperre

open(BNCHR, "<$root/cgi-bin/counter/benachrichtigung.txt");
flock BNCHR, 2;
$zaehlerstand_benachrichtigung = <BNCHR>;
close(BNCHR);

HTML als Ausgabetyp festlegen

print "Content-type: text/html\n\n";

Alte IP-Adressen in ip.txt löschen START

open(IP, "<$root/cgi-bin/counter/ip.txt");
flock IP, 2;
open(IPTMP, ">$root/cgi-bin/counter/ip.txt.tmp");
flock IPTMP, 2;
while (<IP>) {
  my ($l_ip, $l_zeit) = split(/##/, $_);
  chop($l_zeit);
  $alter = $zeit - $l_zeit;
  next if ($alter > $reload);
  print IPTMP "$l_ip##$l_zeit\n";
}
close(IPTMP);
close(IP);

unlink ("$root/cgi-bin/counter/ip.txt");
rename ("$root/cgi-bin/counter/ip.txt.tmp", "$root/cgi-bin/counter/ip.txt");

Alte IP-Adressen in ip.txt löschen ENDE

Zählerstand auslesen

open(COUNT, "<$root/cgi-bin/counter/count.txt");
flock COUNT, 2;
$zaehlerstand = <COUNT>;
close(COUNT);

open(IP, "<$root/cgi-bin/counter/ip.txt");
flock IP, 2;
while (<IP>) {
  my ($l_ip, $l_zeit) = split(/##/, $_);
  chop $l_zeit;
  if ($ip eq $l_ip) {
    print "Besucher: <B>$zaehlerstand</B>";
    exit;
  }
}
close(IP);

$zaehlerstand++;

open(COUNT, ">$root/cgi-bin/counter/count.txt");
flock COUNT, 2;
print COUNT "$zaehlerstand";
close(COUNT);

open(IP, ">>$root/cgi-bin/counter/ip.txt");
flock IP, 2;
print IP "$ip##$zeit\n";
close(IP);

print "Besucher: <B>$zaehlerstand</B>";

if ($zaehlerstand eq $zaehlerstand_benachrichtigung) {
  open (MAIL, "|/usr/sbin/sendmail -t");
  print MAIL "To: -- zensiert für self-html-forum --\n";
  print MAIL "From: -- zensiert für self-html-forum --\n";
  print MAIL "Subject: Zählerstand bei $zaehlerstand\n\n";
  print MAIL "Hallo Chef,\n\n";
  print MAIL "der Zählerstand $zaehlerstand wurde soeben erreicht!\n\n";
  print MAIL "Mit freundlichen Grüßen\n";
  print MAIL "dein Projekt\n";
  close(MAIL);

$zaehlerstand_benachrichtigung_neu = $zaehlerstand_benachrichtigung+10000;

open(BNCHR, ">$root/cgi-bin/counter/benachrichtigung.txt");
  flock BNCHR, 2;
  print BNCHR "$zaehlerstand_benachrichtigung_neu";
  close(BNCHR);
}

  1. Hallo,

    der Fehler ist darin begründet, dass du _nacheinander_ öffnest, schreibst, öffnest, schreibst, etc. Das kann so nichts werden. Überlege doch mal, was passiert, wenn mehrere User gleichzeitg das Script ausführen. Der eine öffnet die Datei, die vom anderen _irgendwann_ im Script noch beschrieben wird. Ein flock hilft dir hier auch herzlich wenig. Ich habe mir jetzt zwar dein Script nicht ganz genau angesehen, aber das Problem ist mir im Grunde bekannt, da ich selbst schon in dieser Situation war.
    Die Lösung besteht darin, dass du die Datei _gleichzeitg_ zum lesen und schreiben öffnen muss, zB:

      
    open FILE, "+<counter.txt" or die "Kann counter.txt nicht zum lesen und schreiben öffnen: $!\n";  
    
    

    Markus.

    --
    http://www.apostrophitis.at
    http://www.pithax.net
    Wenn ich ein toller Programmierer währe, könnte ich vieleicht sogar Packete nach einem gewissen Standart kompelieren...
    Vieleicht progge ich aber auch eine tolle Gallerie, die dann hoffentlich funzt.
    1. Aber ist es nicht verhehrend, wenn eine Datei gleichzeitig von mehreren Benutzern beschrieben wird?

      Die Datei wird von einem Benutzer geöffnet, geändert, gespeichert - in der Zeit ist sie für andere Benutzer gesperrt. Diese werden in eine Art Warteschlange gereiht. Ist der erste Benutzer fertig, so wird die Datei freigegeben und der nächste darf öffnen, ändern, speichern.

      Ich dachte, so läuft das mit FLOCK ...

      1. Aber ist es nicht verhehrend, wenn eine Datei gleichzeitig von mehreren Benutzern beschrieben wird?

        Wird sie auch nicht, da du richtigerweise flock benutzt.
        Um ein vereinfachtes Beispiel meines Counters zu zeigen:

          
              open FILE, "+<counter.txt" or die "Kann counter.txt nicht öffnen: $!\n";  
              flock FILE, 2;  
              my $zahl = <FILE>;  
              seek FILE, 0, 0;  
              truncate FILE, 0;  
              print FILE ++$zahl;  
              close FILE;  
        
        

        Die Datei wird von einem Benutzer geöffnet, geändert, gespeichert - in der Zeit ist sie für andere Benutzer gesperrt. Diese werden in eine Art Warteschlange gereiht. Ist der erste Benutzer fertig, so wird die Datei freigegeben und der nächste darf öffnen, ändern, speichern.

        Ich dachte, so läuft das mit FLOCK ...

        Ich bin zwar auch nicht der größte Perl-Guru, aber ist es nicht so, dass nur der Lese,- und Schreibprozess gesperrt wird, was eigentlich auch nur die logische Schlussfolgerung des Verhaltens des Scripts sein kann?

        Wie auch immer. Ich weiß aus Erfahrung, dass ein separates Öffnen und Schreiben irgendwann immer dazu führt, dass der Counter gelöscht wird.

        Markus.

        --
        http://www.apostrophitis.at
        Wenn ich ein toller Programmierer währe, könnte ich vieleicht sogar Packete nach einem gewissen Standart kompelieren...
        Vieleicht progge ich aber auch eine tolle Gallerie, die dann hoffentlich funzt.
        1. Geht auch:

          open (FILE, "+>counter.txt") or die "$!";??

          oder geht nur " +< "?

          1. 你好 Christian,

            Geht auch:

            open (FILE, "+>counter.txt") or die "$!";??

            oder geht nur " +< "?

            Entweder "+>>" oder "+<", aber nicht "+>" – denn damit wird die Datei beim
            öffnen abgeschnitten, auch wenn noch kein Lock existiert. Und damit gibt es
            dann wieder race conditions und die damit verbundenen undefinierten
            Ergebnisse.

            再见,
             克里斯蒂安

            1. Aber wie mach ich es dann hier?

              open(COUNT, ">$root/cgi-bin/counter/count.txt");
              flock COUNT, 2;
              print COUNT "$zaehlerstand";
              close(COUNT);

              Oder muss es da gar nicht hin?

              1. 你好 Christian,

                Aber wie mach ich es dann hier?

                open(COUNT, ">$root/cgi-bin/counter/count.txt");
                flock COUNT, 2;
                print COUNT "$zaehlerstand";
                close(COUNT);

                Oder muss es da gar nicht hin?

                Da solltest du anders vorgehen:

                  
                open COUNT,'+<', "$root/cgi-bin/counter/count.txt" or die "error: $!";  
                flock COUNT, 2 or die "flock: $!";  
                truncate COUNT,0;  
                close COUNT;  
                
                

                再见,
                 克里斯蒂安

                --
                Neue Hardware eingebaut | Der dritte mir bekannte Block-Nutzer
                On the day *I* go to work for Microsoft, faint oinking sounds will be heard from far overhead, the moon will not merely turn blue but develop polkadots, and hell will freeze over so solid the brimstone will go superconductive. (Eric Raymond als Antwort auf ein Job-Angebot von MS)
                http://wwwtech.de/
                1. Da solltest du anders vorgehen:

                  open COUNT,'+<', "$root/cgi-bin/counter/count.txt" or die "error: $!";
                  flock COUNT, 2 or die "flock: $!";
                  truncate COUNT,0;
                  close COUNT;

                    
                    
                  Aber wo wird da der Zählerstand reingeschrieben?  
                    
                  Was bedeutet truncate?
                  
                  1. 你好 Christian,

                    Da solltest du anders vorgehen:

                    open COUNT,'+<', "$root/cgi-bin/counter/count.txt" or die "error: $!";
                    flock COUNT, 2 or die "flock: $!";
                    truncate COUNT,0;
                    close COUNT;

                    
                    >   
                    >   
                    > Aber wo wird da der Zählerstand reingeschrieben?  
                      
                    Äh, ja, nach dem truncate, sorry.  
                      
                    
                    > Was bedeutet truncate?  
                      
                    Abschneiden.  
                      
                    再见,  
                     克里斯蒂安  
                    
                    -- 
                    [Neue Hardware eingebaut](http://ck.kennt-wayne.de/neue-hardware-eingebaut) | [Der dritte mir bekannte Block-Nutzer](http://ck.kennt-wayne.de/dritter-mir-bekannter-block-nutzer)  
                    1 + 1 = 3 für gosse Werte von 1.  
                      
                    <http://wwwtech.de/>  
                    
                    
                    1. Hier funktioniert das aber nicht, da eine neue datei erstellt wird.

                        
                      # Alte IP-Adressen in ip.txt löschen START #  
                      open IP, "+<$root/cgi-bin/counter/ip.txt" or die "error: $!";  
                      flock IP, 2 or die "flock: $!";  
                      open IPTMP, ">$root/cgi-bin/counter/ip.txt.tmp"  or die "error: $!";  <--------  
                      flock IPTMP, 2 or die "flock: $!";  
                      while (<IP>) {  
                        my ($l_ip, $l_zeit) = split(/##/, $_);  
                        chop($l_zeit);  
                        $alter = $zeit - $l_zeit;  
                        next if ($alter > $reload);  
                        print IPTMP "$l_ip##$l_zeit\n";  
                      }  
                      close IPTMP;  
                      close IP;  
                        
                      unlink ("$root/cgi-bin/counter/ip.txt");  
                      rename ("$root/cgi-bin/counter/ip.txt.tmp", "$root/cgi-bin/counter/ip.txt");  
                      # Alte IP-Adressen in ip.txt löschen ENDE #  
                      
                      

                      Was mach ich da?

                      1. Hier funktioniert das aber nicht, da eine neue datei erstellt wird.

                        Du hast noch nicht verstanden, du öffnest, flockst, liest und schreibst eine einzige Datei, ansonsten macht das flock wenig Sinn.

                        In deinem Beispiel darfst du nur die Datei IP lesen und dann acuh schreiben.

                        Struppi.

                      2. 你好 Christian,

                        Hier funktioniert das aber nicht, da eine neue datei erstellt wird.
                        […]

                        Für temporäre Dateien benutze File::Temp.

                        unlink ("$root/cgi-bin/counter/ip.txt");
                        rename ("$root/cgi-bin/counter/ip.txt.tmp", "$root/cgi-bin/counter/ip.txt");

                        Nicht löschen und umbenennen, nur „umkopieren“ (zeilenweise einlesen und
                        in ip.txt schreiben)! Und vergiss das truncate nicht.

                        再见,
                         克里斯蒂安

                        --
                        Neue Hardware eingebaut | Der dritte mir bekannte Block-Nutzer
                        Wenn auf Erden alle das Schoene als schoen erkennen, so ist dadurch schon das Haessliche bestimmt.
                        http://wwwtech.de/
                        1. Ich steh momentan ein wenig aufm Schlauch ... wie genau sollte der Quelltext aussehen, wenn ich in diesem Fall "File::Temp" verwende?

                          Und wo muss überall truncate hin?

                          1. 你好 Christian,

                            also. Warum liest du nicht mal ein wenig Dokumentation? Ich habe nun
                            wirklich weder Lust noch Zeit dich ans Händchen zu nehmen und dir in
                            Tippel-Schritten alles vorzumachen. perldoc File::Temp ist schliesslich
                            aussagekräftig genug.

                            再见,
                             克里斯蒂安

                            --
                            Neue Hardware eingebaut | Der dritte mir bekannte Block-Nutzer
                            Ganz gleich, welchen Weg ich wähle, ich kehre heim.
                            http://wwwtech.de/
                          2. Ich steh momentan ein wenig aufm Schlauch ... wie genau sollte der Quelltext aussehen, wenn ich in diesem Fall "File::Temp" verwende?

                            Eine temporäre Datei ist hier entweder nicht nötig oder du brauchst die Datei IP nicht zum lesen und schreiben zu öffnen, da du da ja nichts reichschreibst.

                            Struppi.

        2. Hallo Markus,

            
          
          >       open FILE, "+<counter.txt" or die "Kann counter.txt nicht öffnen: $!\n";  
          >       flock FILE, 2;  
          >       my $zahl = <FILE>;  
          >       seek FILE, 0, 0;  
          >       truncate FILE, 0;  
          >       print FILE ++$zahl;  
          >       close FILE;  
          
          

          ich habe da mal eine Frage.

          Wenn man davon ausgeht, dass sich die Zahl immer um 1 erhöht und
          somit die Zahl nicht kleiner wird, könnte man dann "truncate FILE, 0"
          weglassen?

          Beispiel:

          Wenn ich die Zahl 9 durch 10 ersetze, dann werden doch mehr Zeichen
          geschrieben, als vorher schon in der Datei standen. Oder könnte es
          da Probleme geben? Wenn der Counter auf Null zurück gesetzt wird -
          aus irgendwelchen Gründen -, nur dann wäre doch truncate notwendig
          oder? Oder sollte man truncate auf jeden Fall einsetzen?

          Auch meine Vorgehensweise war bisher anders (Beispiel):

            
             open FILE, "$file" or die "Kann $file nicht öffnen!\n";  
             flock FILE, 2;  
             my $file = [ grep !/das will ich nicht/, <FILE> ];  
             seek FILE, 0, 0;  
             print FILE @{$file};  
            
             # bisher habe ich immer den Rest der Datei geleert  
            
             truncate(FILE,tell(FILE);  
          
          

          In welchen Fällen sollte ich eine der beiden Varianten meiden/nutzen?

          Greez,
          opi

          --
          Selfcode: ie:( fl:( br:^ va:) ls:] fo:) rl:( n4:? ss:| de:] ch:? mo:|
          1. 你好 opi,

            Wenn man davon ausgeht, dass sich die Zahl immer um 1 erhöht und
            somit die Zahl nicht kleiner wird, könnte man dann "truncate FILE, 0"
            weglassen?

            Jupp.

            再见,
             克里斯蒂安

            --
            Neue Hardware eingebaut | Der dritte mir bekannte Block-Nutzer
            Die Wirklichkeit hat weder ein Inneres, noch ein Äußeres, noch ein Zentrum.
            http://wwwtech.de/
            1. Hallo,

              你好 opi,

              Wenn man davon ausgeht, dass sich die Zahl immer um 1 erhöht und
              somit die Zahl nicht kleiner wird, könnte man dann "truncate FILE, 0"
              weglassen?

              Jupp.

              ah... und seek doch auch? :-)

              Greez,
              opi

              --
              Selfcode: ie:( fl:( br:^ va:) ls:] fo:) rl:( n4:? ss:| de:] ch:? mo:|
              1. Hallo,

                ah... und seek doch auch? :-)

                das war quatsch. Zu schnell gedacht!

                Greez,
                opi

                --
                Selfcode: ie:( fl:( br:^ va:) ls:] fo:) rl:( n4:? ss:| de:] ch:? mo:|
              2. 你好 opi,

                Wenn man davon ausgeht, dass sich die Zahl immer um 1 erhöht und
                somit die Zahl nicht kleiner wird, könnte man dann "truncate FILE, 0"
                weglassen?

                Jupp.

                ah... und seek doch auch? :-)

                Das könntest du weglassen, wenn du truncate benutzen würdest.

                再见,
                 克里斯蒂安

                --
                Neue Hardware eingebaut | Der dritte mir bekannte Block-Nutzer
                Swen Wacker: Denn wer 'ne Blacklist hat, muss halt daran denken, dass er manches nicht sieht... und vor dem posten die Realitaet einschalten
                http://wwwtech.de/
                1. Hallo,
                  Hallo Christian,

                  ah... und seek doch auch? :-)

                  Das könntest du weglassen, wenn du truncate benutzen würdest.

                  mit mod_perl oder auch FASTCGI wäre nur das Schreiben notwendig ohne
                  seek und ohne truncate :-)

                  Der Gedanke war doch nicht so falsch.

                  Greez,
                  opi

                  --
                  Selfcode: ie:( fl:( br:^ va:) ls:] fo:) rl:( n4:? ss:| de:] ch:? mo:|
                  1. 你好 opi,

                    ah... und seek doch auch? :-)

                    Das könntest du weglassen, wenn du truncate benutzen würdest.

                    mit mod_perl oder auch FASTCGI wäre nur das Schreiben notwendig ohne
                    seek und ohne truncate :-)

                    Wie kommst du darauf? mod_perl bzw. FastCGI entbinden dich nicht der
                    Serialisierung…

                    再见,
                     克里斯蒂安

                    --
                    Neue Hardware eingebaut | Der dritte mir bekannte Block-Nutzer
                    Echte Hacker benutzen Aexte. (Thomas Walter in de.org.ccc)
                    http://wwwtech.de/
                    1. Hallo,

                      mit mod_perl oder auch FASTCGI wäre nur das Schreiben notwendig ohne
                      seek und ohne truncate :-)

                      Wie kommst du darauf? mod_perl bzw. FastCGI entbinden dich nicht der
                      Serialisierung…

                        
                      #!/usr/bin/perl  
                        
                      use strict;  
                      use CGI;  
                      use FCGI;  
                        
                      my $req = FCGI::Request();  
                        
                      # zunaechst wird der aktuelle Stand des Counters ausgelesen.  
                        
                      open FILE, "+<counter.txt" or die "Kann counter.txt nicht oeffnen: $!\n";  
                      flock FILE, 2;  
                      my $zahl = <FILE>;  
                      close FILE;  
                        
                      while ($req->Accept >= 0) {  
                        
                         # hier wird fuer jeden Seitenzugriff der Counter +1 gezaehlt und  
                         # weggeschrieben. da die Startposition des Pointers beim Oeffnen  
                         # der Datei schon an Position 0,0 ist und die naechst hoehere Zahl  
                         # nicht kleiner sein wird, kann sofort in die Datei geschrieben  
                         # werden, ohne das seek oder truncate benutzt wird.  
                        
                         $zahl++;  
                         open FILE, "+<counter.txt" or die "Kann counter.txt nicht ffnen: $!\n";  
                         flock FILE, 2;  
                         print FILE $zahl;  
                         close FILE;  
                        
                         # dynamische HTML-Ausgabe ...  
                      }  
                      
                      

                      Das klappt natürlich nur, wenn nur ein FCGI-Prozess gestartet wird
                      und bei hoher Auslastung der Webserver nicht auf die kommt, noch mehr
                      Prozesse zu starten.

                      Ist der Denkansatz falsch?

                      Greez,
                      opi

                      --
                      Selfcode: ie:( fl:( br:^ va:) ls:] fo:) rl:( n4:? ss:| de:] ch:? mo:|
                      1. 你好 opi,

                        mit mod_perl oder auch FASTCGI wäre nur das Schreiben notwendig ohne
                        seek und ohne truncate :-)

                        Wie kommst du darauf? mod_perl bzw. FastCGI entbinden dich nicht der
                        Serialisierung…
                        [… nähere Erläuterung …]

                        Ach so. Ja, da, da hast du recht.

                        再见,
                         克里斯蒂安

                        --
                        Neue Hardware eingebaut | Der dritte mir bekannte Block-Nutzer
                        Die Summe zweier gerade Primzahlen ist immer eine Quadratzahl.
                        http://wwwtech.de/
      2. 你好 Christian,

        Die Datei wird von einem Benutzer geöffnet, geändert, gespeichert - in
        der Zeit ist sie für andere Benutzer gesperrt. Diese werden in eine Art
        Warteschlange gereiht. Ist der erste Benutzer fertig, so wird die Datei
        freigegeben und der nächste darf öffnen, ändern, speichern.

        So machst du es aber nicht. Um mal zwei Code-Stücke herauszupicken:

          
        open(COUNT, "<$root/cgi-bin/counter/count.txt");  
        flock COUNT, 2;  
        $zaehlerstand = <COUNT>;  
        close(COUNT);  
        
        

        […]

          
        open(COUNT, ">$root/cgi-bin/counter/count.txt");  
        flock COUNT, 2;  
        print COUNT "$zaehlerstand";  
        close(COUNT);  
        
        

        Prozess a läuft in das erste Code-Stück. $zaehlerstand ist korrekt befüllt.
        Dann wird er schlafen gelegt. Prozess b läuft in das erste Code-Stück,
        $zaehlerstand wird auch hier korrekt befuellt. Dann laeuft Prozess a in
        den zweiten Schnippsel, macht das open(COUNT,">...") und wird schlafen
        gelegt. Prozess c läuft in das erste Code-Stück, $zaehlerstand ist jetzt 0
        da die Datei durch das open(COUNT,">...") leer ist. Prozess b läuft weiter,
        macht das open(COUNT,">...") und schreibt $zaehlerstand hinein. Prozess a
        läuft weiter und schreibt seine Version von $zaehlerstand hinein. Welche
        ist nun richtig? Und, um es noch schlimmer zu machen: Prozess c schreibt
        seinen Wert nun auch in die Datei: der Zähler fängt wieder bei 0 an.

        再见,
         克里斯蒂安

        --
        Neue Hardware eingebaut | Der dritte mir bekannte Block-Nutzer
        Wenn du gehst, gehe. Wenn du sitzt, sitze. Und vor allem: schwanke nicht!
        http://wwwtech.de/