non-blocking sockets - HTTP - Client-Seitig



  • tntnet schrieb:

    Erst mal: der Parser ist in der Datei src/http/parser.cpp zu finden.

    Hm.

    chartoprint erstellt ein Stringobjekt - warum ich das nicht gut finde, habe ich bereits erläutert.

    Die Prüfungsfunktionen ( istokenchar , isHexDigit ...) in http zu packen finde ich merkwürdig. Bei mir habe ich die in einem "zentralerem" Modul.

    Dein Stil macht bei mir irgendwie den Eindruck, als ob du mit der Grammatik haderst - hier mal ein Beispiel:

    void HeaderParser::state_qparam(char ch)
    {
        if (ch == ' ' || ch == '\t')
        {
            log_debug("queryString=" << token);
            ev.onUrlParam(token);
            token.clear();
            token.reserve(32);
            state = &HeaderParser::state_protocol0;
            return;
        }
        else
        {
            token += ch;
            return;
        }
    }
    

    Warum machst du da nicht direkt:

    void HeaderParser::state_qparam(char ch)
    {
        if (ch != ' ' && ch != '\t')
        {
            token += ch;
            return;
        }
    
        log_debug("queryString=" << token);
        ev.onUrlParam(token);
        token.clear();
        token.reserve(32);
        state = &HeaderParser::state_protocol0;
    }
    

    Nur mal so als Beispiel.

    Den Stateful-Ansatz verstehe ich zwar, ich frage mich aber, wie lang die CPU damit beschäftigt ist, Funktionen zur Prüfung von einzelnen Buchstaben aufzurufen. Wie oft rufst du token.reserve(32) auf? Kann man das nicht zentralisieren? Ist das das zentrale Token oder nicht? Man erzählt sich immer, dass Kontextwechsel zwischen Userspace und Kernel-Space teuer sind, aber unnötige Wechsel zwischen Funktionen, in denen auch immer wieder Prolog und Epilog teuer sind, ist auch blöd.

    Byte-weises Lesen und Schreiben ist echt langsam. Lesen noch schlimmer, weil Linux kein memmem mit Boyer-Moore-Algorithmus unterstützt. Mit reserve reduzierst du halt den Bloat des Allokators, aber zumindest das zeichenweise Schreiben in den Token Buffer könnte man vektorisieren ( memcpy ist zumindest auf Linux rasend schnell, ich habe einige Zeit versucht, es zu schlagen, und nur mit einem Assembler-Hack, den mir hustbaer gezeigt hat, auf Windows gesiegt.

    Ich kann dir ein memmem geben. Das ist zwar derzeit ein bisschen kaputt, weil ich keine gecachte Jumptable, die zur Needle gehört, angeben kann, aber eigentlich ist das trivial. Der Grund, warum ich das noch nicht getan habe, ist weil ich noch am nachdenken bin, wie ich diese Tabellen am schönsten statisch generieren lasse.

    tntnet schrieb:

    Dieses Vorgeben von Speicher halte ich nicht für die richtige Lösung. In aller Regel benötige ich die Daten, die vom Server kommen eben nicht vollständig aufeinanderfolgend im Speicher. Idealerweise schreibe ich einen stateful Parser, welcher Byte für Byte vom Server liest und darauf durch Zustandsübergänge reagiert. Dadurch brauche ich zu keinem Zeitpunkt den vollständigen Request im Speicher zu halten.

    Was?

    Du ließt vom Server(-socket), und dann bist du der Client, und liest einen Response. Einen Request zu lesen tut man (in der Regel), wenn man selbst der Server ist, und dann muss man nicht von einem selbst lesen.

    Bei Responses machst du das aber nicht, oder? Weil die Dinger wesentlich größer werden können.

    Hm. Mag ich auch nicht besonders. Mir ist es recht, wenn die Bibliothek nur irgendwas vom Socket liest, was nach HTTP aussieht. Es sei denn natürlich, der Nutzer merkt explizit an, dass er nur Requests oder nur Responses empfangen will, dann kann er schon, nachdem die Statuszeile durchkommt, einen EPROTO zurückgeben. Und deswegen habe ich die Codebasen für Requests und Responses auch zusammengelegt.

    Wobei dein Ansatz natürlich cache-lokaler ist als meiner, das will ich dir lassen.

    tntnet schrieb:

    Im Falle von cxxtools bedeutet das je nach http reply, dass die nächsten Daten entweder direkt vom Socket gelesen werden oder dass noch ein chunkedreader (src/http/chunkedreader.cpp) noch dazwischen ist, der eben genau so arbeitet.

    Erkläre mir mal, wie ChunkedReader::onData zero-copy ist. Für mich ist das nicht direkt ersichtlich:

    www.cplusplus.com schrieb:

    Retrieves characters from the controlled input sequence and stores them in the array pointed by s, until either n characters have been extracted or the end of the sequence is reached.

    tntnet schrieb:

    Ich habe jetzt verstanden, dass Du (Dachschaden, keine Beleidigung 😃 ) getaddrinfo für unglücklich hälst.

    Hältst.

    tntnet schrieb:

    Ja im prinzip ist immer das Problem, dass es kein asyncrones Interface to getaddrinfo gibt. Ich glaube, dass an dieser Stelle cachelokalität gar nicht so ins Gewicht fällt, da hier andere Dinge deutlich teuerer sind.

    Wieso? Der Sinn und Zweck von Caches sind, dass man Kopien in Speicher hält, der schneller zugreifbar ist. Der normale DNS-Cache des OS (oder deines Nameservers oder whatever) ist schneller als ein Roundtrip zum nächsten DNS-Server, aber immer noch langsamer, als bereits aufgelöste DNS-Daten in einer sauberen Tabelle zu haben. Du weißt nicht, was da noch für ein Rattenschwanz am DNS-Cache hängt. Diese Unsicherheit hätte ich gerne auch noch eliminiert.

    tntnet schrieb:

    Im Falle von cxxtools gibt es eine Trennung zwischen client und request. Idealerweise habe ich ein Client-Objekt, welches ich länger halte. Dadurch wird der Verbindungsaufbau inklusive Namensauflösung nur ein mal gemacht. Die Verbindung wird dann für mehrere Request wieder verwendet.

    Bei mir wird es auch aufgeteilt - wobei der Nutzer sich jetzt nicht mit der Wiederverwendung von Sockets und dergleichen rumschlagen muss.

    tntnet schrieb:

    Du hast auch noch erwähnt, dass chunked encoding schlecht ist, weil man dadurch kein sendfile verwenden kann. Das schöne ist, dass der Server entscheiden kann, was für den Anwendungsfall geeignet ist.

    Ich meinte das jetzt mehr auf den Client bezogen. Hat sich für mich so angehört, als ob der OP für seine Implementierung nur chunked-transfer unterstützen will, weil Content-Length nicht vertrauenswürdig ist.

    tntnet schrieb:

    cxxtools wirft im Falle von Fehlern exceptions. Ich könnte jetzt noch darüber schreiben, wie praktisch Exceptions sind und wie elegant man damit robuste Programme schreiben kann, aber darüber wurde schon so viel geschrieben.

    Das Gleiche könnte ich über Forward-Goto schreiben, mit dem Unterschied, dass ich nicht den Heap ansprechen muss, um mir ein Fehlerobjekt zu erstellen. Aber egal.



  • Blockade schrieb:

    Du gehst halt davon aus, dass der Server verdammt ehrlich mit dir ist, und dass du seinen Headern traust.

    Ich gehe lediglich davon aus, dass der Server HTTP spricht. Und da muss der Content length header stimmen. Ich habe Eingangs die 3 Varianten erläutert, wie man bei HTTP das Ende erkennt. Content length ist eine der 3 Varianten. Und sehr gebräuchlich. Es kann nicht sein, dass die Größe des Bodies mehr oder weniger Bytes enthält, da genau das der Indikator ist, dass der Response vollständig ist. Mehr Bytes kann schon gar nicht sein, da ich nur exakt diese Anzahl lese und bei weniger ist der Response halt noch nicht an Ende. Wie willst Du Dich auf Deinen Code verlassen? Wie soll er erkennen, dass die Antwort fertig ist?



  • tntnet schrieb:

    Mehr Bytes kann schon gar nicht sein, da ich nur exakt diese Anzahl lese und bei weniger ist der Response halt noch nicht an Ende.

    Warum eigentlich?



  • Wie soll er erkennen, dass die Antwort fertig ist?

    Bei blocking Sockets mach ich das mit Timeouts + Connection: close. Also entweder überprüfen, dass der Server seit geraumer Zeit nicht mehr antwortet, oder überprüfen, ob der Socket geschlossen wurde.

    dachschaden, wie machst du Syntaxprüfung für den Response-Body? Der Server kann dir doch jedes erdenkliche Dateiformat zurückgeben. Im Content-Type-Header kann ja auch nur Müll stehen?



  • Blockade schrieb:

    dachschaden, wie machst du Syntaxprüfung für den Response-Body? Der Server kann dir doch jedes erdenkliche Dateiformat zurückgeben. Im Content-Type-Header kann ja auch nur Müll stehen?

    Ich verwende nicht den Stateful-Ansatz von tntnet, sondern packe halt alles in einen Buffer und schreibe dann Zwischenstationen in ein Stateobjekt, damit ich z.B. nicht immer das Layout neu holen muss.

    Erste Station: Status Line End holen (erstes "\r" oder "\n" oder "\r\n").
    Zweite Station: Header Section End holen (erstes "\r\r" oder oder "\n\n" oder "\r\n\r\n").
    Dann kurz in der Header-Sektion schauen, ob wir "Transer-Encoding: chunked" haben, oder ob wir "Content-Length" haben. In beiden Fällen fängt man beim Ende der Header-Sektion (dem Beginn der Content-Sektion) an zu zählen. Habe ich weniger Bytes, sage ich der Lesefunktion, sie soll mal weitermachen. Habe ich wenigstens genug Bytes (sprich, ich habe sogar mehr als erwartet - wegen Pipelining), dann gebe ich ein A-OK zurück, und die Leseschleife bricht ohne Rollback ab.

    Merke: dieser Parsercode wird einmal aufgerufen, wenn ein HTTP-Objekt geholt werden soll (wegen Pipelining; auf diese Weise kann ich bereits prüfen, ob mir noch Objekte in den Buffer geschrieben wurden, ohne auch nur einmal select oder read/recv aufrufen zu müssen, denn diese können in den Timeout laufen - denn ich habe die HTTP-Daten von der Gegenseite ja schon) und wird auch nur dann wieder ausgeführt, wenn die Parsing-Funktion sagt, dass die Daten bisher schön aussehen, aber dass da noch was fehlt. Wenn sie die Daten nicht versteht, wird an die Lesefunktion zurückgegeben, dass sie aufhören soll und dass wir halt verloren haben (was dann mit einem Rollback von Buffern verbunden ist, aus denen sich der Nutzer aber immer noch die Daten aus einem Positionsobjekt rauspoppeln kann).

    Danach lass ich dann ein Objekt für den einfachen Zugriff auf diese Daten zeigen (keine Kopie, das Ding zeigt direkt auf die Daten im Buffer).

    In Header-Feldern kann Müll drinstehen, das ist wahr. Was sind deine Schutzmöglichkeiten?

    1. Timeout.
    Wenn nicht schnell genug Daten vorliegen, kannst du zumachen und die Ressourcen für was besseres nutzen. Kann ja sein, dass jemand gerade einen DoS versucht.
    2. Bufferlimit.
    Jemand gibt dir eine Content-Length von 12345678901234567890123456789? Kannst du den Socket direkt dichtmachen. Je nachdem, welches Limit du für die Verbindung fahren willst.

    Damit fängst du zu wenig und zu viele Daten ab. Gut, wenn ich einen echten DoS fahren würde, dann würde ich dir erst alle 5 Sekunden oder so ein Byte zusenden, bis du die Content-Length hast. Oder halt nur Chunked-Transfer mit einem 1, bevor der Timeout wieder abläuft. Du könntest ja einstellen, dass du pro Kommunikationsobjekt nur einen Timeout von 5 Sekunden maximal einstellst, anstatt dass der Timeout bei jedem erfolgreichen read/recv erneuert wird. Und bestimmt möchtest du auch prüfen, ob der Kamerad Zinnsoldat, mit dem du verbunden bist, nicht schon 900 andere Verbindungen aufhat oder so. Aber das sind relativ leichte Tuning-Sachen, das hat so direkt erstmal nichts mit HTTP zu tun.



  • dachschaden schrieb:

    Wenn nicht schnell genug Daten vorliegen, kannst du zumachen und die Ressourcen für was besseres nutzen. Kann ja sein, dass jemand gerade einen DoS versucht.

    Wie kann man denn da DoS machen? Was genau macht man damit kaputt, außer einen weiteren offenen Deskriptor zu haben?



  • Blockade schrieb:

    Wie kann man denn da DoS machen? Was genau macht man damit kaputt, außer einen weiteren offenen Deskriptor zu haben?

    Wenn du einen Server aufmachst, machst du in der Regel einen listen -Aufruf. Dem übergibst du ein Backlog, also wie viele eingehende Verbindungen du so haben willst. Wenn ich dich DoSen wollte, würde ich also so viele Verbindungen aufmachen, wie ich wollte, damit die anderen keine Slots bekommen.



  • dachschaden schrieb:

    Offtopic-Bullshit, mein zweiter Post ist wieder on-topic:

    camper schrieb:

    Warum schlägst du es nicht mal nach und lenkst nicht durch Anderes ab?

    In deinem Fall würde dann die "Beleidigung" umschlagen in eine Situation, in der sich die "Linken" (sowohl Deutschland als auch die U.S.A.) über einzelne Wörter echauffieren. "Did you just assume my gender?" hört sich erst mal wie ein netter Scherz an, bis man sich vergegenwärtigt, dass es Leute gibt, die bereits dadurch beleidigt sind, dass man ihr physikalisches Geschlecht benannt hat.

    Und für so 'ne Kinderkacke habe ich wieder Zeit noch Bock. Wenn mir jemand sagt, da und da stinkt mein Code, dann danke ich ihm - wenn die Vorwürfe stimmen. Wenn mir jemand sagt, ich stinke aus dem Mund, dann entschuldige ich mich und versuche, den Geruch zu entfernen/übertünchen. Vielleicht mag es bei dir angebracht sein, den Boten zu erschießen - aber ich mache das nur dann, wenn der Bote Unsinn erzählt.

    Und hier lügst du ja auch nur wieder und lenkst ab.
    1. Offensichtlich nimmst du dir ja doch die Zeit und musst unbedingt das letzte Wort haben. Da ist jede Menge Bock da. Im anderen Thread hast du ja auch groß angekündigt, nicht mehr mitspielen zu wollen, und dann kamen doch noch mehrere Antworten. Kindergarten? Ja, da stimme ich zu.
    2. Ich sehe nicht einmal im Ansatz was der erste Absatz überhaupt bezwecken soll oder mit dem Thema zu tun hat. Du behauptest nur etwas ohne auch nur im Ansatz zu skizzieren, wie dies logisch mit der Diskussion zusammenhängen soll.

    dachschaden schrieb:

    camper schrieb:

    Das Erste, was auffallen muss, ist, dass es auf den Wahrheitsgehalt der Aussage nicht ankommt. Das Wie, Warum und Wann (Kontext!) kann erheblich wichtiger sein.

    Viele Menschen ordnen das so ein, ja.

    Viele Menschen leben auch in einer Blase, in der Sachverhalte schön konsistent bleiben. Und wenn neue Sachverhalte reinkommen, die die alten invalidieren, dann wird zurückgefeuert. Backfire-Effekt, halt.

    Mit sowas halte ich mich nicht auf, sorry. Wenn du dadurch beleidigt wirst, dass ich deinem Code ein negatives, aber objektiv nachvollziehbares Attribut zuweise, welches nicht nur darauf abzielt, die Person anzugreifen ("Was für ein undurchsichtiger Scheißcode"), dann solltest du künftig keinen Code mehr veröffentlichen. Das ist das Beste für uns und für dein Ego.

    Ja, aufhalten tust du dich mit anderen Dingen. Etwa damit, Leuten deutlich zu machen, für wie bescheuert du sie hälst. Meinen Code jedenfalls hast du nicht kritisert - denn um so etwas ging es ja gar nicht. Wiederum postet du nur ein hypothetisches Szenario ohne Relevanz zu dem was faktisch vorfällt.

    dachschaden schrieb:

    camper schrieb:

    Das Beleidigung kann sich auch aus dem, was nicht gesagt aber impliziert wird, ergeben. Auch muss die Beleidigung nicht unbedingt in der Absicht des Senders liegen, sondern kann auf einem Missverständnis beruhen.

    Deswegen gibt es das Konzept der Nachfrage.

    Wahr. Und irrelevant: der Text war gut genug strukturiert um zu erkennen, dass ich zunächst einmal nur die zitierte Wikipediadefinition analysiert habe. Die Subsumtion folgt ja erst im nächsten Absatz.

    dachschaden schrieb:

    camper schrieb:

    Es kann eine Beleidigung sein. Insbesondere (aber nicht ausschlieslich, s.o.) dann, wenn diese Behauptung nicht durch durch entsprechende Beobachtungen gedeckt sind.

    OK, dann denk' dir mal folgenden Sachverhalt:

    - ich sage "X".
    - du sagst, ich hätte "XYZ" gesagt.
    - ich sage, du halluzinierst.

    Jetzt kann es natürlich sein, dass ich nur halluziniert hätte, dass du "XYZ" gesagt hast - aber wenn das so ist, warum hast du dann bisher kein konkretes Beispiel benannt? In diesem Thread oder in dem anderen? Stattdessen geht es hier um Meinungen. Meinungen kann man in der "Jenseits der Programmierung"-Sektion loswerden. Gib mir harte Fakten.

    Hypothetische Szenarien; wie du selbst verlangst, soll ich mir das nur denken, es ist also nicht das, was vorgefallen ist und tatsächlich zur Diskussion steht, nähmlich:
    - du sagst "X"
    - ich antworte erstmals im Thread und erkläre was es bedeutet, wenn du "X" sagst
    - du sagst, ich halluziniere.
    Eben weil ein Werturteil als solches kein wahrnehmbarer Fakt ist (nur die Tatsache, dass es getätigt wurde, ist eines), ist hier keine logische Verbindung zu deiner Erwiderung erkennbar. Mithin - weil ich ja nicht überzeugt bin, dass dir an kognitiven Fähigkeiten mangelt - kann ich nur unterstellen, dass du provozieren und beleidigen willst. Die harten Fakten sind das, was zuvor gepostet wurde. Grundregel in einem Diskurs kann nur sein, das derjenige, der eine Behauptung aufstellt, hierfür auch die Belege zu liefern hat. Das hast du beim Thema "du hallzunierst" bisher nicht ein einziges mal getan. Warum bloß? Um abzulenken gehst du dann gleich noch einen Schritt weiter und analysierest, warum jemand halluzunierte (Link zu Kognitiver Dissonaz etc.). Das ist eine ganz typische Strategie, Diskurs zu vermeiden und nicht zu versuchen, Konsens zu erreichen, sondern bloß recht zu haben. Es offensichtlich auch ein ad hominem Angriff. Warum? Weil es impliziert, dass es solcher Belege wegen Offensichtlichkeit nicht bedarf, und der andere Teilnehmer nur zu blöde ist, das zu erkennen. Er wird also nicht als gleichwertiger Diskussionspartner angesehen. Das ist dann auch eine Form der Herabwürdigung.

    dachschaden schrieb:

    Juristisch ist nämlich Beleidigung definiert:

    Die Beleidigung trotz Wahrheitsbeweis: Eine solche liegt vor, wenn trotz einer Kundgabe wahrer Tatsachen gegenüber Dritten den Betroffenen gewissermaßen "an den Pranger stellen" würde, d.h. bei der Behauptung inhaltlich ein besonders herabwürdigender Tonfall oder eine besonders gehässige Einkleidung feststellbar wäre.

    Iss klar...
    Den Beweis hast du nie erbracht, die Erweislichkeit wird von der anderen Seite bestritten. Klar, dass du natürlich trotzdem nur auf den Fall betrachten musst, dass du recht hättest... also wieder hypothetisches Szenario und ausserdem Thema verfehlt: schliesslich habe ich mich auch ausdrücklich nicht auf eine juristische Definition bezogen. Die ist nähmlich in Hinblick auf Verhaltensnormen im Forum nicht besonders nützlich: wäre die juristische Dimension alles, was zu beachten ist, bräuchten wir so etwas wie Etiquette nicht. Die hätte dann bloß deklatorische Bedeutung und sanktionierte, was sowieso schon - von Gesetzes wegen - verboten ist.

    Deine Argumentation läuft darauf hinaus zu behaupten, dass weil dein Verhalten nicht strafwürdig (i.S.d. Strafrechts) ist - einer Ansicht, der ich ohne Weiteres zustimme - sie auch nicht kritikwürdig sein kann.

    Ich bebsichtige nicht, durch Masse zu beeindrucken. Auf den Rest antworte ich nicht, weil die Argumenation nach dem gleichen - fehlerhaften - Schema erfolgt (ich habe mir auch nicht die Mühe gemacht, bis ganz zum Ende zu lesen: das wäre eine Quälerei wegen mangelnder Relevanz und innerer Konsistenz). Wie ich schrieb, funktioniert meine Argmentation im Wesentlichen auch unter Zugrundelegung eine juristischen Beleidigungsbegriffes; damit sollte deutlich sein, dass ich gleichzeitig meine, dass sie ohne Umformulierung nicht Wort für Wort darunter subsumiert werden kann. Klar dass du natürlich kleinlich trotzdem jeden Satz einzeln genau unter diesem Gesichtspunkt auseinandernehmen must... und dann kommen auch gleich wieder die Wenn-Szenarien ohne jede gezeigte Relevanz. Offensichtlich soll hier nur durch viel Text erschlagen werden.



  • dachschaden schrieb:

    Dein Stil macht bei mir irgendwie den Eindruck, als ob du mit der Grammatik haderst - hier mal ein Beispiel:

    void HeaderParser::state_qparam(char ch)
    {
        if (ch == ' ' || ch == '\t')
        {
            log_debug("queryString=" << token);
            ev.onUrlParam(token);
            token.clear();
            token.reserve(32);
            state = &HeaderParser::state_protocol0;
            return;
        }
        else
        {
            token += ch;
            return;
        }
    }
    

    Warum machst du da nicht direkt:

    void HeaderParser::state_qparam(char ch)
    {
        if (ch != ' ' && ch != '\t')
        {
            token += ch;
            return;
        }
    
        log_debug("queryString=" << token);
        ev.onUrlParam(token);
        token.clear();
        token.reserve(32);
        state = &HeaderParser::state_protocol0;
    }
    

    Nur mal aus Neugier: Was ist daran so schlimm? IF..ELSE ist doch korrektes C++. Die beiden "return" könnten hier allerdings entfallen. Da die Funktion nach dem ELSE sofort endet würde auch das gehen:

    void HeaderParser::state_qparam(char ch)
    {
        if (ch == ' ' || ch == '\t')
        {
            log_debug("queryString=" << token);
            ev.onUrlParam(token);
            token.clear();
            token.reserve(32);
            state = &HeaderParser::state_protocol0;
            return;
        }
    
        token += ch;
    }
    

    Ich finde ja das sich "Wenn A oder B" leichter lesen lässt als "Wenn nicht A und nicht B".



  • temi schrieb:

    Nur mal aus Neugier: Was ist daran so schlimm? IF..ELSE ist doch korrektes C++. Die beiden "return" könnten hier allerdings entfallen. Da die Funktion nach dem ELSE sofort endet würde auch das gehen

    Kritischer Pfad und so. Deine Funktion hat eine Aufgabe; der Hauptpfad sollte nicht irgendwo abzweigen, sondern bis zum Ende durchgebracht werden.

    Korrekt ist das natürlich. Aber auf mich macht das immer den Eindruck, dass die Person nicht über den Code nachgedacht hat. Ich war mal bei einer Firma, in der der Chefprogrammierer den kritischen Pfad in 5 oder 6 if-Scopes hatte (EDIT: über 1000 Zeilen). Was das für die Lesbarkeit bedeutete, muss ich dir hoffentlich nicht erklären.

    temi schrieb:

    Ich finde ja das sich "Wenn A oder B" leichter lesen lässt als "Wenn nicht A und nicht B".

    Finde ich nicht. "Wenn nicht A und nicht B, dann raus. Ansonsten weitermachen." Wobei man sich das "ansonsten" direkt sparen kann, weil ja schon raus ist.



  • dachschaden schrieb:

    OK, dann denk' dir mal folgenden Sachverhalt:

    - ich sage "X".
    - du sagst, ich hätte "XYZ" gesagt.
    - ich sage, du halluzinierst.

    Das ist doch genau die Art und Weise, wie Du kommunizierst. Ich würde auf die "XYZ" aussage nicht mit einem Affront reagieren und meinem Kommunikationspartner angreifen, indem ich sage, dass er halluziniert. Eher würde ich ihn freundlich und respektvoll darauf aufmerksam machen, dass er mich offensichtlich falsch verstanden hat und versuchen, meine Aussage zu präzisieren. Da werfe ich ihm nichts vor, sondern halte es durchaus für möglich, dass ich mich unklar ausgedrückt habe. Die Reaktion "du halluzinierst" ist ein Vorwurf.

    Suche mal nach ich und du Botschaften. Darüber ist viel geschrieben worden.

    Und jetzt nochwas zum Thema:

    dachschaden schrieb:

    Wenn du einen Server aufmachst, machst du in der Regel einen listen -Aufruf. Dem übergibst du ein Backlog, also wie viele eingehende Verbindungen du so haben willst. Wenn ich dich DoSen wollte, würde ich also so viele Verbindungen aufmachen, wie ich wollte, damit die anderen keine Slots bekommen.

    Der Backlog Parameter sagt nicht aus, wie viele Verbindungen der Socket aufnehmen kann. Er sagt lediglich aus, wie viele Verbindungsanfragen gesammelt werden, die noch nicht mit accept angenommen wurden. Schreibe ich einen Server, werde ich jede Verbindung unmittelbar mit accept annehmen und damit den Backlog abbauen.

    Du machst auf mich den Eindruck, als wüsstest Du alles. Aber in dem Fall glaube ich, dass Du was falsch verstanden hast. Oder ich habe mal wieder Deine Erläuterung missinterpretiert oder eventuell sogar halluziniert?

    Ach ja - und dieses if-else geraffel in meinem Code könnte ich tatsächlich klarer schreiben. Du hast Recht. Normalerweise achte ich sehr darauf, aber an der Stelle war ich offensichtlich ein wenig nachlässig. Ich ändere das gleich mal.



  • Jetzt habe ich mir die Stelle im Code noch mal genauer angeschaut. Isoliert betrachtet sollte man das return weg lassen. Aber im größeren Kontext lasse ich es lieber drin. Es ist so klarer.

    Im nächsten State sieht man es:

    void HeaderParser::state_protocol0(char ch)
        {
            if (ch == ' ' || ch == '\t')
            {
                return;
            }
            else if (std::isalpha(ch))
            {
                token.reserve(32);
                token = ch;
                state = &HeaderParser::state_protocol;
                return;
            }
            else
            {
                log_warn("invalid character " << chartoprint(ch) << " in http protocol field");
                state = &HeaderParser::state_error;
                return;
            }
        }
    

    Hier könnten die returns auch weg gelassen werden. Allerdings gleich im ersten if-Block steht ein einsames return. Logisch könnte ich es weg lassen, aber dann ist nicht auf dem ersten Blick klar, dass hier bei einem space oder tab nichts gemacht werden soll. Whitespace soll an der Stelle einfach übersprungen werden, führt also zu keinem anderen Zustand.

    Daher halte ich es für sinnvoll, die returns konsistent im gesamten Parser genau so zu setzen. Leider habe ich mich aber selbst nicht daran gehalten, sondern in anderen Zuständen dann doch aufs return verzichtet. Konsistent ist das nicht. Ich denke, in dem Fall ist das aber entschuldbar, da jeder Zustand für sich sehr übersichtlich ist. Also ich verzeihe mir zumindest mal 😃 .

    Das ist übrigens auch eine schöne Stelle, wo Daten vom Socket gelesen werden und gleich verworfen werden, da sie nicht relevant sind. Dafür brauche ich keinen Platz im Puffer. Auch andere Zeichen führen nur zu Zustandsübergängen und werden nicht zwischengespeichert.

    Ein solcher zustandsbasierter Parser macht das parsen von komplexen Zusammenhängen relativ überschaubar.

    Sicher könnte man bemängelt (und das tut Dachschaden ja auch), dass für jedes Zeichen ein Funktionsaufruf erfolgt. Ich habe andere Parser, wo das ganze in einem großen switch-case Ausdruck statt findet (z.B. src/uri.cpp). Das mit dem Aufruf haben wir mal ausprobiert. Bei Benchmarks haben wir keinen Performanceunterschied ausmachen können.



  • Wenn du überall return machst brauchst du aber auch kein else if mehr.



  • tntnet schrieb:

    Das ist doch genau die Art und Weise, wie Du kommunizierst. Ich würde auf die "XYZ" aussage nicht mit einem Affront reagieren und meinem Kommunikationspartner angreifen, indem ich sage, dass er halluziniert.

    Finde ich nicht. Wenn ich, wie du es formulierst, freundlich und respektvoll darauf aufmerksam mache, dann schone ich vielleicht deine Gefühle, aber du lernst dadurch nicht, es künftig zu vermeiden. Dadurch, dass ich direkt das Faktum nenne, wirst du in der Zukunft versuchen, diese Negativität zu vermeiden und die Texte deines Gegenüber korrekt zu lesen. Sonst ist der Anreiz einfach nicht groß genug.

    Es kostet mich mehr Mühe und Zeit, fehlerhafte Aussagen durchzulesen und zu korrigieren, als dich, sie direkt korrekt niederzuschreiben.

    Ob du das dadurch tust, indem du dein Umfeld änderst, oder indem du dich änderst, ist mir dann relativ latte. Ich bin nur halt gerne von Leuten umgeben, die schlauer sind als ich.

    tntnet schrieb:

    Der Backlog Parameter sagt nicht aus, wie viele Verbindungen der Socket aufnehmen kann. Er sagt lediglich aus, wie viele Verbindungsanfragen gesammelt werden, die noch nicht mit accept angenommen wurden. Schreibe ich einen Server, werde ich jede Verbindung unmittelbar mit accept annehmen und damit den Backlog abbauen.

    man listen(2) schrieb:

    The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow.

    Hm. Ich habe immer "pending connections" als die Anzahl an Verbindungen gelesen, die der Socket offen haben lassen kann, nicht als die Anzahl an Verbindungen, die der Kernel noch vorhält. Aber der Absatz macht wohl deutlich, dass du recht hast:

    man listen(2) schrieb:

    The behavior of the backlog argument on TCP sockets changed with Linux 2.2. Now it specifies the queue length for completely established sockets waiting to be accepted, instead of the number of incomplete connection requests.

    tntnet schrieb:

    Es ist so klarer.

    Ach?

    void HeaderParser::state_protocol0(char ch)
    {
        if (ch == ' ' || ch == '\t')
            return;
    
        if (!std::isalpha(ch))
        {
            log_warn("invalid character " << chartoprint(ch) << " in http protocol field");
            state = &HeaderParser::state_error;
            return;
        }
    
        token.reserve(32);
        token = ch;
        state = &HeaderParser::state_protocol;
        return;
    }
    

    Du findest also, dass das nicht klarer aussieht? Wenn ich mir die Funktion durchlese, sehe ich:

    "Aha, wenn das Zeichen ein Whitespace ist, raus! Super! Aus den Augen, aus dem Sinn.
    Wenn std::isalpha meint, es ist keines, loggen, Fehlerstatus setzen, raus! Wieder super!
    Invaliditätsprüfung ist durch, jetzt geht's an Fleisch der Funktion: reserviere Speicher, packe das Zeichen schonmal rein, und markiere, dass es schonmal ganz gut aussieht."

    Bei deinem Code denke ich:

    "Aha, wenn das Zeichen ein Whitespace ist, raus! Super! Aus den Augen, aus dem Sinn ... nee, warte mal. Wenn nicht, dann prüfen, ob std::isalpha meint, es ist eines. Wenn es eines ist, dann ... ist das das Fleisch der Funktion? Reserviere Speicher, packe das Zeichen da rein, und markiere, dass es schonmal ganz gut aussieht? Sieht so aus. Ansonsten ... das heißt, wenn es kein Zeichen im Alphabet ist ... dann logge und setze Fehlerstatus."

    Merke, wie ich beim ersten Mal direkt annehme, dass es sich hier um die Prüfung auf Korrektheit handelt (Invaliditätsprüfung). Das ist ein vertrautes Konzept, damit kann der Leser sofort arbeiten. Das habe ich kein einziges Mal bei dir im Code gedacht.

    tntnet schrieb:

    Das ist übrigens auch eine schöne Stelle, wo Daten vom Socket gelesen werden und gleich verworfen werden, da sie nicht relevant sind.

    Das habe ich auch nicht angegriffen. Ich habe bereits gesagt, dass dein Code cache-lokaler ist als meiner, was durchaus ein Vorteil sein kann.

    Auf der anderen Seite speichere ich mir ja auch mein Layout ab, und die Daten werden so oder so durch den Cache geschliffen (Schreiben von Kernel- in Userspace). Solange ich also beim nächsten recv dann wieder mit dem Prüfen anfange, macht mir das gar nichts, die neusten Daten sind bereits im Cache.

    Was ist, wenn ich große Requests absetze? File-Upload bspw.? Oder bei Responses? Vielleicht habe ich die Antwort nur übersehen, aber gesehen habe ich noch keine.

    EDIT: Warum immer dieser Code hier?

    token.reserve(32);
    token = ch;
    state = &HeaderParser::state_protocol;
    

    Nach dem dritten mal würde ich mir denken: "Komm, machen wir mal 'ne Funktion draus:

    /*Bin gerade zu faul, den Typen von state zu suchen ...*/
    inline void add_token
    (
        state_type new_state,
        char ch,
        size_t reserve_size
    )
    {
        token.reserve(reserve_size);
        token = ch;
        state = new_state;
    }
    

    , und fertig ist die Laube."

    Wenn ich ganz lustig wäre, dann würde ich noch eine inline-Funktion schreiben:

    inline void add_token_with_default_size
    (
        state_type new_state,
        char ch
    )
    {
        add_token(new_state,ch,32);
    }
    


  • Ok ich hab mir den Code jetzt auch mal kurz angesehen.

    Tausend Zeilen Code für so einen popeligen Header? Dann ein Funktionsaufruf per Zeichen noch dazu? Das find ich recht erschlagend.

    Warum benutzt du keine Streams. Den ganzen Header-Parser hättest du auch mit 3 Methoden und 50 Zeilen Code machen können. Fehlerprüfung inklusive.



  • Ob man jetzt if else oder return oder sonst was schreibt ist denke ich mal relativ egal. So wie es ist, finde ich es schon einigermaßen lesbar. Ich glaube, dass das nicht wirklich relevant ist. Ich habe es mir ja schon verziehen. Vielleicht tut ihr es ja auch.

    @Blockade: Und so ein paar poplige Header sind das nicht. Schau Dir rfc 2616 an. Wenn Du jede Nuance ganz allgemeingültig unterstützen möchstest, ist das nicht mit ein paar string.find-Funktionen getan. Schau Dir einfach mal die continuation header an. Da wird es immer schwerer. Einen stateful parser finde ich persönlich einfacher zu warten und robuster. Es hat sich bewährt.

    Ein einfacher Parser ist schnell geschrieben aber ein robuster vollständig standardkonformer macht da schon ein wenig mehr Arbeit.

    Und ja, es werden Streams benutzt. Ich weiß nicht, wo Du an der Stelle Streams sehen willst.

    Es ist auch Code, den man als normaler Benutzer nicht beachten muss. Dafür sind ja Libraries da.



  • dachschaden schrieb:

    tntnet schrieb:

    Das ist doch genau die Art und Weise, wie Du kommunizierst. Ich würde auf die "XYZ" aussage nicht mit einem Affront reagieren und meinem Kommunikationspartner angreifen, indem ich sage, dass er halluziniert.

    Finde ich nicht. Wenn ich, wie du es formulierst, freundlich und respektvoll darauf aufmerksam mache, dann schone ich vielleicht deine Gefühle, aber du lernst dadurch nicht, es künftig zu vermeiden. Dadurch, dass ich direkt das Faktum nenne, wirst du in der Zukunft versuchen, diese Negativität zu vermeiden und die Texte deines Gegenüber korrekt zu lesen. Sonst ist der Anreiz einfach nicht groß genug.

    Ach, das ist wieder das. Du hältst Dich für unfehlbar. Wenn einer was falsch versteht, was Du gesagt hast, muss der Kommunkationspartner halluzinieren. Dass Du etwas unverständlich formulierst ist völlig undenkbar und Du musst unbedingt und in aller Deutlichkeit dieses Faktum betonen, dass der Partner Deine Aussage nicht korrekt gelesen hat. Und Du musst auch den Kommunikationpartner erziehen, dass er doch Deine ach so wertvollen und wohlformulierten Worte korrekt zu lesen hat. Das nenn ich mal überheblich und "Ego-Trip".



  • tntnet schrieb:

    Ach, das ist wieder das. Du hältst Dich für unfehlbar. Wenn einer was falsch versteht, was Du gesagt hast, muss der Kommunkationspartner halluzinieren. Dass Du etwas unverständlich formulierst ist völlig undenkbar und Du musst unbedingt und in aller Deutlichkeit dieses Faktum betonen, dass der Partner Deine Aussage nicht korrekt gelesen hat.

    OK, jetzt reicht's.

    Gerade erst in dem letzten Post habe ich zugegeben, dass ich die verschissene Manpage nicht richtig gelesen habe. Das kann man als Halluzination interpretieren. Ich hätte es sogar verstanden. Aber vor allem: ich hatte erwartet, dass du das auch raffst.

    Tust du nicht. Confirmation Bias. Alles, was dir gefällt, sieht du, das andere siehst du nicht. Du halluzinierst gerade wieder und siehst es nicht einmal. Kannst du gerne als Beleidigung auffassen.

    Viel Spaß noch auf deiner Halluzination.



  • Ja.

    Ihr habt mir den halben Thread zugekackt.



  • Blockade schrieb:

    Ihr habt mir den halben Thread zugekackt.

    YMMD 😃 😃 😃


Anmelden zum Antworten