| #include <pronix.de> |
|
|
19.29. Schreiben und Lesen - write und read
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Anmerkung |
|
Das Schreiben mit write() wird über einen Puffercache durchgeführt, bevor wirklich auf die Festplatte, Diskette usw. geschrieben wird. Dieses delayed write birgt bei einem Systemabsturz die Gefahr, dass im Cache befindliche Daten nicht physikalisch auf Festplatte oder Diskette geschrieben werden. In diesem Fall können Sie die Datei zum Schreiben im O_SYNC-Modus öffnen. Dieser Modus wartet bei jedem physikalischen Schreibvorgang bis dieser fertig ist und liest dann erst wieder Daten ein. Der Modus hat leider aber den Nachteil, schrecklich langsam zu sein. Für Linux gibt es hier zwei Funktionen, sync() und fsync(), die in diesem Buch allerdings nicht behandelt werden. Linux-User lesen bitte entsprechende man-Pages, falls Sie diese Funktionen benötigen. |
|
Jetzt folgt das Gegenstück zur Funktion write(). Zuerst die Syntax:
int read(int fh, const void *puffer, site_t bytezahl);
Mit der Funktion read() werden bytezahl Bytes aus der Datei mit dem File-Deskriptor fh gelesen. Die Daten werden in die Adresse von puffer abgelegt. Zuvor muss natürlich die Datei mit open() geöffnet werden. Auch hier liefert die Funktion bei Fehler -1, ansonsten, wenn alles richtig verlief, 0 zurück. Hierzu ein Listing, welches eine Datei kopiert:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#ifdef __unix__
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#elif __MSDOS__ || __WIN32__ || _MSC_VER
#include <io.h>
#include <sys\stat.h>
#endif
#define MAXBYTES 1024
int main(int arg, char **argv)
{
int in,out,count;
char buffer[MAXBYTES];
if(argc < 3)
{
printf("Aufruf: programmname quelldatei zieldatei\n");
exit(0);
}
if((in=open(*++argv,O_RDONLY)) == -1)
printf("Fehler open %s\n",argv);
if((out=open(*++argv,O_WRONLY|O_TRUNC|O_CREAT))== -1)
printf("Fehler open %s\n",argv);
while(count = read(in,buffer,MAXBYTES))
write(out,buffer,count);
close(in);
close(out);
return 0;
}
Damit wird die Datei, die als zweites Argument in der Kommandozeile angegeben wird, in die Datei, welche als drittes Argument angegeben wird, kopiert.
Jetzt soll das erste Programm aus dem vorigen Abschnitt zu write() mit der Funktion read() ergänzt werden:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#ifdef __unix__
#include <unistd.h>
#elif __MSDOS__ || __WIN32__ || _MSC_VER
#include <io.h>
#endif
int main()
{
int fh;
char puffer[100];
char pufferneu[100];
strcpy(puffer,"Dieser Text steht in \"test.txt\"\n");
if((fh=open("test.txt",O_RDWR|O_CREAT|O_TRUNC)) == -1)
{
printf("Fehler beim Öffnen der Datei \"test.txt\"\n");
exit (0);
}
if((write(fh, &puffer, sizeof(puffer))) == -1)
{
printf("Fehler bei write........\n");
exit (0);
}
close(fh);
if((fh=open("test.txt",O_RDONLY)) == -1)
{
printf("Fehler beim 2. Oeffnen von test.txt\n");
exit (0);
}
if((read(fh, &pufferneu, sizeof(pufferneu))) == -1)
{
printf("Fehler bei read.........\n");
exit (3);
}
printf("%s",pufferneu);
close(fh);
return 0;
}
Bis zur Funktion write() soweit für Sie nichts Neues. Mit read() wird hier die Größe von sizeof(pufferneu) Bytes mit dem File-Deskriptor fh in die Adresse von pufferneu gelegt. Das Programm dürfte keinem mehr Kopfzerbrechen bereiten.
Daher soll auch das zweite obige Listing mit der Funktion read() bestückt werden. Schließlich wollen Sie die Daten, die geschrieben wurden, auch wieder lesen können:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#ifdef __unix__
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#elif __MSDOS__ || __WIN32__ || _MSC_VER
#include <io.h>
#include <sys\stat.h>
#endif
#define MAXADRESSEN 10
#define MAX 30
struct kunde {
char name[MAX];
char vorname[MAX];
int kundenummer;
char ort[MAX];
char strasse[MAX];
int hausnummer;
int vorwahl;
int telefonnr;
};
struct kunde k[MAXADRESSEN];
static int counter=0;
void neukunde()
{
int fh;
if(counter==MAXADRESSEN)
printf("Kein Speicherplatz mehr frei!!!\n");
else
{
printf("Name...................: ");
fgets(k[counter].name, MAX, stdin);
printf("Vorname................: ");
fgets(k[counter].vorname, MAX, stdin);
k[counter].kundenummer=counter;
printf("Ort....................: ");
fgets(k[counter].ort, MAX, stdin);
printf("Strasse................: ");
fgets(k[counter].strasse, MAX, stdin);
printf("Hausnummer.............: ");
do{
scanf("%d",&k[counter].hausnummer);
}while(getchar() != '\n');
printf("Vorwahl................: ");
do{
scanf("%d",&k[counter].vorwahl);
}while(getchar() != '\n');
printf("Telefonnummer..........: ");
do{
scanf("%d",&k[counter].telefonnr);
}while(getchar() != '\n');
if((fh=open("kunden.dat",O_CREAT|O_RDWR)) == -1)
printf("Konnte\"kunden.dat\" nicht öffnen\n");
else if((write(fh,&k,sizeof(k))) == -1)
printf("Konnte nicht in \"kunden.dat\" schreiben\n");
else
counter++;
}
}
void lese()
{
int fh;
int num;
printf("Bitte geben Sie die Kundennummer ein : ");
scanf("%d",&num);
if((fh=open("kunden.dat",O_RDONLY)) == -1)
{
printf("Kann Kundendatei nicht öffnen");
exit (0);
}
read(fh,&k,sizeof(k));
printf("\n\n");
printf("Name..........%s",k[num].name);
printf("Vorname.......%s",k[num].vorname);
printf("Kundennummer..%d\n",k[num].kundenummer);
printf("Wohnort.......%s",k[num].ort);
printf("Strasse.......%s",k[num].strasse);
printf("Hausnummer....%d\n",k[num].hausnummer);
printf("Vorwahl.......%ld\n",k[num].vorwahl);
printf("Telefonnum....%ld\n",k[num].telefonnr);
}
int main()
{
int wahl;
do{
printf("\t1: Neuen Kunden eingeben\n\n");
printf("\t2: Kunden ausgeben\n\n");
printf("\t3: Programmende\n\n");
printf("\tEingabe :> ");
do{
scanf("%d",&wahl);
}while(getchar() != '\n');
switch(wahl)
{
case 1 : neukunde(); break;
case 2 : lese(); break;
case 3 : printf("bye\n"); break;
default: printf("Falsche Eingabe!!!\n");
}
}while(wahl != 3);
return 0;
}
Das Datenprogramm ist wieder um eine Funktion reicher geworden, nämlich um lese(). Bei dieser wird mit
if((fh=open("kunden.dat",O_RDONLY)) == -1)
die Kundendatei zum Lesen geöffnet und mit
read(fh,&k,sizeof(k));
ausgelesen sowie anschließend auf dem Bildschirm ausgegeben.
Ein paar Zeilen noch zum File-Deskriptor. Folgende Anwendung des File-Deskriptors ist bekannt:
write(fh, &puffer, sizeof(puffer)); read(fh, &puffer, sizeof(puffer));
Aber statt des File-Deskriptors fh können logischerweise auch Ganzzahlen verwendet werden. Folgende Ziffern sind allerdings fest belegt, da diese vordefinierte Deskriptoren sind:
| Dezimalzahl | Bedeutung |
| 0 | Standardeingabe (stdin) |
| 1 | Standardausgabe (stdout) |
| 2 | Standardfehlerausgabe (stderr) |
Ein Listing dazu:
#include <stdio.h>
#ifdef __unix__
#include <unistd.h>
#elif __MSDOS__ || __WIN32__ || _MSC_VER
#include <io.h>
#endif
int main()
{
char puffer[100];
read(0, &puffer, sizeof(puffer));
printf("%s",puffer);
return 0;
}
Mit read(0, &puffer, sizeof(puffer)) wird aus der Standardeingabe (stdin) in die Adresse des Puffers gelesen, also von der Tastatur. Anhand der Ausgabe können Sie auch die Eigenheiten der niedrigeren Ebene erkennen. Hier wird nicht automatisch ein Stringende-Zeichen angehängt, darum müssen Sie sich selbst kümmern. Dasselbe kann auch mit write() auf dem Bildschirm vorgenommen werden:
#include <stdio.h>
#ifdef __unix__
#include <unistd.h>
#elif __MSDOS__ || __WIN32__ || _MSC_VER
#include <io.h>
#endif
int main()
{
char puffer[] = "Ich werde im Low-Level-I/O ausgegeben";
write(1, &puffer, sizeof(puffer));
return 0;
}
Da der File-Deskriptor manchmal so verwendet wird, sollte dies hier nicht unerwähnt bleiben.
lseek() ist dieselbe Funktion, die bei der höheren Ebene fseek() hieß, und dient zum Verschieben des File-Deskriptors in der geöffneten Datei. Hier die Syntax von lseek():
#inlcude <unistd.h> /* für UNIX */ #include <sys/types.h> /* für UNIX */ #inlcude <io.h> /* für MS-DOS/WIN */ long lseek(int fh, long offset, int wie);
Die Datei, in welcher der File-Deskriptor verschoben werden soll, wird mit dem File-Deskriptor fh angegeben, der natürlich zuvor mit open() geöffnet bzw. erzeugt wurde. Um wie viele Bytes der File-Deskriptor von der Position wie verschoben werden soll, wird mit offset angegeben. Die Angaben von wie sind dieselben wie schon bei fseek(). Hier nochmals die Tabelle, die zeigt, welche Möglichkeiten zur Verfügung stehen:
| wie-Angabe | Beschreibung |
| SEEK_SET oder 0 | Schreib-/Lese-Deskriptor vom Dateianfang um offset Bytes versetzen |
| SEEK_CUR oder 1 | Schreib-/Lese-Deskriptor von der aktuellen Position um offset Bytes versetzen |
| SEEK_END oder 2 | Schreib-/Lese-Deskriptor vom Dateiende um offset Bytes versetzen |
Als Rückgabewert gibt diese Funktion den Wert der aktuellen Position des File-Deskriptors zurück:
long aktuelle_position; aktuelle_position = lseek(fh, 0L, SEEK_CUR);
Bei Fehler gibt diese Funktion -1 zurück. lseek() sollte allerdings nicht auf kleiner als 0 geprüft werden, sondern auf -1, da es durchaus sein kann, dass es Gerätedateien gibt, die einen negativen Wert zurückliefern. Weitere Möglichkeiten von lseek():
Deskriptor auf den Dateianfang setzen:
lseek(fh, 0L, SEEK_SET);
Deskriptor um 100 Bytes von der aktuellen Position nach vorne versetzen:
lseek(fh, 100L, SEEK_CUR);
Deskriptor um 10 Bytes von der aktuellen Position zurücksetzen:
lseek(fh, -10L, SEEK_CUR);
Deskriptor auf das letzte Byte setzen (nicht EOF):
lseek(fh, -1L, SEEK_END);
Ein Beispiel zu lseek() kann ich mir sparen, da diese Funktion genauso eingesetzt wird wie fseek(); nur, dass anstatt eines Streams hierbei ein File-Deskriptor verwendet wird.
Manchmal benötigen Sie von einem offenen Stream den File-Deskriptor. Die Syntax dieser Funktion lautet:
int fileno(FILE *fz);
fileno() ist erforderlich, falls eine Datei mit fopen() geöffnet wurde, um den Stream für Funktionen einzusetzen, die einen File-Deskriptor benötigen (z.B. Funktionen wie dup(), dup2() oder fcntl()). Hier ein Listing:
#include <stdio.h>
#include <stdlib.h>
#ifdef __unix__
#include <unistd.h>
#else
#include <io.h>
#endif
int main()
{
FILE *fz;
int fd,fd2;
char datei[255];
printf("File-Deskriptoren zu stdin, stdout und stderr : ");
printf("%d, %d und %d\n"
,fileno(stdin),fileno(stdout),fileno(stderr));
printf("Welche Datei wollen Sie öffnen : ");
scanf("%s",datei);
fz=fopen(datei, "r");
if(!fz)
{
perror(NULL);
exit(0);
}
fd = fileno(fz);
printf("File-Deskriptor zur Datei %s lautet %d\n",datei,fd);
fd2=dup(fd);
printf("File-Deskriptor, der kopiert wurde lautet %d\n",fd2);
return 0;
}
Zu Beginn des Programms werden erst die File-Deskriptoren zu stdin, stdout und stderr ausgegeben, diese sollten immer 0, 1 und 2 sein. Anschließend wird der File-Deskriptor in einer von Ihnen geöffneten Datei ausgegeben. Dieser File-Deskriptor wird jetzt mit der Funktion dup() dupliziert und ebenfalls auf dem Bildschirm ausgegeben.
Mit der Funktion fdopen() erhalten Sie aus einem File-Deskriptor einen FILE-Zeiger:
#include <stdio.h> FILE *fdopen(int fd, const char *modus);
fdopen() ist das Gegenstück zu fileno(). Als modus, wie die Datei geöffnet wird, können dieselben Modi wie bei der Funktion open() genutzt werden.
fdopen() wird oft auf File-Deskriptoren angewandt, die von Funktionen zurückgegeben werden, die Pipes oder Kommunikationskanäle in Netzwerken einrichten. Das kommt daher, weil einige Funktionen (open(), dup(), dup2(), fcntl(), pipe() ...) in Netzwerken nichts mit Streams anfangen können und File-Deskriporen benötigen. Um aber wieder aus Deskriptoren einen Stream (FILE-Zeiger) zu erzeugen, ist die Funktion fdopen() erforderlich. Hierzu ein kurzes Beispiel:
#include <stdio.h>
#ifdef _unix_
#include <unistd.h>
#else
#include <io.h>
#endif
int main()
{
FILE *fz, *fz2;
int fd,fd2;
char datei[255];
printf("Welche Datei wollen Sie erzeugen: ");
scanf("%s",datei);
fz=fopen(datei, "w+");
if(!fz)
perror(NULL);
fd = fileno(fz);
printf("File-Deskriptor zur Datei %s lautet %d\n",datei,fd);
fd2=dup(fd);
printf("Der File-Deskriptor, der kopiert wurde %d\n\n",fd2);
printf("Wir wollen einen STREAM oeffnen....\n");
fz2 = fdopen(fd2, "w");
if(!fz2)
perror(NULL);
fprintf(fz,"Dieser Text steht in %s\n",datei);
fprintf(fz2,"Dieser Text steht auch in %s\n",datei);
fprintf(stdout,"Es wurde etwas in die "
"Datei %s geschrieben",datei);
return 0;
}
Die beiden Funktionen fileno() und fdopen() wurden nur kurz behandelt, da diese vorwiegend in der Netzwerkprogrammierung verwendet werden.