Binär Input/Output @ PHP

Lesen und Schreiben von Binär-Dateien

Allgemein wird 'Binärdatei' für Dateien verwendet, die mit einem Text-Editor nicht lesbar sind, d.h. nicht-druckbare Zeichen enthalten. Anzahl und Bedeutung solcher Dateien geht zurück, da man versucht, möglichst viel mit lesbarem Text zu codieren (z.B. XML). Ausführbare Programme, Grafik und wenige spezielle Dateien sind 'binär' codiert. Hier wird gezeigt, wie sie mit PHP bearbeitet werden.
PHP PHP Hypertext Processor (File Input / Output, Text, Webseiten)
Wozu binär ? Welche Binär-Dateien werden sinnvoll bearbeitet ?
Pfad Der Weg zu einer Datei (Details)
Test-Datei zum Live-Test der Funktionen dieser Seite
Lesen (1 Schritt) Lesen einer Binär-Datei in einem Schritt
Lesen (Schleife) Zeilenweises Lesen einer Binär-Datei in einer Schleife
Schreiben (1 Schritt) Schreiben einer Binär-Datei in einem Schritt
Schreiben (Schleife) Zeilenweises Schreiben einer Binär-Datei in einer Schleife
Random Access Zugriff auf beliebige 'Datensätze' einer Datei

Wozu 'binär' ?

Genau genommen sind alle Dateien binär codiert, d.h. mit Bits, die nur die Werte 0 oder 1 annehmen können. Mit 'Bnärdatei' (binary data) meint man nicht ganz exakt solche Dateien, die nicht-druckbare Zeichen enthalten, d.h. mit einem Text-Editor nicht lesbar sind. Anzahl und Bedeutung von Binär-Dateien nimmt rasch ab, da man heute versucht, möglichst viel Information in Form von lesbarem Text darzustellen (z.B. → XML).
Das erleichtert viele Vorgänge in der Informatik, und es verringert die Abhängigkeit von Herstellern und den unzähligen von ihnen erfundenen spezialisierten Datei-Formaten (z.B. *.doc, *.xls, .. )
In einigen Fällen werden uns jedoch Binär-Dateien noch einige Zeit erhalten bleiben:

Ausführbare Programme (Windows: *.exe, *.com, *.dll )
Sie zu Lesen oder zu Schreiben macht keinen Sinn, da sie in Maschinen-Code abgefasst sind.

Grafik (Pixel-Bilder), z.B. *.gif, *.jpeg, *.png
Dafür gibt es in PHP eigene spezialiserte Funktionen.
Details zum Erzeugen und Verändern von Grafik mit PHP.

Spezielle Binär-Daten:

Sinnvoll zu bearbeiten sind Daten in speziellen Formaten mit nicht-druckbaren Zeichen.

Wenn sie damit arbeiten wollen, dann solten sie den inneren Aufbau der betreffenden Dateien kennen: Die meisten verfügen über einen Datei-Kopf (header) mit Angaben über den Inhalt (Anzahl und Format der Daten), dann folgen die eigentlichen Daten (body).

Tipp: Im Internet finden sie Angaben über die Struktur von hunderten bekannten Datei-Typen.

Pfad

Pfad ist ein String(Zeichenkette), welcher den Weg zu einer Datei oder zu einem Verzeichnis beschreibt.
Wenn sie Dateien lesen und / oder schreiben wollen, dann sollten sie folgende Begriffe kennen:
Aktuelles Verzeichnis (current working directory, cwd)
Pfad-Separator-Zeichen
Relativer Pfad, Absoluter Pfad, URL-Pfad
Auf dieser Webseite werden aus Sicherheitsgründen ausnahmslos absolute Pfade verwendet.
Details zum Thema Datei-Input/Output (I/O).
Live--PHP:
phpversion = 5.3.3-7+squeeze9

Warning: preg_replace() [function.preg-replace]: Unknown modifier 'n' in /home/topsoft.at/www.topsoft.at/pstrainer/entwicklung/php/inout/binary_inout.php on line 248
getcwd() =

Warning: preg_replace() [function.preg-replace]: Unknown modifier 'n' in /home/topsoft.at/www.topsoft.at/pstrainer/entwicklung/php/inout/binary_inout.php on line 252
dirname(__FILE__) =
DIRECTORY_SEPARATOR = /

Test-Dateien

Für die Live-Beispiele dieser Seite werden 2 Test-Dateien verwendet.
Datei bin_io.dat enthält in 256 Bytes alle Werte (0..255), die ein Byte annehmen kann.
Details im Kapitel ↓ Lesen einer Binärdatei.
Datei bin_ieee.dat enthält binär codierte Zahlen in Strukturen zu je 16 Byte. Jede Struktur enthält Datum und Zeit (Integer), einen UNIX-Timestamp (Double) und ein Byte 0xFF als End-Marke.
Details im Kapitel ↓ Binärdatei schreiben.
Beim Öffnen dieser Webseite wird geprüft, ob die Test-Dateien existieren.
Falls nicht, werden sie erstellt, und die Daten werden eingetragen.
Wenn die Datei existiert, aber größer >100 Byte ist, wird sie gelöscht.
An die Datei bin_ieee.dat wird eine neue Struktur von 16 Byte Länge angehängt.
Live--PHP:
Datei 'bin_io.dat' wird neu erzeugt.
Daten (0..255) werden eingetragen.

Datei 'bin_ieee.dat' wird neu erzeugt.
Neue Daten-Struktur:
Integer = 2012, 5, 20, 16, 3, 35
Binär   = DC 07 05 14 10 03 23
Double  = 1337522615.0
Binär   = 00 00 C0 6D 3E EE D3 41
Binär   = FF
Neue Länge der Datei = 16 byte

Binärdatei in einem Schritt lesen

Am schnellsten arbeitet die PHP-Funktion file_get_contents() (PHP >4.3). Sie gibt den Inhalt einer kompletten Datei als Binär-String zurück. Die Funktion benötigt als Argument den Pfad zur Datei.
Das Ergebnis - ein String - bedeutet in diesem Fall keine lesbare Zeichenkette, sondern eine Kette beliebiger Bytes. Jedes Byte in der Kette kann einen beliebigen Wert (0..255) annehmen.
$bdat = file_get_contents($path);

Verwenden sie diese Methode, wenn irgend möglich, d.h. wenn die Datei-Größe mit Sicherheit unter einer verarbeitbaren Maximalgröße liegt. Nach dem Lesen steht der gesamte Inhalt in einer einzigen Variablen zur Verarbeitung bereit.
Rechts ein (vereinfachtes) Beispiel für die Hexadezimal-Anzeige (Dump) der Daten.
Die Daten werden mit file_get_contents() gelesen.
Mit strlen wird die Länge des Binär-Strings $bdat bestimmt.
Danach läuft eine Schleife über jedes einzelne Byte des Strings.
Mit substr() wird je 1 Byte isoliert, mit ord dessen Wert (0..255) bestimmt.
Danach kann man das Byte mit sprintf() formatieren und ausgeben. Das Zeichen "X" im Format-Code bewirkt die hexadezimale Ausgabe. Bytes mit Werten 32..127 können auch als Zeichen ausgegeben werden.
Mit dem → Modulo-%-Operator wird ein Zeilen-Umbruch nach je 8 Byte erreicht.
$bdat = file_get_contents($path);
$bdc = strlen($bdat);
for($i=0;$i<$bdc;$i++) {
$d = substr($bdat,$i,1);
$c = ord($d);
print sprintf('%02X ',$c);
/* Ausgabe als Zeichen
if($c>32 && $c<128) {print $d;}
else {print ".";}
*/
if($i % 8) {print "<br>\n";}
}
Live-Live-PHP-PHP:
Der Hex-Dump von bin_io.dat, in einem <iframe> angezeigt:
Live--PHP:
Der Hex-Dump von bin_ieee.dat, in einem <iframe> angezeigt:

Binärdatei in einer Schleife lesen

Jede Datei kann wie eine Binärdatei gelesen werden. 'Text' ist lediglich ein Spezialfall, in dem die Bytes nur druckbare Zeichen (und einige Steuerzeichen wie \n) enthalten.
Die Datei wird mit Funktion fopen() geöffnet: Die Funktion benötigt als Argumente den Pfad zur Datei und den 'Modus'. Als Modus wird 'rb' (read binary) angegeben. Die Funktion liefert ein file-handle ($fp), welches von allen folgenden Funktionen für den Zugriff auf die Datei verwendet wird.
Funktion fread() liest einen Binärstring (hier $bdat) von der Datei. Sie benötigt ein file-handle ($fp) und die Anzahl ($rb) zu lesender Bytes.
Falls das Ende der Datei erreicht ist, werden evtl. weniger Bytes gelesen als verlangt.
Die tatsächlich gelesene Anzahl Bytes können sie z.B. mit Funktion strlen() feststellen.
$path = 'test.jpg';
$fp = fopen($path,'rb');
$rb = 16;
if($fp) {
while(!feof($fp)){
$bdat = fread($fp,$rb);
$gelesen = strlen($bdat);
// Verarbeitung hier
}
fclose($fp);
}
Alternativ können sie eine Binärdatei auch Byte-weise lesen.
Funktion fgetc() liest bei jedem Aufruf 1 Byte von der Datei.
An Stelle der Ausgabe tritt normalerweise eine Analyse oder Verarbeitung des Inhalts.
while(!feof($fp)){
$c = fgetc($fp);
$d = ord($c);
print sprintf('%02X ',$d);
}
Live--PHP: Ausgabe der Daten

Von jeder Struktur wird ausgegeben:
Der Hex-Code (15 byte)
Die decodierten Zahlen: Jahr (2byte unsigned Integer), Monat . . . Sekunde (1byte unsigned Integer)
Der Unix-Timestamp (8byte double) als Zahl und ISO-Datum.
Details zur Decodierung im nächsten Kapitel ↓ Verarbeitung
(1) DC07 05 14 10 03 23 0000C06D3EEED341 FF
Integer = 2012, 5, 20, 16, 3, 35,
Double = 1337522615.0 = 2012-05-20 16:03:35



Verarbeitung einer Binärdatei

Ein einfacher, aber seltener Fall ist die Verarbeitung einzelner Bytes. Sie eignet sich für die Verarbeitung während des Lesens ebenso wie nach dem Lesen der Datei.
Vom Lese-Ergebnis $bdat wird mit Funktion substr() je 1 Byte kopiert und mit ord in eine Zahl 0..255 umgewandelt.
Der Wert ($d) jedes Bytes wird als 2stellige Hexadezimalzahl 00..FF ausgegeben.
Für dezimale Zahlenwerte verwenden sie print "$d ";
for($i=0;$i<$gelesen;$i++) {
$c = substr($mybin,$i,1);
$d = ord($c);
print sprintf('%02X ',$d);
}
Wenn die Binär-Datei eine bekannte Struktur hat, dann lesen sie jeweils genau die passende Anzahl Bytes pro Schleife und verarbeiten sie unmittelbar anschließend.
Beispiel: Eine Datei enthält Strukturen aus Gleitkomma-Zahlen (je 4 Byte nach → IEEE-754).
Dazu werden je 4 Byte pro Schleife gelesen und mit unpack() decodiert.
Diese 'mitlaufende' Decodierung benötigt wenige Resourcen und kann Binär-Dateien beliebiger Länge verarbeiten.
$fp = fopen($path,'rb');
while(!feof($fp)){
$bdat = fread($fp,4);
$ra = unpack('fr',$bdat);
print $ra['r']."<br />\n";
}
Wenn die Binärdatei Strukturen variabler Länge hat, dann muss Lese-Vorgang und Verarbeitung getrennt werden. Am einfachsten wird die Datei in einem Schritt gelesen (siehe voriges Kapitel), anschließend verarbeitet.

Wenn die Datei größer ist (oder sein könnte), dann muss der Lese-Vorgang schrittweise in einer Schleife erfolgen.
Für die Verarbeitung der gelesenen Daten sind 3 Fälle möglich:
Es liegen zu wenige Daten für die Verarbeitung vor - In diesem Fall wird der Lese-Vorgang wiederholt, wobei die neuen Daten an die vorhandenen angehängt werden.
Es liegen genau so viele Daten vor, wie zur Verarbeitung benötigt.
Es liegen mehr Daten vor, als zur Verarbeitung benötigt. - In diesem Fall wird ein Teil verarbeitet, der Rest an den nächsten Lese-Vorgang weitergegeben.

Binärdatei in einem Schritt schreiben

Am schnellsten arbeitet die PHP-Funktion file_put_contents() (PHP >=5).
Sie arbeitet wie hintereinander geschaltete Funktionen fopen(), fwrite() und fclose() und gibt die Anzahl erfolgreich geschriebenr Bytes zurück.
$bw = file_get_contents($path,$bdat);

Binärdatei in einer Schleife schreiben

Hier wird zunächst einmal die 'Herstellung' binärer Daten simuliert: Die Variable $bin wird der Reihe nach mit allen Werten gefüllt, die ein Byte annehmen kann: 0..255 bzw. 0x00..0xFF
Mit ($i=33;$i<128;$i++) erhält man nur druckbare → ASCII-Zeichen.
$bin = '';
for($i=0;$i<256;$i++) {
$bin .= chr($i);
}
Die Binärdatei wird genauso wie eine Text-Datei geöffnet. Als Modus wird wb (write binary) angegeben.
Funktion fwrite() schreibt Daten in die geöffnete Datei.
$outpath = 'bin.dat';
$fp = fopen($outpath,'wb');
fwrite($fp,$bin);
fclose($fp);
Tipp: In diesem Beispiel wird eine Binärdatei von 256 Byte Länge erstellt, die jeden möglichen Byte-Wert von 0 bis 255 enthält. Diese Datei ist praktisch zum Debuggen aller Vorgänge rund um binäre Dateien.
Für größere Dateien führen sie fwrite() in einer Schleife aus.
Hier wird eine Datei von 64kB Größe erzeugt. Die Byte-Folge 0..255 (Variable $bin, siehe oben) wird 256mal wiederholt.
$outpath = 'bin.dat';
$fp = fopen($outpath,'wb');
for($i=0;$i<256;$i++) {
fwrite($fp,$bin);
}
fclose($fp);
Codierung von Binärdaten: Funktion pack() codiert zahlreiche verschiedene Formate, z.B. Gleitkomma (4 Byte nach IEEE-754).
Das Ergebnis ist ein Binärstring (hier $bin ), der in eine Datei geschrieben werden kann.
Details zu den Funktionen pack und unpack sowie zur Codierung von Gleitkomma-Zahlen nach IEEE-754.
$r = 12345.56789;
$bin = pack('f',$r);

Random Access - Zugriff auf beliebige 'Datensätze'

Dateien können so aufgebaut werden, dass sie 'Datensätze' enthalten.
Ein Datensatz ist durch eine Struktur fixer Länge definiert. Bei bekannter Länge kann man den 'Offset' eines Datensatzes (Abstand vom Beginn der Datei in Byte) berechnen und auf jede Struktur einzeln zugreifen.
Diese Technik war in der IT-Anfangszeit weit verbreitet, wurde jedoch mittlerweile durch effiziente Datenbanken ersetzt.
Grundlage der Random Access Technik ist die Manipulation des Datei-Zeigers ( file pointer ).

Dateizeiger (file pointer)

Die 'aktuelle Position' in der geöffneten Datei wird in einer eigenen Variablen, dem file pointer gespeichert.
Zu Beginn zeigt der file-pointer an den Datei-Anfang.
Nach jedem Lese-Vorgang wird der file pointer an den Beginn der folgenden (ungelesenen) Zeile gesetzt.
Am Ende der Datei zeigt der file pointer feof (end-of-file) an.

Funktion ftell() gibt den aktuellen Wert des file pointer zurück (als offset, d.h. Abstand vom Datei-Anfang in byte).
$position = ftell($fp);
Funktion fseek() setzt den file pointer an die gewünschte Stelle. Sie benötigt das file-handle und einen ganzzahligen Offset.
Ohne weitere Angabe gilt der Offset vom Datei-Anfang.
Mit der Konstanten SEEK_CUR gilt der Offset relativ zur aktuellen Position, mit der Konstanten SEEK_END relativ zum Datei-Ende.

Funktion rewind() setzt den file pointer an den Datei-Anfang.
$iof = 10;
// file pointer setzen:
fseek($fp,$iof); // vom Anfang
fseek($fp,$iof,SEEK_CUR); // vor
fseek($fp,-$iof,SEEK_CUR); // zurück
fseek($fp,-$iof,SEEK_END); // vor Ende
rewind($fp); // an den Anfang
Random Access Read
Das Lesen von Strukturen wird hier am Beispiel der Test-Datei bin_io.dat (s.o.) demonstriert. Als Struktur (record) wird eine 'Zeile' zu 16 Byte definiert. Das Beispiel rechts liest 3 Records, die per Zufall ausgesucht werden.
 
Random Access Write
Das Schreiben von Strukturen funktioniert analog zum Lesen.
Eine besondere Aufgabe ist allerdings das Schreiben neuer records nach dem Datei-Ende.
 

Letzte Änderung dieser Seite: 2011-10-13 14:33:35