PHP/Anwendung und Praxis/Zugriffszähler mit Onlineanzeige

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Informationen zum Autor

Name:
Thomas Schmieder
E-Mail:
Homepage:
bitworks.de

In diesem Artikel wird gezeigt, wie eine Zugriffszählung mit IP-Sperre und eine Onlineanzeige realisiert werden können. Das Ergebnis kann berechtigten Besuchern mittels einer Grafik angezeigt werden.

Der Nutzen dieser Zugriffszählung wird zwar in der Fachwelt immer belächelt, eignet sich aber als praktisches Beispiel hervorragend, um den Zusammenhang zwischen HTML, JavaScript und PHP aufzuzeigen und für die Vertiefung des Umgangs mit Dateien. Zum praktischen Einsatz kommt hier auch das File Locking.

Inhaltsverzeichnis

[Bearbeiten] Zielvorgabe

Für eine Webseite soll eine Zählung der Seitenbesucher erstellt werden, sowie eine "Online-Anzeige" für gerade in der Seite beschäftigte Besucher. Eine Anmeldung der Benutzer soll hierfür nicht notwendig sein. Fehler, die durch das Verfahren Besucher einfach an ihrer IP festzumachen entstehen, werden hingenommen.

Damit aber Mehrfachbesuche derselben Besucher innerhalb kurzer Zeitabstände (1-3 Stunden, zusätzlich nachgetriggert) nicht doppelt gezählt werden und auch Counter-Floading verhindert werden kann, sind die IPs der Besucher für diese Sperrzeit zu registrieren und von der Zählung auszunehmen. Als "online" soll ein Besucher gelten, der innerhalb der letzten 30-60 Sekunden einen Request ausgelöst hat, bzw. die Seite noch im Browser geöffnet hält.

[Bearbeiten] Programmteile

Welche Programmelemente benötigen wir? Welches Programmelement übernimmt welche Aufgabe?

[Bearbeiten] Das Zählscript in PHP

Beispiel: visitcount.php
  1. <?php  ### visitcount.php ### 26.10.2016 ###
  2.  
  3. # zählt die Requests und merkt sich die IP der Requestoren für die Dauer der
  4. # COUNTER_LOCKTIME. In dieser Zeit wird ein erneuter Request derselben IP 
  5. # nicht gezählt. Ein erneuter Request wird auch nach Ablauf von COUNTER_LOCKTIME
  6. # nicht gezählt, wenn zwischendurch kein Request mit einer abweichenden IP
  7. # eingetroffen ist.
  8. #
  9. # Hinweis:
  10. # IPs als Grundlage für die Client-Erkennung zu benutzen ist keine saubere
  11. # Methode. Durch NAT, Proxies und automatische Lastverteilungen, sowie
  12. # Anonymisierungsdienste können die IPs nicht einem bestimmten Client zugeordnet
  13. # werden. Für die Funktionsweise dieses Zählers ist die Genauigkeit aber 
  14. # hinreichend.
  15.  
  16. # Zeitumstellung ist noch nicht berücksichtigt
  17.  
  18. ## Innerhalb dieser Zeit in Sekunden wird die IP nicht als Neuzugriff gezählt
  19. ## soll Counter-Floading verhindern
  20. defined('COUNTER_LOCKTIME') or define('COUNTER_LOCKTIME', 3600); 
  21.  
  22. ## Innerhalb dieser Zeit muss die Online-Meldung des Clients erfolgen
  23. ## Am Client kleiner, als die Hälfte einstellen. Es könnte auch mal ein Request 
  24. ## verloren gehen
  25. defined('COUNTER_REFRESHTIME') or define('COUNTER_REFRESHTIME', 62); 
  26.  
  27. ## Counterfile entweder als '.htcounter.dat' oder außerhalb der Document_Root anlegen
  28. ## Das angegebe Verzeichnis und das File müssen für den PHP-Prozess beschreibbar sein!
  29. $counterfile = rtrim($_SERVER['DOCUMENT_ROOT'],'/') . '/../data/counter.dat';   
  30. #$counterfile = '.htcounter.dat';   
  31.  
  32. ## Funktionensammlung einbinden, wenn vorhanden.
  33. @include (rtrim($_SERVER['DOCUMENT_ROOT'],'/') . '/../includes/functions.inc'); 
  34.  
  35.  
  36. #------------------------------------------------------------------------------
  37. ## Zähler nur anzeigen, wenn der User ihn sehen darf. Sonst nur Zählpixel ausgeben.
  38. ## Dummy zum Weiterentwickeln für Systeme mit Zugriffsrechten
  39. ## siehe hierzu Artikel "Login- und Rechtesystem ##
  40.  
  41. if (!function_exists('user_access')) 
  42. {
  43.     function user_access($mode, $module) { return true; } 
  44. }
  45.  
  46. #------------------------------------------------------------------------------
  47. function visitcount($filename, $time, $retrigger = true, $clean = true)
  48. {
  49.     ## Zählerdatei zum Lesen und Schreiben öffnen und, 
  50.     ## falls nicht vorhanden, vorher anlegen
  51.     $fh = fopen($filename, 'cb+');  
  52.  
  53.     if (!$fh) return false;
  54.     if (!flock($fh, LOCK_EX))
  55.     {
  56.         fclose($fh);
  57.         return false;
  58.     }
  59.  
  60.     ## Schreiben sauber beenden, auch wenn die Verbindung abgebrochen wird    
  61.     $userabort = ignore_user_abort("1");
  62.  
  63.     ## Defaults für das Array
  64.  
  65.     $_data = array();
  66.     $_data['time'] = array();
  67.     $_data['time'][$_SERVER['REMOTE_ADDR']]  = $time;
  68.     $_data['count'] = 1;
  69.     $_data['lastIP'] = $_SERVER['REMOTE_ADDR'];
  70.  
  71.  
  72.     $filesize = filesize($filename);
  73.  
  74.     ## Datei nur lesen und auswerten, wenn auch Daten drinstehen könnten
  75.     if ($filesize > 0)
  76.     {
  77.         $filedata = fread($fh, $filesize);
  78.  
  79.         ## Fileformat checken
  80.         if (false === ($_data = unserialize($filedata))
  81.              || !isset($_data['time'], $_data['count'], $_data['lastIP']))
  82.         {
  83.             ## Default-Datenstruktur wiederherstellen
  84.             $_data = array();
  85.             $_data['time'] = array();
  86.             $_data['time'][$_SERVER['REMOTE_ADDR']]  = $time;
  87.             $_data['count'] = 1;
  88.             $_data['lastIP'] = $_SERVER['REMOTE_ADDR'];            
  89.         }
  90.         else
  91.         {
  92.             ## Nur berücksichtigen, wenn die Requestor-IP noch 
  93.             ## nicht in der Liste der letzten Requests steht 
  94.  
  95.             if (!isset($_data['time'][$_SERVER['REMOTE_ADDR']])) 
  96.             {    
  97.  
  98.                 ## IP in Liste eintragen  
  99.                 $_data['time'][$_SERVER['REMOTE_ADDR']] = $time;
  100.  
  101.                 ## nur zählen, wenn zwischendurch ein Request 
  102.                 ## von einer anderen IP eingetroffen ist
  103.                 if ($_data['lastIP'] != $_SERVER['REMOTE_ADDR'])   #
  104.                 {
  105.                     $_data['count']++; 
  106.                 }    
  107.             }
  108.             elseif ($_data['time'][$_SERVER['REMOTE_ADDR']] + COUNTER_LOCKTIME < $time)            
  109.             {
  110.                 $_data['time'][$_SERVER['REMOTE_ADDR']] = $time;
  111.  
  112.                 if ($_data['lastIP'] != $_SERVER['REMOTE_ADDR'])
  113.                 {
  114.                     $_data['count']++;
  115.                 }    
  116.             }
  117.             elseif ($retrigger)
  118.             {
  119.                 $_data['time'][$_SERVER['REMOTE_ADDR']] = $time;                
  120.             }
  121.             else
  122.             {
  123.                 ## ignore visit
  124.             }
  125.  
  126.             ## IPs nach Zeit wieder freigeben für die Zählung
  127.             if ($clean)
  128.             {
  129.                 foreach ($_data['time'] as $ip => $lasttime)
  130.                 {
  131.                     if($time - $lasttime > COUNTER_LOCKTIME)
  132.                     {
  133.                         unset($_data['time'][$ip]);
  134.                     }
  135.                 }
  136.             }
  137.         }
  138.     }    
  139.  
  140.     ## Onlineanzeige berechnen, alle Zugriffe, die nicht älter als
  141.     ## COUNTER_REFRESHTIME sind, werden als "online" gewertet
  142.     $useronline = 0;
  143.     foreach ($_data['time'] as $key => $lasttime)
  144.     {
  145.         if ($lasttime > $time - COUNTER_REFRESHTIME) $useronline++;
  146.     }
  147.  
  148.     ## Daten zurückschreiben
  149.     $filedata = serialize($_data);
  150.     fseek($fh, 0, SEEK_SET);
  151.     fwrite($fh, $filedata);
  152.     ftruncate($fh, strlen($filedata));
  153.     fclose($fh);
  154.  
  155.     ignore_user_abort("$userabort");
  156.  
  157.     $_result = array('visitors'=>$_data['count'], 'useronline'=>$useronline);
  158.  
  159.     return $_result;
  160.  
  161. }
  162. #------------------------------------------------------------------------------
  1. function writetext($text, $font)
  2. {
  3.     // content-type für das Bild setzen
  4.     header("Content-type: image/gif");
  5.  
  6.     $width=185;
  7.  
  8.     // Das leere Bild im Speicher erzeugen
  9.     $im = imagecreatetruecolor($width, 20);
  10.  
  11.     // Ein paar Farben festlegen
  12.     $white = imagecolorallocate($im, 255, 255, 255);
  13.     $grey  = imagecolorallocate($im, 128, 128, 128);
  14.     $black = imagecolorallocate($im, 0, 0, 0  );
  15.     $blue  = imagecolorallocate($im, 0, 0, 255);
  16.     $red   = imagecolorallocate($im, 255, 0, 0);    
  17.  
  18.     // Das leere Bild mit rotem Hintertgrund füllen
  19.     imagefilledrectangle($im, 0, 0, $width-1, 19, $red);
  20.  
  21.     // Das rote Bild innen weiß füllen
  22.     imagefilledrectangle($im, 1, 1, $width-2, 18, $white);
  23.  
  24.     // Einen Schatten hinter den Text legen
  25.     imagettftext($im, 13, 0, 10, 17, $grey, $font, $text);
  26.  
  27.     // Den Text ins Bild einsetzen
  28.     imagettftext($im, 13, 0, 9, 16, $blue, $font, $text);
  29.  
  30.     // Das Bild ausgeben. imagegif() erzeugt einen schärferen Text als imagejpeg()
  31.     imagegif($im);
  32.     imagedestroy($im);
  33. }
  1. #==============================================================================
  2. # php main
  3. #==============================================================================
  4.  
  5.  
  6. $font = './ARIAL.TTF';
  7. #$font = 'C:/WINNT/Fonts/GLACIERN.TTF';
  8.  
  9. if (false !== ($_result = visitcount($counterfile, time() )))
  10. {
  11.    $text = "Online:{$_result['useronline']}  Visits:{$_result['visitors']}";      
  12. }
  13. else
  14. {
  15.    $text = "http://bitworks.de";
  16. }
  17.  
  18. if (!isset($_GET['noshow']) && user_access('view', 'visitcount')) 
  19. {
  20. #	header("Content-Type: image/gif");  ## Header wird bereits in writetext() gesetzt
  21.     writetext($text, $font);
  22. }
  23. else
  24. {
  25.     header("Content-Type: image/gif");
  26.     readfile('1pix.gif');
  27. }
  28.  
  29. ?>

[Bearbeiten] Der Online-Trigger in JavaScript

Beispiel: visitcount_trigger.js
  1. function counter_refresh(pic_source)
  2. {
  3.     var pic = document.getElementById('img_counter');
  4.     var jetzt = new Date();
  5.  
  6.     if(pic)
  7.     {
  8. 	pic.src = pic_source + '?time=' + jetzt.getTime();
  9. 	window.setTimeout("counter_refresh(pic_source)",30000);
  10.     }
  11. }

Der aktuelle Timestamp wird für den Request an den Ressourcebezeichner angehängt, um ein Neuladen der Ressource zu erzwingen.

[Bearbeiten] die Ausgabe als Grafikdatei

Für die Ausgabe als Grafikdatei ist die Funktion writetext() im PHP-Script zuständig. Sie benötigt einen TTF-Font im angegebenen Verzeichnis. Sie erzeugt aus einem gegebenem Text (Online: n Visits: m) eine Grafik. Diese Grafik wird dann beim Aufruf des Scriptes angezeigt.

[Bearbeiten] Zusammenspiel im HTML

Um den Zähler in ein HTML-Dokument einzubauen, lassen wir darin einfach ein img-Element anzeigen und in regelmäßigen Zeitabständen mittels Javascript aktualisieren. Da das Image immer dieselbe Größe und Position behält, fällt es nicht auf, dass der Browser neu rendert.

Beispiel: index.html
<!DOCTYPE html>
<html lang="de">
 
<head>
    <title>Meine Webseite</title>  
    <script src="/visitcount/visitcount_trigger.js"></script>  
</head>  
 
 
<body onLoad="counter_refresh('/visitcount/visitcount.php');">
    <div id="counter">
        <img id="img_counter" src="/visitcount/visitcount.php" alt="counter">
    </div>
</body>
</html>

Da die Pfade absolut zur Domain-Root angegeben wurden, kann der Zähler in jede Seite der Verzeichnisstruktur der Website eingebaut werden.

[Bearbeiten] Verzeichnisstruktur

In der Verzeichnisstruktur des Webaccounts müssen die Komponenten folgendermaßen platziert werden:

ACCOUNT
   +---htdocs
   |      +---visitcount
   |      |        +--visitcount.php   
   |      |        +--ARIAL.TTF 
   |      |        +--1px.gif
   |      |        +--visitcount_trigger.js
   |      |                          
   |      +---index.html                             
   |
   +---includes
   |      +---functions.inc
   |      +--- ...
   |      
   +---data
   |      +---visitcount.dat
   |      +--- ...
  ...

Der User des Webservers (z. B. "www-data") muss Leserechte für das includes-Verzeichnis haben sowie Schreib- und Leserechte für das data-Verzeichnis. Die DOCUMENT_ROOT liegt auf "htdocs". Die Verzeichnisse "includes" und "data" sind per http/s nicht erreichbar.

[Bearbeiten] Zubehör

  • Truetype-Font
  • GIF-Datei mit einem transparenten Pixel mit Namen "1pix.gif"

[Bearbeiten] So sieht es aus

Meine Werkzeuge
Namensräume

Varianten
Aktionen
Übersicht
Index
Mitmachen
Werkzeuge
Spenden
SELFHTML