Hallo,
ich beschäftige mich mit boost.asio für TCP/IP und habe jetzt einen
"gewissen Überblick" würde aber gerne nochmal auf ein paar Dinge
eingehen:
Frage 1: asynchron vs synchron:
Wenn ich es richtig verstehe läuft es so:
async_write: blockiert nicht, code wird anschließend sofort fortgeführt.
write: blockiert solange bis Daten in die Netzwerkkarte/OS geschrieben
sind.
https://www.boost.org/doc/libs/1_75_0/doc/html/boost_asio/reference/async_write/overload1.htmlhttps://www.boost.org/doc/libs/1_75_0/doc/html/boost_asio/reference/write/overload1.html
Zu async allerdings das:
ZITAT: "data muss jedoch solange existieren, bis die asynchrone
Operation beendet ist und die Variable nicht mehr benötigt wird. Indem
data als globale Variable definiert ist, ist sichergestellt, dass sich
der Gültigkeitsbereich der Variablen über die gesamte asynchrone
Operation erstreckt."
https://dieboostcppbibliotheken.de/boost.asio-netzwerkprogrammierung
Ich frage mich daher was das asynchrone senden von Daten für einen
realen Vorteil hat, wenn doch die Quellvariablen solange existieren
müssen, bis die asynchrone Operation beendet ist. Natürlich ist das
reine async_write zunächst schneller weil es nicht blockiert aber dafür
zu sorgen, dass die Quellvariablen im Scope vorhanden bleiben müssen,
hebelt die Vorteile von der asynchronen Operation doch völlig aus!? Ich
sehe da eigentlich das synchrone write im Vorteil denn direkt danach
darf die Quelle verschwinden/überschrieben werden. Das bietet meiner
Meinung nach auch weniger Fehlerpotential. Liege ich da richtig oder hab
ich was übersehen?
Die folgenden Fragen 2+3 sind natürlich ohne konkrete Messungen nicht
präzise zu beantworten und je nach Infrastruktur auch etwas anders, es
geht mir eher eine grobe Vorstellung zu bekommen, einen Überblick wie
sich das etwa verhält. Ganz grobe Einschätzungen/Tendenz wäre schon
super interessant für mich.
Frage 2: Geschwindigkeit:
Wie schnell ist das synchrone write ungefähr abgearbeitet, sind das ganz
grob eher ns oder ms (Wenn man Beispielsweise 80 Byte sendet)?
Fällt eher der Basis-overhead für das write ins Gewicht oder hängt das
komplett nur von der Datenmenge ab die gesendet wird?
Ich vestehe es so, dass write die Daten in die Netzwerkkarte/OS schickt
und wenn diese alles aufgenommen hat gehts weiter. Wenns erstmal ins OS
geht und dann in die Netzwerkkarte dann ist ein synchrone write
sicherlich auch recht flott oder.
Frage 3: Datenmenge:
Gibt es eine ungefähre Datenmenge ab der sich das synchrone write
merklich verlangsamt weil Netzwerkkarte/OS nicht mehr
aufnehmen/verarbeiten kann?
Also sind zB 80 Byte und 2 MB fast gleich schnell aber dann 50MB
besonders langsam? Gibt es sowas wie eine Grafik wo man Datengröße und
dessen Dauer eines synchronen writes im Zusammenhang sieht? Ist das mit
steigender Datenmenge eher eine lineare Kurve oder exponentiell oder
gibts irgendwo einen Sprung (weil zB Netzwerkkarte bestimmten Speicher
hat)?
Frage 4: Bleibt ein gesendeter Daten-Block immer zusammenhängend?:
Vorausgesetzt es läuft im Netzwerk alles normal und Fehlerfrei, kann man
sich darauf verlassen, dass der Empfänger immer das komplette Stück
Daten in der gleichen Byte-Reihenfolge bekommt wie es durch write
rausging?
Beispielsweise wenn jetzt in verschiedenen Threads mehrere writes
parallel daten senden, bekommt der Empfänger per read dann immer die
vollständigen Daten-Blöcke ohne das sich die Daten der verschiedenen
writes "vermischen"?
Viele Grüße
AsioPP schrieb:> Natürlich ist das> reine async_write zunächst schneller weil es nicht blockiert aber dafür> zu sorgen, dass die Quellvariablen im Scope vorhanden bleiben müssen,> hebelt die Vorteile von der asynchronen Operation doch völlig aus!?
Nennt sich "Design-Fehler". Das async write müsste die Daten kopieren
(kostet Zeit) oder eine callback-function (mit handle) aufrufen die dann
den Speicher evtl. deallokiert.
Die Lösung mit call back wäre besser, denn das async-write kann nicht
vorher sicherstellen, dass genug freier Puffer im TCP/IP stack frei ist.
Nick M. schrieb:> AsioPP schrieb:>> Natürlich ist das>> reine async_write zunächst schneller weil es nicht blockiert aber dafür>> zu sorgen, dass die Quellvariablen im Scope vorhanden bleiben müssen,>> hebelt die Vorteile von der asynchronen Operation doch völlig aus!?>> Nennt sich "Design-Fehler". Das async write müsste die Daten kopieren> (kostet Zeit) oder eine callback-function (mit handle) aufrufen die dann> den Speicher evtl. deallokiert.> Die Lösung mit call back wäre besser, denn das async-write kann nicht> vorher sicherstellen, dass genug freier Puffer im TCP/IP stack frei ist.
Dir ist klar, dass es sich um C++ handelt? Warum sollte man da ein
handle benutzen? Was willst du noch als weitere callback-function neben
handler?
mh schrieb:> Was willst du noch als weitere callback-function neben handler?
Dir ist klar, dass ein handler was Anderes ist wie ein handle?
Und was hat OOP damit zu tun? Willst du an async-write ein Objekt
übergeben?
AsioPP schrieb:> Ich frage mich daher was das asynchrone senden von Daten für einen> realen Vorteil hat, wenn doch die Quellvariablen solange existieren> müssen, bis die asynchrone Operation beendet ist.
Der Vorteil ist nicht unbedingt die leichtere Programmierbarkeit, du
kannst nur ohne deine CPU zu viel in Wartezustände zu halten deine
Netzwerkkarte unter Last halten und hast dein viel Flexibilität,
Performant heisst in den meisten Fällen nicht on-the-fly Allokation oder
Datenkopien
AsioPP schrieb:> Vorausgesetzt es läuft im Netzwerk alles normal und Fehlerfrei, kann man> sich darauf verlassen, dass der Empfänger immer das komplette Stück> Daten in der gleichen Byte-Reihenfolge bekommt wie es durch write> rausging?
Ja dafür sorgt der TCP Stack, aber keine Magie sorgt dafür von dir
sinnlos zerhackte Paketen irgendwie wieder kombinierte werden
Nick M. schrieb:> mh schrieb:>> Was willst du noch als weitere callback-function neben handler?>> Dir ist klar, dass ein handler was Anderes ist wie ein handle?> Und was hat OOP damit zu tun? Willst du an async-write ein Objekt> übergeben?
Ich habe nichts von OOP geschrieben. Man kann Objekte benutzen, ohne OOP
anzuwenden. Und warum sollte man kein Objekt übergeben? Es gibt
Requierments an den template Parameter AsyncWriteStream, wenn ein Objekt
die erfüllt, kann man es an dieser Stelle nutzen, so wie das alle
Beispiele und Tutorials auf der Boost::asio Seite tun.
Nick M. schrieb:> mh schrieb:>> Ich habe nichts von OOP geschrieben.>> Und warum hast du mich dann auf C++ hingewiesen? Dachtest du ich bin von> VB ausgegangen?
Nein, aber deine Post mit
Nick M. schrieb:> eine callback-function (mit handle)
klingt sehr nach C. Und async_write hat einen callback, nur eben kein
handle, weil keins gebraucht wird.
Nick M. schrieb:> mh schrieb:>> nur eben kein handle, weil keins gebraucht wird.>> Ach, dein handle nennt sich *this. Darum "brauchst du keines".
Am Ende läuft es auf einen this-Pointer hinaus, man benutzt eben
Objekte. Aber ein Lambda das man als handler übergibt hat wenig mit
einem "handle" zu tun. Aber wenn du umbedingt willst, dann hat
async-write eine callback-funktion mit handle, es gibt also keinen Grund
von Design-Fehler zu sprechen.
mh schrieb:> dann hat> async-write eine callback-funktion mit handle, es gibt also keinen Grund> von Design-Fehler zu sprechen.
Dann ist es tatsächlich kein Designfehler. D'accord.
Dann ist aber das (aus dem Ursprungsposting):
"Indem data als globale Variable definiert ist, ist sichergestellt, dass
sich der Gültigkeitsbereich der Variablen über die gesamte asynchrone
Operation erstreckt."
durchaus irreführend und wurde vom Autor (nicht der TO) falsch
verstanden. Und dann vom TO falsch übernommen.
Nick M. schrieb:> mh schrieb:>> dann hat>> async-write eine callback-funktion mit handle, es gibt also keinen Grund>> von Design-Fehler zu sprechen.>> Dann ist es tatsächlich kein Designfehler. D'accord.>> Dann ist aber das (aus dem Ursprungsposting):> [...]
Dann verstehe ich ernsthaft nicht, warum du antwortest, oder nicht
wenigstens anmerkst, dass du dich nicht mit asio auskennst.
Der TO hat sogar einen Link auf die Beschreibung von async_write
gepostet, in dem alles nötige beschrieben wird.
mh schrieb:> Der TO hat sogar einen Link auf die Beschreibung von async_write> gepostet, in dem alles nötige beschrieben wird.
Ich zitiere nochmal:
AsioPP schrieb:> ZITAT: "data muss jedoch solange existieren, bis die asynchrone> Operation beendet ist und die Variable nicht mehr benötigt wird. Indem> data als globale Variable definiert ist, ist sichergestellt, dass sich> der Gültigkeitsbereich der Variablen über die gesamte asynchrone> Operation erstreckt."
Da gibts doch wenig zu diskutieren drüber das eine globale Variable ist
und warum das so reichlich unsinnig oder zumindest bewusst schlampig
ist.
cppbert3 schrieb:> Der Vorteil ist nicht unbedingt die leichtere Programmierbarkeit, du> kannst nur ohne deine CPU zu viel in Wartezustände zu halten deine> Netzwerkkarte unter Last halten und hast dein viel Flexibilität,> Performant heisst in den meisten Fällen nicht on-the-fly Allokation oder> Datenkopien
Alles klar. Mal angenommen man will die Sache asyncron machen und hat
den Fall, dass sich die zu sendenden Daten in einem Scope befinden der
nach dem async_write schnell verlassen wird. Was wäre denn die normale
Lösung um Daten für async_write solange am Leben zu halten wie nötig?
(Eine globale Variable wie in dem Beispiel was ich verlinkt habe wäre
wohl unsinnig (und sowieso unschick) wenn es um viele hintereinander
auszuführende async_write geht). Soweit nochmal zu Frage 1.
cppbert3 schrieb:> Ja dafür sorgt der TCP Stack, aber keine Magie sorgt dafür von dir> sinnlos zerhackte Paketen irgendwie wieder kombinierte werden
Sehr gut, danke damit wäre Frage 4 auf jeden Fall schon mal geklärt. Ja
stimmt zerhacken ist nicht so gut, macht vielleicht erst ab einer
gewissen sehr großen Datenmenge Sinn.
Fällt jemandem noch etwas zu Frage 2+3 ein, ganz grob? Diese Themen
sollten in anderen Sprachen und anderen libs vermutlich ähnlich sein.
Nick M. schrieb:> das eine globale Variable ist> und warum das so reichlich unsinnig oder zumindest bewusst schlampig> ist.
Wegen diesem Beispiel mit der globalen Variable kam mir auch das ganze
async_write ja auch direkt blöd vor. Aber genau das interessiert mich
noch, was ist denn die bessere Variante? also die Lösung um Daten für
async_write solange am Leben zu halten wie nötig?
Nick M. schrieb:> mh schrieb:>> Der TO hat sogar einen Link auf die Beschreibung von async_write>> gepostet, in dem alles nötige beschrieben wird.>> Ich zitiere nochmal:>> AsioPP schrieb:> [...]>> Da gibts doch wenig zu diskutieren drüber das eine globale Variable ist> und warum das so reichlich unsinnig oder zumindest bewusst schlampig> ist.
Darüber nicht, das ist offensichtlich falsch. Aber das kommt auch nicht
von der offiziellen Seite.
Kurz vor dem von dir zitierten Text, stehen diese beiden Links auf die
echte boost::asio Doku.
AsioPP schrieb:> https://www.boost.org/doc/libs/1_75_0/doc/html/boost_asio/reference/async_write/overload1.html> https://www.boost.org/doc/libs/1_75_0/doc/html/boost_asio/reference/write/overload1.html
Der Aufwand war also minimal, auch wenn man boost::asio noch nicht
selbst benutzt hat.
AsioPP schrieb:> Fällt jemandem noch etwas zu Frage 2+3 ein, ganz grob? Diese Themen> sollten in anderen Sprachen und anderen libs vermutlich ähnlich sein.
Das sind Fragen, die man so nicht beantworten kann. Das hängt ganz stark
davon ab, was du für ein System (Hardware, OS, ...) hast und was sonst
noch auf diesem System läuft.
Nick M. schrieb:> Da gibts doch wenig zu diskutieren drüber das eine globale Variable ist> und warum das so reichlich unsinnig oder zumindest bewusst schlampig> ist.
Wie kommst du auf globale? Ich denke auch das du keine Ahnung von Asio
hast und nur am spekulieren bist, das hilft nicht wirklich
Ok man sollte wohl bei async_write einen boost::asio::buffer nutzen wie
es aussieht. Also verhält sich wohl ein bisschen so wie ein
smart-pointer.
boost::asio::async_write(s, boost::asio::buffer(data, size), handler);
Ist folgende Aussage korrekt?: mit obiger Zeile werden die Daten (data,
size) im RAM (boost::asio::buffer) für exakt dieses async_write
zwischengespeichert und bleibt auch nach verlassen des Scopes im RAM
erhalten? Und wird nach erfolgter write operation gelöscht?
Ist das so?
Und noch eine Frage zu:
"The program must ensure that the stream performs no other write
operations (such as async_write, the stream's async_write_some function,
or any other composed operations that perform writes) until this
operation completes."
https://www.boost.org/doc/libs/1_75_0/doc/html/boost_asio/reference/async_write/overload1.html
Heißt das ich darf während einer async_write operation parallel keine
weitere starten?
AsioPP schrieb:> Nick M. schrieb:> das eine globale Variable ist> und warum das so reichlich unsinnig oder zumindest bewusst schlampig> ist.>> Wegen diesem Beispiel mit der globalen Variable kam mir auch das ganze> async_write ja auch direkt blöd vor. Aber genau das interessiert mich> noch, was ist denn die bessere Variante? also die Lösung um Daten für> async_write solange am Leben zu halten wie nötig?
Stell dir vor du hast einen Ringpuffer oder Queue die beim receive
weiterschaltet auf den nächsten Send buffer usw.
Viele Leute die das Async Pattern nicht (richtig) verstanden haben
denken immer das wäre eine coolere Variante von Blocking, Effizients
bedeutet im Normalfall nicht das dadurch dein Code einfacher oder
weniger Fehleranfällig wird, das muss man erstmal akzeptieren, viele
haben aber auch keine solche Anforderungen, ich hab z.B. mit Asio eine
1024 Geräte (Sever) Simulation mit ordentlich Datendurchsatz realisiert
welche die CPU kaum stresst und weil ich so detailierte Kontrolle über
meine Puffer habe auch ein sehr schnelles Speichermanagment für meine
Server mit geringer Latenz
So was wird sehr schwer wenn die Konzepte zuu highlevel sind, wobei Asio
mehrere Abstraktionsschichten hat wo man immer mehr Kontrolle übernehmen
kann, nicht ohne Grund wird Asio und seine Executor einen grossteil des
C++23 Networkstacks bilden
AsioPP schrieb:> Ok man sollte wohl bei async_write einen boost::asio::buffer> nutzen wie es aussieht. Also verhält sich wohl ein bisschen so wie ein> smart-pointer.>> boost::asio::async_write(s, boost::asio::buffer(data, size), handler);> Ist folgende Aussage korrekt?: mit obiger Zeile werden die Daten (data,> size) im RAM (boost::asio::buffer) für exakt dieses async_write> zwischengespeichert und bleibt auch nach verlassen des Scopes im RAM> erhalten? Und wird nach erfolgter write operation gelöscht?> Ist das so?
Einfach ein bisschen die Beispiele anschauen, Asio KANN das für dich
übernehmen oder es ist nur ein Slice auf dein Managment, viel
Kombinationsmöglichkeit hier um dich nicht auf ein Konzept
einzuschränken was natürlich das lernen erschwert :)
AsioPP schrieb:> Und noch eine Frage zu:> "The program must ensure that the stream performs no other write> operations (such as async_write, the stream's async_write_some function,> or any other composed operations that perform writes) until this> operation completes."> https://www.boost.org/doc/libs/1_75_0/doc/html/boost_asio/reference/async_write/overload1.html>> Heißt das ich darf während einer async_write operation parallel keine> weitere starten?
Multithreading mit Blocking
oder Asynchron mit n Producern
nicht noch Multithreading in die Asynchronität rein bringen, ist
doppeltdreifach gemoppelt, sonst brauchts ja wieder überall Locks um das
abzusichern, was wieder ineffizient ist
Async ist ein komplettes Pattern nicht einfach nur eine andere Art von
Write
Das ist kein einfaches async_write mit waitstate,event
Das boost asio async (Reactor,Proactor?) Pattern erlaubt Asynchronität
ohne Threads, da Threads ein zu bauen ist so sinnvoll wie gleichzeitig
vorwärts und rückwärts fahren
AsioPP schrieb:> Heißt das ich darf während einer async_write operation parallel keine> weitere starten?
Auf der gleichen Verbindung ist das doch völlig sinnfrei und warum
sollte das für andere Verbindungen notwendig sein, "Use the Force äh
your Brain, AsioPP"
cppbert3 schrieb:> Auf der gleichen Verbindung ist das doch völlig sinnfrei
Falls bei dir nichts Threads irgendeine Flusskontrollaufgabe machen, was
bei Asio aber einfach nicht nötig, eher sogar hinderlich unsinnig ist
cppbert3 schrieb:> Auf der gleichen Verbindung ist das doch völlig sinnfrei und warum> sollte das für andere Verbindungen notwendig sein
Ich meinte eher ob man ein async_write ausführen kann wenn ein anderes
zuvor noch nicht fertig ist. Also zB ganz fiktiv eine while(1) dort
entstehen Daten die bei jedem Durchlauf geschickt werden sollen. Wenn
ein neuer Schleifendurchlauf ein neues async_write aufruft wäre das ein
Problem wenn das vorige async_write noch nicht fertig ist im
Hintergrund?
AsioPP schrieb:> Wenn> ein neuer Schleifendurchlauf ein neues async_write aufruft wäre das ein> Problem wenn das vorige async_write noch nicht fertig ist im> Hintergrund?
Vermutlich, die Doku sagt, dass du das nicht machen darfst. Du musst an
der Stelle warten, was nicht so wirklich schön ist, oder es über einen
geeigneten handler regeln. Du solltest dir dringend die ganze Doku
durchlesen und dir die Beispiele angucken. Wenn du verstanden hast, wie
die Beispiele funktionieren, kannst du nochmal über deine eigene
Anwendung nachdenken. (Um die c++17 Beispiele solltest du vermutlich
einen Bogen machen, da coroutinen wahrscheinlich wenig zu deinem asio
Verständnis beitragen werden.)
AsioPP schrieb:> Wegen diesem Beispiel mit der globalen Variable kam mir auch das ganze> async_write ja auch direkt blöd vor.
Das ist völlig i.O. das für blöd zu halten. Der Fehler liegt in deiner
Quelle für das Zitat, nicht bei dir.
Die Lösung ist, den Handler vom Async_write zu verwenden der dann ein
free auf die Daten macht. Du musst also einen allokierten Speicher
übergeben. Das ist der Preis dafür dann gleich mit was Anderem
weitermachen zu können.
Meiner Meinung nach ist es dass immer wert (solange man irgendwann auch
überprüfen kann, ob die Daten auch geschickt wurden).
Oder man überprüft vorm Versenden, ob der Puffer für den socket nicht am
Überlaufen ist.
mh schrieb:> Der Aufwand war also minimal, auch wenn man boost::asio noch nicht> selbst benutzt hat.
Und dennoch hat der TO das zitiert auf das ich mich bezogen hab.
Ihm hat die Lösung zu Recht jedenfalls nicht gefallen. Dass er das mit
dem Handler nicht gelesen hat, ist nicht mein Problem.
Ok vielen Dank schon mal an alle für den Input das war schon mal sehr
interessant alles, was Frage 1 angeht da tendiere ich gerade stark zum
synchronen write da mir das asynchrone doch zu viele Stoplperfallen hat,
was natürlich auch an meinen noch beschränkten Kenntnissen liegt.
Sollte write "zu langsam" sein, was ich eigentlich nicht unbedingt
erwarte, kann man dann immer noch mal tiefer ins asynchrone einsteigen.
Was die Fragen 2+3 angeht muss ich dann einfach selber mal messen und
rumprobieren.
AsioPP schrieb:> Wenn> ein neuer Schleifendurchlauf ein neues async_write aufruft wäre das ein> Problem wenn das vorige async_write noch nicht fertig ist im> Hintergrund?
Mal ernsthaft:
Wenn sich das async_write nennt, non-blocking ist, dann muss es auch
asynchron arbeiten können. Und halt dann eine queue bereithalten für die
noch wartenden writes.
Andernfalls wäre es ein async-write das nach der ersten Verwendung ein
blocking sync_write ist und dann absolut lächerlich.
AsioPP schrieb:> Frage 1 angeht da tendiere ich gerade stark zum> synchronen write da mir das asynchrone doch zu viele Stoplperfallen hat,> was natürlich auch an meinen noch beschränkten Kenntnissen liegt.> Sollte write "zu langsam" sein, was ich eigentlich nicht unbedingt> erwarte, kann man dann immer noch mal tiefer ins asynchrone einsteigen.> Was die Fragen 2+3 angeht muss ich dann einfach selber mal messen und> rumprobieren.
Das ist genau das richtige Vorgehen, nimm synchron, wenn du damit besser
klar kommst, bis du einen Grund hast asynchron zu nutzen.
Nick M. schrieb:> mh schrieb:>> Der Aufwand war also minimal, auch wenn man boost::asio noch nicht>> selbst benutzt hat.>> Und dennoch hat der TO das zitiert auf das ich mich bezogen hab.> Ihm hat die Lösung zu Recht jedenfalls nicht gefallen. Dass er das mit> dem Handler nicht gelesen hat, ist nicht mein Problem.
Natürlich ist es nicht dein Problem, wenn du auf Fragen antwortest, ohne
Ahnung vom Thema zu haben und ohne auf diesen Umstand hinzuweisen. Das
ist das Problem des Fragestellers, der mit deiner unsinnigen Antwort
leben muss.
Nick M. schrieb:> AsioPP schrieb:>> Wenn>> ein neuer Schleifendurchlauf ein neues async_write aufruft wäre das ein>> Problem wenn das vorige async_write noch nicht fertig ist im>> Hintergrund?>> Mal ernsthaft:> Wenn sich das async_write nennt, non-blocking ist, dann muss es auch> asynchron arbeiten können. Und halt dann eine queue bereithalten für die> noch wartenden writes.>> Andernfalls wäre es ein async-write das nach der ersten Verwendung ein> blocking sync_write ist und dann absolut lächerlich.
Mal ernsthaft:
Du hast ausgiebig gezeigt, dass du von diesem Thema keine Ahnung hast.
Es ist lächerlich, dass du ein Urteil über boost::asio fällst, deren
Entwickler offensichtlich deutlich mehr Ahnung haben als du.
AsioPP schrieb:> cppbert3 schrieb:> Auf der gleichen Verbindung ist das doch völlig sinnfrei und warum> sollte das für andere Verbindungen notwendig sein>> Ich meinte eher ob man ein async_write ausführen kann wenn ein anderes> zuvor noch nicht fertig ist. Also zB ganz fiktiv eine while(1) dort> entstehen Daten die bei jedem Durchlauf geschickt werden sollen. Wenn> ein neuer Schleifendurchlauf ein neues async_write aufruft wäre das ein> Problem wenn das vorige async_write noch nicht fertig ist im> Hintergrund?
Für die selbe oder unterschiedliche Verbindungen?
Dein write_async stösst in seinem Feedback das nächste write in deiner
Queue oder wie auch immer sich deine Befehle ergeben an, sonst nutzt du
nicht das async Pattern sondern erwartest das du nur eine einfache
asynchrone Api hast, was Asio nicht ist
AsioPP schrieb:> Ok vielen Dank schon mal an alle für den Input das war schon mal> sehr interessant alles, was Frage 1 angeht da tendiere ich gerade stark> zum synchronen write da mir das asynchrone doch zu viele Stoplperfallen> hat,
Wollte ich dir auch gerade zu raten, wenn man das async Pattern nicht
richtig versteht kommt da komischer Code raus, bei synchron bist du
schon sicher also nimm erst mal das
mh schrieb:> Es ist lächerlich, dass du ein Urteil über boost::asio fällst, deren> Entwickler offensichtlich deutlich mehr Ahnung haben als du.
Du meinst also dass dieses kaputte Verhalten von async_write so wie du
es beschreibst Ausdruck von viel Ahnung ist?
Ich lass das gerne so stehen!
Wenn du dich z.b. nur im 5-10 Verbindungrahmen bewegst merkst du keinen
Unterschied, besonders dann wenn dein gewohntes Asynchronprinzip Threads
sind
Wenn du hunderte oder tausende Verbindungen hast merkt man schnell das
es dann sehr ineffizient wird, bei dem Asyc Pattern von Asio würde das
nicht sonderlich ins Gewicht fallen, dafür muss man sich aber auf das
Pattern einlassen sonst bringt es nichts und auch erst dann erkennst du
den Nutzen deutlich
Nick M. schrieb:> mh schrieb:> Es ist lächerlich, dass du ein Urteil über boost::asio fällst, deren> Entwickler offensichtlich deutlich mehr Ahnung haben als du.>> Du meinst also dass dieses kaputte Verhalten von async_write so wie du> es beschreibst Ausdruck von viel Ahnung ist?>> Ich lass das gerne so stehen!
Ja, sehr viel Ahnung - schon mal einen Netzwerkserver geschrieben der
mehrere zehntausen Verbindungen managed? Wenn du das brauchst kommst
nicht um das Pattern rum - du siehst die effiziens und eleganz nur nicht
weil du damit noch nicht intensiv gearbeitet hast oder solche
Anforderungen noch nicht auf deinem Tisch gelegen haben
Nick M. schrieb:> mh schrieb:>> Es ist lächerlich, dass du ein Urteil über boost::asio fällst, deren>> Entwickler offensichtlich deutlich mehr Ahnung haben als du.>> Du meinst also dass dieses kaputte Verhalten von async_write so wie du> es beschreibst Ausdruck von viel Ahnung ist?>> Ich lass das gerne so stehen!
Nur weil du behauptes, dass es kaputt ist, ist es das noch lange nicht.
Mir ist diese asynchrone Verhalten schon häufiger über den Weg gelaufen
und ich habe bis heute noch Niemanden getroffen, der das Verhalten
verstenden hat und sich darüber beschwert.
mh schrieb:> Nick M. schrieb:> mh schrieb:> Es ist lächerlich, dass du ein Urteil über boost::asio fällst, deren> Entwickler offensichtlich deutlich mehr Ahnung haben als du.>> Du meinst also dass dieses kaputte Verhalten von async_write so wie du> es beschreibst Ausdruck von viel Ahnung ist?> Ich lass das gerne so stehen!>> Nur weil du behauptes, dass es kaputt ist, ist es das noch lange nicht.> Mir ist diese asynchrone Verhalten schon häufiger über den Weg gelaufen> und ich habe bis heute noch Niemanden getroffen, der das Verhalten> verstenden hat und sich darüber beschwert.
Er hatte die Anforderungen vielleicht noch nicht und vielleicht keiner
Erfahrung mit der Qualität der Boost oder Boost-nahen Libs, da sei auch
unbegründete Kritik erlaubt, hab auch schon genug Neztwerlibs gesehen
die Schrott waren
mh schrieb:> Nur weil du behauptes, dass es kaputt ist, ist es das noch lange nicht.
Dann ist der Name kaputt. Asynchron ist das jedenfalls nicht.
Ich hab genau sowas auf einem µC impelemntiert der über TCP/IP Daten von
verschiedenen Quellen über einen Socket schickt. Jede Quelle schickt ihr
Datenpacket an einen Task. Und der baut sich eine Queue auf in der die
Packete chronologisch stehen und auch so abgearbeitet werden. Die
Absender können derweilen einfach weiterlaufen und auch gerne noch ein
Paket anhängen. Und wenn die Queue überläuft (weil Server weg) wird
derweilen über SD-Card weiter gequeued. Bis die überläuft (nach 4 GB).
Genauso läuft es mit der SD-Card, da können beliebig viele Daten
geschickt werden. Die Absender werden nicht blockiert, die SD-card
arbeitet sequentiell alles ab und schickt sich sogar eigene Kommandos.
Lese-Anfragen werden dann asynchron beantwortet wenn sie in der Queue
an der Reihe sind.
Also ich weiß schon wovon ich rede und kann gute, schlechte und kaputte
Implementierungen sehr wohl erkennen.
cppbert3 schrieb:> Für die selbe oder unterschiedliche Verbindungen?
ja genau die selbe Verbindung. Mir gings nur um die Aussage:
"The program must ensure that the stream performs no other write
operations (such as async_write, the stream's async_write_some function,
or any other composed operations that perform writes) until this
operation completes."
https://www.boost.org/doc/libs/1_75_0/doc/html/boost_asio/reference/async_write/overload1.html
Das bedeutet doch (basierend auf einer simplen Verbindung), dass
async_write für den Anwendungsfall wo async_write oft sehr schnell
hintereinander aufgerufen wird nicht so gut geeignet ist, man könnte
vielleicht sagen, async_write ist perfekt wenn danach anderer code kommt
der sofort ausgeführt werden soll aber nicht unbedingt wenn danach sehr
schnell ein weiteres async_write kommt richtig?
Nick M. schrieb:>> mh schrieb:>> Nur weil du behauptes, dass es kaputt ist, ist es das noch lange nicht.> Also ich weiß schon wovon ich rede und kann gute, schlechte und kaputte> Implementierungen sehr wohl erkennen.
Es stellt kein in Frage das du schon was mit asynchronität und Netzwerk
gemacht hast, ich stelle in Frage das du weisst wie eine generische Api
aussehen muss die gut genug ist den nächsten C++ Standard zu definieren
und die u.a. das 1Mio Connection Problem lösen kann, was Asio schafft
Bei Asio sind die Namen genau so klar und verständlich wie in jeder
Async Pattern Api Implementation, es ist eben keine einfache Asyc Api
sondern das Pattern um u.a. das 1Mio Connections Problem abzudecken, das
ist etwas völlig anderes, weil die Skalierung in den Dimensionen
wirklich ein Problem ist
cppbert3 schrieb:> Er hatte die Anforderungen vielleicht noch nicht und vielleicht keiner> Erfahrung mit der Qualität der Boost oder Boost-nahen Libs, da sei auch> unbegründete Kritik erlaubt, hab auch schon genug Neztwerlibs gesehen> die Schrott waren
Keine Ahnung haben ist also eine ausreichende Begründung, um Unsinn von
sich zu geben? Das ist vielleicht als erste Reaktion so gerade Ok, aber
irgendwann sollte man feststellen, dass man keine Ahnung hat und die
Klappe halten.
Nick M. schrieb:> mh schrieb:>> Nur weil du behauptes, dass es kaputt ist, ist es das noch lange nicht.>> Dann ist der Name kaputt. Asynchron ist das jedenfalls nicht.
Natürlich ist das asynchron. Asynchron heißt nicht "ich kann machen was
ich will" und es heißt auch nicht, dass man unter keinen Umständen
warten muss.
Nick M. schrieb:> Ich hab genau sowas auf einem µC impelemntiert der über TCP/IP Daten von> verschiedenen Quellen über einen Socket schickt. [...]
Glückwunsch ... das heißt nicht, dass deine Vorstellung von asynchron
die einzig Wahre ist.
AsioPP schrieb:> cppbert3 schrieb:> Für die selbe oder unterschiedliche Verbindungen?>> ja genau die selbe Verbindung. Mir gings nur um die Aussage:> "The program must ensure that the stream performs no other write> operations (such as async_write, the stream's async_write_some function,> or any other composed operations that perform writes) until this> operation completes."> https://www.boost.org/doc/libs/1_75_0/doc/html/boost_asio/reference/async_write/overload1.html>> Das bedeutet doch (basierend auf einer simplen Verbindung), dass> async_write für den Anwendungsfall wo async_write oft sehr schnell> hintereinander aufgerufen wird nicht so gut geeignet ist, man könnte> vielleicht sagen, async_write ist perfekt wenn danach anderer code kommt> der sofort ausgeführt werden soll aber nicht unbedingt wenn danach sehr> schnell ein weiteres async_write kommt richtig?
Nicht wirklich
Beispiel
First packet
Write current time ...löst on async write ende aus
On asyc write ende
Write current time ...und im Kreis
Dann werden die Pakete so schnell rausgeschickt wie der Netzwerkstack
leisten kann
Dein sync write sieh eben so aus
Loop:
Write current time
Wait write ende
>loop
> Dein sync write sieh eben so aus>> Loop:> Write current time> Wait write ende> loop
Und das wait write ende ist eben einfach zu implementieren aber
ineffizent bei vielen Verbindungen, das ist der einzige Grund warum die
asio Api nicht ganz trivial ist
mh schrieb:> Nick M. schrieb:>> Ich hab genau sowas auf einem µC impelemntiert der über TCP/IP Daten von>> verschiedenen Quellen über einen Socket schickt. [...]> Glückwunsch ... das heißt nicht, dass deine Vorstellung von asynchron> die einzig Wahre ist.
Wenn er eine uC implementierung als Erfahrung ranzieht ist doch schon
klar das die starken skalierungsrelevante Charakter der Asio API für ihn
nicht so relevant ist
cppbert3 schrieb:> First packet> Write current time ...löst on async write ende aus> On asyc write ende> Write current time ...und im Kreis>> Dann werden die Pakete so schnell rausgeschickt wie der Netzwerkstack> leisten kann
Jeder Aufruf von async_write müsste dann aber als Bedingung haben, dass
der Handler eines vorigen async_write die operationen complete hat oder?
Weil wenn man einfach schnelle viele async_write hintereinander ausführt
würde man doch gegen die zitierte Regel verstoßen.
AsioPP schrieb:> cppbert3 schrieb:> First packet> Write current time ...löst on async write ende aus> On asyc write ende> Write current time ...und im Kreis> Dann werden die Pakete so schnell rausgeschickt wie der Netzwerkstack> leisten kann>> Jeder Aufruf von async_write müsste dann aber als Bedingung haben, dass> der Handler eines vorigen async_write die operationen complete hat oder?> Weil wenn man einfach schnelle viele async_write hintereinander ausführt> würde man doch gegen die zitierte Regel verstoßen.
Das ist dein Verständisproblem: das write end ist kein Zustand sonder
der anstosser für das nächste write oder die Datenerzeugung dafür, oder
das aulesen aus einer Queue mit Userbefehlen die während dessen
asynchron aufgelaufen sind
cppbert3 schrieb:> Das ist dein Verständisproblem: das write end ist kein Zustand sonder> der anstosser für das nächste write oder die Datenerzeugung dafür, oder> das aulesen aus einer Queue mit Userbefehlen die während dessen> asynchron aufgelaufen sind
Ahh okay ich versuchs nochmal:
Ist es also so dass async_write blockiert wenn ein voriges async_write
noch nicht fertig ist? Und normal blockiert es nicht. Also bisschen wie
ein Mutex.
Falls das jetzt immer noch nicht richtig ist brächte ich eine Sendung
mit der Maus Erklärung ;-)
Lass dich von dem heartbeat timer und dem async wait nicht verwirren,
das ist nur ein effizienter sleep, schlussendlich wird nach Zeit x
wieder start_write aufgerufen
AsioPP schrieb:> cppbert3 schrieb:>> First packet>> Write current time ...löst on async write ende aus>> On asyc write ende>> Write current time ...und im Kreis>>>> Dann werden die Pakete so schnell rausgeschickt wie der Netzwerkstack>> leisten kann>> Jeder Aufruf von async_write müsste dann aber als Bedingung haben, dass> der Handler eines vorigen async_write die operationen complete hat oder?> Weil wenn man einfach schnelle viele async_write hintereinander ausführt> würde man doch gegen die zitierte Regel verstoßen.
Ja, deswegen benutzt man den "handler". Ein minimales Beispiel:
1
Queuequeue;
2
3
voidhandler()
4
{
5
if(!queue.empty())
6
{
7
async_write(queue.pop(),handler);
8
}
9
}
10
11
voidmain()
12
{
13
queue=irgendwas();
14
handler();
15
}
Der handler wird automatisch aufgerufen, wenn async_write fertig ist.
Genau dafür ist er da. Im handler kannst du jetzt gucken, ob es mehr
Daten gibt und das nächste async_write aufrufen. Das ist ein bisschen so
wie Rekursion (es ist aber keine!). Wie du im handler Zugriff auf diese
Daten hast, ist deine Angelegenheit. Eine Möglichkeit ist sowas wie ne
Queue.
Und ob die Queue global oder nicht ist liegt komplett in deinem
ermessen, nichts erzwingt globale Variablen, nur der Entwickler selbst
egal in welcher Situation
Und der handler muss keine free-function sein, es geht auch ein Funktor
oder Lambda, d.h. wie du den Zugriff auf deine Daten gestaltest ist sehr
flexibel und dank Template-lastgkeit auch voll inline Fähig (falls
möglich)
Ok verstehe immer mehr danke.
mh schrieb:> void handler()> {> if(!queue.empty())> {> async_write(queue.pop(), handler);> }> }>> void main()> {> queue = irgendwas();> handler();> }
Ich überlege gerade, wenn man das in eine while(1) steckt würde man
gegen die Regel verstoßen da handler() ständig aufgerufen wird:
void main()
{
while(1)
{
queue = irgendwas();
handler();
}
}
In der While(1) würde man besser die queue nur mit den Daten des
jeweiligen Durchlaufs füllen und async_write bewusst NICHT aufrufen weil
man sonst gegen die Regel verstößt.
handler() müsste aber doch immer getriggert werden wenn die queue nach
dem leer sein einen neuen Dateneintrag bekommen hat was immer passiert
wenn:
- beim ersten Durchlauf
- wenn queue leer gesendet wurde und dann wieder ein neuer Eintrag
kommt.
Solange Daten in der queue sind ruft sich handler() sozusagen selbst auf
aber sobald alles gesendet wurde und die queue leer ist bräuchte man
immer dann gezielt einen neuen Trigger wie macht man das?
AsioPP schrieb:> Sozusagen wenn das die Basis wäre:>> std::queue<daten> queue_daten;>> while(1)> {> // hier entstehen Daten:> daten quell_daten;>> // füge in queue hinzu:> queue_daten.push(quell_daten);>> // ???> // handler(); würde gegen Regeln verstoßen> }
Ganz klarer Regelverstoss, lasss das while weg und stosse die nächste
Aktion über den Handler an, du versuchst das irgendwie in ein
synchronablauf zu zwingen, was einfach völlig falsch ist
Bei synchron ist eine Schleifer oder sowas dein Antrieb, bei asynchron
mit Asio ist dein Handler derjenige der sagt was als nächstes kommt
AsioPP schrieb:> Solange Daten in der queue sind ruft sich handler() sozusagen selbst auf> aber sobald alles gesendet wurde und die queue leer ist bräuchte man> immer dann gezielt einen neuen Trigger wie macht man das?
In dem du write aufrufst wenn neue Daten in die Queue gekommen sind
da könntest du z.B. mit einer Condition drauf warten, oder mit if
!queue.empty in deinem while poller (was aber einfach nur CPU Zeit
frisst)
Am bestes so was
Starte write wenn neue Queuedaten da sind
Write -> handle "loop" leert Queue so lange Daten da sind dann Ende,
start erst wieder mit den nächste Queuedaten die rein kommen
AsioPP schrieb:> Sozusagen wenn das die Basis wäre:>> std::queue<daten> queue_daten;>> while(1)
Nein, Basis ist in der Regel eine Instanz von `boost::asio::io_context`.
Auf diesem Objekt rufst Du `.run()` auf. Das Objekt verwendest Du auch,
um damit `acceptor` und `socket` zu konstruieren. All diese Objekte, die
Du verwendest, um Verbindungen anzunehmen oder die Verbindungen selbst,
nehmen eine Referenz auf dieses Objekt. Immer, wenn irgendwo ein
Ereignis aufgetreten ist und der mit einer IO-Operation verknüpfte
callback aufgerufen wird, wird dies aus dem Kontext von
`boost::asio::io_context::run` passieren. Wenn Du irgendwo in dem
asynchronen System einen blockenden Funktionsaufruf ausführst, oder
selbst blockst, können keine callbacks mehr ausgeführt werden und das
gesamte System steht.
Boost.Asio hat eigentlich ein Vielzahl von sehr guten Beispielen.
Boost.Asio ist aber auch nicht ganz trivial. Nimm Dir mal die Zeit und
lese die Beispiele.
Zu Deiner Ursprünglichen Frage: Wenn Du einen Puffer an async_write()
weiter gibst, dann muss der Speicherbereich bis zur Ausführung des
callbacks gültig bleiben. Die Asio Beispiele erreichen dies in der Regel
dadurch, dass sie den Puffer in einem Kontext zur Verbindung (socket)
speichern und einen shared_ptr<> auf diesen Kontext mit im callback
speicher (boost::bind(), std::bind()). Wenn dann mehrere asynchrone
Operationen für einen Kontext ausstehen (Lesen, Schreiben, Timeout) und
jeder callback zu dieser Operation einen shared_ptr<> enthält, dann ist
sichergestellt, dass exakt solange der Kontext erhalten bleibt.