[ICON] Interprozess-Kommunikation (IPC)

Heute ist Stammtischzeit:
Jeden Donnerstag 20:30 Uhr hier im Chat.
Wer Lust hat, kann sich gerne beteiligen. ;)
  • Hallo zusammen,

    viele User haben sich gefragt, wie man Daten oder Anweisungen von einem Programm zu einem anderen übertragen kann.

    Die klassische Lösung besteht wohl darin, dass das eine Programm eine Datei schreibend öffnet und dort ihr Anliegen ablegt. Das andere Programm schaut dann mal nach, liest aus, wertet aus, führt aus.

    Problematisch wird's, wenn gleichzeitig geschrieben und gelesen werden soll. Solche Sachen programmtechnisch zu regeln, ist zwar mal ganz interessant - aber lästig wie Fliegen im Sommer.

    Die Lösung, die ich heute zeigen will, nutzt sog. named pipes was gleichbedeutend mit FIFO-Dateien ist. FIFO steht für First in - first out. Also was zuerst hereinkommt = geschrieben wird, kommt auch zuerst heraus = ausgelesen. Das HERAUS ist wörtlich zu nehmen. Die Datei ist danach leer!

    Hier die beiden Dateien:
    IPC_Master.icn


    und IPC_Servant.icn

    Erläuterungen und Deutungen zum Quellcode

    Die FIFO-Datei heiße fifo_test. Diese wird zunächst gelöscht. Die Erfahrung hat gezeigt, dass Fehlermeldungen kommen, wenn diese Datei als named pipe geöffnet werden soll und bereits existiert.
    Durch Aufrufen des Linux-Kommandos mkfifo wird die danach genannte Datei als FIFO-Datei nutzbar gemacht. Dass diese Dateiart etwas besonderes darstellt, wird schon dadurch deutlich, wenn man sich das Ergebnis dieses Befehls über

    Code
    ls -al fifo_test


    anschaut:

    Code
    prw-rw-r-- 1 andreas andreas 0 Aug 14 23:39 fifo_test


    Das p in der ersten Spalte macht den Unterschied zu herkömmlichen Dateien (-) oder Verzeichnissen (d).

    Danach wird das Gegenstück zu diesem Programm aufgerufen: IPC_Servant. Wichtig ist hier die Verwendung des Symbols & - darüber wird der angegebene Befehl im Hintergrund ausgeführt. Wichtig hierbei ist aber, dass sich IPC_Master und IPC_Servant die gleiche Standard-Ausgabe "teilen".

    Danach geht's in die Endlosschleife.
    Eines von 6 Linux-Kommandos wird zufällig (Zufallszahlen-Operator ?) ausgewählt und in Abhängigkeit dieser Zufallszahl die Variable befehl mit einer Zeichenkette belegt.
    Dieser Befehl wird zur Kontrolle ausgegeben. Wichtiger ist aber das Speichern in der named pipe fifo_test. Auch hier kommt das &-Symbol zum Einsatz. Würde man dieses Symbol weglassen, würde die FIFO-Sache nicht funktionieren. (Ausprobieren macht AHA!). Das hängt damit zusammen, dass Schreiben erst mit dem Auslesen beendet werden kann. Das Ergebnis ist nämlich eine leere Datei fifo_test. Das klingt im Vergleich mit herkömmlichen Datei-Operationen (Schreiben, Lesen) widersinnig. Aber FIFO bedeutet hier im wahrsten Sinne des Wortes: FIRST IN => FIRST OUT. Somit läuft das Schreiben im Hintergrund, das bereits im Hintergrund laufende IPC_Servant kann erkennen, dass da was hineingeschrieben wurde. Sobald die Daten ausgelesen wurden, ist dann auch der Schreibvorgang abgeschlossen.

    Hier habe ich noch den Operator \ eingesetzt. Dies bedeutet soviel wie: Wenn der danach folgende Ausdruck zu einem Ergebnis führt, das nicht &fail und nicht &null ist, dann führe den Ausdruck davor mit dem auf diese Weise versehenen Ausdruck aus. Wenn der Ausdruck keinen Wert liefert (&null) oder bei der Auswertung dieses Ausdrucks ein Fehler zurück gemeldet wird (&fail), dann wird diese Funktion davor nicht ausgeführt - denn ein Fehler ist da im wahrsten Sinne des Wortes vorprogrammiert ;) . Und wiederum wäre es nervig, Code zu programmieren, um solche Sachen abzufangen. \ und / sind quasi Icon's Exception-Programmierung! Ein einziges Zeichen statt mehrere Zeilen...

    Dann wird noch für 2,5 Sekunden getrödelt (die CPU dankt's). Den Wert habe ich so hoch gesetzt, damit man die Programmausgabe des Programms IPC_Servant auch noch erkennen kann.


    IPC_Servant macht dann Folgendes:
    Eine kurze Mitteilung, dass das Programm gestartet wurde, wird vor der Endlos-Schleife abgesetzt.

    Die Datei fifo_test wird zum Lesen geöffnet und die Zeile mit dem Kommando, das IPC_Master hinein geschrieben hat, ausgelesen. Denkt immer dran: Die Datei ist genau jetzt LEER - aber noch geöffnet! Es kann zwar nichts mehr herausgelesen werden, aber jederzeit wieder was hineingeschrieben werden.

    Die ausgelesene Zeile mit dem Kommando wird auf dem Bildschirm angezeigt, über system() ausgeführt und die Datei wieder geschlossen.

    Anmerkung: Will man eine Pipe (Eingabe-Ausgabe-Umleitung) ausführen lassen und / oder das Ergebnis einer Pipe oder eines simplen Linux-Kommandos erhalten, geht das nicht mehr mit dem Befehl system(). Hier muss man eine Pipe öffnen:

    Code
    p := []
    if pipe := open("Kommando1 | Kommando2 | ... | KomnmandoN", "p") then
    {   while put(p, read(pipe))
       close(pipe)
    }


    Exkurs:
    Die Zeile

    Code
    while put(p, read(pipe))

    möchte ich noch beleuchten. Der Form nach handelt es sich hierbei um eine Endlosschleife ohne formale Abfrage eines Abbruchs - sollte man also nicht machen. Dies ist in der Funktion read() versteckt - die Funktionalität dahinter ist Icon's goal directed-Konzept geschuldet.

    Die Funktion read(pipe) liest eine Zeile aus einer Datei, die dem Filehandle pipe zugewiesen ist. Das Ergebnis ist eine Zeichenkette, wenn es geklappt hat - oder &null, wenn's da nichts zu Lesen gab, d.h. der Lesezeiger auf dem Ende der Datei liegt.
    Das funktioniert vielleicht ein zweites und ein drittes Mal. Jede Zeile wird in der Liste p abgelegt. In anderen Programmiersprachen kennt man das als Array. Die Listenbehandlung in Icon ist allerdings sehr flexibel. Wenn ich als Programmierer keine Vorstellung habe, wie viele Elemente denn eine solche Liste aufnehmen können soll, dann definiere ich eine solche Liste einfach durch

    Code
    p := []

    oder

    Code
    p := list()


    und denke mir dabei "Räscheknäscht, kümmere Du Dich um die Verwaltung. Wenn über put() ein neues Element eingefangen wird, dann erhöhe die Anzahl der Elemente in der Liste p. Wie Du das machst, ist mir sowas von egal. Ebenso, wenn die Liste vorne oder hinten angeknabbert wird. Ich will nur über Indizes einen Zugriff auf gültige Elemente haben."

    In den seltenen Fällen, in denen ich den Listenumfang kenne, dann schreibe ich so

    Code
    p := list(17)

    wenn es sich um 17 Elemente handeln soll.

    Und wenn die 17 Elemente den gleichen Startwert erhalten sollen, dann so:

    Code
    p := list(17, 0)

    oder

    Code
    p := list(17, "Neu")


    Anmerkung: Mehrdimensionale Listen gibt's in Icon natürlich auch. Die nennen sich wie in anderen Programmiersprachen auch Matrizen. Mehr dazu ist in den Icon-Tutorials verbuddelt.

    Irgendwann ist das Dateiende erreicht. read(pipe) scheitert und liefert &null bzw. &fail zurück. Da dieser Fehler hier nicht abgefangen wird, schlägt er sich weiter durch.

    Code
    while put(p, read(pipe))


    ==>

    Code
    while put(p, &fail)


    ==>
    put() scheitert dann auch und liefert nicht mehr das neue Element sondern ... na ja ... auch wieder &fail
    ==>

    Code
    while &fail


    bricht dann die Endlosschleife sofort und direkt ab.

    Auch wieder so ein klassischer Einzeiler, der in anderen Programmiersprachen eine Schleife mit "hin und her", "falls und falls doch nicht erfordern würde.
    Exkurs-Ende


    In der Liste p befinden sich dann die ausgelesenen Ergebnisse.

    • Die Liste p kann leer sein =Kein Ergebnis
    • Die Liste p kann ein Element enthalten = Ein Ergebnis
    • Die Liste p kann beliebig viele Elemente enthalten = Mehrere Ergebnisse, die entweder über Erweiterung der Pipe oder innerhalb des eigenen Programmes ausgewertet werden sollen.


      Falls jemand die Anzahl der Elemente in der Liste = Größe der Liste wissen möchte, das geht über den Größen-Operator *

      Code
      *p

    Danach wird dann wieder die Zeit tot geschlagen.

    Das eine Programm schreibt, das andere liest aus und führt aus. Kurz, knapp, bündig, stabil, ...


    Beste Grüße

    Andreas

    Ich bin wirklich nicht darauf aus, Microsoft zu zerstören. Das wird nur ein völlig unbeabsichtigter Nebeneffekt sein.
    Linus Torvalds - "Vater" von Linux

    Linux is like a wigwam, no windows, no gates, but with an apache inside dancing samba, very hungry eating a yacc, a gnu and a bison.

    3 Mal editiert, zuletzt von Andreas (23. Dezember 2017 um 13:50)

  • Hallo zusammen,

    im zweiten Teil geht es darum, wie man Variablen inkl. Werte von einem Programm in ein anderes Programm übertragen kann.

    Dazu sind minimale Veränderungen an den Quell-Codes aus Beitrag #1 erforderlich:

    IPC_Master.icn:

    Änderungen:
    Eine Zeile

    Code
    a := 7


    wird eingefügt. Hiermit wird eine Variable a auf den Wert 7 gesetzt. Das ist so unspektakulär wie trivial. Aber es wird noch besser ...

    Aus den 6 Fällen werden 7 Fälle.

    Im Fall 7

    Code
    name(a) || " := " || a


    wird eine Zeichenkette erzeugt. name(a) erzeugt den Namen der Variablen a, also die Zeichenkette "a". Dieser wird ergänzt um den Zuweisungsoperator := und den Wert der Variablen a. Das Ergebnis wäre hier "a := 7". Ich hätte statt dieses verketteten Ausdrucks auch direkt "a := 7" schreiben können. Aber dann wäre das mit der Variablenübergabe vielleicht nicht so klar geworden.


    IPC_Servant.icn

    Neu ist jetzt folgender Block:

    Code
    if find(":=", z) then
    {   var := z[1:find(":=",z) - 1]
        val := z[2 + find(":=",z):0]
        while val[1] == " " do val := val[2:0]
    
    
        variable(z[1:find(":=",z) - 1]) := val
        write("Ergebnis-Wert: ", variable(name(var)), " := ", a)
    }
    else system(z)

    Hier wird geprüft, ob der Zuweisungsoperator := in der Variablen z enthalten ist. OK - im Fall 7 des Programms IPC_Master.icn ist das der Fall. In dem Moment soll eine Variablenübergabe durchgeführt werden.
    Dies bedeutet:
    - die Variable, die in IPC_Master.icn definiert wurde, soll auch hier gültig sein
    - der Wert, der in IPC_Master.icn definiert wurde, soll auch hier der gleichen Variablen zugewiesen sein

    Genau dies erledigen die beiden Zeilen, die mit var := bzw val := beginnen. Die erste Zeile ermittelt den Variablennamen, die zweite Zeile liest den zugeordneten Wert aus.

    Die kleine while-Schleife entfernt ggf. führende Leerzeichen des zugewiesenen Wertes.

    Das Magische an dem Block erfolgt in den Zeilen

    Code
    variable(z[1:find(":=",z) - 1]) := val
    write("Ergebnis-Wert: ", variable(name(var)), " := ", a, " oder ", name(variable(var)), " := ", a)

    Die erste Zeile erzeugt eine Variable über die Funktion variable("variablenname"), worüber variablenname ein ansprechbarer Bezeichner / Identifier / Variablenname wird. In dem Fall ist das Ergebnis von z[1:find(":=",z) -1] der Name der von IPC_Master.icn übergebenen Variablen, hier also einfach nur a.
    Dieser Variablen a wird der in IPC_Master.icn zugewiesene Wert 7 zugewiesen, letztlich wird in dieser Zeile a := 7 umgesetzt, nur dass Variablenname und Wert aus der Datenübergabe von IPS_Master.icn stammen. Diese beiden Zeilen stellen also einen allgemein verwendbaren Ausdruck für Variablenübernahme dar.

    Die nächste Zeile gibt die Daten aus. variable(name(var)) liefert hierbei den Bezeichner der Variablen a.

    Und dass das Ganze auch wirklich so funktioniert, macht die Verwendung der Variablen a deutlich. a wurde ja nur über die Zeile zuvor sehr versteckt angesprochen. Da hier der Wert 7 ausgegeben wird, verdeutlicht, dass die Zeile oben drüber erfolgreich verlief. Anderenfalls wäre die Zeile nicht ausgegeben worden, falls a kein ausgebbarer Wert zugewiesen wurde.

    Anmerkung: In einer allgemeinen Umsetzung muss a natürlich wieder durch val ersetzt werden.

    Da komme ich gerade auf eine ganz andere Idee... Ein Programm mit graphischer Oberfläche auf dem PC, sowas wie das GPIO-Control Center (s. Icon-Tutorial Teil 24 bzw. 25), mit dem man dann allerdings auf dem PC die GPIOs auf dem Raspberry Pi steuern kann. Dazu bedarf es eigentlich nur einer Socket-Verbindung zwischen PC und Raspberry Pi. Die Datenübertragung erfolgt analog dem Beispiel hier. Und als Datenübergabe etwas wie

    Code
    GPIO(17,1)


    zum Einschalten eines GIO oder

    Code
    status := GPIO(17)


    zum Abfragen eines GPIO oder

    Code
    GPIO([8,9,10], 0)


    zum gleichzeitigen Ausschalten einer Liste definierter GPIO-Pins.

    :s Das wäre doch eine abwechslungsreiche Programmieraufgabe für die kommende Woche im Hotel...


    Bis bald...

    Beste Grüße

    Andreas

    Ich bin wirklich nicht darauf aus, Microsoft zu zerstören. Das wird nur ein völlig unbeabsichtigter Nebeneffekt sein.
    Linus Torvalds - "Vater" von Linux

    Linux is like a wigwam, no windows, no gates, but with an apache inside dancing samba, very hungry eating a yacc, a gnu and a bison.

    2 Mal editiert, zuletzt von Andreas (23. Dezember 2017 um 13:59)

Jetzt mitmachen!

Du hast noch kein Benutzerkonto auf unserer Seite? Registriere dich kostenlos und nimm an unserer Community teil!