E-Mail: |
![]() |
Homepage-URL: |
![]() |
Bei Fragen zu diesem Beitrag bitte den Autor des Beitrags kontaktieren!
![]() ![]() |
Jeder Feature-Artikel erhält sein eigenes Unterverzeichnis. Sie können also problemlos Grafiken und andere Dateien Ihrem Artikel hinzufügen. Wenn Sie CGI-Scripts installieren wollen, setzen Sie sich gegebenenfalls vorher mit der SELFHTML Redaktion in Verbindung.
In diesem Abschnitt gibt es noch ein paar Hinweise zum SELFHTML-gerechten Arbeitsstil in solchen Artikeln wie diesem. Löschen Sie den Inhalt dieses Unterabschnitts und überschreiben Sie ihn mit Ihren eigenen Inhalten.
Absätze werden stets in <p>...</p> eingeschlossen (schließendes </p> ist wichtig!). Quellcode im Fließtext wird in <code>...</code>
eingeschlossen, Tastatureingaben in
<kbd>...</kbd> und Namen von Dateien und URLs in <var>...</var>. Größere Quelltext-Beispiele kommen in einen eigenen Kasten. Das sieht so aus:
Anzeigebeispiel: So sieht's aus
<html> <!-- irgendwas. Bitte auch hier alle HTML-eigenen Zeichen maskieren --> </html> |
Wenn zuvor ein Beispiel-Code in einem gelben Kasten steht, sollte es stets eine Erläuterung dazu geben. Was tut der Code? Auf welche Stellen im Beispiel sollte man besonders achten usw.
Auch Tabellen sollten ein einheitliches Outfit haben:
|
Natürlich kann die Tabelle auch mehr Spalten haben. Aber benutzen Sie dieses Grundgerüst.
Und zum Schluß noch ein paar Link-Beispiele (die Links funktionieren so nicht - aber bitte notieren Sie sie so, vor allem mit den relativen Pfadangaben zu Links innerhalb des SELF-Raums):
Link zu einem Kapitel auf SELFHTML aktuell
Link zu einer Seite auf SELFHTML aktuell
Link zu einem Kapitel von SELFHTML
Link zu einer Seite von SELFHTML
Link zu einer fremden, deutschsprachigen Seite
Link zu einer fremden, englischsprachigen Seite
Wir würden uns freuen, wenn Sie einen code-basierten HTML-Editor verwenden. Denn SELFHTML ist pingelig, was sauberes Kodieren betrifft, viel pingeliger als die meisten Wysiwyg-Editoren jedenfalls ;-)
![]() ![]() |
Dieser Artikel richtet sich an Fortgeschrittene.
Zum Verständnis der bechriebenen Techniken benötigen Sie einige Grundlagen:
![]() ![]() |
Zum Speichern von Daten werden in der Infornationstechnik verschiedene Techniken
verwendet. Die verschiedenen Hardware-Plattformen verwenden hier unterschiedliche
Konzepte. Wir wollen uns in diesem Artikel mit der Thematik der Dateien auf DOS
auseinandersetzen. "Halt" werden nun vielleicht einige rufen, was
interessiert mich diese Museumstechnik, ich habe doch das neueste Linux oder Windows
auf meiner Maschine.
Falsch: auch diese Systeme laufen nach dem DOS Konzept. Sie teilen Ihre Speichermedien
in die transienten (RAM) und permanenten (hier Disk) Speicher ein, und sind damit
klassische Disk Operating Systems. Dies
ermöglicht uns eine einheitliche Aussage über die Techniken der Datenspeicherung und
die Strategien für die gemeinsame Verwendung.
Alle Daten, die wir für eine Wiederverwendung konservieren wollen, werden aus dem
Hauptspeicher unserer Maschine auf eine Festplatte (oder vergleichbare Medien)
übertragen. Diese Aufgabe erledigt das Betriebssystem für das Anwendungsprogramm.
Direkte Zugriffe eines Anwendungsprogramms auf die Hardware sind zwar möglich,
kommen für uns aber nicht in Betracht. Die Betriebssysteme der betrachteten Systeme
MAC und PC organisieren die Festplatten mittels eines
Filesystems. Die üblichen Filesysteme teilen die Speichermedien dabei in
Cluster ein und regeln die Zugriffe darauf. Allerdings
gestatten sie dem Anwendungsprogramm nicht, direkt auf einen Cluster zuzugreifen.
Sie stellen dafür in einem API unterschiedliche
Interrupts zur Verfügung. Die von uns für die
Programmerstellung eingesetzten Hochsprachen nutzen
diese Systemroutinen (Interrupts) des Application Programmers Interface (API) zum
Aufbau eigener Funktionen. Diese Funktionen stellen sie uns als Programmierern
dann für die Verwendung zur Verfügung. Allgemein werden die Daten für die
Konservierung in Dateien gespeichert. Die hier
betrachteten Dateien bestehen aus einem oder mehreren organisierten Clustern. Sie haben
einen Dateinamen, einen Anfang und eine unbestimmte
Größe. Zur besseren Organisation werden sie in Directories
(Verzeichnisse, Ordner) zusammengefasst.
Bei Zugriffen auf unsere gespeicherten Daten müssen wir also nur den Dateinamen und den
Verzeichnisnamen wissen. Beide zusammen ergeben den
qualifizierten Pfad zur Datei. Dieser wird noch durch den vorangestellten
Datenträgernamen oder sein Äquivalent, den
Gerätenamen ergänzt. Wir müssen also nicht wissen,
wo die Daten genau auf der Festplatte gespeichert sind, oder welche Cluster in welcher
Reihenfolge dazugehören. Das regeln die Filesysteme als Subsyteme der Betriebssysteme
für uns.
Hauptspeicher und Dateien sollten wir uns wie einen "Bindfaden" vorstellen.
Auf diesem Bindfaden sind die Daten nacheinander angeordnet. Jeder Datenplatz
hat eine eindeutige Adresse. Es handelt sich also um
lineare Speichersysteme
Die Organisation der Daten in Dateien hat den Vorteil, dass wir so sehr leicht
zusammengehörige Daten in der richtigen Reihenfolge von einerm Platz zum anderen
kopieren können. Das funktioniert dann sogar über das Internet. Das Quellsystem
muss dabei nichts über das Betriebssystem oder die Festplatte des Zielsystmes
wissen. Beide verwenden ihre eigene Organisation für die Speicherung der Daten.
Das ist einer der Gründe, wenn nicht sogar der wichtigste, für die Verwendung
von Dateien.
![]() ![]() |
Der Zugriff auf Dateien findet über den Namen statt. Für die praktische Umsetzung
hat sich das Verfahren mittels File-Handle
durchgesetzt. Es gibt auch noch ein älteres Verfahren mittels
File Control Block (FCB). Dies wird uns aber nicht
weiter interessieren.
Ein Handle ist im Prinzip nur ein Index in eine Tabelle. Diese Tabelle enthält
dann die eigentlichen Informationen über die Datei. Warum aber nun nocheinmal
am Rad gedreht, und den Namen der Datei gegen ein Handle getauscht? Ganz einfach:
Um eine Datei innerhalb eines Programmes mehrfach öffnen zu können, benötigt
man ohnehin ein Unterscheidungskriterium. Da bietet es sich an, den Dateinamen
durch eine bestimmte Funktion der Hochsprache und des API gleich durch das Handle
zu ersetzen. Außerdem ist ein Handle i.d.R. kürzer, als der qualifizierte Bezeichner
der Datei, was einen angenehmen Nebeneffekt für den armen Programmierer darstellt.
Auf die Handletabelle haben wir als Hochsprachen-Programmierr keinen Zugriff.
Allerdings wissen alle File-Funktionen der
Hochsprache, wo diese Tablle zu finden ist und verwenden sie. Wir müssen den
Funktionen nur das Handle übergeben, und diese regeln alles für uns. So kann
auch kein Schritt vergessen werden und alle Zugriffe, die über das
Betriebssystem auf die Dateien stattfinden, sind einheitlich.
In Multithreading- und Multiusersystemen stehen die Informationen nicht direkt in
der Handletabelle, die den Funktionen der Hochsprache zugänglich ist, sondern
in einer zentralen Tabelle des Betriebssystems. Jede Programminstanz hat nun
nochmals einen eigenen Auszug daraus. Die Handles der "privaten" Tabelle beginnen
bei 1 und gehen heute üblicherweise bis 255. Das Handle 0 dient als Kennung
für eine ungültige Ressource. Die "privaten" Handles verweisen auf die gemeinsamen
des Betriebssystems. Die API Funktionen können so den Weg bis zu den eigentlichen
Dateiinformationen verfolgen und diese auslesen oder setzen.
Um ein Handle zu bekommen, benötigen wir also eine Funktion:
Anzeigebeispiel: So sieht's aus
<?php ### get_handle.php ### ini_set('track_errors','1'); ## Fehlerbehandlung einschalten error_reporting(E_ALL); $dateiname = './textdatei.txt'; ## Dateinamen zuweisen $fh = fopen($dateiname,'r'); ## Dateihandle besorgen, Öffnungsmode 'lesen' echo "Das Dateihandle lautet: $fh"; ## Handle besorgen fclose($fh); ## Datei schließen, Handle freigeben ?> |
Das Script hat den Auftrag, eine Datei, deren qualifizierter Bezeichner in $dateiname angegeben wird, zu öffen und zwar im Modus r. Das erhaltene Dateihandle wird dann mit Echo ausgegeben. Wenn die Funktion erfolgreich war, muss das Handle ungleich 0 sein. Anschließend wird versucht, die Datei wieder zu schließen. Sollte das Handle gleich 0 gewesen sein, könnten Fehlermeldungen ausgegeben werden. Für unsere weiteren Versuche wollen wir die Fehlermeldungen immer alle anzeigen lassen, wenn dies nicht ausdrücklich anders erwähnt wird.
Das Script hat nun einige Fehlermeldungen erzeugt. Die Meldung in der ersten Zeile ist die wichtigste: eine Warnung, dass die zu öffnende Datei nicht vorhanden ist. Da es sich nur um eine Warnung handelt, wird das Script nicht abgebrochen, sondern weiter ausgeführt. Die zweite Warnung ist daher eine Folge des ersten Fehlers. Das Script konnte die Datei zum Handle 0 nicht schließen, da das Handle ungültig ist. Bei gemeinsam genutzen Dateien dürfen solche Fehler aber nicht geduldet werden, da bei hunderten von Zugriffen auf eine Webseite sonst sehr schnell ein Datenchaos entsteht. Wir wollen uns daher in Zukunft auch immer um eine geigente Fehlerbehandlung bemühen.
Stellen wir also als erstes die Ursache für den Fehler ab.
Bitte legen Sie eine leere Datei mit dem Namen 'textdatei.txt' an
und kopieren diese in das Scriptverzeichnis. Der Webserver benötigt auf dieses
Verzeichnis Leserechte und damit unsere folgenden Versuche ebenfalls funktionieren
erteilen Sie dem Webserver bitte auch Schreib- und Pfad-Rechte auf das Scriptverzeichnis
sowie Schreibrechte auf die gemeinsam genutzten Dateien (hier: 'textdatei.txt')
Dann wiederholen Sie den ersten Versuch.
Anzeigebeispiel: So sieht's aus
Diesmal hat das Script Erfolg gehabt. Die Datei konnte zum lesen geöffnete werden
und das beschaffte Handle kann angezeigt werden. Durch die automatische Typerkennung
ergänzt PHP den numerischen Wert des gültigen Handles automatisch um den Text
'Ressource id #'. Im ersten Versuchsschritt war der Wert false und
wurde daher von echo $fh
nicht angezeigt.
Um später Fehler zu vermeiden, werden hier nochmals die Öffnungsmodi für Dateien beschrieben. Bitte Testen sie das Verhalten der anschließend aufgeführten Funktionen in Kombination mit den den Öffnungsmodi selbstständig durch.
|
Außerdem kann man alle Modi noch mit b kombinieren (Beispiel: wb+)
für eine binäre Öffnung der Dateien. Dies dient der Kompatibilität der Systeme
Es wird empfohlen, dieses Flag bei Windows immer zu verwenden.
Mehr Info zu den Öffnungsmodi
Die Öffnungsmodi haben nichts mit den Prozessrechten/Userrechten auf die Dateien
zu tun. Die Datei- und Verzeichnisrechte aus dem Betriebssystem haben Vorrang.
Bitte testen Sie einmal selbst, was passiert, wenn für eine Datei mittels
chmod()
die Rechte r--r--r--
eingestellt werden,
und diese dann mit dem Mode w+ geöffnet wird.
|
Ich bitte um Verständnis, dass ich hier nur die für den Artikel relavanten
Funktionen kurz in Erinnerung rufe und nicht näher darauf eingehe. Die
vollständige Dokumentation finden Sie unter
PHP-Manual: Filefunktionen
![]() ![]() |
Um Dateien gemeinsam benutzen zu können, müssen Programme bestimmte Regeln einhalten. Ohne diese Regeln
Wir müssen hier allerdings unterscheiden zwischen der Applikationsschicht, in der diese Aussagen zutreffen und der Filesystem-Schicht des Betriebssystems, in der bereits Regeln vereinbart sind und diese Aussagen deshalb so nicht zutreffen.
Erinnern wir uns aber nochmal an den Abschnitt "Warum Dateien?" und verdeutlichen uns einmal die Vorgänge in vereinfachter Form:
Dateien bestehen aus Clustern. Wenn auf Betriebssystemebene eine Leseanforderung an eine Datei kommt, wird von der Festplatte immer mindestens ein Cluster gelesen, auch wenn nur ein einziges Byte dieses Clusters benötigt wird. Der Cluster wird in einem Buffer zwischengespeichert. Aus dem Cluster werden die angeforderten Bytes an den Lesebuffer der Applikation übertragen. Wenn diese nun auch nur ein Byte verändern will, wird dieses mit der passenden Positionsangabe von der Applikation an das Betriebssystem übergeben. Das Filesystem liest den dazugehörigen Cluster wieder vollständig ein, tausch das Byte an der passenden Stelle aus und schreibt ihn komplett zurück auf die Festplatte. Durch Caching-Mechanismen in allen Schichten inclusive der Hardware wird dieser Vorgang noch um ein Mehrfaches komplexer. Eine genauere Betrachtung wollen wir uns hier ersparen, da wir ja Internet-Dialog-Applikationen erstellen wollen und keine Hard-Disk-Driver.
Was aber durch dieses Beispiel bereits deutlich wird ist, dass zwischen dem Holen der Daten und dem Wegschreiben der veränderten Daten Zeit vergeht. Innerhalb dieser Zeit könnte eine andere Applikation mit der Datei andere Dinge tun.
Die einzelnen Schreib- und Leseanforderungen in der Hardwareschicht sind gegeeinander abgegrenzt, sweit es sich um ein einzelnes Statement handelt. Das bedeuet, dass jeweils der gesamte Buffer der Applikationsschicht ungestört gefüllt oder weggeschrieben werden kann. Während dieser Zeit kann keine andere Applikation eine Anforderung an die Datei absetzen. Allerdings reicht ein Bufferinhalt oft nicht aus, und so muss man die Anforderungen zwangsweise in einer Schleife mehrmals ausführen. Während der Zeit vom Schliefenfuß zum Schleifenkopf vergeht aber schon wieder Zeit. Außerdem muss der Buffer erneut gefüllt oder ausgelesen werden. Auch dabei vergeht Zeit. Zwischen zwei Schreib- oder leseanforderungen en eine Datei uf der untersten Ebene vergeht also genug Zeit, dass eine ander Applikation zwischenschlüpfen kann.
Wir erkennen also, dass beim gemeinsamen Arbeiten mit Dateien viel Unerwünschtes passieren kann. Während der Eine noch seine 17 Cluster lesen lässt, beginnt der Andere bereits an einer anderen Stelle der Datei, 3 Cluster zu schreiben. Da Datensätze aber i.d.R. nicht vor Clustergrenzen halt machen, sondern meistens "irgenwie" über die Platte verteilt liegen, gibt es keine einzige Möglichkeit zu einem geordenten Miteinander. Man hat daher das Filesharing erfunden. Hierbei handelt es sich um einen Regelsatz, der sich entweder in die Betriebssystemfunktionen für die Dateizugriffe eingliedert (Mandatory Locking) oder parallel zu diesen Funktionen geführt wird (Advisory Locking).
![]() ![]() |
Außerdem gibt es für die Locking-Funktionen noch eine Unterscheidung im dynamischen (zeitlichen) Verhalten:
In verbindungsorientierten Programmen, also z.B. einer mehrnutzerfähigen Warenwirtschaft auf File-Server-Basis, ist es möglich, einen Datensatz für einen Benutzer (A) solange zu sperren, wie er daran arbeitet. Das nennt man dann pessimistic Locking. Niemand anders (B), als der Prozess dieses Users (A) darf dann Veränderungen an diesem Datensatz durchführen.
Sorgt man dafür, dass auch niemand anders (B) diesen Datensatz während der Bearbeitung durch den Prozess (A) lesen darf, spricht man von einem exclusive Lock.
Erlaubt man während der eigenen Bearbeitung eines Satzes anderen Benutzern zumindest das Lesen, spricht man auch von einem shared Lock oder auch Read Lock.
Da selbst Entwickler von Datenbanken, APIs oder Interpretersprachen die Trennung
zwischen Hardwareschicht und Softwareschicht nicht realisieren, oder allergisch auf
alle Techniken reagieren, die nicht Unix-Standard von 1970 sind, kommen dann auch mal
solche Sätze wie:
flock() wird antiquierten Dateisystemen wie FAT und dessen Derivate nicht unterstützt,
und gibt deshalb in solchen Umgebungen immer FALSE zurück
(Dies ist speziell bei Windows 98 Benutzern der Fall).
zustande. Würden sich die Entwickler mit einer Portierung des Mandatory Locking in
die Strategie des Advisory Locking intensiv beschäftigt haben, hätten sie eine
Lösung gefunden. (Inzwischen haben sie, der Satz stand aber immer noch im Manual)
Noch besser wäre es, wenn sie zusätzlich zur Methode des
"antiquierten" und "hineingebastelten" Mandatory Lockings auch
das Advisory Locking anbieten würden. In NOVELL sowie den neueren Unix-Derivaten,
speziell in LINUX wird die Methode genauso wie in allen WinDOS Derivaten unterstützt.
In PHP hat diese Methode unter der Funktionsgruppe dio_* Einzug gefunden ab Version
4.2.0.
Zumindest führt dieses häufig auf Unwissenheit oder Arroganz beruhende Verhalten von Entwicklern zu einer erheblichen Begriffsverwirrung. Hinzu kommt der Fakt, dass die Begriffe häufig von einer Schicht in die andere 1:1 durchgeschleppt werden, was eigentlich nicht zulässig ist.
|
Alle Funktionen, die direkt über Namen arbeiten, und kein Handle zurückliefern, sind ohne weitere Maßnahmen nicht alleine für den konkurrierenden Betrieb geeignet. Hierzu gehören u.a.:
Für diese und für viele andere Funktionen muss eine besondere Strategie benutzt werden. Man sperrt entweder kurzzeitig das gesamte Verzeichnis, in dem man die Funktionen anwenden will, oder 'klammert' die Operation mit einem speziellen Lockfile (Dummy).
![]() ![]() |
Mandatory Locking ist eine Methode der Zugriffskontrolle, die derartig im Kernel des Betriebssystems verankert ist, dass kein Prozess bei einem Open, Read oder Write auf die durch ein Handle definierte Datei vorbeikommt. Sie wird bereits gleichberechtigt mit einem Open initialisiert und kann dann parallel dazu zwischen verschiedenen Zuständen wechseln. Alle Zugriffe bestehender (älterer) Programme, die bisher keine Rücksicht auf die Methoden des Locking genommen haben, werden automatisch mit dem vom Betriebssystem vorbestimmten Lock Modus behandelt. Dieser ist bei DOS, NOVELL, und Windows bis 98SE "Exclusive" für den jeweils früheren Prozess, bei Windows NT, 2000 und folgenden "None". Bei Unix und Linux sind mir die Verhaltensweisen noch nicht als einheitlich bekannt. Bei Windows NT und folgenden sind bei Workstationversionen und parallelen Prozessen auf diesen zudem "Merkwürdigkeiten" zu beachten, die im lokalen Chaching begründet sind.
Hierbei muss man streng unterscheiden zwischen den allgemeinen im Filesystem oder Netzwerkbetriebssystem festgelegten Dateirechten, den eigenen beantragten und erteilten Rechten auf die Datei und den bei der Beantragung eines Handles oder später mittels diess Handles anderen Prozessen zugestandenen Rechten.
# /etc/fstab: static file system information.
#
# <file system> <mount point> <type> <options> <dump> <pass>
/dev/hda1 / ext2 errors=remount-ro,mand 0 1
/dev/hda2 none swap sw 0 0
proc /proc proc defaults 0 0
/dev/fd0 /floppy auto user,noauto 0 0
/dev/cdrom /cdrom iso9660 ro,user,noauto 0 0
|
Um bei Linux-System das Mandatory Locking nutzen zu können, muss das Dateisystem mit
der Option -o mand
mounted werden. Da man ein aktives Filesystem
aber nicht einfach herunterfahren kann, bietet es sich an, die Option gleich
in den Files System Table fstab einzufügen.
Beim nächsten Reboot sollten die Funktionen
dann zur Verfügung stehen.
Die Mandatory File Funktionen stehen in PHP ab der Version 4.2.0 zur
Verfügung
(PHP: dio_*)
PHP muss mit der Option --enable-dio
compiliert worden sein.
Beim Mandatory File Locking werden bereits alle erforderlichen Einstellungen
mit der Öffnungsanforderung der Datei aktiviert. Die Maßnahmen sind vom
Betriebssystem derartig gekapselt, dass kein andere IO-Prozess
'dazwischenfunken' kann. Im PHP-Manual findet man die Besschreibung
unter
(PHP: dio_open)
Auf Hardwareebene wird dieser Lock so realisiert, dass das
Betriebssystem einen Lese- oder Schreibzugriff von Byteposition[Satzbeginn] bis
Byteposition[Satzende] ablehnt und mit einem Fehler quittiert. Diese Sperrart ist
also nur für Random Access Files sinnvoll, in denen
die Daten(-sätze) immer mit festen Positionen korrelieren. Die Sprerren werden
nach dem Öffnen der Datei mit dio_fopen
durch die Funktion
(PHP: dio_fctl)
![]() ![]() |
Advisory Locking ist eine Methode, die im Betriebssystem für alle Prozesse
gleichermaßen zugänglich der Methode des File Open untergeordnet ist. Sie
steht aber eigenständig neben den vorhandenen Zugriffsmethoden auf Dateien
und sollte von jedem Prozess freiwillig beachtet werden. Kein Prozess wird
allerdings zur Beachtung gezwungen.
Laut PHP Manual wird das Advisory Locking auf Windows-Systemen nicht unterstützt,
was an der fehlenden Portierung des vorhandenen Mandatory Locking auf diesen
Systemen zum Mandatory Locking liegen dürfte. Die von mehreren Forumsmitgliedern
durchgeführten eigenen Versuche mit dem Musterscript haben aber das Gegenteil
ergeben. Nur mit dem gezielten Buffer-Flush gab es bei Windows Probleme.
Auch bei dieser Methode muss man streng unterscheiden zwischen den allgemeinen
im Filesystem oder Netzwerkbetriebssystem festgelegten Dateirechten, den
eigenen beantragten und erteilten Rechten auf die Datei und den bei der
Beantragung eines Locks mittels vorhandenem Handle oder später mittels dieses
Handles anderen Prozessen zugestandenen Rechten.
![]() ![]() |
PHP bietet mit der Funktion
flock()
einen einfachen Weg, Dateien für die Dauer der Veränderung zu sperren. Es wird
das advisory Locking verwendet. Das bedeutet,
dass alle zugriffsberchtigten Prozesse freiwillig dieselben Lockingmechanismen
beachten müssen. Das Verfahren ist im Prinzip folgendes:
Gehen wir als erstes Szenario einmal davon aus, dass die Datei, die unsere Daten enthält, bereits existiert. In der Datei soll ein Array gespeichert werden, dass für jeden Aufrufer die IP und die Häufigkeit seines Aufrufes enthält.
<?php ### count_visitors.php ### ini_set('track_errors','1'); ## verdeckte Fehlermeldung aktivieren ## $php_errormsg wird nun gepflegt $dateiname = 'visitors.dat'; $fh = fopen($dateiname,'r+'); ## Datei zum Lesen und Schreiben öffnen if(!$fh) { ## Fehlerbehandlung, wenn Dateiöffnung fehlgeschlagen ist. die('Es ist der Fehler 002 aufgetreten. Bitte informieren Sie den Operator'. '<a href="mailto:webmaster@example.com?subject=Fehler 002 '. 'beim Counter&body='.php_errormsg.'">per eMail</a>'); } $lock_ok = flock($fh,LOCK_EX); ## Dateisperre beantragen. $file_len = filesize($dateiname); ## Dateigröße erfragen if($file_len > 0) { $datastream = fread($fh,$file_len); ## vorhandene Daten lesen $_visitors = unserialize($datastream); ## Array wiederherstellen if ($_visitors === false) ## Fehler beim Umwandeln { mail('webmaster@example.com','Fehler 105 beim Visitor-Zähler',date('Y-m-d H:i:s')); $_visitors = array(); ## Array neu anlegen } } else ## es waren keine Daten da { $_visitors = array(); ## Array neu anlegen } $_visitors[$_SERVER['REMOTE_ADRESS']]++; ## Besucher zählen $datastream = serialize($_visitors); ## Array serialisieren $seek_ok = fseek($fh,0,SEEK_SET); ## Dateizeiger auf Anfang zurückstellen ## 0 = kein Fehler, (-1) = Fehler $write_ok = fwrite($fh,$datastream); ## Daten zurückschreiben $trunc_ok = ftruncate($fh,strlen($datastream)); ## Datei trimmen auf neue Datenlänge fclose($fk); ## Datei schließen und freigeben echo "<p>Sie wurden gezählt</p>; ?> |
Um Fehlermeldungen auch still abfragen zu können, wird die Fehlerverfolgung
eingeschaltet. Es wird die Zählerdatei zerstörungsfrei zum Lesen und anschließenden Schreiben
geöffnet. Die Datei muss vorhanden sein, sonst wird ein Fehler ausgelöst. Ebenso, wenn die
Datei für den Webserver nicht les- oder beschreibbar ist. Der Fehler wird hier dem User
beispielhaft angezeigt sowie der eMailclient des Users aufgerufen.
Konnte die Datei zum Lesen und Schreiben geöffnet werden, wird sie exclusiv gesperrt
. Ein exclusive Lock ist immer dann zwingend nötig,
wenn vorhandene Daten verändert werden sollen und das Locking so einfach
wie möglich bleiben soll. Dabei muss unbedingt der gesamte Lese- und Schreibvorgang durch
die exclusive Sperre geschützt werden. Verwendet man nur ein
shared Lock, könnte es passieren,
dass mehrere Benutzer gleichzeitig Daten auslesen, um diese dann später in veränderter Form
zurückzuschreiben. Das würde an einer Stelle Parallelität erzeugen, an der strengste
Serialisierung der Prozesse notwendig ist. Die Voreinstellung für flock()
ist
die blockierende Sperranforderung. Das bedeutet, dass die
Ausführung des Scriptes an dieser Stelle so lange 'angehalten' wird, bis die
Sperranforderung erfolgreich war, oder aber die max_execution_time erreicht ist.
Im Erfolgsfall wird das Script fortgeführt, im Misserfolgsfall abgebrochen und der
exit_handler des Scriptes aufgerufen. Alle anderen
Statements würden dann ignoriert werden.
War die Dateisperre erfolgreich, wird die Länge der Datei festgestellt. Die Funktion
filesize()
darf nur innerhalb des Schutzbereiches durch flock()
benutzt werden, da anderenfalls noch eine Änderung der Dateigröße durch fremde Prozesse
nach dem Abfragezeitpunkt möglich wäre. Ist die Datei leer, wird der Inhalt neu erstellt.
Dies kommt im Prinzip nur ein einziges Mal direkt nach der Installation des Zählers und
dem Anlegen der Datei vor. Entählt die Datei Daten, werden diese komplett ausgelesen und
anschließend in ein Array decodiert. Wenn die Dekodierung scheitert, erhält der Webmaster
still eine Warn-Email und das Datenarray wird neu erzeugt.
Im Array wird dann unter dem Index der IP des Besuchers die Zählung vorgenommen. Das Array
wird wieder in einen Stream umgewandelt (serialisiert). Der Dateizeiger steht vom
Holen der Daten noch am Ende der Datei. Deshalb wird er nun
auf den Dateianfang zurückgesetzt. Anschließend werden die geänderten Daten
weggeschrieben. Da zu einer sauberen Programmierung auch gehört,
dass man vorhersehbare Fehler vermeidet, wird die Datei noch auf die neue zulässige Länge
gekürzt bevor sie dann geschlossen wird. Die Funktion ftruncate()
stellt hier
diese Kürzung sicher. Der Dateizeiger muss dafür innerhalb des Dateibereiches, also maximal
hinter dem letzten Byte der Datei stehen. Das ist durch den vorangegangenen Schreibprozess
vom Dateianfang ausgehend auf jeden Fall gewährleistet. Wenn sichergestellt ist, dass die
Länge der Datei immer nur wachsen kann, kann man auf das ftruncate()
auch
verzichten. Durch das fclose()
wird die Datei geschlossen und auch die
Dateisperre automatisch wieder freigegeben.
Der Nachteil dieses Lockingverfahrens liegt darin, dass der Webserver für die Dauer des Lockversuches das Script 'anhält' und der User solange warten muss. Bei größeren Datenmengen und/oder Bearbeitungszeiten führt das zu unerwünschten Wartezeiten am Client. Die Wartezeit stellt allerdings für den Prozessor nahezu keine Last dar, da die interne Idle-Funktion die Wartezeit den übrigen Prozessen zur Verfügung stellt. In ungünstigen Fällen kann das aber für den einzelnen Benutzer schon mal 30 Sekunden Wartezeit bis zur Fehlermeldung bedeuten.
![]() ![]() |
Einen nicht wartenden Lock oder auch non blocking Lock
schauen wir uns beispielhaft für das Holen und Anzeigen der aktuellen Zugriffsdaten an.
Hier sollen ausschließlich Daten angezeigt und nicht verändert werden. Daher ist ein
shared lock ausreichend.
Das Verfahren ist im Prinzip folgendes:
<?php ### show_visitors.php ### error_reporting(E_ALL ^ E_NOTICE); ## alle Fehler außer Systemwarnungen anzeigen #error_reporting(E_ALL); ## alle Fehler anzeigen, nur zur Entwicklung ## passendes Statement aktivieren ini_set('track_errors','1'); ## verdeckte Fehlermeldung aktivieren ## $php_errormsg wird nun gepflegt $dateiname = 'visitors.dat'; ## Dateiname der Log-Datei $error_no = 0; ## Merker für die Fehlerbearbeitung #---- Error Codes ---------------- # 0: kein Fehler aufgetreten #---- Low Level Errors ----------- # 1: # 2: File not Found # 3: File already exists # 4: Could not read file, no data # 5: Could file not open # 6: Could file not lock file # 7: #---- Data Errors ---------------- # 11: No valid data format #------------------------------------------------------------------------------ # Datei im shared-Lock Modus zum Lesen öffnen function readlock($lockfile_name,&$error_no) { if ($error_no > 0) return false; for ($x=0; $x < 5; $x++) ## 5 Öffnungsversuche { if($lh = @fopen($lockfile_name,"rb")) break; ## oder bei Erfolg abbrechen usleep(8000); ## 8ms warten bis zum nächsten } ## Öffnungsversuch if (!$lh) { $error_no = 5; ## konnte Datei nicht öffnen return false; ## bei Misserfolg Funktionsende } # Lockversuch for ($x=0; $x<5; $x++) ## 5 Lockversuche { if (@flock($lh,LOCK_SH + LOCK_NB)) return $lh; usleep(8000); ## 8ms warten } $error_no = 6; ## konnte Datei nicht sperren @fclose($lh); return false; } #------------------------------------------------------------------------------ # Daten aus der Datei holen function get_data($lockfile_name,&$error_no) { if ($error_no > 0) return false; $fh = readlock($lockfile_name,$error_no); if ($error_no > 0) return false; $filedata = fread($fh,filesize($lockfile_name)); if (!$filedata) { $error_no = 4; ## File enthält keine Daten return false; } $_data = unserialize($filedata); if (!is_array($_data)) { $error_no = 11; ## Dateiformat passt nicht return false; } fclose($fh); ## Datei schließen return $_data; ## Daten zurückliefern } #------------------------------------------------------------------------------ function make_out_table($_data) { $out_table ="<table>\n"; $out_table .= " <tr bgcolor=\"#CCCCDD\">\n"; $out_table .= " <th align=\"right\">lfd. Nr</th>\n"; $out_table .= " <th align=\"left\">IP-Nummer</th>\n"; $out_table .= " <th align=\"right\">Besuche</th>\n"; $out_table .= " </tr>\n"; $_data=array_flip($_data); ## Schlüssel und Wert vertauschen natsort($_data); ## IPs sortieren lassen $_data=array_flip($_data); ## Schlüssel und Wert zurücktauschen, ## was bei zwei Spalten eigentlich überflüssig ist $_color = array('#AAFFEE','#AAEEFF'); $count = 0; if(is_array($_data)) { foreach ($_data as $ip => $visits) { $count++; $out_table .= " <tr bgcolor=\"".$_color[$count%2]."\">\n"; $out_table .= " <td align=\"right\">$count</td>\n"; $out_table .= " <td>$ip</td>\n"; $out_table .= " <td align=\"right\">$visits</td>\n"; $out_table .= " </tr>\n"; } } else { $out_table .= " <tr bgcolor=\"".$_color[$count%2]."\">\n"; $out_table .= " <td align=\"right\"> </td>\n"; $out_table .= " <td>keine (weiteren) Daten</td>\n"; $out_table .= " <td align=\"right\"> </td>\n"; $out_table .= " </tr>\n"; } $out_table .="</table>\n"; return $out_table; } #------------------------------------------------------------------------------ # main - Berechnung der Ausgaben #------------------------------------------------------------------------------ $_data = get_data($dateiname,$error_no); switch ($error_no) { case 0: $out = make_out_table($_data); break; default: $out = "Es ist Fehler $error_no aufgetreten"; } ############################################################################### # HTML output ############################################################################### ?> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <HTML> <HEAD> <TITLE>Besucheranzeige</TITLE> <META NAME="Author" CONTENT="Thomas Schmieder"> <META NAME="Keywords" CONTENT="Besucheranzeige"> </HEAD> <BODY> <h3>Besucheranzeige</h3> <?php echo $out;?> </BODY> </HTML> |
Das Script gliedert sich in folgende Bereiche:
Es wird eine eigene Funktion readlock()
für den lesenden
Zugriff auf die Daten bereitgestellt. Die Funktion übernimmt den Pfad und
Dateinamen der Datendatei sowie eine Fehlernummer. Durch die Voranstellung
eines &
vor der Variablen im Funktionskopf erhält die Funktion
Zugriff auf die externe Variable $error_no
, sodass sich eine Änderung
dieser Variablen innerhalb der Funktion auch außerhalb auswirkt. Diese Aufrufart
nennt man auch Call by Reference. das ermöglicht
uns, mehr als einen 'Rückgabewert' zu produzieren.
Wenn noch kein Fehler aufgetreten ist, enthält diese Fehlernummer den Wert
0.
. Trat bereits vor dem Funktionsaufruf ein Fehler auf
($error_no > 0
), bricht die Funktion sofort ab und gibt
false zurück. Anderenfalls wird versucht, die angegebene Datei zu öffnen.
Dieser Versuch wird bis zu fünf Mal in einer Schleife wiederholt. Ist der
Öffnungsversuch bereits vor dem fünten Mal erfolgreich, wird die Schleife mit
break;
abgebrochen und mit dem nächsten Funktionsblock fortgefahren.
Konnte die Datei nicht geöffnet werden, wird die error_no auf einen passenden
Fehlerwert gesetzt und die Funktion mit dem Rückgabewert false
verlassen.
Bei erfolgreicher Dateiöffnung wird versucht, die Datei zu sperren. Die
Codes für den Lockmode und das Sperrverhalten müssen mit OR
verknüpft werden. Da es sich um Bitflags
handelt, die keine gemeinsamen Bits haben, hat eine Addition hier dasselbe Ergebnis.
War der Lockversuch innerhalb fünf Versuchen erfolgreich, wird die Funktion
mit dem Rückgabewert des gültigen File Handles
verlassen. Der Fehlercode verbliebt bei 0.
Konnte keine Sperre durchgeführt werden, wird die Fehlernummer auf einen
passenden Wert gesetzt, die Datei geschlossen und die Funktion mit dem Rückgabewert
false verlassen.
Die Funktion get_data()
öffnet mittels readlock()
eine Datendatei, holt aus der Datei alle Daten und wandelt sie in ein Array um.
Auch diese Funktion beachtet und pflegt den als Referenz übergebenen Fehlerwert
error_nr.
make_out_table()
erzeugt aus dem übergebenen Datenarray einen
HTML-String, der eine vollständige Tabelle enthält und gibt diesen String zurück.
Die Sektion 'Hauptprogramm' enthält den Steuerfluss des Scriptes.
Mit der Funktion get_data()
werden die Daten ausgelesen. Nach
Auswertung des Fehlerwertes eroor_no wird die Ausgabe berechnet.
In der HTML-Ausgabe finden keinerlei Berechnungen oder Entscheidungen mehr statt.
Alle Ausgaben sind bereits berechnet. Es wird immer eine valide HTML-Seite nach
W3C
ausgegeben.
![]() ![]() |
Änderungen am Datenbestand, die auf dem Server automatisch vorgenommen werden, können i.d.R. so schnell abgewickelt werden, dass der Lese- und Schreibvorgang innerhalb eines Requests in einem exclusive lock zusammengefasst werden können. Wenn aber ein Benutzer Daten verändern will, geht das nicht mehr. Der Vorgang sieht dann vereinfacht folgendermaßen aus:
![]() ![]() |
In vielbesuchten Seiten ist es meistens lästig, immer die ganze Datei zu sperren, da dies zu unerwünschten Verzögerungen führt. Ohnehin sind die meisten Zugriffe nur lesender Art. Da wird es reichen,
![]() ![]() |
![]() ![]() |
![]() ![]() |
![]() ![]() |
![]() ![]() |
![]() ![]() |
SelfHTML 8.0
PHP Online-Manual
PHP Sources
PC intern 3.0/4.0, Tischer
Hardware Interrupts, Brown & Kyle
Netware Interrupts, Brown & Kyle
Linux Unix Systemprogrammierung, Helmut Herold
Besonderer Dank gilt vielen Mitgliedern des Selfhtml-Forums, die mich bereitwillig
und qualifiziert unterstützt haben.
Es waren diverse
Versuche auf verschiedenen Client- und Server-Systemen notwendig, um
verbindliche Aussagen treffen zu können. Die Hinweise auf Abweichungen vom
"Normalen" verdanke ich diesen vielfältigen Versuchen.
![]() |
![]() ![]() ![]() |