Programmiertechnik/Dateien sperren/in PHP

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

PHP bietet eine Funktion flock an, die folgende Signatur besitzt:

Beispiel
bool flock (resource $handle, int $operation [, int &$wouldblock])

Allgemeines

Im folgenden werden die Parameter erläutert:

Parameter Erläuterung
$handle Der Dateideskriptor der Datei, die zu sperren ist (wie er von fopen zurückgegeben wird)
$operation Welche Operation durchzuführen ist (sperren, entsperren) – siehe unten
$wouldblock Falls nicht auf das Freiwerden der Sperre gewartet werden soll (LOCK_NB), wird die hier angegebene Variable auf true gesetzt, falls die gewünschte Sperre nicht gesetzt werden konnte, weil bestehende Sperren dies verhinderten.

Die Funktion gibt true im Erfolgsfall und false im Fehlerfall zurück. Folgende Operationen sind möglich:

Operation Erläuterung
LOCK_EX Sperrt die Datei exklusiv (zum Schreiben geeignet). Diese Sperre kann nur dann etabliert werden, wenn keine andere Sperre (welcher Art auch immer) auf der Datei vorhanden ist, daher wartet die Funktion, bis alle aktuellen Sperren freigeworden sind. Während diese Sperre aktiv ist, kann keine andere Sperre auf die Datei etabliert werden.
LOCK_SH Sperrt die Datei mitbenutzbar (zum Lesen geeignet). Diese Sperre kann nur etabliert werden, wenn keine bisherige Sperre des Typs LOCK_EX vorhanden ist – allerdings kann sie problemlos mit weiteren Sperren des Typs LOCK_SH koexistieren.
LOCK_UN Entsperrt die Datei.
LOCK_NB LOCK_NB oder LOCK_SH | LOCK_NB.

Die Funktion stellt eine direkte Schnittstelle zur UNIX-artigen Funktion flock dar. Falls auf einem UNIX-artigen Betriebssystem flock nicht zur Verfügung stehen sollte (da die meisten dies anbieten, dürfte dieser Fall extrem selten sein), versucht die Funktion das Verhalten über fcntl zu emulieren. Unter Windows wird das Verhalten über LockFileEx emuliert, das jedoch im Gegensatz zu seinen UNIX-Pendants erzwungene Sperren erzeugt.

Eine Datei auslesen

Beispiel
<?php
$fp = fopen ('counter.txt', 'r');
if (!is_resource ($fp)) {
  die ('Konnte die Datei nicht öffnen!');
}
if (!flock ($fp, LOCK_SH)) {
  die ('Sperren der Datei fehlgeschlagen!');
}
$aktuellerZaehler = (int) fread ($fp, filesize ('counter.txt'));
fclose ($fp);

echo "Aktueller Zählerstand: $aktuellerZaehler";
?>

Das obige Beispiel zeigt, wie man Inhalt einer Datei durch Sperren geschützt auslesen kann. Zuerst wird die Datei zum Lesen geöffnet ('r') geöffnet, damit man einen Dateideskriptor erhält. Die erste Aktion nach dem Öffnen der Datei ist das Sperren derselben. Da hier kein LOCK_NB angegeben wurde, kehrt flock erst dann zurück, sobald es möglich ist, eine Sperre zu errichten, d. h. eine eventuelle, alte Sperre entfernt wurde. Falls es aus irgend einem Grund nicht möglich ist, die Datei zu sperren, gibt flock false zurück. Da hier die Datei ausschließlich gelesen werden soll, wird hier LOCK_SH verwendet. Nach dem Sperren wird die Datei ausgelesen, danach wird die Datei geschlossen, dabei wird die Sperre automatisch freigegeben.

Ausschließliches Beschreiben einer Datei (1)

Ausschließliches Beschreiben einer Datei (1)
<?php
touch ('counter.txt');
$fp = fopen ('counter.txt', 'r+');
if (!is_resource ($fp)) {
  die ('Konnte die Datei nicht öffnen!');
}
if (!flock ($fp, LOCK_EX)) {
  die ('Sperren der Datei fehlgeschlagen!');
}
ftruncate ($fp, 0);
fwrite ($fp, '0');
fclose ($fp);

echo "Zähler wurde zurückgesetzt.";
?>

Das obige Beispiel zeigt, wie man den Inhalt einer Datei durch Sperren geschützt neu schreiben kann, wenn man vorher kein Interesse am bestehenden Inhalt hat. Dazu wird die Datei im Lese-Schreib-Modus geöffnet. Hier wird der Modus 'r+' gewählt. Dies ist wichtig, damit es zwar möglich ist, auf die Datei zu schreiben, der Dateiinhalt beim Öffnen jedoch nicht zerstört wird, da zu dem Zeitpunkt die Datei noch nicht gesperrt werden kann. Dies würde bei 'w+' oder 'w' geschehen. Danach wird eine exklusive Sperre errichtet und nun erst wird der Dateiinhalt gelöscht (über ftruncate) und danach neu beschrieben. Bisher unerwähnt ist der Aufruf der Funktion touch ganz am Anfang. Dieser legt eine leere Datei an, sofern sie bislang nicht existierte, und ändert ansonsten nur die Zugriffszeit der Datei (was harmlos ist, aber hier im Prinzip auch unnötig). Dies ist für das Öffnen der Datei im Modus 'r+' wichtig, denn dieser Modus erfordert eine existierende Datei und würde andernfalls fehlschlagen. Das Öffnen mit dem Modus 'w' würde die Datei, sofern sie bislang nicht existiert hat, neu anlegen, ohne dass man sich darum kümmern müsste. Wenn man sich also sicher ist, dass die Datei garantiert existiert, kann man den touch-Aufruf auslassen.

Streng genommen besteht zwischen dem Aufruf von touch und dem Aufruf von fopen auch eine race condition – allerdings nur in dem Fall, dass ein weiteres Programm die Datei mittendrin löschen könnte. Da dies aber in Verbindung mit Dateisperren sowieso nicht portabel realisierbar ist (vgl. die Anmerkungen zu den Betriebssystemen oben), stellt diese Möglichkeit keine wirklich bedeutende Einschränkung dar.

Ausschließliches Beschreiben einer Datei (2)

Ausschließliches Beschreiben einer Datei (2)
<?php
$fp = fopen ('counter.txt', 'a');
if (!is_resource ($fp)) {
  die ('Konnte die Datei nicht öffnen!');
}
if (!flock ($fp, LOCK_EX)) {
  die ('Sperren der Datei fehlgeschlagen!');
}
ftruncate ($fp, 0);
fwrite ($fp, '0');
fclose ($fp);

echo "Zähler wurde zurückgesetzt.";
?>

Dieses Beispiel gleicht dem obigen Beispiel – allerdings wird in diesem Fall die Datei im Anhänge-Modus ('a') geöffnet. Dies führt im Normalfall dazu, dass jeder Inhalt, den man schreibt, immer an das Ende der Datei angehängt wird, egal, ob man die Dateiposition per fseek oder ähnlichem verstellt. Allerdings führt hier das ftruncate dazu, dass das Dateiende wieder bei Byte 0 ist, nachdem die Funktion den kompletten Dateiinhalt löscht. Damit wird das gleiche Verhalten, wie bei der anderen Variante mit 'r+' und touch erzielt, jedoch tritt hier die oben beschriebene race condition nicht auf – mit dem Nachteil, dass im Gegensatz zu 'r+' während des Schreibvorgangs ein Umpositionieren des Positionszeigers der Datei ohne Effekt ist.

Lesen und Schreiben in eine Datei

Lesen und Schreiben in eine Datei
<?php
$fp = fopen ('counter.txt', 'r+');
if (!is_resource ($fp)) {
  die ('Konnte die Datei nicht öffnen!');
}
if (!flock ($fp, LOCK_EX)) {
  die ('Sperren der Datei fehlgeschlagen!');
}
$aktuellerZaehler = (int) fread ($fp, filesize ('counter.txt'));
$aktuellerZaehler++;
fseek ($fp, 0, SEEK_SET);
ftruncate ($fp, 0);
fwrite ($fp, (string) $aktuellerZaehler);
fclose ($fp);

echo "Zähler wurde um eins erhöht und beträgt jetzt $aktuellerZaehler.";
?>

In diesem Beispiel geht es darum, eine Datei zuerst auszulesen, auf den Inhalt zu reagieren und den Inhalt danach wieder in die Datei zurückzuschreiben. Wichtig ist hierbei, dass die Datei über den Verlauf der gesamten Operation exklusiv gesperrt bleibt, damit keine race conditions auftreten. Die hier dargestellte Methode ist eine Abwandlung der ersten Methode zum Schreiben der Datei. Allerdings wird hier vor dem Löschen des Dateiinhalts die Datei zuerst gelesen. Mittels fseek wird der Dateizeiger wieder auf den Anfang gesetzt, da dies nicht automatisch durch ftruncate geschieht. Auf das Ausführen von touch wird hier verzichtet, da vor dem Auslesen die Datei bereits existieren sollte. Wenn dies nicht immer der Fall ist, kann dies hier natürlich wieder eingefügt werden.

Alternativen zu flock

Auf UNIX-artigen Betriebssystemen kann man in PHP unter einigen Versionen auch die Funktion dio_fcntl() nutzen, die eine direkte Schnittstelle zu der Betriebssystemfunktion fcntl(2) ist (die PHP-Funktion flock ist entweder eine Schnittstelle zu flock(3) oder zu fcntl(2), falls flock(3) auf dem betreffendem System nicht vorhanden ist, was jedoch extrem selten sein sollte). Allerdings benötigt dies die dio-Erweiterung, die nur bei PHP zwischen Versionen 4.2 und 5.0 mitgeliefert wurde – und dort im Normalfall nicht aktiviert war. Seit PHP 5.1 ist sie getrennt von PHP in PECL vorhanden. Dort wird sie allerdings nicht gewartet, es gibt also kein offizielles Paket. Zudem funktioniert dies auch nur, wenn die Datei selbst über die dio-Erweiterung geöffnet wurde.

Weblinks