In vielen Anwendungen ist es notwendig Parameter oder andere Nutzdaten von einer Programmiersprache in eine andere zu übergeben. Dabei greift man oftmals auf eine Dateischnittelle oder eine Datenbank zurück: Die zu übergebenden Daten werden in eine Textdatei oder Datenbank geschrieben und aus der anderen Programmiersprache heraus greift man dann darauf zurück. Dieser Weg ist relativ umständlich, zeitintensiv und im Zusammenhang mit Einplatinencomputern recht ressourcen-beanspruchend.
Im Falle des Raspberry Pi und Banana Pi sind Monitoring-Aufgaben beliebte Einsatzgebiete. Dabei ist eine Steuerung und Visualisierung in einem Webinterface immer wünschenswert. Ich persönlich schreibe meine (Steuer-) Programme gerne in C/C++ und nehme über ein Webinterface die Visualisierung bzw. Programmsteuerung vor. Die Kommunikation wird mittels einem Unix Domain Socket vorgenommen. Im nachfolgenden möchte ich ein Beispiel vorstellen. Dabei setze ich ein gewisses Vorwissen und Grundkenntnisse in Sachen HTML, PHP und C voraus. Fragen beantworte ich gerne in den Kommentaren :)
Client
Das Webinterface stellt in unserer Kommunikationsstrecke den Client dar, von dem aus Nachrichten und Befehle an den Server versendet werden. Der Client besteht der Einfachheit halber nur aus einem simplen Formular- Beim Absenden wird das eingegebene Kommando via PHP über ein Unix Domain Socket versenden. Anschließend wird noch eine Antwort vom Server empfangen uns ausgegeben.
client.php
<html> <head> <title>Client</title> </head> <body> <form method="post" action=""> Nachricht / Befehl: <input type="text" name="command" /> <input type="submit" value="Senden" /> </form> <br /> <?php if(isset($_POST["command"])) { $command = $_POST["command"]; $resv = ""; $timeout = 20; $socket = stream_socket_client('unix:///var/run/mysocket.sock', $errorno, $errorstr, $timeout); stream_set_timeout($socket, $timeout); echo("Nachricht senden: " . $command . "<br>\n"); if(!fwrite($socket, $command)) { echo("Fehler beim senden der Nachricht<br>\n"); } echo("Antwort empfangen:<br>\n"); if (!($resv = fread($socket, 1024))) { echo("Es konnte keine Nachricht empfangen werden<br>\n"); } else { echo($resv."<br>\n"); } echo("Fertig"); } ?> </body> </html>
Server
Mit Hilfe einer C-Anwendung erstellen wir einen Server, der die Anfragen von unserem Webinterface entgegen nimmt. Der Server wird ebenfalls an ein Unix Domain Socket gebunden und lauscht an diesem. Der Einfachheit halber nehmen wir hier nur den vom Webformular gesendeten Inhalt entgegen, geben diesen aus und senden ihn zurück.
Um den Codeschnippsel in der Praxis einzusetzen wäre es denkbar, einen Befehl den man eigentlich im Terminal absetzt, zu übergeben, auszuführen und die Antwort zurückzusenden. Gleichzeitig kann man sich eigene Kommandos definieren und die Aktion des Servers darauf festlegen. Auf Basis des Codeausschnittes ist also angefangen von einer webbassierten Konsole sehr viel möglich.
Die einzelnen Bedeutungen des Quelltextes sind im Code mit einem Kommentar grob beschrieben. Gerne beantworte ich Fragen in den Kommentaren.
server.c
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/un.h> #include <sys/types.h> #include <sys/socket.h> #define SOCKET_FILE "/var/run/mysocket.sock" #define BACKLOG 1 #define BUFSIZE 1024 #define command "Nachricht / Befehl: " #define PERM "0666" #define WAIT 0 int main() { int socket_server, socket_accept, n; struct sockaddr_un server_addr, remote_addr; socklen_t remote_addr_size; char resv[BUFSIZE], out_str[BUFSIZE]; mode_t mode = strtol(PERM, 0, 8); // Unix Domain Socket erstellen socket_server = socket(AF_UNIX, SOCK_STREAM, 0); if (socket_server == -1) { perror("[Fehler] Server Socket"); } // Socket an Datei binden memset(&server_addr, 0, sizeof(struct sockaddr_un)); server_addr.sun_family = AF_UNIX; strncpy(server_addr.sun_path, SOCKET_FILE, sizeof(server_addr.sun_path) - 1); unlink(server_addr.sun_path); if (bind(socket_server, (struct sockaddr *) &server_addr, sizeof(struct sockaddr_un)) == -1) { perror("[Fehler] Bind"); } // Server Socket soll auf eingehende Verbindungen warten if (listen(socket_server, BACKLOG) == -1) { perror("[Fehler] Listen"); } // CHMOD if (chmod(SOCKET_FILE, mode) == -1) { perror("[Fehler] CHMOD Socket Datei"); } while(1) { // Eingehende Verbindung akzeptieren und übergeben printf("[Status] Warte auf Verbindung...\n"); remote_addr_size = sizeof(struct sockaddr_un); socket_accept = accept(socket_server, (struct sockaddr *) &remote_addr, &remote_addr_size); if (socket_accept == -1) { perror("[Fehler] Accept Socket"); } printf("[Status] Client verbunden\n"); // Daten empfangen n = recv(socket_accept, resv, BUFSIZE, 0); if (n < 0) { perror("[Fehler] Daten empfangen"); } // Empfangene Daten ausgeben snprintf(out_str, strlen(command)+1, "%s", command); strncat(out_str,resv, n); printf("%s\n",out_str); fflush (stdout); // Warten (optional) sleep(WAIT); // Antwort senden if (send(socket_accept, out_str, strlen(out_str), 0) < 0) { perror("[Fehler] Daten senden"); } // Socket schließen close(socket_accept); } return 0; }
Test
Jetzt testen wir die Kommunikations zwischen PHP und C mittels Unix Domain Socket. Dazu muss euer Einplatinencomputer (zum Beispiel Raspberry Pi und Banana Pi) über einen Webserver mit PHP verfügen. Ich verwende für das Beispiel den Apache Webserver. Die Dateien client.php und server.c habe ich in das Verzeichnis /var/www gelegt.
Über den Webbrowser deiner Wahl ruft man die IP-Adresse des Einplatinencomputers auf und navigiert zur client.php . Außerdem melden wir uns via SSH oder per Remote Desktop am Pi an. Im Terminal wechseln wir in das Verzeichnis des Webservers.
cd /var/www
Dort liegt der Quellcode unseres Servers. Diesen kompilieren wir jetzt und starten ihn.
gcc -o server server.c
./server
Der Server wartet nun auf eine eingehende Verbindung.
Über das Formular auf unserem kleinen Webinterface senden wir nun einen Befehl, zum Beispiel Hallo. Mit dem Klick auf Senden wird die eingegebene Nachricht an den Server gesendet. Dieser gibt das Kommando aus, sendet die Ausgabe ebenfalls an die client.php zurück und wartet auf die nächste Verbindung.
Quellen (Stand: 24.11.14): Wikipedia, php.net – stream-socket-client, php.net – Unix Domain: Unix and UDG, Troy D. Hanson
Hilft mir gerade sehr bei der Implementierung in mein eigenes System. Es ist immer praktisch wenn man sich an einem funktionierenden Beispiel entlang hangeln kann. Vielen Dank dafür!
Guten Tag Tony,
bin sehr an deinem Beispiel interessiert. Leider funktioniert es auf meinem raspberry Pi 3 nicht und ich finde den Fehler aus eigener Kraft nicht. Folgendes Verhalten:
Sobald ich den Server starte, läuft mir das Terminal-Fenster (über) davon, soll heißen ich habe keine stehende Zeile “WARTE AUF VERBINDUNG”, in meinem Fenster wiederholt sich immer wieder :
Nachricht / Befehl:
[Fehler] Daten senden: Bad file descriptor
[Status] Warte auf Verbindung…
[Fehler] Accept Socket: Invalid argument
[Status] Client verbunden
[Fehler] Daten empfangen: Bad file descriptor
Nachricht / Befehl:
[Fehler] Daten senden: Bad file descriptor
[Status] Warte auf Verbindung…
[Fehler] Accept Socket: Invalid argument
[Status] Client verbunden
[Fehler] Daten empfangen: Bad file descriptor
Nachricht / Befehl:
[Fehler] Daten senden: Bad file descriptor
[Status] Warte auf Verbindung…
[Fehler] Accept Socket: Invalid argument
[Status] Client verbunden
[Fehler] Daten empfangen: Bad file descriptor
Nachricht / Befehl:
Starte ich die Client.php sieht alles gut aus, setzte ich dann eine Nachricht ab, wird folgender Text ausgegeben:
Nachricht senden: alles ok!
Fehler beim senden der Nachricht
Antwort empfangen:
Es konnte keine Nachricht empfangen werden
Fertig
Habe beide File direkt aus deine Seite ausgeschnitten und bei mir eingefügt, somit sollte es auch keine Tippfehler geben.
Für deine Hilfe, einen Tipp , etc. wäre ich sehr dankbar.
MfG
Robert
Hallo Robert,
danke für dein Feedback! Ich schau die Tage da mal rein und geb dir eine Rückmeldung. Muss das dann selber erst nochmal so installieren wie im Artikel.
Gruß,
Tony
sudo ./server wird dir helfen.
Hallo Robert,
ich hatte gerade das gleiche Problem, weil ich sudo vergessen hab (sudo ./server)