#include <pronix.de>

Home

 Programmieren
C-Programmieren
+ OpenBook
+ Linuxprogrammierung
+ Gtk+
+ Win32-API
Perl
CGI

 Bücher
C von A bis Z
C M&T easy
Rezensionen

 Service
Links
Feedback
Mailingliste
Newsletter

 Sonstiges
FAQ
Impressum
 

5. Interprozesskommunikationen (IPC)

5.3.1 FIFO - named Pipes           zurück Ein Kapitel tiefer zum Inhaltsverzeichnis

Mit den Pipes die Sie bisher verwendet haben, konnten Sie nur mit den Prozessen kommunizieren, die miteinander Verwandt waren. Also nur zwischen geforkten Prozessen. Mit FIFO's (benannte Pipes) haben Sie nun die Möglichkeit mit einem völlig fremden Prozess zu kommunizieren (Daten austauschen).

Intern (wenn man ein bisschen den Kernel betrachtet) ist eine FIFO nichts anderes als eine tatsächliche Implementierung einer Pipe, wie Sie dies im Kapitel zuvor kennen gelernt haben. Der Systemaufruf mkfifo() bedeutet also nichts anderes, das eine Pipe als Filesystem-Objekt repräsentiert wird.

Auf der Shell läßt sich eine FIFO folgendermaßen erstellen ...

mkfifo fifo1

Nun befindet sich in Ihrem Verzeichnis eine FIFO. Sie könnten jetzt in die FIFO etwas schreiben ...

echo hallo welt > fifo1

... und aus dieser FIFO wieder lesen mit ...

cat fifo1

Natürlich müssen Sie auch die Zugriffrechte der FIFO vergeben, wer in diese FIFO etwas schreiben darf und wer aus Ihr lesen kann. Man kann in einer FIFO nun mit mehreren Fenstern (Prozessen) etwas reinschreiben und andersrum können Sie mehrere zugleich auslesen lassen.

FIFO's sind auch eine Halbduplex IPC, was bedeutet das auch mit FIFO's, wie schon bei den Pipes, kein mehrfaches Auslesen möglich ist.

Die FIFO's arbeiten ebenfalls wie die Pipes nach dem first in - first out Prinzip. Das heißt, die Daten die zuerst geschrieben werden, werden auch wieder als erstes Ausgegeben. Mit FIFO's werden häufig die typischen Client/Server-Anwendungen geschrieben ...

FIFO - Client/Server Anwendung

An diesem Beispiel kann einen typischen Anwendungsfall erkennen. Vier Clients, in diesem Fall Uwe, Otto, Franz und Egon wollen auf zwei Drucker zugreifen. Alle vier Clients schicken nun eine Schreibanforderung an den Drucker. Diese Schreibaufforderung wird zuerst von unserem FIFO angenommen und der Reihe nach, wie die Anforderungen eingetroffen sind, sortiert. Der Server ließt nun aus dieser FIFO, bearbeitet diese Anforderungen und leitet diese an den Entsprechenden Drucker weiter. Wenn jetzt aber z.B. Uwe und Egon ein 100 Seiten starkes Skript ausdrucken lassen, sollte der Server natürlich an Otto und Franz eine Meldung zurückschicken das sich der Auftrag in der Warteschlange befindet und momentan kein Drucker mehr frei ist. Der Server schickt also eine Antwort an den Client. Entweder eine Bestätigung des Auftrags oder eine Warteaufforderung ...

FIFO - Client/Server Anwendung

Hier kann man erkennen, dass der Server in eine FIFO schreibt und die Clients diesmal aus der FIFO lesen. Dies soll erst mal veranschaulichen worum es bei FIFO's überhaupt geht.

Nun aber zu den Grundlagen wie FIFO's in C zu bewerkstelligen sind. Diese Art von FIFO's sind übrigens nicht gleichzusetzen mit den FIFO's, die Sie mit doppelt verketteter Listen erstellt habe. Das Prinzip (first in - first out) ist zwar dasselbe aber FIFO's sind keine normalen Dateien.

Hier der Syntax der Funktion um eine FIFO anzulegen ...

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pfadname, mode_t mode);

Bei Erfolg bekommen Sie 0 zurück und bei Fehler -1. Als ersten Parameter geben Sie den Pfad an, wo Sie diese FIFO anlegen wollen. Als zweiten Parameter geben wir den Modus der FIFO an.

Wollen wir uns das ganze mal in Praxis ansehen und eine FIFO erstellen ...

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{

/*Wir erzeugen eine FIFO*/
  if((mkfifo("fifo0001", O_RDWR)) == -1)
    {
      fprintf(stderr, "Konnte keine FIFO erzeugen.........\n");
      exit(0);
    }
  return 0;
}

Wenn Sie jetzt ls -l in einer Konsole eingeben, dürften Sie bei der Auflistung folgenden Eintrag finden ...

p---------    1    tot    tot    0  Jun  9  11:33    fifo0001|

An dem p können Sie erkennen, dass es bei dieser Datei um eine Pipe oder FIFO handelt. Es lässt sich aber auch am Dateinamen erkennen: fifo0001|

Nun wollen wir doch mal die Funktion stat(), zur Überprüfung der Dateiart auf die FIFO loslassen ...

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
  struct stat attribut;

  unlink("fifo0001"); /*Falls fifo0001 schon vorhanden ist*/
/*Wir erzeugen eine FIFO*/
  if((mkfifo("fifo0001", O_RDWR)) == -1)
    {
      fprintf(stderr, "Konnte keine FIFO erzeugen.........\n");
      exit(0);
    }

  if(stat("fifo0001", &attribut) == -1)
    {
      fprintf(stderr, "Fehler bei fstat............\n");
      exit(0);
    }

  if(S_ISREG(attribut.st_mode))
      printf("fifo0001 ist eine reguläre Datei\n");
  else if(S_ISDIR(attribut.st_mode))
      printf("fifo0001 ist ein Verzeichnis\n");
  #ifdef S_ISSOCK
  else if(S_ISSOCK(attribut.st_mode))
      printf("fifo0001 ist ein Socket\n");
  #endif
  else if(S_ISFIFO(attribut.st_mode))
      printf("fifo0001 ist eine FIFO oder Pipe\n");
  else printf("Kann Dateiart nicht feststellen\n");

  return 0;
}

Spätestens ab jetzt dürfte Ihnen klar geworden sein, dass eine FIFO keine normale Datei ist, sondern eben eine weitere Dateiart!

Jetzt wollen wir mal etwas in die FIFO schreiben und wieder etwas aus ihr lesen ...

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
  int fd_fifo; /*handle for FIFO*/

  char puffer[]="This text is for the fifo\n";
  char buf[100];

  unlink("/tmp/fifo0001.1");
/*We create a FIFO*/

  if((mkfifo("/tmp/fifo0001.1", O_RDWR)) == -1)
    {
      fprintf(stderr, "Can't creat a fifo.........\n");
      exit(0);
    }
/*We open the fifo for read and write*/
  if((fd_fifo=open("/tmp/fifo0001.1", O_RDWR)) == - 1)
    {
      fprintf(stderr, "Can't open the fifo.....\n");
      exit(0);
    }

  write(fd_fifo,puffer,strlen(puffer)) ;
  if(read(fd_fifo, &buf, sizeof(buf)) == -1)
     fprintf(stderr, "Error! can't read from FIFO.......\n");
  else
     printf("read now from FIFO : %s\n",buf);

  return 0;
}

Zuerst erstellen Sie eine FIFO mit mkfifo, öffnen diese mit open zum Lesen und Schreiben. Anschließend schreiben Sie etwas in die FIFO (write) und lesen dies anschließend gleich wieder aus (read). Man kann an diesem Programm sehen, dass bei den FIFOs, wie bei einer normale Datei, die elementaren E/A-Funktionen oder aber auch die Standard E/A-Funktionen verwendet werden können.

Wie weiter oben schon erwähnt, sollten Sie beim Erstellen einer FIFO die Zugriffsrechte festlegen.

Es kann allerdings auch sein, dass auf Ihrem System die Funktion mkfifo() nicht vorhanden ist. In diesem Fall müssen Sie die Funktion ...

int mknod(char *pfadname, int mode, int dev);

...verwenden. Als Pfadname geben Sie auch hier die reguläre Unix-Verzeichnisangabe und den Namen der FIFO an. Für mode müssen Sie hier die Konstante S_IFIFO aus der Headerdatei <sys/stat.h> mit den Zugriffsrechten verwenden. Der Parameter dev wird nicht benötigt. Die Verwendung von mknod sieht dann wie folgt aus ...

if(mknod("/tmp/fifo0001.1", S_IFIFO | S_IRUSR | S_IWUSR, 0) == - 1)
{ /*Kann keine fifo erzeugen...........Fehler*/

Der Rückgabewert bei Erfolg ist 0 und bei Fehler, wie eben gesehen -1. Diese erzeugt FIFO kann anschließend genauso verwendet werden wie die mit mkfifo() erzeugte FIFO.

Bevor Sie ein Client/Server-Beispiel programmieren werden, müssen Sie noch einiges wissen, worauf man beim Zugriff auf FIFO's achten muss ...

Wenn Sie beim öffnen der FIFO mit open nicht den Modus O_NONBLOCK verwenden, wird die Öffnung der FIFO sowohl zum Schreiben als auch zum Lesen blockiert. Beim Schreiben wird solange blockiert bis ein anderer Prozess die FIFO zum Lesen öffnet. Beim Lesen wiederum wird ebenfalls solange blockiert bis ein anderer Prozess in die FIFO schreibt.

Das Flag O_NONBLOCK kann nur bei Lesezugriffen verwendet werden. Wird mit O_NONBLOCK versucht die FIFO mit Schreibzugriffen zu öffnen, führt dies zu einem Fehler beim öffnen der FIFO.

Wenn man eine FIFO zum Schreiben mit close oder fclose schließt, bedeutet dies für die FIFO zum Lesen ein EOF.

Und wie bei den Pipes gilt schon, falls mehrere Prozesse auf das selbe FIFO schreiben, muss darauf geachtet werden, dass niemals mehr als PIPE_BUF Bytes auf einmal geschrieben werden. Dies, damit die Daten nicht durcheinandergemischt werden. Das könnten Sie mit folgendem Beispiel ermitteln ...

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
  unlink("fifo0001");

/*Wir erzeugen eine FIFO*/
  if((mkfifo("fifo0001", O_RDWR)) == -1)
    {
      fprintf(stderr, "Konnte keine FIFO erzeugen.........\n");
      exit(0);
    }

 printf("Es können maximal %ld Bytes auf einmal in die FIFO geschrieben werden\n",
                pathconf("fifo0001", _PC_PIPE_BUF));
  printf("Außerdem können max %ld FIFOs geöffnet sein\n", sysconf(_SC_OPEN_MAX));
  return 0;
}

Beim Versuch in eine FIFO zu schreiben, die momentan keinen Prozess zum Lesen geöffnet hat, sollte das Signal SIGPIPE generiert werden.

Hierzu nun ein kleines Beispiel wo Sie einen Signalhandler für SIGPIPE einrichten, eine FIFO erzeugen, mit dem Kindprozess in diese FIFO schreiben und mit dem Elternprozess dies wieder auslesen. Wenn Sie so wollen ein einfaches Server/Client Beispiel (Eltern/Kind). Hier das Beispiel ...

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>

static volatile sig_atomic_t sflag;
static sigset_t signal_neu, signal_alt, signal_leer;

static void sigfunc(int sig_nr)
{
  fprintf(stderr, "SIGPIPE erhalten. Programmabbruch...\n");
  exit(0);
}

void signal_pipe(void)
{
  if(signal(SIGPIPE, sigfunc) == SIG_ERR)
   {
      fprintf(stderr, "Konnte Signal SIGPIPE nicht erzeugen..........\n");
      exit(0);
    }
/*Wir entfernen alle Signale aus der Signalmenge*/
  sigemptyset(&signal_leer);
  sigemptyset(&signal_neu);
  sigaddset(&signal_neu, SIGPIPE);
/*Jetzt setzen wir signal_neu und sichern die*/
/* noch aktuelle Signalmaske in signal_alt*/
  if(sigprocmask(SIG_UNBLOCK, &signal_neu, &signal_alt) < 0)
    exit(0);
}

int main()
{
  int r_fifo, w_fifo; /*handle for FIFO*/

  char puffer[]="This text is for the fifo\n";
  char buf[100];
  pid_t pid;

  signal_pipe();

  unlink("/tmp/fifo0001.1");
/*We create a FIFO*/

  if((mkfifo("/tmp/fifo0001.1", O_RDWR)) == -1)
   {
     fprintf(stderr, "Can't creat a fifo.........\n");
     exit(0);
   }

  pid=fork();
  if(pid == -1)
     { perror("fork"); exit(0);}
  else if(pid > 0) /*Elternprozess ließt aus der FIFO*/
    {
      if (( r_fifo=open("/tmp/fifo0001.1", O_RDONLY)) < 0)
        { perror("r_fifo open"); exit(0); }
      while(wait(NULL)!=pid); /*Wir warten auf das Ende vom Kindprozess*/
      read(r_fifo, &buf, sizeof(buf)); /*Lesen aus der FIFO*/
      printf("%s\n",buf);
      close(r_fifo);
    }
  else /*Kindprozess schreibt in die FIFO*/
    {
      if((w_fifo=open("/tmp/fifo0001.1", O_WRONLY)) < 0)
          { perror("w_fifo open"); exit(0); }
      write(w_fifo, puffer, strlen(puffer)); /*Schreiben in die FIFO*/
      close(w_fifo); /*EOF*/
      exit(0);
    }
  return 0;
}

5.3.2. Kommunikation zwischen zwei getrennten Prozessen mit einer FIFO  zurück Ein Kapitel tiefer Ein Kapitel höher zum Inhaltsverzeichnis

Nun verwenden wir als Problemstellung das Bild vom Kapitel zuvor ...

FIFO Client/Server Prinzip

Zugegeben, das Problem löst in unserem Fall normalerweise der Druckerdämon lpd. Aber so fällt es einem leichter es sich vorzustellen. Nur verwenden wir in unserem Beispiel einen Drucker, da es wohl die Standardausrüstung der meisten Leser sein wird. Und schließlich wollen Sie das ganze ja auch in der Praxis testen. Hier der Quellcode dazu ...

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define BUF 8192

void an_den_drucker(char *text)
{
 FILE *p;
/*Pipe zum Tool lpr erstellen zum Schreiben auf lpr*/
 p=popen("lpr","w");
 if(p == NULL)
  {
    fprintf(stderr,"Konnte keine Pipe zu \'lpr\' erstellen!!\n");
    exit(0);
  }
 /*An den Drucker schreiben*/
 printf("Sende Auftrag an den Drucker......\n");
 fprintf(p, "%s", text);
 fflush(p);
 pclose(p);
}


int main()
{
  char puffer[BUF], inhalt[BUF];
  int fd,n;
  inhalt[0]='\0';

  if(mkfifo("fifo1.1", O_RDWR) < 0)
   {
    fprintf(stderr,"Konnte keine FIFO erzeugen!!\n");
    exit(0);
   }
  /*Empfänger ließt nur aus der FIFO*/
  fd=open("fifo1.1", O_RDONLY);
  if(fd == -1)
   {
     perror("open : ");
     exit(0);
   }
  while(1) /*Endlosschleife*/
   {
    if(n=read(fd,puffer,BUF))
      an_den_drucker(puffer);
    sleep(2);
   }

 return 0;
}

Ich nenne das Programm einfach mal 'polling'. Das Programm läuft nun in einer Endlosschleife und liest etwas aus der FIFO, sofern dort etwas steht. Ansonsten wird 'polling' durch die Funktion read blockiert.

Das Programm polling dient also als Empfänger oder auch als Server wenn Sie so wollen. Sie können das Programm ja mal testen. Starten Sie polling. Und nun schreiben Sie etwas von anderen Fenstern oder Terminals in die FIFO. Beispielsweise mittels ...

echo hallo Welt > fifo1.1

...und aus wieder einem anderen Fenster oder Terminal ...

cat datei.txt > fifo1.1

Sie können aus beliebig (nicht ganz) vielen Fenstern und Terminals Nachrichten in die FIFO schreiben. Das Programm polling ließt anschließend aus der FIFO, und leitet das gelesen mit Hilfe einer Pipe an den Drucker weiter.

Man könnte das Ganze als eine Art Faxempfangsprogramm in einem lokalen Netzwerk sehen. Da nicht jeder mit der Shell vertraut ist wollen wir noch ein Programm schreiben, mit dem man Daten in diese FIFO schreiben kann. Also das Sendeprogramm. Jeder der nun auf seinem Rechner dieses Programm hat, kann auf diese FIFO schreiben (sofern die Zugriffrechte richtig gesetzt sind).

In unserem Fall starten Sie das Programm von verschiedenen Fenstern oder Terminals. Hier das Programm mit dem Sie eine Nachricht in die FIFO schreiben können ...

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define BUF 8192

int main()
{
  char puffer[BUF], inhalt[BUF];
  int fd,n;
  inhalt[0]='\0';

  /*Der Sender schreibt nur in die FIFO*/
  fd=open("fifo1.1", O_WRONLY);
  if(fd == -1)
   {
     perror("open : ");
     exit(0);
   }
  printf("Bitte geben sie Ihre Nachricht ein (Mit STRG+D beenden)\n");
  while(fgets(puffer,BUF,stdin) != NULL)
    strcat(inhalt,puffer);
  write(fd,inhalt,BUF);

  return 0;
}

Nun können Sie wieder in mehreren Fenstern das Programm (ich nenne es einfach mal sender) starten und alles in die FIFO schreiben. Unser Programm polling schickt die gelesenen Dateien anschließend an den Drucker weiter.

Nun wurde die Problemstellung gelöst. Im nächsten Kapitel werden Sie sehen wie man es bewerkstelligen kann, das unser Programm 'polling' jedem aufgerufenen 'sender'-Programm eine Nachricht schickt, dass seine Daten angekommen sind und ausgedruckt wurden.

5.3.3. Der Empfänger antwortet dem Sender           zurück Ein Kapitel tiefer zum Inhaltsverzeichnis

Wir wollen nun mit dem Empfänger dem Sender antworten, dass wir seine Daten erhalten haben ...

Solche FIFO's in einfacher Richtung sind oft ausreichend zur Datenerfassung für Statistiken z.B., wie diese gar in der Abrechnung der Mobilkommunikation eingesetzt werden. Nur das dort um einige mehr Clients etwas in die FIFO schreiben und der Server damit wohl recht stark beschäftigt ist. Wenn man bedenkt was das für den Server bedeutet wenn die Telefoneinheiten im Sekundentakt berechnet werden. Kaum zu glauben das man das noch richtig bewerkstelligen kann ;)

Nun weiter zum Thema. Wenn man Anwendungen benötigt bei denen der Client anfragen an den Server schickt und der Client eine Antwort als Bestätigung benötigt um etwas auszuführen. Wie sollen den die einzelnen Client wissen, welche Antwort nun für sie bestimmt ist? Zuerst müssen die einzelnen Clienten (Sender) sich eine eigene FIFO einrichten, aus der diese eine Antwort vom Server lesen können.

Damit der Server auch weiß wohin mit der Antwort, müssen die Clienten im eine Eindeutige Indenfikation mitschicken. Am einfachsten sind Sie in einem solchem Fall mit der PID des Prozesses beraten. Mit dieser PID soll der Client die Antwort-FIFO erstellen. Hat ein Client Beispielsweise die PID 1001, erstellt dieser eine FIFO namens fifo.1001.

Wollen wir uns zuerst den Client (Sender) ansehen ...

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define BUF 8192

int main()
{
  char puffer[BUF], inhalt[BUF],delete_fifo[BUF];
  int fd,fdx,n;
  sprintf(puffer,"fifo.%d\0",getpid());
  inhalt[0]='\0';
  /*Erste Zeile der Nachricht enthält die PID*/
  sprintf(inhalt,"%d\n",getpid());


  if(mkfifo(puffer, O_RDWR|0666) < 0)
   {
    fprintf(stderr,"Konnte keine FIFO erzeugen!!\n");
    exit(0);
   }
  fd=open("fifo1.1", O_WRONLY);
  fdx=open(puffer, O_RDWR);
  if(fd == -1 || fdx == -1)
   {
     perror("open : ");
     exit(0);
   }
  strcmp(delete_fifo, puffer);

  printf("Bitte geben sie Ihre Nachricht ein (Mit STRG+D beenden)\n");
  while(fgets(puffer,BUF,stdin) != NULL)
    strcat(inhalt,puffer);
  write(fd,inhalt,BUF);
  if(read(fdx,puffer,BUF))
   printf("%s\n",puffer);
  /*Antwort-FIFO wieder löschen*/
  unlink(delete_fifo);

  return 0;
}

Da der Server wohl schon selbst genügend zu tun hat, übernimmt der Client auch wieder das Löschen der FIFO zum Empfang der Antwort vom Server. Nun wollen wir das Programm des Servers schreiben, der alle Nachrichten von den Clienten aufnimmt und an den Drucker über eine Pipe weiterleitet. Anschließend wertet der Server die erste Zeile der Nachricht aus, und sendet eine Bestätigung an den Clienten, der Ihm diese Nachricht geschickt hat. In der ersten Zeile befindet sich ja die PID des Clienten. Hierzu nun der Quellcode des Servers ...

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define BUF 8192

void an_den_drucker(char *text)
{
 FILE *p;
/*Pipe zum Tool lpr erstellen zum Schreiben auf lpr*/
 p=popen("lpr","w");
 if(p == NULL)
  {
    fprintf(stderr,"Konnte keine Pipe zu \'lpr\' erstellen!!\n");
    exit(0);
  }
 /*An den Drucker schreiben*/
 printf("Sende Auftrag an den Drucker......\n");
 fprintf(p, "%s", text);
 fflush(p);
 pclose(p);
}


int main()
{
  char puffer[BUF], inhalt[BUF],antwort[BUF],pid[6];
  int r_fd,w_fd,n,i;
  inhalt[0]='\0';

  if(mkfifo("fifo1.1", O_RDWR) < 0)
   {
    fprintf(stderr,"Konnte keine FIFO erzeugen!!\n");
    exit(0);
   }
  /*Empfänger ließt nur aus der FIFO*/
  r_fd=open("fifo1.1", O_RDONLY);
  if(r_fd == -1)
   {
     perror("open : ");
     exit(0);
   }

  while(1) /*Endlosschleife*/
   {
    if(n=read(r_fd,puffer,BUF)!=0){

      an_den_drucker(puffer);
      /*Pid des Aufrufenden Prozesses ermitteln*/
      n=0,i=0;
      while(puffer[n] != '\n')
          pid[i++]=puffer[n++];
      pid[++i]='\n';
      strcpy(antwort,"fifo.");
      strncat(antwort,pid,i);

      w_fd=open(antwort,O_WRONLY);
      if(w_fd==-1)
       { perror("open :"); exit(0); }
      write(w_fd,"Habe Ihre Anfrage soeben erhalten\n",
      sizeof("Habe Ihre Anfrage soeben erhalten\n"));
      close(w_fd);
      }
   }

 return 0;
}

Weiter mit 6.1. Terminalattribute (tcgetattr, tcsetattr)




















 

 

© 2000 - 2003 Jürgen Wolf