Dies ist eine statische Kopie unseres alten Forums. Es sind keine Interaktionen möglich.
This is a static copy of our old forum. Interactions are not possible.

Nagezahn

Junior Schreiberling

  • "Nagezahn" is male
  • "Nagezahn" started this thread

Posts: 198

Date of registration: Feb 9th 2010

Location: Nordstadt

1

Tuesday, August 7th 2012, 10:18pm

Probleme mit Pipes (Linux, C++)

Hallo zusammen,

ich mühe mich gerade mit Pipes ab.

Folgendes Szenario: Ich habe zwei Prozesse (P1 und P2), wobei einer vom anderen mittels fork() erzeugt wird. Vorher erstelle ich zwei Pipes, trigger und response. In einem Testprogramm schreibt P1 in trigger eine Nachricht, die P2 aus trigger liest. Daraufhin schreibt P2 eine Nachricht in response, die P1 wiederum lesen soll. Die nicht benötigten Enden jeder Pipe werden nach dem Fork entsprechend geschlossen.

So weit die Theorie. Wenn ich den Teil mit response weglasse, dann funktioniert das auch. Sobald ich Hin- und Rückrichtung drin habe, blockiert das Programm - allem Anschein nach ist dafür das read() auf response verantwortlich. Bereits herausgefunden habe ich, dass es ebenfalls funktioniert, wenn trigger mit close() geschlossen wird, bevor read() auf response aufgerufen wird. Der Nachrichtenaustausch soll aber über längere (unbestimmte) Zeit stattfinden, so dass ich die Pipes nach dem erstmaligen Beschreiben nicht einfach schließen kann.

Leider verstehe ich auch überhaupt nicht, woher dieses Verhalten kommt. Alle anderen Bestandteile des Programmes habe ich, so weit möglich, bereits auskommentiert. Noch nicht probiert habe ich non-blocking Pipes, das wäre evtl. der nächste Versuch, aber blocking wäre mir lieber.

Hat jemand eine Idee, wo die Ursache für das beobachtete Verhalten liegen kann? Stecke so tief in der Materie/in Linux nicht drin, wäre also für Hinweise dankbar. :)

Grüße

  • "Schokoholic" is male

Posts: 2,518

Date of registration: Oct 4th 2006

Location: Hannover

Occupation: Haarspaltung

2

Wednesday, August 8th 2012, 12:25am

[...] schreibt P1 in trigger eine Nachricht, die P2 aus trigger liest. [...]

Hier könnte ein Problem liegen. Was ist für dich "eine Nachricht"? Wenn ich mich recht erinnere, blockt read() so lange, bis entweder der Buffer vollständig gefüllt ist oder der File Descriptor keine Daten mehr liefert. Meine Vermutung wäre deshalb, dass nicht read(response) das Problem ist, sondern read(trigger). Wenn du das schreibende Ende von trigger mit close() schließt, returnt auch read(trigger) an der Gegenstelle und schreibt dann die Antwort in response. Wenn die Antwort größer ist, als der Buffer auf der read(response)-Seite, returnt dieses auch umgehend und scheint zu funktionieren.

Fazit: Wenn du "Nachrichten" austauschen willst, musst du entweder selbst Nachrichtengrenzen (Message Boundaries) implementieren oder einen Kommunikationsweg nutzen, der diese beibehält. Wenn es ausschließlich um Kommunikation auf einem Rechner geht, kannst du mit der Funktion socketpair() ein Paar von Unix Domain Sockets (quasi lokale Sockets) an Stelle der Pipes benutzen.Wenn du als Typ SOCK_DGRAM oder SOCK_SEQPACKET verwendest, werden Nachrichtengrenzen beim Senden und Empfangen beibehalten. Ich würde SOCK_SEQPACKET empfehlen, weil du bei SOCK_DGRAM keine Nachrichten empfangen kannst, die größer sind als dein Empfangsbuffer.

Eine bessere Erklärung des Unterschieds zwischen SOCK_DGRAM und SOCK_SEQPACKET findest du hier: http://stackoverflow.com/questions/10104…t-vs-sock-dgram

Nagezahn

Junior Schreiberling

  • "Nagezahn" is male
  • "Nagezahn" started this thread

Posts: 198

Date of registration: Feb 9th 2010

Location: Nordstadt

3

Wednesday, August 8th 2012, 2:07am

Quoted

bis entweder der Buffer vollständig gefüllt ist oder der File Descriptor keine Daten mehr liefert

Das war ein entscheidender Hinweis, danke! :) Problem war, dass ich in einer Schleife read aufgerufen habe, die verlassen werden sollte, wenn's nix mehr zu lesen gibt. Da read aber anscheinend so lange blockiert, bis entweder Daten ankommen oder alle schreibenden Enden geschlossen wurden, war es kein Wunder.

Ich rufe jetzt read und write für jede Richtung genau einmal auf, damit funktioniert's. So lange die Nachricht (bisher einfach nur ein String) kleiner ist als die Buffergröße der Pipe (oder etwas in der Richtung, kann mich grad nicht erinnern), dann ist die Übertragung über die Pipe atomic - es kommt also genau das auf einmal an, was man reingeschoben hat, oder gar nix (wenn's eine Unterbrechung gab).

  • "Schokoholic" is male

Posts: 2,518

Date of registration: Oct 4th 2006

Location: Hannover

Occupation: Haarspaltung

4

Wednesday, August 8th 2012, 4:44pm

Atomische Operationen und Message Boundaries mit Pipes

So lange die Nachricht (bisher einfach nur ein String) kleiner ist als die Buffergröße der Pipe (oder etwas in der Richtung, kann mich grad nicht erinnern), dann ist die Übertragung über die Pipe atomic - es kommt also genau das auf einmal an, was man reingeschoben hat, oder gar nix (wenn's eine Unterbrechung gab).

Das ist eine gefährlich falsche Annahme. Das "atomisch" bezieht sich nur darauf, dass die write()s von mehreren Prozessen auf die selbe Pipe nicht interleaved werden können, wenn eine bestimmte Nachrichtengröße nicht überschritten wird [1]. Laut POSIX mindestens 512 Byte, bei Linux sogar 4096.

Die Eigenschaft, die du beschreibst -- also dass jedes read() genau das liefert, was von genau einem write() geschrieben wurde -- wird nämlich gerade nicht garantiert. Wenn ein Programm write(pipe, "foo", 3); write(pipe, "bar", 3); ausführt, dann wird ein darauffolgendes read(pipe) die 6 Zeichen "foobar" zurückgeben. Oder, um man 7 pipe [2] zu zitieren: "The communication channel provided by a pipe is a byte stream: there is no concept of message boundaries.". Ein Artikel [3] beschreibt das Problem (das scheinbare Funktionieren des Codes) sehr gut, allerdings in Bezug auf TCP.

In Spezialfällen -- also wenn nie zwei write()s in Folge ausgeführt werden, bzw. wenn immer write() und read() im Wechsel aufgerufen werden -- wird deine Lösung funktionieren. Da das aber eine implizite Annahme ist, machst du deinen Code damit weder zukunftssicher noch gut wartbar. Wenn du zum Beispiel mal von Pipes auf TCP/IP umsteigen willst, jemand anderes deinen Code verändert, oder sonst was passiert, kann diese Annahme zu Problemen führen. Ich weiß nicht, ob das in deinem Projekt relevant ist, aber für die Nachwelt möchte ich das hier trotzdem richtig gestellt wissen. ;)

Wie oben bereits geschrieben, gibt es diese Problem nicht, wenn man ein socketpair() mit Typ SOCK_SEQPACKET oder zur Not (nur lokal) SOCK_DGRAM verwendet.

[1] http://tldp.org/LDP/lpg/node13.html
[2] http://www.kernel.org/doc/man-pages/onli…an7/pipe.7.html
[3] http://www.codeproject.com/Articles/3749…Message-Framing


PS: Folgendes Beispiel demonstriert das Problem:
[codefile=pipe.c]#include <unistd.h>
#include <stdio.h>

int main(void) {
int pipefd[2];
pipe(pipefd);

int pid = fork();
if (pid == 0) { // child
write(pipefd[1], "foo", 3);
write(pipefd[1], "bar", 3);
}
else if (pid > 0) { // parent
sleep(1);
char buffer[100];
int count = read(pipefd[0], buffer, 99); // Read from pipe
buffer[count] = '\0'; // Terminate string
printf("Parent received: %s\n", buffer); // Probably print "foobar"
wait(pid); // Clean up child process
}
else { // error
return 1;
}
return 0;
}[/codefile]

Nagezahn

Junior Schreiberling

  • "Nagezahn" is male
  • "Nagezahn" started this thread

Posts: 198

Date of registration: Feb 9th 2010

Location: Nordstadt

5

Wednesday, August 8th 2012, 9:38pm

Oha, da habe ich offensichtlich nicht genau genug gelesen, was atomic bedeutet. Danke für die Klarstellung und die Links! :)

Dann werde ich mir mal Gedanken machen, wie ich das vernünftig angehe bzw. kapsele.