Raspberry Pi

Kommunikation zwischen PHP und C via Unix Domain Sockets

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.

Screenshot SSH

Screenshot SSH

Ü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.

Screenshot SSH

Screenshot SSH

Quellen (Stand: 24.11.14): Wikipedia, php.net – stream-socket-client, php.net – Unix Domain: Unix and UDG, Troy D. Hanson

Hinterlasse eine Antwort

Kommentare

  • 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