Raw sockets blockiert



  • Hallo, ich habe eine Networking Library gebaut, die immer funktioniert hat, dann habe ich einen Fehler mit dem cp Kommando gemacht und musste ein bisschen neu programmieren und jetzt weiß ich nicht mehr so richtig, wie ich es vorher gemacht habe und klappt nicht mehr. Für mich sieht der Code einwandfrei aus.

    Ich habe hier meine Sniffer Klasse:

    class sniffer{
        net::raw_socket socket;
    
    ...
    public:
    ...
        sniffer(std::ostream& output = std::cout, int bufsiz = 4096)
            : stream{std::addressof(output)}, buffer_size{bufsiz} {}
    ...
    
        void run();
    };
    

    Es wird der Default-Konstruktor von net::raw_socket aufgerufen. In der Methode run() wird dann ein receive() gemacht um ein Packet von maximal 4 Kilobytes empfangen:

    void sniffer::run(){
        net::ethernet_header eth_header;
        net::ip_header ip_header;
    
        while(true){
            socket.receive(buffer, buffer_size);
    ...
        }
    }
    

    Die receive Funktion und die Konstruktoren meiner raw_socket -Klasse sieht dann so aus:

    class raw_socket : public socket{
        public:
            raw_socket()
                : raw_socket{family_type::ipv4} {}
    
            virtual ~raw_socket() {}
    
            explicit raw_socket(int fd)
                : socket{fd} {}
    
            explicit raw_socket(family_type family, protocol_type type = protocol_type::raw) // das da wird durch den Default Ctor aufgerufen
                : socket{family, socket_type::raw, type} {}
    ...
            template<template<typename, typename...> class Container, typename T, typename... Args>
            void receive(Container<T, Args...>& container, int max){
                container.reserve(max);
                ::ssize_t transferred;
                char byte;
    
                while(max-- > 0 && (transferred = ::recv(file_descriptor(), &byte, 1, 0)) > 0)
                    container.push_back(byte);
    
                if(transferred < 0)
                    throw network_error{std::strerror(errno)};
            }
        };
    

    Und in der receive() -Funktion, beim ::recv um genau zu sein, bleibt meine Methode bereits beim ersten Aufruf hängen. Warum ist das so?

    Falls es hilft: Es geht noch tiefer. In meine Socket Klasse:

    class socket{
    ...
    public:
            socket(int fd)
                : opened{true}, descriptor{fd} {}
    
            socket(family_type family, socket_type type, protocol_type protocol){ // das da wird aufgerufen
                create(family, type, protocol);
            }
    ...
            void create(family_type family, socket_type type, protocol_type protocol){
                descriptor = detail::socket(
                    static_cast<int>(family), static_cast<int>(type), static_cast<int>(protocol)
                );
    
                opened = true;
            }
    ...
        };
    

    Also schlussendlich erstelle ich meinen Raw-Socket, wie folgt:

    descriptor = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)
    

    Ich weiß auch nicht, was ich dazu noch sagen soll... Für mich sieht alles richtig aus.
    Wenn noch mehr Informationen benötigt werden, bitte sagen.



    1. du hast kein C++ Problem
    2. du sagst nicht, was eigentlich nicht funktioniert


  • manni66 schrieb:

    1. du hast kein C++ Problem

    Ja, aber ich hab alles in Klassen gewrappt. Ich denke, darüber lässt sich jetzt streiten, wo das am besten hingehört. Ins Linux-Forum um genau zu sein, aber da wird mir nicht so schnell jemand antworten 🙂

    1. du sagst nicht, was eigentlich nicht funktioniert

    Doch, das steht irgendwo in der Mitte meines Posts. ::recv blockt schon, wenn er das erste Byte empfangen möchte.



  • Das gehört krass ins Linux-Subforum.

    schnief schrieb:

    Hallo, ich habe eine Networking Library gebaut, die immer funktioniert hat, dann habe ich einen Fehler mit dem cp Kommando gemacht und musste ein bisschen neu programmieren und jetzt weiß ich nicht mehr so richtig, wie ich es vorher gemacht habe und klappt nicht mehr. Für mich sieht der Code einwandfrei aus.

    Dein Scheißernst? Also ich hab' Einwände.

    template<template<typename, typename...> class Container, typename T, typename... Args>
            void receive(Container<T, Args...>& container, int max){
                container.reserve(max);
                ::ssize_t transferred;
                char byte;
    
                while(max-- > 0 && (transferred = ::recv(file_descriptor(), &byte, 1, 0)) > 0)
                    container.push_back(byte);
    
                if(transferred < 0)
                    throw network_error{std::strerror(errno)};
            }
    

    So, was machst du hier? Du machst immer einen Kernel-Call ( recv ), um ein Byte (!!!) von dem Socket zu lesen, und haust das dann in deinen Container. Das ist 'ne Menge Overhead für rein gar nichts.

    Du hast es schon in deinem Threadtitel beschrieben, dein Socket blockiert beim recv/read . Natürlich tut er das. Sockets werden standardmäßig so erstellt, dass sie blockieren. Das ist dann super, wenn du für jede Netzwerkkommunikation deinen eigenen Thread hast. Das API ist aber ein bisschen älter, damals hatten wir noch nicht unbedingt leichtgewichtige Kernel-Threads (haben wir im Grunde immer noch nicht, aber sei's drum). Deswegen hat man das damals mit nicht-blockierenden Calls und "User-Threads" implementiert. Man hat den Kernel angefragt, ob Daten vorhanden sind, und wenn nicht, dann wurde in der Zwischenzeit ein anderer Socket (oder Thread) bemüht, und man hat ein Event Notification API bemüht ( select für POSIX, epoll speziell für Linux, kqueue für XXXBSD). Dadurch hat man sich Kontextwechsel gespart, aber man konnte nicht parallel von allen Sockets lesen. Das ging nur "beinahe parallel" - also solange Socket 1 keine Daten hat ( recv gibt EAGAIN / EWOULDBLOCK zurück), kann man versuchen, von Socket 2, 3 oder 4 zu lesen. Aber immer nur von einem zur gleichen Zeit. Da nichts blockieren würde, sieht das dann aus wie "beinahe parallel". Und wenn man besagtes ENAPI verwendet, dann rast die CPU auch nicht, Socket 1 - 4 immer wieder zu prüfen, stattdessen wartet dein Programm geduldig.

    In Hinblick auf deinen bisher verwendeten Code, der mir zeigt, dass du vieles zur Socket-Programmierung einfach nicht weißt, ist die Frage nicht, wie man Sockets nicht-blockierend erstellt. Die Frage ist: wie sieht das große Ganze aus?

    Wenn du einen Kernel-Thread pro ausstehender I/O-Transaktion hast, dann reicht es auch, select zu verwenden, und das mit den nicht-blockierenden Sockets zu lassen. select lässt die CPU runterfahren, sodass auch andere Threads zur Ausführung kommen können, und du kannst einen Timeout angeben, und wenn der abgelaufen ist, kannst du die Verbindung als abgebrochen betrachten.

    Wenn du einen Haufen Sockets über einen Thread verwalten willst, dann verwende epoll bzw. epoll_wait/epoll_pwait . Oder das BSD-Äquivalent. Was immer die da haben, ich glaub', die haben daran gearbeitet, seit ich's mir das letzte Mal angeschaut habe. select skaliert nämlich O(n) statt O(1) wie epoll , und kqueue "hatte besondere Bedürfnisse" (um es politisch korrekt auszudrücken), weil sie ebenfalls O(n) skaliert haben. Aber wie gesagt, das ist eine ganze Weile her, vielleicht haben sie's schon gefixt.

    Ohne ENAPI nicht-blockierende Sockets zu haben lässt dich jedes Mal einen Kontextwechsel haben, nur um zu prüfen. ob jetzt Daten vorhanden sind oder nicht. Wenn welche vorhanden sind, werden diese eingefügt. Wenn nicht, musst du halt noch mal probieren. Und noch mal. Und noch mal. Und jedesmal hast du einen Kontextwechsel. Deswegen hat man ENAPIs erfunden, welche einen benachrichtigen, wenn was zum Lesen da ist.

    Und nimmt mal einen größeren Buffer - also, beim recv jetzt. 4 KiB können es schon sein. Selbst, wenn du dann in deinem Container (*würg*) dann alles wieder Byte-Weise einfügst, hast du wenigstens nicht mehr Kontextwechsel für jedes Byte, welches du einliest. Vielleicht hast du die noch beim push_back . Keine Ahnung. Der ganze C++-Ranz ist so abstrakt, dass ich mir nie die Mühe gemacht habe, das zu prüfen. Kann sein, dass deswegen jedes Mal neuer Speicher reserviert wird (sprich, es wird gelockt und wieder unlockt, und eventuell muss wieder Speicher reserviert werden, und dann hast du im schlimmsten Fall 3 Kontextwechsel). Kann auch sein, dass die ordentlich mit Speicher umgehen und du alles ohne Kontextwechsel hinbekommen kannst. Aber wenigstens beim recv kannst du massig einsparen.

    Ach, und noch eine Sache - die ist jetzt aber Protokoll-spezifisch. Du hast max , welches angibt, wie viele Bytes du vom Socket maximal erwartest. Wenn dein Protokoll so funktioniert, Klasse. Aber es gibt auch Protokolle, die können das nicht. HTTP zum Beispiel. Die Crackpots damals haben In-band-Signaling gemacht, sprich, du musst die Nachricht parsen, um zu wissen, wie viele Bytes denn jetzt ankommen. Das wusste man schon damals, dass das blöd ist und man lieber Out-of-Band-Signaling macht. Und dann muss man noch den Header ignorieren und erst dann zu zählen beginnen. Und wenn's blöd läuft, dann ist der Server nicht korrekt konfiguriert und gibt dir selbst statische Daten chunk-weise raus, und du musst lesen, bis du auf einen Chunk mit 0 Bytes Größe triffst. Habe ich alles schon erlebt. Und dann muss man sich ein wenig anstrengen, um das performant verarbeitet zu bekommen.

    Weil ich nicht weiß, welche Protokolle du unterstützen willst, lasse ich das mal komplett optional. Es ist gut möglich, dass du wirklich weißt, wie viele Byte am Socket ankommen sollen. Aber es gibt auch das Szenario, dass du das eben NICHT weißt. Und dann musst du einen Check auf die Daten machen, die du bisher hast. Und dabei nicht vergessen, dass Daten komplett unvollständig ankommen können. Und auch nicht, dass mehr Bytes als erwartet ankommen können, Pipelinings wegen.



  • schnief schrieb:

    manni66 schrieb:

    1. du hast kein C++ Problem

    Ja, aber ich hab alles in Klassen gewrappt. Ich denke, darüber lässt sich jetzt streiten, wo das am besten hingehört. Ins Linux-Forum um genau zu sein, aber da wird mir nicht so schnell jemand antworten 🙂

    1. du sagst nicht, was eigentlich nicht funktioniert

    Doch, das steht irgendwo in der Mitte meines Posts. ::recv blockt schon, wenn er das erste Byte empfangen möchte.

    Wie kommst du darauf, dass es nicht blockieren soll, wenn es nichts zu lesen gibt?
    Nur wenn der socket als non blocking konfiguriert ist blockt recv nicht.

    Lesen der Doku hilft 😉



  • firefly schrieb:

    Wie kommst du darauf, dass es nicht blockieren soll, wenn es nichts zu lesen gibt?

    Ich starte meinen Sniffer, geh zum Browser, mache F5 und es kommt nichts. Absolut gar nichts. Und sonst hat es immer perfekt geklappt.



  • Mach doch einfach ein Rollback auf die funktionierende Version.



  • dachschaden schrieb:

    Das gehört krass ins Linux-Subforum.

    Ok, sry.

    So, was machst du hier? Du machst immer einen Kernel-Call ( recv ), um ein Byte (!!!) von dem Socket zu lesen, und haust das dann in deinen Container. Das ist 'ne Menge Overhead für rein gar nichts.

    Ok. Dann mach ich es so:

    template<template<typename, typename...> class Container, typename T, typename... Args>
            void receive(Container<T, Args...>& container, int max){
                container.resize(max);
                ::ssize_t transferred, total{};
    
                while(max > 0 && (transferred = ::recv(file_descriptor(), (char*)&container[total], max - total, 0)) > 0){
                    max -= transferred;
                    total += transferred;
                }
    
                if(transferred < 0)
                    throw network_error{std::strerror(errno)};
            }
    

    Das müsste dann besser ein. Danke für die Info.

    Du hast es schon in deinem Threadtitel beschrieben, dein Socket blockiert beim recv/read . Natürlich tut er das. Sockets werden standardmäßig so erstellt, dass sie blockieren. Das ist dann super, wenn du für jede Netzwerkkommunikation deinen eigenen Thread hast. Das API ist aber ein bisschen älter, damals hatten wir noch nicht unbedingt leichtgewichtige Kernel-Threads (haben wir im Grunde immer noch nicht, aber sei's drum). Deswegen hat man das damals mit nicht-blockierenden Calls und "User-Threads" implementiert. Man hat den Kernel angefragt, ob Daten vorhanden sind, und wenn nicht, dann wurde in der Zwischenzeit ein anderer Socket (oder Thread) bemüht, und man hat ein Event Notification API bemüht ( select für POSIX, epoll speziell für Linux, kqueue für XXXBSD). Dadurch hat man sich Kontextwechsel gespart, aber man konnte nicht parallel von allen Sockets lesen. Das ging nur "beinahe parallel" - also solange Socket 1 keine Daten hat ( recv gibt EAGAIN / EWOULDBLOCK zurück), kann man versuchen, von Socket 2, 3 oder 4 zu lesen. Aber immer nur von einem zur gleichen Zeit. Da nichts blockieren würde, sieht das dann aus wie "beinahe parallel". Und wenn man besagtes ENAPI verwendet, dann rast die CPU auch nicht, Socket 1 - 4 immer wieder zu prüfen, stattdessen wartet dein Programm geduldig.

    Ok. Ich weiß, dass es blockiert. Aber selbst, wenn etwas empfangen werden sollte (ich hab im Browser F5 gedrückt), passiert nichts in meinem Sniffer Programm. Da wird nicht ein einziges Byte empfangen. Trotz F5, ich hab das mit dem Debugger gecheckt.

    In Hinblick auf deinen bisher verwendeten Code, der mir zeigt, dass du vieles zur Socket-Programmierung einfach nicht weißt, ist die Frage nicht, wie man Sockets nicht-blockierend erstellt. Die Frage ist: wie sieht das große Ganze aus?

    Ja, das Ganze müsste auch mit Blocking Sockets klappen. Hat es ja auch einmal. Das Große Ganze sieht für mich, also für das, was ich weiß, ziemlich zufriedenstellend aus. Gut, da sind noch einige Stellen, an dem du mir ne Schelle verpassen kannst, aber ich entwickel mich ja schließlich auch weiter. Ich werde nicht die ganze Library jetzt auf Github hochladen, weil die Library hat trotzallem geistigen Wert für mich und will nicht alles rausgeben.

    Wenn du einen Kernel-Thread pro ausstehender I/O-Transaktion hast, dann reicht es auch, select zu verwenden, und das mit den nicht-blockierenden Sockets zu lassen. select lässt die CPU runterfahren, sodass auch andere Threads zur Ausführung kommen können, und du kannst einen Timeout angeben, und wenn der abgelaufen ist, kannst du die Verbindung als abgebrochen betrachten.

    Ja, ok, aber ich wollte es ohne select() machen. Das müsste klappen.

    Wenn du einen Haufen Sockets über einen Thread verwalten willst, dann verwende epoll bzw. epoll_wait/epoll_pwait . Oder das BSD-Äquivalent. Was immer die da haben, ich glaub', die haben daran gearbeitet, seit ich's mir das letzte Mal angeschaut habe. select skaliert nämlich O(n) statt O(1) wie epoll , und kqueue "hatte besondere Bedürfnisse" (um es politisch korrekt auszudrücken), weil sie ebenfalls O(n) skaliert haben. Aber wie gesagt, das ist eine ganze Weile her, vielleicht haben sie's schon gefixt.

    Ohne ENAPI nicht-blockierende Sockets zu haben lässt dich jedes Mal einen Kontextwechsel haben, nur um zu prüfen. ob jetzt Daten vorhanden sind oder nicht. Wenn welche vorhanden sind, werden diese eingefügt. Wenn nicht, musst du halt noch mal probieren. Und noch mal. Und noch mal. Und jedesmal hast du einen Kontextwechsel. Deswegen hat man ENAPIs erfunden, welche einen benachrichtigen, wenn was zum Lesen da ist.

    Ja, ok, da muss ich mich jetzt etwas mehr da reinlesen. Ich wollte meine Library auch ziemlich Windows-gerecht machen, also ohne großartig Dinge zu ändern es aber auch auf nem Windows-Rechner klappt. Darum wäre epoll jetzt nicht so die beste Wahl für meine Networking Library.

    Und nimmt mal einen größeren Buffer - also, beim recv jetzt. 4 KiB können es schon sein. Selbst, wenn du dann in deinem Container (*würg*) dann alles wieder Byte-Weise einfügst, hast du wenigstens nicht mehr Kontextwechsel für jedes Byte, welches du einliest. Vielleicht hast du die noch beim push_back . Keine Ahnung. Der ganze C++-Ranz ist so abstrakt, dass ich mir nie die Mühe gemacht habe, das zu prüfen. Kann sein, dass deswegen jedes Mal neuer Speicher reserviert wird (sprich, es wird gelockt und wieder unlockt, und eventuell muss wieder Speicher reserviert werden, und dann hast du im schlimmsten Fall 3 Kontextwechsel). Kann auch sein, dass die ordentlich mit Speicher umgehen und du alles ohne Kontextwechsel hinbekommen kannst. Aber wenigstens beim recv kannst du massig einsparen.

    Ja, ok, hab ich gemacht. Siehe den Code oben.

    Ach, und noch eine Sache - die ist jetzt aber Protokoll-spezifisch. Du hast max , welches angibt, wie viele Bytes du vom Socket maximal erwartest. Wenn dein Protokoll so funktioniert, Klasse. Aber es gibt auch Protokolle, die können das nicht. HTTP zum Beispiel. Die Crackpots damals haben In-band-Signaling gemacht, sprich, du musst die Nachricht parsen, um zu wissen, wie viele Bytes denn jetzt ankommen. Das wusste man schon damals, dass das blöd ist und man lieber Out-of-Band-Signaling macht. Und dann muss man noch den Header ignorieren und erst dann zu zählen beginnen. Und wenn's blöd läuft, dann ist der Server nicht korrekt konfiguriert und gibt dir selbst statische Daten chunk-weise raus, und du musst lesen, bis du auf einen Chunk mit 0 Bytes Größe triffst. Habe ich alles schon erlebt. Und dann muss man sich ein wenig anstrengen, um das performant verarbeitet zu bekommen.

    Ich hab viele receive() Funktionen in meiner Library, nicht nur eine. Einmal mit int max einmal mit nem Delimiter und einmal ohne nichts. Also solange ::recv halt was größeres als Null zurückgibt. Dass man bei HTTP die Content Length parsen muss ist mir klar.

    Weil ich nicht weiß, welche Protokolle du unterstützen willst, lasse ich das mal komplett optional. Es ist gut möglich, dass du wirklich weißt, wie viele Byte am Socket ankommen sollen. Aber es gibt auch das Szenario, dass du das eben NICHT weißt. Und dann musst du einen Check auf die Daten machen, die du bisher hast. Und dabei nicht vergessen, dass Daten komplett unvollständig ankommen können. Und auch nicht, dass mehr Bytes als erwartet ankommen können, Pipelinings wegen.

    Ok. Gibt es noch ein umfangreicheres Buch, als Beejs Guide? Ich finde Networking ja irgendwo total spannend und habe ehrlich gesagt kleinere Komplikationen dich nachzuvollziehen. Damit will ich nicht sagen, dass du falsch liegst! Du wirfst halt mit Wörtern um dich, die mir noch nicht bekannt sind und ich auch durch einen Wikipedia-Artikel immer noch nicht ganz nachvollziehen kann.

    Ansonsten, mega großes Dankeschön für den fetten Beitrag. 👍
    Ich werde ihn mir noch einige Male durchlesen.

    Aber warum blockiert denn nun ::recv() immer noch trotz F5 im Browser?

    Mach doch einfach ein Rollback auf die funktionierende Version.

    Ich benutze kein Git.



  • schnief schrieb:

    Ok. Ich weiß, dass es blockiert. Aber selbst, wenn etwas empfangen werden sollte (ich hab im Browser F5 gedrückt), passiert nichts in meinem Sniffer Programm. Da wird nicht ein einziges Byte empfangen. Trotz F5, ich hab das mit dem Debugger gecheckt.

    😃 Deswegen habe ich ja gegen Ende diesen kleinen Abschnitt bezüglich der Anzahl der Bytes, die du erwartest, geschrieben. Denn sagen wir mal, du erwartest 4 KiB an Daten, sprich, max ist 4096 (im Übrigen, nimm für solche Größen size_t ... würde ich sagen, wenn du in C programmierst, aber C++ ist inzwischen sein ganz eigener Kosmos, da kann ich nur: "Also, in C macht man das so ..." sagen). Aber es kommen nur 2048 Bytes an. Dann hast du am Ende:

    while(2048 > 0 && (transferred = ::recv(file_descriptor(), (char*)&container[2048], 4096 - 2048, 0)) > 0)
    {
        max -= transferred;
        total += transferred;
    }
    

    Oder ein bisschen verständlicher: weil max noch nicht 0 ist, versucht recv (blockierend oder nicht), Daten zu lesen. Wenn es blockiert - tja, dann blockiert es halt, da hast du deinen Fehler. Wenn nicht, dann wird recv -1 zurückgeben:

    man recv schrieb:

    RETURN VALUE
    These calls return the number of bytes received, or -1 if an error occurred. In the event of an error, errno is set to indicate the error.

    Und errno hat dann EAGAIN oder EWOULDBLOCK . Damit wird dann die Schleife abgebrochen, und der Check auf transferred < 0 wird zum Werfen einer Exception führen.

    Wenn daher immer noch kein Byte angezeigt wird, würden mir folgende Möglichkeiten einfallen:

    1. Dein Socket ist immer noch blockierend.
    2. Die Exception wird irgendwo sang- und klanglos abgefangen.

    schnief schrieb:

    Ja, das Ganze müsste auch mit Blocking Sockets klappen. Hat es ja auch einmal.

    Dann hattest du entweder die Anzahl der Bytes, die dir die Gegenseite schickt, richtig geraten, oder du hast select verwendet. Was anderes bleibt da nicht wirklich übrig. Blockierende Sockets blockieren halt. Deswegen verwendet man select - um der Gegenseite ein bisschen Zeit zu geben, Daten zu schicken, ohne dass man komplett die Kontrolle über den Thread verliert. Wenn select dann zurückkehrt und FD_ISSET == 0 ist, sind keine Daten angekommen, und man kann sich den recv sparen.

    schnief schrieb:

    Gut, da sind noch einige Stellen, an dem du mir ne Schelle verpassen kannst

    Ich kann dir schon eine Schelle dafür verpassen, dass du C++ verwendest. 😃 Aber das bringt keinen weiter.

    schnief schrieb:

    Ja, ok, aber ich wollte es ohne select() machen. Das müsste klappen.

    Siehe oben. Entweder du rätst richtig, oder du verwendest select mit blockierenden Sockets.

    Und lass dir gesagt sein, Programmierer sind schlecht im Raten ...

    schnief schrieb:

    Ja, ok, da muss ich mich jetzt etwas mehr da reinlesen. Ich wollte meine Library auch ziemlich Windows-gerecht machen, also ohne großartig Dinge zu ändern es aber auch auf nem Windows-Rechner klappt. Darum wäre epoll jetzt nicht so die beste Wahl für meine Networking Library.

    Dann nimm select . Microsoft hat sich bemüht, ein paar POSIX-Funktionen zur Verfügung zu stellen. Mein Bibliothekscode, der auch Netzwerkkommunikation kann, nutzt die auch, und ich kann zumindest sagen, dass es bei mir und den Maschinen, auf denen der Code läuft (bisher allerdings immer nur Windows 7-Systeme) kompiliert und funktioniert. Sogar ziemlich gut funktioniert. Besser jedenfalls als sämtliches Python- und Perl-Gerappel da draußen, und da meine ich hauptsächlich die Stabilität der Kommunikation.

    schnief schrieb:

    Ok. Gibt es noch ein umfangreicheres Buch, als Beejs Guide? Ich finde Networking ja irgendwo total spannend und habe ehrlich gesagt kleinere Komplikationen dich nachzuvollziehen. Damit will ich nicht sagen, dass du falsch liegst! Du wirfst halt mit Wörtern um dich, die mir noch nicht bekannt sind und ich auch durch einen Wikipedia-Artikel immer noch nicht ganz nachvollziehen kann.

    Leider wüsste ich kein Buch dazu. 😕 Ich habe mir mein Wissen immer aus verschieden Quellen zusammengeklaubt oder Offenbarungen gehabt, während ich es implementiert habe. Auch eine Menge auf CPU-Ebene getestet. Und auch hier ordentlich rumgelurkt.

    Vielleicht kennt aber noch jemand anderes hier paar gute Referenzen.



  • Ich versteh nicht wo dein Problem mit C++ ist 😛

    Ich finde, damit kommt man richtig geschmeidiger an sein Ziel ran, wenn man es richtig benutzt. Man hat RAII, Ressourcen freigeben und so geht jetzt ganz automatisch von statten. Man hat die STL, in C müsste man entweder eine Library benutzen oder selber Listen bauen => ständig das Rad neu erfinden. In C++ hat man Templates! In C wäre das nur mit Makrogefummel möglich. Und das sieht sauhässlich aus 😉 Man hat Exceptions. Exceptions sind geil. Besser als setjmp.h. Man hat Referenzen. Das ist wie ein konstanter Pointer, der obendrein sogar besser aussieht. In C++ steckt einfach mehr drin, als in C. Reines C ist mir zu mager. Performancemäßig kommt C++ auch an C ran. Gut, die IOstreams sind vielleicht etwas (nur etwas) lahmer als FILE. Aber auf die paar Taktzyklen kommt es nicht an. Ich habe noch nie ein Programm geschrieben, das so an der Performance gelitten hat, dass ich es mit C versucht hätte.

    Bei mir war C++ die erste Sprache, die ich gelernt habe (bei dir war es wohl C) und ich glaube, es liegt auch eigentlich nur daran. In seiner "Muttersprache" fühlt man sich immer noch am wohlsten.

    Oder warum hasst du C++?

    Back to topic:

    Ich habe die Zeile socket.receive(buffer, buffer_size); durch socket.receive(buffer, 1); ersetzt, habe den Sniffer gestartet, F5 im Browser gedrückt. Nichts! Und ich werde select() so lange nicht benutzen, bis das mit ::recv() wieder klappt. Weil, ich sags dir, das hat mal geklappt! Ich hatte den Sniffer manchmal Stunden am Laufen und hat mir immer den gewünschten Output geliefert. Auch wenn das Packet natürlich kleiner war als 4 KB.

    Kann es am Interface liegen? Ich hab Ethernet Kabel und WLAN. Wenn das Ethernet Kabel (funktioniert mit so nem Adapter) nicht funktioniert, benutze ich WLAN. Und derweil benutze ich WLAN. Oder ist es garantiert, dass immer das richtige Interface ausgewählt wird?



  • Bezüglich C++: Ach, darüber habe ich schon so oft geschrieben hier. Am Ende kommt es aber darauf zurück, dass man sich nicht mehr damit beschäftigt, wie teuer bestimmte Operationen sind. Dein erster Code war ja das perfekte Beispiel dafür. Ohne dir zu Nahe treten zu wollen, aber du bist halt ein typischer C++-Programmierer, der sich nicht damit beschäftigt/beschäftigen will, was bestimmter Code für eine Laufzeit hat. Ist ja auch in Ordnung. Deswegen haben wir Abstraktionen. Aber C++ abstrahiert einfach mies.

    Aber in C++ ist das halt schwieriger zu durchschauen, weil nicht nur die Sprache, sondern auch die ganzen Libs drumrum einem das Gefühl geben sollen, sich ja nicht weiter mit dem Code beschäftigen zu wollen. C++ ist eine schlechte Antwort auf die Frage nach besserer Abstraktion, die wir uns schon in C gestellt haben. Die Antwort darauf ist nicht alles noch mehr hinter einfach zu verwendenden Abstraktionen zu verstecken. Die Antwort darauf ist kleiner, modularer Code, den man zusammenstecken kann zu größeren Funktionen. Die Leute, die auf Geschwindigkeit setzen, nehmen den kleinen, die Leute, die Sachen nur schnell fertig bekommen wollen, den zusammengesteckten Code. Damit sind wir dann auch das "ständig das Rad neu erfinden"-Argument weg, das blödeste Argument, dass ich seinerzeit gehört habe - weil es nämlich verschiedene Typen Räder gibt. Die ersten Räder waren Steinräder, danach kamen massive Holzräder, dann einfache, dann komplexere Speichenräder. Ich will dich mit einem Steinrad radfahren sehen. 😉

    C++ verführt einen dazu, mies zu programmieren. C hat kein gutes Framework des Anspruchs wegen, dass es überall kompilieren soll, aber es legt einem auch keine Steine in den Weg. In C bezahlt man für das, was man nutzt. In C++ sieht man mitunter gar nicht mal mehr, was man nutzt.

    Über RAII habe ich mich in der Vergangenheit auch ausgelassen. Es nimmt mir Kontrolle darüber, wann ich Ressourcen reserviere, indem es sofort bereits einen Konstruktor aufruft, ohne dass ich das will.

    Exceptions sind blöd. Es muss wieder ein Objekt auf dem Heap erzeugt und dies dann immer durchgereicht werden. Das ist unnötig langsam und kompliziert. Lieber habe ich Funktionen, die mir Fehlercodes zurückgeben. Und vielleicht hast du's schon gemerkt, auch recv gibt keinen Fehlercode zurück, sondern sagt mir nur, dass errno einen Fehlercode hat. Darüber habe ich mich auch schon aufgeregt. Das sorgt dann nämlich für besonders bescheuerte Entgleisungen, dass recv nur mit ssize_t arbeiten kann. Das ist sauhässlich.

    Referenzen sind ein Argument. Nicht wegen des Aussehens, sondern weil es so nicht möglich ist, mir einen NULL-Pointer unterzuschieben. Aber eventuell benötigt man NULL-Pointer. Wir reden jetzt nicht mal von Kernel-Programmierung. Selbst im User-Space kann ein Prozess versuchen, Speichersparsam zu sein, und virtuelle Adressen unterhalb des .text-Segments zu reservieren. Dann sind NULL-Pointer erlaubt. Aber manchmal halt nicht. Dann will ich keine NULL-Pointer erlauben.

    Für das erste Szenario sind mir Referenzen dann schön im Weg. Im zweiten Szenario ersparen sie mir einen Check. Aber ich kann für das erste Szenario, wenn NULL nicht erlaubt ist, auch abort aufrufen. Wenn mir DANN jemand SIGABRT abfängt, ist das nicht mehr mein Problem. Ähnlich wie bei SIGSEGV . Das aber nur bei wirklich kritischem Code. Ansonsten gebe ich einfach EFAULT zurück.

    schnief schrieb:

    Bei mir war C++ die erste Sprache, die ich gelernt habe (bei dir war es wohl C) und ich glaube, es liegt auch eigentlich nur daran. In seiner "Muttersprache" fühlt man sich immer noch am wohlsten.

    Nö. Ich habe zuerst C++ gelernt. Und dann gemerkt, dass das einfach nicht meins ist. Dann habe ich erst Perl und dann C gelernt. Und C war meins.

    schnief schrieb:

    Oder warum hasst du C++?

    Ich hasse es nicht. Es ist eine Verführerin. Ich lasse mich nicht gerne verführen.
    Das muss aber jeder für sich selbst entscheiden.

    schnief schrieb:

    Ich habe die Zeile socket.receive(buffer, buffer_size); durch socket.receive(buffer, 1); ersetzt, habe den Sniffer gestartet, F5 im Browser gedrückt. Nichts!

    Wenn du "Nichts!" sagst - heißt das, dass die Schleife nicht einmal ausgeführt wird?

    Ich würde mir nach jedem recv in der Schleife den Inhalt des Containers ausgeben lassen. Wenn die Schleife nicht ausgeführt wird - liest du vom richtigen Socket?

    schnief schrieb:

    Kann es am Interface liegen? Ich hab Ethernet Kabel und WLAN. Wenn das Ethernet Kabel (funktioniert mit so nem Adapter) nicht funktioniert, benutze ich WLAN. Und derweil benutze ich WLAN. Oder ist es garantiert, dass immer das richtige Interface ausgewählt wird?

    Wie sagst du denn an, dass du ein spezifisches Interface haben willst? Wie sieht der Code an der Stelle aus?



  • Am Ende kommt es aber darauf zurück, dass man sich nicht mehr damit beschäftigt, wie teuer bestimmte Operationen sind. Dein erster Code war ja das perfekte Beispiel dafür.

    Pass auf, im C hätte ich es vermutlich aus Unwissenheit nicht anders gelöst.

    Aber in C++ ist das halt schwieriger zu durchschauen, weil nicht nur die Sprache, sondern auch die ganzen Libs drumrum einem das Gefühl geben sollen, sich ja nicht weiter mit dem Code beschäftigen zu wollen. C++ ist eine schlechte Antwort auf die Frage nach besserer Abstraktion, die wir uns schon in C gestellt haben. Die Antwort darauf ist nicht alles noch mehr hinter einfach zu verwendenden Abstraktionen zu verstecken. Die Antwort darauf ist kleiner, modularer Code, den man zusammenstecken kann zu größeren Funktionen.

    Aber diesen kleinen, modularen Code, erreichst du doch, indem du die Abstraktionsmechanismen für C++ benutzt! Ich habe mal einen copy-thread-Programm geschrieben, das mir einen Forums-Thread kopiert, aber nicht nur für ein einziges Forum. Da war ich glücklich, dass ich die C++sche Abstraktionsmechanismen hatte. Also habe ich eine abstrakte Klasse geschrieben, die gewisse Dinge verallgemeinert haben und bei den zu vererbenden Klassen musste ich nicht mehr viel programmieren. Einfach nur noch zwei virtuelle Funktionen überschreiben, einmal um die Anzahl an Seiten zu parsen und einmal um den Thread-Titel zu parsen.

    Über RAII habe ich mich in der Vergangenheit auch ausgelassen. Es nimmt mir Kontrolle darüber, wann ich Ressourcen reserviere, indem es sofort bereits einen Konstruktor aufruft, ohne dass ich das will.

    Nein, eben nicht. Ein std::unique_ptr hat zum Beispiel auch einen Default Konstruktor. Mit reset() kannst du den Pointer dann nachsetzen. Andererseits, warum willst du das tun, du erstellst eine Instanz ja schließlich auch erst, wenn du sie brauchst. Und, soweit ich weiß, gibt es auch Optimierungen, die einen leeren Konstruktor einfach wegoptimieren, aus einem leeren Konstruktor entsteht also ein blankes Nichts. Gut, beim std::unique_ptr<> wird der Pointer dann intern auf Null gesetzt, aber wen kümmert das schon. Dir ist doch sicherlich klar, dass das Premature Optimization ist und dass diese paar Taktzyklen doch eigentlich recht egal sind.

    Exceptions sind blöd. Es muss wieder ein Objekt auf dem Heap erzeugt und dies dann immer durchgereicht werden. Das ist unnötig langsam und kompliziert.

    Naja, stell dir vor du schreibst einen Descent Recursive Mathe Parser. Wenn ich dann in der parse_term() -Funktion einen unerwünschten Term habe, bombe ich den Fehler einfach per Exception nach außen, raus aus der parse_term() , raus aus der parse_power() , raus aus der parse_product() , etc. Bis es irgendwo angelangt, wo ich sie abfange und schlussendlich "Neyneyney" ausgeben kann. Und ehrlich gesagt kriegst du das Performance-mäßige eigentlich gar nicht mit. Wo es vielleicht nerven könnte, ist in einer Schleife, die du X mal durchiterierst. Und selbst dann, kannst du es doch aus Performance Gründen anders lösen.

    Aber ich selbst bin jetzt auch kein Profi, d.h. ich will mal nicht behaupten, dass ich in alle dem richtig liege. Will dir auch nicht zu nahe treten, weiß auch nicht was für performance-kritische Programme du schreibst. Aber ich sag ja nur. Bei mir zumindest hat performance-mäßig immer alles gut geklappt. Und wenn es einen Grund gibt auf Exceptions zu verzichten, dann kann man es ja immer noch anders lösen.

    Wenn du "Nichts!" sagst - heißt das, dass die Schleife nicht einmal ausgeführt wird?

    Ich würde mir nach jedem recv in der Schleife den Inhalt des Containers ausgeben lassen. Wenn die Schleife nicht ausgeführt wird - liest du vom richtigen Socket?

    Das heißt, dass es bis ins ::recv() gelangt und ab da einfach blockt. Container ausgeben hab ich bereits. Dazu kommt es halt nicht mehr.

    Wie sagst du denn an, dass du ein spezifisches Interface haben willst? Wie sieht der Code an der Stelle aus?

    Ich habe gelesen mit struct ifreq kann man irgendwie in Konjunktion mit dem Socket ein Interface auswählen. Das werde ich dann mal versuchen. Oder noch besser: Ich zieh das Ethernet Kabel raus. Also wenns dann nicht funktioniert, dann weiß ich auch nicht mehr...



  • Ganz ehrlich, den Fehler kannst du in einem Minimalbespiel in 10 - 20 Zeilen demonstrieren. Dann findeste den Fehler wahrscheinlich auch alleine.



  • courier schrieb:

    Ganz ehrlich, den Fehler kannst du in einem Minimalbespiel in 10 - 20 Zeilen demonstrieren. Dann findeste den Fehler wahrscheinlich auch alleine.

    Es liegt an IPPROTO_RAW. Wenn ich IPPROTO_TCP verwende, bekomme ich irgendwie einen Segmentation Fault... Ich werde mich noch genauer damit beschäftigen und sage Bescheid, wenn ich den Fehler endgültig gefunden habe.

    Danke für den Denkanstoß, courier.


Anmelden zum Antworten