#include "../main.c"

#8076769
Lesenswert?

Guten Morgen!

Nochmal, weil es so schön ist:

1
#include "../main.c"

Gibt es dafür Schläge oder sogar eine spezielle Hölle? Weil, es ist überaus praktisch.

Es gibt ca. 60 Funktionen int foo(const char*); Die werden bisher alle in einem einzigen main() benutzt. Jetzt soll jede Funktion ein eigenständiges Programm werden und per klassischem CGI vom httpd aufgerufen werden. An den Funktionen selbst muss fast nichts geändert werden, die brauchen nur ein main() drum herum. Diese ca. 60 main() wären alle identisch, nur ist main.c z.Zt. noch ziemlich ausbaufähig:

1
#include <fcntl.h>
2

3
typedef int(*plugin_pt)(const char *);
4
extern plugin_pt plugin;
5

6
int
7
main (void)
8
{
9
   const char  *method, *uri, *query;
10
   int  fd;
11

12
   fd = open ("/var/log/bozo", O_WRONLY | O_APPEND);
13
   log_init (fd);
14
   method = getenv ("REQUEST_METHOD");
15
   uri    = getenv ("REQUEST_URI");
16
   query  = getenv ("QUERY_STRING");
17
   log_printf (1, "%s %s", method, uri);
18
   if (query == NULL) {
19
      query = "";
20
   }
21
   return plugin (query);
22
}
1
#include <stdio.h>
2
#include "foo.h"
3
//...
4
#include "../main.c"
5
plugin_pt plugin = foo;
6

7
int foo (const char *query)
8
{
9
}
: Bearbeitet durch User
#8076784
Lesenswert?

Sowas würde ich per Linker lösen.

1
// include/server_framework/plugin.h
2
typedef void plugin_main_f(void);
3
extern plugin_main_f plugin_main;
1
// src/plugin/a/main.c
2
#include <stdio.h>
3
#include <server_framework/plugin.h>
4
void plugin_main(void){ printf("Plugin A\n"); }
1
// src/plugin/b/main.c
2
#include <stdio.h>
3
#include <server_framework/plugin.h>
4
void plugin_main(void){ printf("Plugin B\n"); }
1
// src/server_framework/main.c
2
#include <server_framework/plugin.h>
3
int main(){ plugin_main(); }
1
cc -I include/ src/server_framework/main.c -c -o server_framework_main.o
2
cc -I include/ src/plugin/a/main.c -c -o plugin_a.o
3
cc -I include/ src/plugin/b/main.c -c -o plugin_b.o
4
cc server_framework_main.o plugin_a.o -o plugin_a
5
cc server_framework_main.o plugin_b.o -o plugin_b

src/server_framework/* kann man natürlich auch in eine Library packen, statisch oder dynamisch geht beides. (Zumindest unter Linux, unter Windows geht vermutlich nur statisch, wenn da die main drin ist).

1
mkdir lib
2
cc -fPIC -I include/ src/server_framework/main.c -c -o server_framework_main.o # -fPIC brauchts nur für shared libs
3
ar r lib/libserver_framework.a server_framework_main.o # Statische lib
4
cc -shared lib/libserver_framework.a -o lib/libserver_framework.so # Optional, dynamische lib
5

6
cc -Llib plugin_a.o -o plugin_a -lserver_framework
7
cc -Llib plugin_b.o -o plugin_b -lserver_framework
8

9
// LD_LIBRARY_PATH falls dynamische Library verwendet wird. Alternativ, in /usr/local/lib installieren und ldconfig aufrufen
10
LD_LIBRARY_PATH=$PWD/lib/ ./plugin_a

(Unter Windows kann man nicht das selbe Archiv für die Statische lib und die DLL nehmen, dort braucht man für die .o / .obj schon andere Optionen)

Muss man halt einmal ein Makefile dafür erstellen.

PS: Ich nehme gerne -Wl,--whole-archive libserver_framework.a -Wl,--no-whole-archive damit keine undefinierten oder doppelten Symbole wegen Linkerreihenfolge rumschwirren

: Bearbeitet durch User
#8076867
Lesenswert?

Wenn eine Problem anscheinend eine Lösung erfordert, die signifikant von den sonst üblichen Vorgehensweisen abweicht, ists meistens ein xy-Problem.

C hat Includefiles üblicherweise als Header, und die toolchain bietet einen linker. Das reicht.

C bietet aber auch immer die Möglichkeit, sich sehr effektiv und mit Erfolgsgarantie selber in den Fuß zu schiessen.

Oliver

#8076892
Lesenswert?

Ja, ok, überzeugt :)

Daniel A. schrieb:

src/server_framework/* kann man natürlich auch in eine Library packen, statisch oder dynamisch geht beides.

Diese Funktionen benutzen sowieso eine shared library. Ich hab' jetzt mal nichts weiter gemacht als main da rein zu packen (die include-Datei ist jetzt leer). Das funktioniert ohne weitere Änderung sofort. Das Beste: die 60 Programme müssen nach Änderungen an main nicht neu gelinkt werden. Allerdings ist main() jetzt wirklich gut versteckt ;)

Die Namen der Funktionen möchte ich nicht ändern, manchmal ist func ganz nützlich. Also behalte ich den Funktionspointer; ein Makro funktioniert ja nicht, wenn main() in der lib steckt.

Vielen Dank für Trost und Rat!

Oliver S. schrieb:

C bietet aber auch immer die Möglichkeit, sich sehr effektiv und mit Erfolgsgarantie selber in den Fuß zu schiessen.

Deswegen frage ich ja Euch ;)

#8077420
Lesenswert?

Bauform B. schrieb:

Es gibt ca. 60 Funktionen int foo(const char*); Die werden bisher alle in einem einzigen main() benutzt. Jetzt soll jede Funktion ein eigenständiges Programm werden und per klassischem CGI vom httpd aufgerufen werden.

Per CGI? Wow, sowas habe ich schon lange nicht mehr gesehen. Die Performance der schnellen C-Binaries wird dann auch weitgehend zunichte gemacht, und ein Microservice (zur Not über einen Reverse Proxy, sowas kann der Apache httpd) oder FastCGI wären womöglich klügere Lösungen.

Aber sei es drum: wenn es unbedingt ein CGI sein soll, würde es die Geschichte vielleicht vereinfachen, einen ähnlichen Weg wie bei Busybox zu gehen. Das ist nur ein Binary, das die gewünschte Funktion über den Dateinamen ermittelt, für jede Funktion wird dann also ein Softlink angelegt (benötigt beim Apaches fürs Cgi-Bin-Verzeichnis eine Option "FollowSymLinks" oder "SymLinksIfOwnerMatch"). Hardlinks würden natürlich auch gehen.

Ein statisch kompilierbares C++-Programm, das das Busybox-Konzept realisiert und je nach dem Namen des Binary eine C-Funktionen aufruft oder einen Fehler aus- und !=0 zurück gibt, hab ich hier einmal angehängt. Es ist aber nur ein schneller Hack ohne große Ambitionen bezüglich Codequalität etc.. HTH, HF!

Angehängte Dateien:
#8077601
Lesenswert?

Whatever floats your boat.

Wenn das jetzt Dein Problem einmalig löst und es damit gut ist, mache es.

Mit Script/Make/Linker-Skills gibt es viele elegantere Alternativen. Aber ganz ehrlich: Für den Anwendungsfall gibt es keinen Schönheitspreis.

Wenn Du für jeden Batchbuild (der 60) jetzt >> 10 Minuten an Arbeit brauchst und das noch zig Male die nächsten Tage, dann kann man das sicher reduzieren. Dann frag einfach nochmal nach.

#8078057
Lesenswert?

Ein T. schrieb:

Per CGI? Wow, sowas habe ich schon lange nicht mehr gesehen. Die Performance der schnellen C-Binaries wird dann auch weitgehend zunichte gemacht Aber sei es drum: wenn es unbedingt ein CGI sein soll, würde es die Geschichte vielleicht vereinfachen, einen ähnlichen Weg wie bei Busybox zu gehen.

Mit einem fetten Binary wird es doch noch langsamer? Ich sehe das so: Speziell bei Linux ist ein fork() noch relativ preisgünstig, das execve() kostet extra. Insofern sollten viele kleine CGI-Programme mit einer shared library günstiger sein als etwas nach Art der Busybox. In dieser speziellen Anwendung spielt das sowieso keine Rolle, es passt praktisch alles in den Cache, inkl. Daten und man erwartet vielleicht ein Dutzend Benutzer gleichzeitig.

Solange ich noch dran rumbastel, hat fork() noch den Vorteil, dass ein CGI abstürzen darf ohne den Rest zu stören. Der httpd selbst muss nur neu starten, wenn /seine/ Config geändert würde. Und übersetzen geht auch 60 Mal schneller ;)

Die alte Version war noch extremer als der ganze neumodische Kram wie FastCGI. Alle Funktionen waren in den eigentlichen httpd eingebaut, also brauchte man nur ein fork() pro Request. Das hätte man auch noch sparen können, aber es hat auch Vorteile, siehe oben.

Bruno V. schrieb:

Für den Anwendungsfall gibt es keinen Schönheitspreis.

Schade :)

Wenn Du für jeden Batchbuild (der 60) jetzt >> 10 Minuten an Arbeit brauchst und das noch zig Male die nächsten Tage, dann kann man das sicher reduzieren.

Mir geht's schnell genug

1
time (make clean && make)
2
real    0m10.506s
3
user    0m8.787s
4
sys     0m1.729s

vor allem nach einer typischen Änderung:

1
$ time make
2
real    0m0.286s
3
user    0m0.218s
4
sys     0m0.081s
Persönliche Seite #8078076
Lesenswert?

Bauform B. schrieb:

Insofern sollten viele kleine CGI-Programme mit einer shared library günstiger sein als etwas nach Art der Busybox.

Shared Objects können im Speicher bleiben sodass mehrere Prozesse davon profitieren können. Bei einem Binary mit allen Funktionalitäten (umgeschaltet per argv[0] oder argv[1] oder whatever) dürften diese vollständig im Cache landen, wodurch die Aufrufe an alle Funktionen auch ziemlich schnell gehen. Insbesondere weil ja nur der Programmcode (.text, .rodata) groß ist, und der auch einfach in mehrere Prozesse einblendet werden kann.

Die interessante Frage ist ob eine permanent geladene Web-App (Flask, Java Servlet...?) nicht doch schneller wäre...

Persönliche Seite #8078090
Lesenswert?

Hans W. schrieb:

Ich schätze, dass dazu wieder zusätzliche Software nötig ist, die nicht bei jedem Request neu geladen wird (Redis?).

Klar, diese Daten muss jeder Prozess dann immer irgendwie neu beschaffen. Das wäre der Gag von Techniken wie Servlets oder auch node.js wo die Applikation permanent läuft, beliebige Daten im Speicher halten kann, und immer wieder neue Anfragen verarbeitet.

#8078112
Lesenswert?

Hans W. schrieb:

Niklas G. schrieb:

Shared Objects können im Speicher bleiben sodass mehrere Prozesse davon profitieren können.

Bei Programmcode ist mir das klar.

Aber wie geht das mit Daten, insbesondere Session Daten und Caches für DB Abfragen?

Im Prinzip ganz genauso, es gibt shared memory für Daten. Jedes Programm mapped statt Programmcode einen gemeinsamen Datenblock. Oder auch einen privaten, der aber länger lebt als das Programm.

Braucht man sowas wirklich? Ja, man möchte natürlich verschiedene Benutzer unterscheiden können, z.B. mit einem Session Cookie. Aber das heißt doch nur so, weil man Username und Passwort nicht direkt in so ein Cookie packen will, oder?

#8079175
Lesenswert?

Bauform B. schrieb:

Im Prinzip ganz genauso, es gibt shared memory für Daten. Jedes Programm mapped statt Programmcode einen gemeinsamen Datenblock. Oder auch einen privaten, der aber länger lebt als das Programm.

Mit Shared Memory zu arbeiten ist allerdings eine ziemlich... sagen wir mal, klassische Designentscheidung, insbesondere weil sich derartige Architekturen nicht besonders gut verteilen bzw Loadbalancen lassen. Da könnten ein Valkey oder Redis mehr Freiheiten anbieten.

Braucht man sowas wirklich? Ja, man möchte natürlich verschiedene Benutzer unterscheiden können, z.B. mit einem Session Cookie. Aber das heißt doch nur so, weil man Username und Passwort nicht direkt in so ein Cookie packen will, oder?

Je nach Anwendungsfall kann man das schon machen, aber... okay, Paßworte eher nicht. Aber den Benutzernamen, seine Gruppen, Rollen, und Berechtigungen in die Cookies zu packen, ist zum Beispiel bei den JWTs von OpenID Connect der Standard. Und da diese Daten kryptografisch signiert sind, besteht dann auch keine Gefahr von unbefugter Manipulation. Derartige Cookies enthalten je nach Anwendungsfall bereits alle benötigten Daten, so daß keine weiteren Netzwerk-Roundtrips zum Server mehr notwendig sind.

#8079220
Lesenswert?

Niklas G. schrieb:

Klar, diese Daten muss jeder Prozess dann immer irgendwie neu beschaffen. Das wäre der Gag von Techniken wie Servlets oder auch node.js wo die Applikation permanent läuft, beliebige Daten im Speicher halten kann, und immer wieder neue Anfragen verarbeitet.

So oft wie man node.js patchen muss kann man da nicht von permanent laufen reden.

Beitrag #8079760 wurde von einem Moderator gelöscht.
Beitrag #8079795 wurde von einem Moderator gelöscht.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren