Logik hinter Winsockets
-
Hallo!
Ich bin momentan dabei einen Webclient zu reversen und mir dabei gleich winsock Kenntnisse anzueignen.
Aber ich versteh das ganze zum Teil grad nicht, vlt. kann ja jemand meine Vorstellungen korrigieren.
Also ich untersuche alles mit Wireshark. Bei connect() werden 3 Packets ohne "Inhalt" ausgetauscht. Passiert da beim Server schon irgendwas, wie dass der Client in eine Liste eingetragen wird die dann senden können?
Dann wird ein Packet mit einem BYTE = 1 gesendet. Daraufhin kommen kontinuirlich erwiderte kleine selbe Packets vom Server die wohl das OS bearbeitet, weil ich ja auf break bin?
Wenn mir der Server jetzt Daten sendet werden die sofort gesendet unabhängig von recv()? Das recv() dient nur um den Buffer vom Socket auszulesen, während die Daten schon da sind? Wenn ich richtig liege, wie viel wird da wo "zwischengespeichert"?
Und was ich gar nicht verstehe ist, dass dann plötzlich 52 Bytes in einer send() rausgehen und dann wird ein zweites Packet mit auch etwa 50 Bytes gesendet obwohl das Programm auf break ist? Wer sendet das, wenn nur 1 send() aufgerufen wurde?
Lg
-
@CUser1 Winsocket sind nicht groß anders als das Original BSD-Socket.
Ein Server lauscht auf einem Port. Bei einer Verbindung wird dann aber auf einen anderen umgeleitet, damit der Serverport wieder frei wird.
Bei Wikipedia steht: "connect() Macht die Adressinformation (z. B. IP-Adresse und Port) eines anderen Sockets bekannt. Ordnet dem Socket einen freien lokalen Port zu. Im Falle eines STREAM Sockets wird versucht eine neue TCP/IP-Verbindung zu etablieren. Wird auf Client-Seite benutzt."
( https://de.wikipedia.org/wiki/Socket_(Software)#BSD_Sockets_API )Aber schau mal bei http://www.zotteljedi.de/socket-tipps/index.html vorbei.
Er hat sich sogar an einem Buch versucht: http://www.zotteljedi.de/socket-buch/index.html
-
@DirkB Ich dachte mir ich komme drum rum jede Funktion einzeln zu recherchieren, aber egal soll so sein. Danke für Tipp mit der Literatur, weißt du das mit recv()? Also wenn der Server was sendet, macht der das sofort und recv liest den Inhalt bloß von einem Buffer am Rechner "offline" aus?
-
@CUser1 sagte in Logik hinter Winsockets:
Also wenn der Server was sendet, macht der das sofort
Mag bei dir passieren, muss aber nicht
und recv liest den Inhalt bloß von einem Buffer am Rechner "offline" aus?
Das ist doch auch bei anderen höheren IO-Sachen (Datei, USB, ..) der Fall
TCP/IP (dafür wurde Sockets gemacht) ist eine paketorientierte Übertragung. Daten werden gesammelt oder auch gesplittet übertragen.
Teilweise können die Pakete auch in falscher Reihenfolge ankommen, das dann bei TCP aber korrigiert wird. Da braucht man einen Puffer.
Auch wenn du viele kleine Pakete sendest, kann recv() dir ein großes liefern.
-
@DirkB sagte in Logik hinter Winsockets:
Auch wenn du viele kleine Pakete sendest, kann recv() dir ein großes liefern.
Das ist wichtig! Viele, die sich zum ersten Mal damit beschäftigen, erwarten nämlich, dass ein recv - Aufruf x Bytes liefert, wenn der Sender mit einem send - Aufruf x Bytes gesendet hat. Dem ist aber nicht so, es ist denkbar, dass der Sender x Bytes mit einem Aufruf sendet, und mehrere recv - Aufrufe beim Empfänger erforderlich sind, um die x Bytes abzuholen.
-
@Belli Vermutlich eben wegen dem Timing, wegen Latenz usw. also liege ich richtig, dass irgendwo alles zwischengespeichert wird.
Warum haltet das Programm an, wenn man recv aufruft? Wartet es einfach sinnlos bis es etwas zum lesen gibt? Umgeht man das in dem man mit select() vor dem recv() prüft ob was da ist oder so?
-
@CUser1 sagte in Logik hinter Winsockets:
Umgeht man das in dem man mit select() vor dem recv() prüft ob was da ist oder so?
Ich hab da ewig nix mehr mit gemacht.
Soweit ich das in Erinnerung habe, ist das eine Möglichkeit, eine andere ist es, den Socket in den non-blocking Mode zu schalten.
-
@CUser1 sagte in Logik hinter Winsockets:
also liege ich richtig, dass irgendwo alles zwischengespeichert wird.
Alles nicht, aber das, was noch nicht versandt, bzw. noch nicht abgerufen wurde.
Warum haltet das Programm an, wenn man recv aufruft? Wartet es einfach sinnlos bis es etwas zum lesen gibt?
Du machst das
recv()
ja nicht aus Spaß, sondern weil du Daten haben möchtest. Wenn keine vorhanden sind wird gewartet. Du kannst aber prüfen, ob Daten vorhanden sind.
Ist ja beim lesen vonstdin
(Tastatur) nicht anders.Umgeht man das in dem man mit select() vor dem recv() prüft ob was da ist oder so?
Ja - zumindest bei Server-Sockets ist das sinnvoll, da man so mehrere Ports überwachen kann.
select()
wartet auch, hat jedoch auch einen timeout Parameter.Unter BSD (oder anderen Unixvarianten) kannst du statt
recv()
auchread()
verwenden.
-
@CUser1 sagte in Logik hinter Winsockets:
Also ich untersuche alles mit Wireshark. Bei connect() werden 3 Packets ohne "Inhalt" ausgetauscht. Passiert da beim Server schon irgendwas, wie dass der Client in eine Liste eingetragen wird die dann senden können?
Das passt so. Typischer Verbindungsaufbau: Client schickt SYN, Server schickt SYN+ACK, Client schickt ACK. Was dabei passiert ist dass im TCP Stack beider Rechner eine Verbindung angelegt wird. Das Serverprogramm bekommt dann die Mitteilung dass es eine neue Verbindung gibt und "akzeptiert" diese dann typischerweise. Damit sind Client und Serverprogramm verbunden. Daten wurden dabei noch keine ausgetauscht. Theoretisch kann man soweit ich weiss beim 3. Paket schon Daten mitschicken, aber die wenigsten Anwendungen tun das (man muss dazu eine spezielle Socket-Funktion verwenden).
Kannst du dir vorstellen wie wenn du jmd. anrufst und der abhebt ohne dass noch irgendwer was gesagt hat. Die Verbindung existiert, aber es wurden noch keine Daten ausgetauscht.
Wenn mir der Server jetzt Daten sendet werden die sofort gesendet unabhängig von recv()?
Ja.
Das recv() dient nur um den Buffer vom Socket auszulesen, während die Daten schon da sind? Wenn ich richtig liege, wie viel wird da wo "zwischengespeichert"?
Ja. Google "receive window" oder "RWin".
Und was ich gar nicht verstehe ist, dass dann plötzlich 52 Bytes in einer send() rausgehen und dann wird ein zweites Packet mit auch etwa 50 Bytes gesendet obwohl das Programm auf break ist? Wer sendet das, wenn nur 1 send() aufgerufen wurde?
Das ist jetzt schwer zu beantworten wenn wir nicht wissen was genau du gemacht hast. Und: bist du sicher dass nur 1x send aufgerufen wurde? Ein Paket raus und (200ms später) nochmal ein Paket raus klingt verdächtig danach dass 2x schnell hintereinander send aufgerufen wurde. Siehe z.B. https://forums.codeguru.com/showthread.php?418246-send-send-recv-200ms-delay-suggestions
Grund für die 200ms Pause dabei ist ein Zusammenspiel aus Nagle's Algorithm und Delayed ACK.
-
@CUser1 sagte in Logik hinter Winsockets:
@Belli Vermutlich eben wegen dem Timing, wegen Latenz usw. also liege ich richtig, dass irgendwo alles zwischengespeichert wird.
Google Nagle's Algorithm.
Warum haltet das Programm an, wenn man recv aufruft? Wartet es einfach sinnlos bis es etwas zum lesen gibt? Umgeht man das in dem man mit select() vor dem recv() prüft ob was da ist oder so?
Das Programm wartet dann, ja. Aber ich verstehe nicht wieso du das als sinnlos bezeichnest. Wenn das Programm Daten empfangen und dann verarbeiten möchte, was soll es denn sonst machen als warten bis Daten eintreffen? Wenn das Programm nicht warten will gibt es ja andere Möglichkeiten (asynchronous IO oder non-blocking IO).
-
Warum haltet das Programm an, wenn man recv aufruft?
Blockierende Aufrufe sind quasi ein service, den Dir die Biblio liefert ....
Normal kannst Du auch pollen (non blocking sockets), aber das ist grad bei wiederholenden asynchronen Dingen nicht unbedingt der einfachste/intuitivste Weg.Du hasst also meistens die Wahl:
-> non assync mit pollen
-> pseudo assync über features wenn es framework / biblio supported
-> richtig/eigenes assync (multithreading / multiprozessing) und dann kannst "easy" blocking sockets verwenden.Da netztwerk und assync von natur aus irgendwie passen wird der workflow mit letzterem meist auch einfacher ....
Andere Programmiersprachen tun sich hingegen mit pseudo nebenläufigkeit (Assynchron Input Output) leichter und implementieren da schon support.
C++ und socket schnittstelle ist ...... besser ne abstraktere Bib verwenden IMHO (fürs Üben und verständniss mal abgesehen)
-
@RHBaum sagte in Logik hinter Winsockets:
-> richtig/eigenes assync (multithreading / multiprozessing) und dann kannst "easy" blocking sockets verwenden.
Das ist doch auch synchrones IO, bloss halt mit Threads.
"Richtiges" async ist mit APIs wieCreateIoCompletionPort
oderkqueue
. Das ist dann aber eher noch komplizierter als non-blocking.ps: Asynchronous hat kein doppel-S.
-
@hustbaer Also beim Debuggen sind alle Threads auf Pause, dann wird mit Singlestep 1 send() gecalled mit 52 Bytes vermutlich "Auth Inhalt". Und wenn es da drüber läuft, wird das gesendet und obwohl der Process eingefroren ist, wird dann vom Server was erwidert und dann sendet es nochmal etwa 50 Bytes aber m. E. auch was programmspezifisches.
Aber ich verstehe nicht wieso du das als sinnlos bezeichnest.
Weil ich dann einen neuen Thread brauche, weil ich ja auch andere Sachen bearbeiten will.
Was genau versteht man unter synchron in dem Kontext? Nehmen da beide Endpunkte die Uhrzeit bevor sie anfangen was zu tun oder um was geht es da?
-
@CUser1 sagte in Logik hinter Winsockets:
@hustbaer Also beim Debuggen sind alle Threads auf Pause, dann wird mit Singlestep 1 send() gecalled mit 52 Bytes vermutlich "Auth Inhalt". Und wenn es da drüber läuft, wird das gesendet und obwohl der Process eingefroren ist, wird dann vom Server was erwidert und dann sendet es nochmal etwa 50 Bytes aber m. E. auch was programmspezifisches.
Welches "es" sendet nochmal 50 Byte? Das angehaltene Programm? Das wäre komisch. Ich vermute eher dass 2x
send
aufgerufen wird.[quote]Aber ich verstehe nicht wieso du das als sinnlos bezeichnest.[/quote]
Weil ich dann einen neuen Thread brauche, weil ich ja auch andere Sachen bearbeiten will.
Naja wieso verwendest du dann blocking-IO wenn du nebenbei auch noch andere Sachen machen willst?
Was genau versteht man unter synchron in dem Kontext? Nehmen da beide Endpunkte die Uhrzeit bevor sie anfangen was zu tun oder um was geht es da?
Synchron heisst dass die Funktion die Arbeit abgeschlossen hat wenn sie zurückkehrt.
Non-blocking IO ist synchron: kann sein dass was gemacht wurde oder aber auch nicht, aber auf jeden Fall passiert nichts mehr nachdem die Funktion zurückgekommen ist.
Blocking IO ist auch synchron: wenn kein Fehler passiert wird die Funktion immer was machen, und wartet eben ggf. so lange wie nötig.
Asynchron nennt man APIs wo man z.B. mit einem Aufruf sagen kann "fang jetzt bitte an Daten zu empfangen, bitte an diese Adresse schreiben, maximale Grösse ist X, gib bescheid wenn du fertig bist". Die Operation läuft dann im Hintergrund. Bis die Operation abgeschlossen ist darfst du dann den an die Funktion übergebenen Puffer auch nicht freigeben - wird ja im Hintergrund noch verwendet. (Beim asynchronen Empfangen kommt man meist eh nicht auf die Idee dass man den Puffer freigeben wollen würde. Die Einschränkung gilt aber genau so für's asynchrone Senden.)
Und dann gibt es irgend einen Mechanismus mit dem man informiert wird dass die Operation jetzt abgeschlossen ist, inklusive Ergebnis (OK oder Fehlercode).
Wie das funktioniert hängt von der jeweiligen API ab. Und es gibt leider keine die auf allen Systemen funktioniert. Nichtmal eine die auf allen POSIX Systemen funktioniert.Wenn's dir nur um Windows geht: IO Completion Ports.
-
@CUser1 sagte in Logik hinter Winsockets:
Und wenn es da drüber läuft, wird das gesendet und obwohl der Process eingefroren ist, wird dann vom Server was erwidert und dann sendet es nochmal etwa 50 Bytes
Die Sockets sind ja ein Bestandteil vom Betriebssystem und nicht vom Programm.
Die Daten werden ja im Hintergrund gesendet und empfangen.
Du kannst deinen Prozess im Debugger anhalten, aber nicht das Betriebssystem.
-
Vielleicht hilft ein wenig ASCII Art:
Synchron:
----R##########r---------
Asynchron:
----R#r#############N----
Legende:
R
: Aufruf derrecv
Funktion
r
:recv
Funktion kehrt zurück
N
: Benachrichtigung dass die Operation abgeschlossen ist.
#
: Time-Slot in dem die Operation noch aktiv ist, d.h. in dieser Zeit können Daten vom OS in deinen Lesepuffer geschrieben werden.
-
Time-Slot "ausserhalb" der Operation
-
@DirkB sagte in Logik hinter Winsockets:
Die Sockets sind ja ein Bestandteil vom Betriebssystem und nicht vom Programm.
Die Daten werden ja im Hintergrund gesendet und empfangen.
Du kannst deinen Prozess im Debugger anhalten, aber nicht das Betriebssystem.Das stimmt schon. Trotzdem wird das OS nicht für 52 Byte ein Paket rausknallen wenn es noch weitere 50 Byte zu senden hat und die insgesamt 102 Byte gemeinsam an
send
übergeben wurden. Als erste Daten einer Verbindung. Das macht Windows einfach nicht, und soweit ich weiss auch kein anderes System.Daher meine Vermutung dass der Code 2x schnell hintereinander
send
aufruft, erst mit 52 Byte und dann nochmal mit 50 Byte.
-
@CUser1 Über welche Funktion genau steppst du drüber? Die
send
Funktion der Winsock API?
-
@hustbaer Das zweite Packet ist Server -> Client, war wohl gestern schon zu müde. Alles Unfug mit 2 mal send().
Was ich gerade draufgekommen bin: recv() liefert nicht 0 wenn keine Daten zum Lesen vorhanden sind, sondern wenn es sonst welche Probleme gibt oder? Weil sonst würde bei !recv() ja das Example Programm schließen.
Noch was:
Ich habe gelesen bei TCP ist Reihenfolge wie gesendet wird nicht immer korrekt. Hat man darum immer einen switch der die Packet ID vergleicht? Das jeweilige Packet selbst ist aber immer gleich oder? Also wenn ich jetzt mit send() 8192 Bytes rausknalle, dann kommen die zumindest richtig an. Weil sonst würde ja bei einem Billard PC Spiel zuerst der Queue benutzt und dann der Spieler bewegt?
Und nochwas: Wird über die Packet size geprüft ob alles übertragen wurde? Weil der Server kennt den Inhalt des Packets ja ggf. nicht und wenn nur einmal send() aufgerufen wird muss das ganze ja zuverlässig ankommen. Bleibt es in der send solang bis es einen Callback gibt oder so? Wenn man von Packetverlusten spricht, bedeutet das dass im Hintergrund etwas 10 mal gesendet wird, weil die Data nicht übereinstimmt mit der mitgesendeten Datasize?
-
@CUser1 sagte in Logik hinter Winsockets:
@hustbaer Das zweite Packet ist Server -> Client, war wohl gestern schon zu müde. Alles Unfug mit 2 mal send().
OK. Das ist dann normal. Wie schon geschrieben wurde wird das Paket ja wenn es ankommt erstmal vom OS bearbeitet und im Empfangspuffer abgelegt. Das funktioniert auch wenn das Programm gerade im Debugger angehalten ist. Und das reicht damit dir das Paket von Wireshark/... angezeigt wird.
Was ich gerade draufgekommen bin: recv() liefert nicht 0 wenn keine Daten zum Lesen vorhanden sind, sondern wenn es sonst welche Probleme gibt oder? Weil sonst würde bei !recv() ja das Example Programm schließen.
Naja, das ist doch alles ganz gut dokumentiert, nicht?
recv
liefert 0 wenn die Verbindung von der Gegenseite "graceful" geschlossen wurde. Also ein "normaler" Disconnect. Oder -1 bei Fehler. Wobei im non-blocking Modus auch "keine Daten verfügbar" als Fehler gilt.Noch was:
Ich habe gelesen bei TCP ist Reihenfolge wie gesendet wird nicht immer korrekt. Hat man darum immer einen switch der die Packet ID vergleicht? Das jeweilige Packet selbst ist aber immer gleich oder? Also wenn ich jetzt mit send() 8192 Bytes rausknalle, dann kommen die zumindest richtig an. Weil sonst würde ja bei einem Billard PC Spiel zuerst der Queue benutzt und dann der Spieler bewegt?
Mit Switches hat das nichts zu tun. TCP Daten werden in Paketen verschickt. Rausgeschickt werden die dabei normalerweise schon immer in der richtigen Reihenfolge. Nur können unterschiedliche Pakete u.U. unterschiedliche Wege zum Ziel nehmen, und daher auch in der falschen Reihenfolge ankommen. Dazu ist in jedem Paget eine sog. "Sequence Number" enthalten. Das ist quasi die Position die das 1. Byte des Pakets im gesamten Stream einnimmt. Anhand dieser Sequence Number kann der Empfänger die Pakete dann wieder in der richtigen Reihenfolge zusammensetzen. Darum kümmert sich der TCP/IP Stack - das Programm muss dazu nichts extra machen.
Und nochwas: Wird über die Packet size geprüft ob alles übertragen wurde? Weil der Server kennt den Inhalt des Packets ja ggf. nicht und wenn nur einmal send() aufgerufen wird muss das ganze ja zuverlässig ankommen. Bleibt es in der send solang bis es einen Callback gibt oder so? Wenn man von Packetverlusten spricht, bedeutet das dass im Hintergrund etwas 10 mal gesendet wird, weil die Data nicht übereinstimmt mit der mitgesendeten Datasize?
Wenn das Serverprogramm
send
sagt, dann werden die Daten erstmal in einen Sende-Puffer kopiert der vom TCP/IP Stack verwaltet wird. Dann werden die Pakete verschickt. Die Daten bleiben aber so lange im Puffer bis die Gegenseite sie mit einem ACK Paket "quittiert" hat. (Hierbei kommt wieder die Sequence Number zum Einsatz.) Dauert es zu lange bis die Daten quittiert werden, dann schickt der TCP/IP Stack sie einfach nochmal -- und startet den "warten auf ACK" Timer neu. D.h. die Daten werden evtl. auch drei mal oder öfter verschickt. Wenn das sehr oft passiert, dann macht der Stack erstmal eine kleine Pause (paar Sekunden) wo er keine Pakete mehr schickt. Danach probiert er dann erstmal ein Paket zu schicken. Wird dieses wieder nicht quittiert macht der Stack eine etwas längere Pause. Und irgendwann gibt er auf und erklärt die Verbindung für kaputt. Ab dem Zeitpunkt bekommt das Server Programm von allen Socket Funktionen dann einen Fehler gemeldet. Es wird auch ein Paket zum Client geschickt das diesem mitteilt dass die Verbindung abgebrochen wurde. Wobei dann die Chance gross ist dass der Client das Paket nie bekommt - es hat ja davor schon immer nur Probleme gegeben.Kommt allerdings nach so einer Retransmission ein passendes ACK daher, dann wird wieder weiter gesendet.
Interessant dabei ist auch dass das der einzige zuverlässige Mechanismus ist über den bei TCP/IP die Sendegeschwindigkeit kontrolliert wird. Wenn der Sender zu schnell sendet so dass irgend ein Teil in der ganzen Kette bis zum Empfänger nicht mehr mit kommt, dann werden irgendwo Pakete verworfen. Das merkt der Sender dann weil er ja kein ACK bekommt. Er versendet dann nicht nur die Daten neu, sondern fährt auch die Sendegeschwindigkeit ein wenig zurück. Sobald für einen längeren Zeitraum keine Retransmissions mehr nötig waren fährt der Sender die Sendegeschwindigkeit wieder ein bisschen hoch. Bis wieder Pakete verloren gehen. Usw. So pendelt sich die Sendegeschwindigkeit im Schnitt quasi von selbst auf dem Niveau ein bei dem alle Glieder der Übertragungskette gerade noch mitkommen.
-
@hustbaer Das heißt um es kurz zu wiederholen:
connect() ist notwendig, damit send() und recv() verwendet werden können.Werden die ACK Packets mit Size 54 also immer als Art Callback versendet, wenn einer der beiden Seiten eine Info verschickt hat? Empfangsbestätigung sozusagen.
Angenommen ich schicke jetzt 400 Byte auf einmal, dann kommen die unterschiedlich schnell an, wie du beschrieben hast, und dann haben die eine Sequencenumber wie Packet 1/3 , 3/3, 2/3 und erst wenn beim Empfänger im TCP Stack (ist der Teil vom virtuellen RAM des Prozesses?) zu 3/3 zusammengebaut und sobald das vorhanden ist, liefert recv() den Inhalt?
Kann es sein das recv auch 3 unterschiedliche Packete aufeinmal liefert? Wie oben erwähnt mit 3 mal send()? Was ist wenn da dann das 3te vor dem ersten ankommt? Muss da der Server vorher auf das ACK warten, um sowas zu vermeiden? Oder alles auf einmal schicken, und den Client parsen lassen, damit man weniger ACKs braucht? ACK = Acknowledged = Angekommen?
Und dieses rote Connection interrupt packet ist dann der abrupte Shutdown? Wird dass vom OS gesendet, wenn es merkt dass ein Prozess terminiert, der noch Sockets registriert hat?