Entwurf eines binären Protokoll

17/05/2008 - 00:23 von Andreas Volz | Report spam
Hallo,

ich möchte Daten zwischen zwei Prozessen austauschen. Dafür nehme ich
einen FIFO. Ich habe mir ein Protokoll ausgedacht:

1 byte - header start mark (0xEA)
4 byte - packet size (header+size+payload)
4 byte - id
x byte - data

Das ganze habe ich in C++ implementiert und es funktioniert soweit gut.
Jetzt habe ich ein paar Fragen dazu.

Aktuell lese ich auf der Leser-Seite immer ein Byte nach dem anderen.
Auf der Schreiber-Seite schreibe ich immer einen Block mit einem
write() Aufruf. Derzeit ist das kein Problem, weil es nur wenige Bytes
sind. Spàter habe ich vor auch mal ein paar MB als 'data' zu
übertragen. Kann ich da (unter Linux) Probleme bekommen, wenn ich das
in einem Rutsch schreibe? Sollte man das in mehrere write() Aufrufe,
mit kleinem Puffer, unterteilen?

Ein write() Aufruf hat Erfolg und meldet eine bestimmte Anzahl von
Bytes zurück. Kann man dann davon ausgehen, dass die bytes auf jeden
Fall von der Leser-Seite abgeholt werden?

Was passiert, wenn meine Applikation wàhrend eines write() Aufrufs auf
einen FIFO stirbt? Wird die Leser-Applikation erhalten was bis dahin
schon im FIFO drinnen ist oder nichts?

Wàre dafür evtl. die Option 'O_SYNC' von open() zustàndig? In der
Manpage steht aber es wàre für Linux (Stand: Linux 0.99pl7) nicht
implementiert.

Um den Hintergrund der Fragen oben etwas zu erklàren. Es könnte ja
vorkommen, dass das "header start mark" nochmal in einem der anderen
Felder vorkommt. Wenn ein write() in einen FIFO auch mal weniger Bytes
als verlangt schreiben könnte, dann müsste ich alle Vorkommen von 0xEA
im restlichen Paket maskieren. Sonst interpretiere ich u.U. den Start
eines neuen Pakets fàlschlich als Daten eines vorherigen abgebrochenen
Pakets.

Gruß
Andreas


Technical Blog <http://andreasvolz.wordpress.com/
 

Lesen sie die antworten

#1 Volker Grabsch
17/05/2008 - 13:28 | Warnen spam
Andreas Volz schrieb:
ich möchte Daten zwischen zwei Prozessen austauschen. Dafür nehme ich
einen FIFO. Ich habe mir ein Protokoll ausgedacht:

1 byte - header start mark (0xEA)
4 byte - packet size (header+size+payload)
4 byte - id
x byte - data



Grundsatzfrage, der Unix-Tradition folgend:

Muss es unbedingt ein Binàrprotokoll sein? Textprotokolle, am
besten menschenlesbar, bieten große Vorteile. Stichworte wàren
hier Transparenz, Nachvollziehbarkeit, einfacheres Debugging.

Kommt natürlich auf deine Anwendung an.

Im Allgemeinen ist es übrigens sogar noch besser, wenn du
bereits existierende Protokolle verwenden kannst, etwa XML-RPC,
JSON-RPC oder einen Aufruf via SSH, und den "data"-Bereich
da hindurch tunneln.

Kommt aber wiegesagt sehr auf deine Anwendung an. Wenn du
mehr darüber erzàhlst, werden auch die Empfehlungen besser.

Aktuell lese ich auf der Leser-Seite immer ein Byte nach dem anderen.
Auf der Schreiber-Seite schreibe ich immer einen Block mit einem
write() Aufruf.



Sollte kein Problem sein. Eventuell wird der write()-Aufruf blockiert,
bis es genügend read()-Aufrufe auf der Gegenseite gibt. Aber das
Problem hast du /immer/, egal ob du in großen oder kleinen Blöcken
arbeitest.

Ist diese Blockierung ein Problem, solltest du ein nicht-blockierendes
Interface benutzen. Schlag mal nach unter:

man select

Derzeit ist das kein Problem, weil es nur wenige Bytes
sind. Spàter habe ich vor auch mal ein paar MB als 'data' zu
übertragen. Kann ich da (unter Linux) Probleme bekommen, wenn ich das
in einem Rutsch schreibe? Sollte man das in mehrere write() Aufrufe,
mit kleinem Puffer, unterteilen?



Es wird eh nicht "in einem Rutsch" geschrieben. Hast du die Daten
also eh im RAM, dann schieb sie auch mit einem write() durch die
Leitung. Alles andere macht deinen Code nur fehleranfàlliger.

Kommen die Daten nicht aus dem RAM, sondern z.B. aus einer Datei,
dann kopiere sie nicht in den RAM, sondern schiebe sie einfach
"direkt" dem write()-Befehl unter. Wirf hierzu mal einen Blick in:

man mmap

Ein write() Aufruf hat Erfolg und meldet eine bestimmte Anzahl von
Bytes zurück. Kann man dann davon ausgehen, dass die bytes auf jeden
Fall von der Leser-Seite abgeholt werden?



Darüber solltest du keine Annahmen treffen. Der andere Prozess
könnte auch nach Erhalt der Daten, aber kurz bevor er sie verarbeitet
hat, abschmieren.

Wenn du wirklich diese Info brauchst, sieh in deinem Protokoll eine
Antwort vor, etwa "OK" bzw. "Error". Wenn der zweite Prozess die Daten
erhalten /und/ erfolgreich verarbeitet hat, sendet er ein "OK". Erst
nach Erhalt dieser Nachricht darf der erste Prozess beruhigt sein.

Was passiert, wenn meine Applikation wàhrend eines write() Aufrufs auf
einen FIFO stirbt? Wird die Leser-Applikation erhalten was bis dahin
schon im FIFO drinnen ist oder nichts?



Auch hierüber solltest du keine Annahmen treffen, siehe oben.

Dein Protokoll ist offenbar sehr fragil, besagte "OK"-Rückmeldung
dürfte es deutlich stabiler machen.

Wàre dafür evtl. die Option 'O_SYNC' von open() zustàndig? In der
Manpage steht aber es wàre für Linux (Stand: Linux 0.99pl7) nicht
implementiert.



Eben. Daher nicht darauf verlassen.

Um den Hintergrund der Fragen oben etwas zu erklàren. Es könnte ja
vorkommen, dass das "header start mark" nochmal in einem der anderen
Felder vorkommt. Wenn ein write() in einen FIFO auch mal weniger Bytes
als verlangt schreiben könnte, dann müsste ich alle Vorkommen von 0xEA
im restlichen Paket maskieren. Sonst interpretiere ich u.U. den Start
eines neuen Pakets fàlschlich als Daten eines vorherigen abgebrochenen
Pakets.



All diese Probleme sind làngst technisch gelöst worden, von Leuten,
die sehr viel mehr Zeit und Energie in Protokolle steckt haben, als
du und ich jemals könnten.

Bist du sicher, dass du das Rad unbedingt neu erfinden musst, und
die selben Probleme und Fallstricke nochmals durchleben möchtest?
Bau doch lieber auf existierende Protokolle auf!


Gruß,

Volker

"Wenn du der Meinung bist, der andere sei ein Depp, dann überlass das
Antworten denjenigen, die nicht dieser Meinung sind."

Ähnliche fragen