Benutzer:TS/Zugriffszähler mit Onlineanzeige
Informationen zum Autor
- Name:
- Thomas Schmieder
- E-Mail:
- selfhtml@bitworks.de
- 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
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.
Programmteile
Welche Programmelemente benötigen wir? Welches Programmelement übernimmt welche Aufgabe?
Das Zählscript in 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 #------------------------------------------------------------------------------
165 function writetext($text, $font)
166 {
167 // content-type für das Bild setzen
168 header("Content-type: image/gif");
169
170 $width=185;
171
172 // Das leere Bild im Speicher erzeugen
173 $im = imagecreatetruecolor($width, 20);
174
175 // Ein paar Farben festlegen
176 $white = imagecolorallocate($im, 255, 255, 255);
177 $grey = imagecolorallocate($im, 128, 128, 128);
178 $black = imagecolorallocate($im, 0, 0, 0 );
179 $blue = imagecolorallocate($im, 0, 0, 255);
180 $red = imagecolorallocate($im, 255, 0, 0);
181
182 // Das leere Bild mit rotem Hintertgrund füllen
183 imagefilledrectangle($im, 0, 0, $width-1, 19, $red);
184
185 // Das rote Bild innen weiß füllen
186 imagefilledrectangle($im, 1, 1, $width-2, 18, $white);
187
188 // Einen Schatten hinter den Text legen
189 imagettftext($im, 13, 0, 10, 17, $grey, $font, $text);
190
191 // Den Text ins Bild einsetzen
192 imagettftext($im, 13, 0, 9, 16, $blue, $font, $text);
193
194 // Das Bild ausgeben. imagegif() erzeugt einen schärferen Text als imagejpeg()
195 imagegif($im);
196 imagedestroy($im);
197 }
198 #==============================================================================
199 # php main
200 #==============================================================================
201
202
203 $font = './ARIAL.TTF';
204 #$font = 'C:/WINNT/Fonts/GLACIERN.TTF';
205
206 if (false !== ($_result = visitcount($counterfile, time() )))
207 {
208 $text = "Online:{$_result['useronline']} Visits:{$_result['visitors']}";
209 }
210 else
211 {
212 $text = "http://bitworks.de";
213 }
214
215 if (!isset($_GET['noshow']) && user_access('view', 'visitcount'))
216 {
217 # header("Content-Type: image/gif"); ## Header wird bereits in writetext() gesetzt
218 writetext($text, $font);
219 }
220 else
221 {
222 header("Content-Type: image/gif");
223 readfile('1pix.gif');
224 }
225
226 ?>
Der Online-Trigger in JavaScript
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.
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.
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.
<!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.
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.
Zubehör
- Truetype-Font
- GIF-Datei mit einem transparenten Pixel mit Namen "1pix.gif"
So sieht es aus
- Mit Anzeige: visitcount.php
- Ohne Anzeige: visitcount.php?noshow