Pipe-streaming

Individuelle Technologien brauchen individuelle APIs. Doch, wenn es möglich ist, unterschiedliche Technologien unter eine API zu stellen, dann entstehen ungeahnte Möglichkeiten.

Und das gilt auch für Pipes.


Während unter Unix alles eine Datei ist, führt Windows zusätzliche APIs ein, wenn man mit Pipes arbeiten möchte.
Um diese Individualitäten auszugleichen, habe ich neben der gate_pipe Abstraktion nun auch den Pipestream eingeführt, doch wie sich zeigt, gibt es da einige Tücken, auf die man achten muss.

Blockaden und Fehler

Pipes verhalten sich sowohl unter Windows und Linux recht seltsam, wenn sie geschlossen werden, fehlerhaft sind oder gerade nichts anzubieten haben.

Ein read oder write an einem Ende erfordert entweder ein write / read auf der anderen Seite, oder die Funktion blockiert … außer man hat Puffer aktiviert, wo dann ein paar Bytes ohne Blockaden geschrieben werden, ohne dass sie sofort gelesen werden müssen.

Schlimmer ist das Schließen von Pipes, die dann entweder per Signal, oder speziellem Fehlercode die Gegenseite abbrechen lassen.

Da Lesefunktionen quasi für immer blockieren können, ist nebem dem klassischen Lesen und Schreiben auch noch eine is_data_available() Funktion notwendig. So kann ein Programm den aktuellen Status prüfen und im Falle eines Leerlaufes etwas anderes tun.

Bei Prozessen, die über “Named Pipes” kommunizieren, ist das besonders wichtig, denn nur so kann man feststellen, ob ein Prozess noch läuft, auf dessen Daten man per Pipe wartet.

Win32 Implementierung

CreatePipe() schafft zwei Enden einer anonymen Pipe. Interessanter sind aber benannte Pipes, die mit CreateNamedPipe() angelegt werden. Sie müssen im Namensraum \\.\Pipe\ liegen und eindeutig sein, z.B.:
\\.\Pipe\my_named_pipe.
Danach kann dieser Pfad beliebig oft mit CreateFile() zum Lesen oder Schreiben geöffnet werden.

Ob Daten zum Lesen verfügbar sind, ermittelt PeekNamedPipe().
Tatsächlich gelesen wird mit ReadFile(), wobei im Fehlerfall ein ERROR_BROKEN_PIPE einem “End-of-stream” entspricht.

Geschrieben werden Daten wie bei Dateien mit WriteFile().

POSIX Implementierung

pipe() erzeugt anonyme Pipes, aber nur mkfifo() lässt uns eine benannte Pipe anlegen. Einmal angelegt kann diese Pipe dann mit open() zum Lesen oder Schreiben geöffnet werden.

Leseaktivitäten lassen sich wie bei Sockets mit select() feststellen.

Zum Lesen und Schreiben sind wie bei Dateien einfach read() und write() zu benutzen. Doch bei write() muss auch auf das Signal SIGPIPE beachtet werden, denn dieses wird gesendet, wenn die Pipe bereits geschlossen ist, während ein write() ausgeführt wird.
Vergisst man darauf, wird der eigene Prozess vom Signal-Händler gekillt.

Auf den Namen kommt es an

Unter Windows gibt es bestimmte Konventionen, wie ein Pipe-Name aussehen soll. Unter Linux muss der Pipe-Pfad ein beliebiger gültiger Dateisystempfad sein, denn genau dort wird die Pipe angelegt.

Um plattformunabhängig zu sein, erstellt das GATE-Framework einen zufälligen, aber eindeutigen Namen bei jedem Erzeugungsaufruf.
Eben diesen Namen kann man dann auch für die Gegenseite benutzen.

Vererbung

Und am Ende braucht man auch noch eine Möglichkeit eine Pipe per “ID” an ein Kind zu vererben. Hierzu wird das Pipe-Handle bzw. der File-Deskriptor dupliziert und sichergestellt, dass er an Kindprozesse vererbt werden kann.
Unter Windows geschieht dies mit DuplicateHandle() mit dem bInheritHandle Parameter und in POSIX-Umgebungen, wo grundsätzlich alles vererbt werden kann, muss das Duplikat vom automatischen Schließen beim fork()/exec() ausgenommen werden.

Fazit

Pipes … diese Dinge erscheinen so einfach und klein, haben mir aber durch ihre unterschiedlichen Eigenschaften auf jeder Plattform jede Menge Steine in den Weg gelegt, als ich eine einheitliche Schnittstelle für die Arbeit mit ihnen aufsetzen wollte.

Zumindest ist jetzt mal ein Prototyp im GATE Framework etabliert. Doch bis dieser in allen Details eingesetzt und getestet werden kann, wird wohl noch etwas Zeit vergehen.

📧 📋 🐘 | 🔗 🔔
 

Meine Dokus über:
 
Weitere externe Links zu:
Alle extern verlinkten Webseiten stehen nicht in Zusammenhang mit opengate.at.
Für deren Inhalt wird keine Haftung übernommen.



Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!