Daten über Pipe am Childprozess in Empfang nehmen

P

pingu12

Jungspund
Hallo,

Habe mir gerade eine kurze Einführung nach C++ durchgelesen und gemerkt, dass ich aus Zeitgründen auf das eigenständige Lösen dieses Problems erstmal verzichten muss.

Ich möchte aus dem lighttpd (webserver) die accesslog-daten nicht in eine Datei schreiben, sondern in einem eigenen kleinen c++Prozess abfangen und verarbeiten. Letzteres sollte ich kurzfristig hinkriegen, das Abfangen allerdings macht mir noch Probleme.

Hier ein Auszug aus der mod_accesslog des lighttpd der meines Erachtens für den Start des Childs (also meinem Prozess) verantwortlich ist:

(Im Angang befindet sich der komplette Code)

Code:
#ifdef HAVE_FORK
			/* create write pipe and spawn process */

			int to_log_fds[2];
			pid_t pid;

			if (pipe(to_log_fds)) {
				log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed: ", strerror(errno));
				return HANDLER_ERROR;
			}

			/* fork, execve */
			switch (pid = fork()) {
			case 0:
				/* child */

				close(STDIN_FILENO);
				dup2(to_log_fds[0], STDIN_FILENO);
				close(to_log_fds[0]);
				/* not needed */
				close(to_log_fds[1]);


Ich wäre sehr dankbar wenn mir jemand kurz z.B. mit einem Beispielcode erklärt wie ich nun in meinem Child-Prozess an die gewünschten Daten komme.

Gruß
Pingu
 

Anhänge

  • mod_accesslog.txt
    21,8 KB · Aufrufe: 5
Mach doch eine FIFO-Datei, lass den Webserver reinschreiben und dein Tool davon lesen? Könnte so funktionieren.

Gruess
Joel
 
Tut es auch. Gerade probiert. ;)

mkfifo -m 666 access.log.pipe

und accesslog.filename = "/<path>/access.log.pipe". Der Rest mit open() und read() aus <unistd.h> bzw. <fcntl.h>
 
Tja, danke tr0nix und der_Kay, dass ihr euch meinem Problem angenommen habt ..

.. ich bin mir jedoch noch nicht sicher ob ich jetzt weiter bin oder nicht da ich mit der fifo-Datei wenn man sie denn überhaupt als solche bezeichnen darf, wenig vertraut bin.

Ist es denn sinnvoll das Problem über die fifo zu lösen statt der "normalen" pipe wie sie z.B. logprozesse wie cronolog benutzen ?
 
Eine FIFO ist eine "normale" pipe, eine sog. "named pipe". Das, was Du vorhast, ist genau das, wofür named pipes gemacht sind.

Darum: Nur zu! :)
 
Habe es mal versucht, erhalte jedoch am Ende der ausgelesenen Zeile ein \n gefolgt mit 3-4 Random-Zeichen. Woran mag das liegen?`

Code:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstdlib>
#include <memory>
#include <typeinfo>

int main()
{
  int fd_fifo;
  char buf[200];

  if((fd_fifo=open("/home/logauswertung/c/fifo99", O_RDWR)) == - 1)
    {
      fprintf(stderr, "Can't open file.\n");
      exit (0);
    }

  int index;

  if(read(fd_fifo, &buf, sizeof(buf)) == -1)
    fprintf(stderr, "Can't read from FIFO.\n");
  else
    printf("%s");

  return 0;
}
 
Zuletzt bearbeitet:
Da sich mir bei der Lösung mit der Fifo keine Vorteile in Performance, Sicherheit oder Schwierigkeit der Realisierung aufzeigen werde ich den Ansatz jetzt nicht weiter verfolgen. Eine Pipe zum Childprozess ist denke ich nicht schwieriger zu realisieren, hat aber den Vorteil, dass der Prozess vom lighttpd gestartet und im Problemfall auch neugestartet werden kann. Scheint mir nicht schwieriger zu sein und da ich ohnehin bei 0 anfange..

Sollte ich mich in einem der Punkte irren bitte ich darum mich zu korrigieren, da ich für die paar Zeilen die 90% hier in 2 Minuten lauffähig machen den ganzen Tag bastel.
 
Zuletzt bearbeitet:
Ich glaube es liegt am printf. "\n" ist ja eigentlich newline aber wird nicht immer als Terminalsignal so Interpretiert. Wenn du es mit "normaler" Pipe irgendwie hinkriegst kannst du das natürlich auch gerne probieren, ich denke jedoch einen Prozess immer wieder zu spawnen wegen jeder einzelnen Zeile die reinkommt ist suboptimal.
 
Das flag "O_RDWR" ist für eine "FIFO" "verboten", siehe
http://www.opengroup.org/onlinepubs/000095399/functions/open.html

Wenn Du unbedingt lighthttpd patchen willst, will ich Dich nicht abhalten. Nochmal: Eine "FIFO" ist eine pipe. Ein Prozess schreibt hinein, ein anderer liest daraus (und öffnet sie dementsprechend O_RDONLY).

"Sicherheit" erreichst Du über die Datei-Rechtemaske.

Ich verstehe nicht, wo für Dich das Problem liegt.
 
Zuletzt bearbeitet:
@tronix: Bei der Ausgabe ist doch gar kein \n enthalten. Das kommt nur in den Fehlermeldungen vor. Und der Prozess wird auch nur einmal von lighttpd gestartet.

@der_Kay: Es ist nicht nötig den lighttpd zu patchen, mod_accesslog gibt die Möglichkeit einen Prozess zu spawnen statt die Logs in eine Datei zu schreiben.

RD_ONLY konnte das Problem mit den Randomzeichen nicht lösen. Es ist nach wie vor so, dass ich meinen Prozess starte und sobald ich dann einen Logeintrag durch Seitenaufruf erzeuge kommt eine Logzeile und in der neuen Zeile dann jene Randomzeichen.
 
Zuletzt bearbeitet:
Du musst natürlich die mittels fread() gelesene Zeichenkette selbst terminieren! Die Nullbytes werden nicht von selbst generiert, denn fread() ist eine byte-orientierte Operation. Deshalb hast Du die Speichersuppe ab buf[nbytes_read] bis zum ersten folgenden '\0'im Output. ;)
Code:
int nbytes_read;
// ...
if(-1 != (nbytes_read=read(fd_fifo, &buf, sizeof(buf)-1))) {
    buf[nbytes_read]='\0';
   // ...
} else { ...
Du kannst auch ein memset(buf, 0, sizeof(buf)); vor fread() machen. Du wirst wahrscheinlich nicht drumherum kommen, einen Ringpuffer zu implementieren, der die gelesenen Teile nach einen Newline abschneidet und als verarbeitet zurückliefert.

Um Deine ursprüngliche Frage von oben zu beantworten: Was es mit dem von Dir entdeckten (undokumentierten?) Feature bei fork() im Quellcode auf sich hat ist, dass wenn accesslog.filename mit '|' beginnt, mod_accesslog versucht, die folgenden Zeichen als als Shellkommando zu interpretieren, das "Kommando" zu starten versucht und die Logdaten in Filedescriptor 0 (STDIN) des Kindprozesses schreibt. Das ist aber sehr holprig, weil es mit lighthttpd mit der Prozessterminierung nicht so genau nimmt. Man kann z. B. durchaus

accesslog.filename = "|tee > ./access-`date +%H%M%S`.txt"

schreiben, aber z. B. accesslog.filename = "|gzip - > ./access-`date +%H%M%S`.gz" geht nicht, weil der GZip-Prozess beim Runterfahren flöten geht, bevor er die Daten auf die Platte schreiben kann.

Du kannst Deinen Code (mit den kleinen Veränderungen) problemlos übernehmen, Du musst nur den Filedeskriptor austauschen. Aber: Implementieren musst das Gleiche, die Funktionsweise ist identisch, aber wenn Du eine named pipe ausliest, bekommt Dein Prozess immer das EOF mit, kann syncen und ordentlich runterfahren, während, wenn er als Kind von lighthttpd läuft, einfach an undefinierter Stelle weggeschossen wird. Zusammen mit den unverarbeiteten Logdaten.

p.s.: Im Gegensatz zum '|'-Feature funktioniert das Beispiel mit GZip, wenn man die named pipe (accesslog.filename= "access.log.pipe") einsetzt, korrekt:

gzip -9 - < access.log.pipe > access.gz

access.gz enthält die komprimierten Logdaten, wenn lighthttp heruntergefahren wird.
 
Zuletzt bearbeitet:
Was pingu12 hier zusammenschreibt, ist kein C++, sondern C. In C++ verwendet man nicht printf, fprintf, fread usw. usf., sondern die IOStream-Klassen. Damit kann man sich auch den ganzen Schwachsinn mit irgendwelchen Puffern usw. sparen. Man erzeugt einen ifstream, welcher von der FIFO liest. Dann liest man mit getline eine Zeile ein und fertig ist die Laube.
 
Zuletzt bearbeitet:
@hello world: äh ja, es spricht hier auch niemand von C++ und das Subforum heisst C/C++?
 
Wer klugscheißen will, der sollte es wenigstens können.
Ich möchte aus dem lighttpd (webserver) die accesslog-daten nicht in eine Datei schreiben, sondern in einem eigenen kleinen c++Prozess abfangen und verarbeiten.

Sein eigentlicher Code ist aber weder ordentliches C noch C++. Schon bei den Includes ist er inkonsistent und bindet einerseits stdio.h und andererseits cstdlib ein. Ziemlich grausig, aber nicht weiter verwunderlich angesichts der Tatsache, dass C und C++ trotz der eklatanten Unterschiede ständig von irgendwelchen ahnungslosen Leuten in einen Topf geworfen werden...
 
Zuletzt bearbeitet:
Kann es sein, dass du leicht aggro bist? Wenn er von C++ redet aber in C coded ist das vielleicht einfach nur ein Typo. Jeder Anfänger includiert mal zuviel - und wenn der Anfänger C Grundknowhow hat und am Anfang eines C++ Buches steckt, mischt er auch gerne mal.

Also: komm runter und erinnere dich an deine Anfänge.
 
Danke Hello World für die konstruktiven Hinweise, werde mir die Streamklasse gleich vornehmen und besonders versuchen die beiden Sprachen auseinander zu halten..

Ich versuche im Moment nur möglichst zeiteffizient dieses kleine Programm zusammen zu schustern. Habe das gleich durch den ersten Satz meines 1. Posts versucht klarzustellen. Ich kann deine Emotion völlig nachvollziehen und habe selber denkbar wenig Spaß dran in einer Sprache rumzudocktorn wo ich keine Ahnung habe. Leider bin ich Aufgrund von Zeitdruck erst in den nächsten Monaten in der Lage ein koordiniertes Lernen von Beginn an zu absolvieren.
 
Aber vergiss das mit den lighttpd-Kindprozessen und erzeug eine named pipe.
 
Zuletzt bearbeitet:
Der_Kay: Ja, hast mich überzeugt mit der Fifo.

Folgender Code spuckt jetzt bei jeder generierten Logzeile jene direkt aus. Was ich jedoch noch nicht verstehe ist

a) Wieso bekomme ich keine Daten die vor der Ausführung meines Programms geschrieben wurden ?
b) Wieso erhalte ich keine Ausgabe wenn ich das << "\n" weg lasse?

Code:
int main()
{
     int data, index;
     ifstream sFifo;

     sFifo.open("/home/logauswertung/c/fifo99", ifstream::in);

     char zeile[2000];
     while(sFifo.good()) 
     {
       sFifo.getline(zeile,2000);
       cout << zeile << "\n";
     }

     sFifo.close();

     return 0;

}


Da das Ziel ja ein Datenbankeintrag auf Basis spezifischer Logeinträge ist gibt es noch einiges zu tun:

Ich dachte an das Definieren einer Regex die ich über 'zeile' laufen lasse um die entsprechende Information auszulesen.

Und weiter? Auf Basis der Information aus dem LOG müssen weitere Information aus der DB gelesen werden und dann mit den neuen Informationen ein Schreibzugriff erfolgen.

Bisher wollte ich die Information an ein local ausgeführtes PHP-Script schicken, da ich das dann beherrsche aber nun frage ich mich ob es nicht Vorteile bringt das ebenfalls aus meinem Programm oder einem child heraus zu machen. Mein Gedanke gegen PHP: Im Falle von temporärer Überforderung bei den DB-Verbindungen würde der Server immer weiter PHP-Prozesse starten was immer weiter Ressourcen verbrauchen würde.

Mein erster Gedanke : SQL-Verbindung herstellen und direkt in der while Schleife DB-Anfrage, Eintrag machen.

Die Lösungen sollten immer in Hinblick auf Performance optimiert sein. Liege ich da einigermaßen richtig mit dem Weg ?
 
Zuletzt bearbeitet:
a) Die Schreiboperation durch lightppd/mod_accesslog in die pipe blockiert solange, wie kein Prozess auf das Leseende zugreift. Erst wenn Dein Prozess lesend auf die pipe zugreift, fängt lighttpd an zu funktionieren. Datenverlust dürfte es also keinen geben.

b) Das weggelassene newline hat Auswirkung auf das "flush()"-en des Streams, obwohl ich hier keine definitive Ausage machen konnte. Es ist üblich, anstatt '\n' das std::endl zu verwenden, dies impliziert std::flush.

Wie HelloWorld schrieb, solltest Du anstatt istream::getline() lieber das globale std::getline() verwenden, das erspart in der Tat den Umgang mit der Größenbeschränkung des Puffers.

PHP:
// read_pipe.cpp: g++ -o read_pipe read_pipe.cpp

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main ( int argc, const char** argv)
{
	istream* pistr;
	ifstream ifs;

	if ( argc > 1) {
		ifs.open(argv[1]); // todo: Testen
		pistr = &ifs;
	}
	else {
		pistr = &cin;
	}

	string str;
	while ( pistr->good()) {
		getline( *pistr, str );
		cout << str << flush;
	}

	ifstream* p;
	if ( NULL != (p = dynamic_cast<ifstream*>(pistr))) {
		p->close();
	}
	return 0;
}

Diesen Code kannst Du separat mit dem Namen der pipe als Kommandozeilenargument starten, oder als Kindprozess über lighttpd starten. (accesslog.filename="|read_pipe").

Was Dein Projekt betrifft: Klar macht man zu Beginn des Tools eine persistente Verbindung zu einem RDBMS via dessen C/C++-API auf und in der Schleife jagt man eine RegExp über die Zeile, SELECTed/INSERTed usw. Schneller geht es nicht.

Da Du PHP erwähnt hast: Bist Du Dir ganz ganz sicher, dass Du im Hinblick auf lighttpd/C++ usw. die richtigen Tools fürs Backend am Start hast? Es geht schliesslich "nur" um (statisches?) HTML, das ist alles andere als ein Stream- bzw. RealTime-Protokoll. Und am Ende sitzt (wahrscheinlich) ein Mensch am Browser. Der bekommt doch die paar Millisekunden PHP-Processing gar nicht mit und fast immer ist das Netzwerk die Bremse. Ich könnte mir vorstellen, dass das bisschen Performancevorteil aus lightppd/C++ im Gesamtzusammenhang ein Staubkorn im Weltall ist.

Wenn ich PHP hätte, würde ich es auch einsetzen.
 
Zuletzt bearbeitet:
Da Du PHP erwähnt hast: Bist Du Dir ganz ganz sicher, dass Du im Hinblick auf lighttpd/C++ usw. die richtigen Tools fürs Backend am Start hast? Es geht schliesslich "nur" um (statisches?) HTML, das ist alles andere als ein Stream- bzw. RealTime-Protokoll. Und am Ende sitzt (wahrscheinlich) ein Mensch am Browser. Der bekommt doch die paar Millisekunden PHP-Processing gar nicht mit und fast immer ist das Netzwerk die Bremse. Ich könnte mir vorstellen, dass das bisschen Performancevorteil aus lightppd/C++ im Gesamtzusammenhang ein Staubkorn im Weltall ist.

Es geht nicht um HTML, sondern um große Dateien. Diese ganze Logauswertungsgeschichte dient vor allem auch dazu festzustellen wann eine Datei fertig übertragen ist. Dieses Feature gibt es leider noch in keinem mir bekannten httpd. Und wenn ich das php-script bei jeder Datei offen lasse bis ich meine DB-Eintragungen gemacht habe macht es ja dann doch erheblich was aus von der Performance her würde ich sagen.
 

Ähnliche Themen

Pipefehler unter Solaris 10 X86

Squid nur zum maskieren der eigenen IP, nicht für Webserver auf port 80

Zurück
Oben