Socket-Verhalten
-
Hallo Leute,
es geht sich um folgendes:
Ich habe eine Client-Applikation.
Diese hat eine Socketverbindung zum Server.
Ich warte innerhalb der Serverschleife mittels Select auf Daten:
int Socket::WaitingForData(long para_sec, long para_usec) { this->timeout.tv_sec = para_sec; this->timeout.tv_usec = para_usec; FD_ZERO(&this->readset); FD_SET(this->sock, &readset); this->WaitStatus = ::select(this->sock, &this->readset, NULL, NULL, &this->timeout); if(this->WaitStatus == SOCKET_ERROR) { //std::cout << "SOCKET_ERROR" << __LINE__ << __FILE__ << std::endl; return -1; } else if (this->WaitStatus == 0) { // std::cout << "Time elapsed on select()" << std::endl; return 0; } else if (this->WaitStatus > 0) { FD_ISSET(this->sock, &this->readset); return 1; }; }Falls Daten vorhanden sind, rufe ich die ersten 42Bytes der Daten ab.
Die Daten sind so aufgebaut
[42 Byte Header(inkl. Größe der Nutzdaten) ||xxxxxxxxxxxNutzdatenxxxxxxxxxxx]int Socket::RecvHeader(char * headerbuf){ this->SockReturn = 0; while((this->SockReturn = ::recv(this->sock,headerbuf,42,0)) < 42) { ::Sleep(1); }; return this->SockReturn; };Da ich durch den Header nun weiß, wie viel ich abrufen muss, rufe ich folgenden Code auf. Und hier tritt auch das Problem auf.
while((this->iRecv = ::recv(this->sock,recvbuf,NUTZDATENLAENGE,0)) < NUTZDATENLAENGE) { ::Sleep(1); };Er blockt. Das ist natürlich selbstverständlich, da ich durch die Schleife so lange warte, bis die Anzahl der angegebenen Bytes gelesen wurden.
Jedoch passiert nichts. Es blockt so lange, bis der Server ein Timeout hat(10Sekunden) und dieser die Daten erneut! schickt. Allerdings kann ich mit den Daten nicht weiter arbeiten, da sich innerhalb dieser Daten eine Identifikationsnummer geändert hat.
Dieses Problem tritt unterschiedlich auf. Mal läuft es Stundenlang ohne Probleme, mal tritt dieses Problem 2,3 mal in der Stunde auf.
Es lässt sich weder auf Seiten des Servers, noch auf Client-Seite ein Fehler bisher erkennen.Jemand schonmal etwas von solch einem Verhalten mitbekommen?
Es handelt sich um einen TCP/IP - SOCK_STREAM.
Bin dankbar für jede Antwort.
Danke im Voraus.
-
Du hast einen Datenstrom, d.h du musst dich darum kümmern, daraus Pakete der passenden Größe zusammenzubasteln. Recv macht das nicht für dich. Recv kann dir Einzelbytes liefern, oder auch gleich fünfeinhalb Pakete auf einmal.
-
MFK schrieb:
Du hast einen Datenstrom, d.h du musst dich darum kümmern, daraus Pakete der passenden Größe zusammenzubasteln. Recv macht das nicht für dich. Recv kann dir Einzelbytes liefern, oder auch gleich fünfeinhalb Pakete auf einmal.
Wie darf ich das verstehen?
Denn es funktioniert ja, nur manchmal eben einfach nicht..
-
zuckerlie schrieb:
Wie darf ich das verstehen?
Wie es da steht. Du darfst nicht erwarten, dass das, was du auf der einen Seite mit einem send-Aufruf abschickst, auf der anderen Seite mit einem recv-Aufruf abrufen kannst. Diese Strukturinformation wird nicht transportiert. Das ist die Bedeutung von Datenstrom.
zuckerlie schrieb:
Denn es funktioniert ja, nur manchmal eben einfach nicht..
Wenn das jemals funktioniert hat, dann nur durch entspanntes Zeitverhalten beim Sender, so dass die Einzelpakete durch hinreichend große Sendepausen voneinander getrennt waren.
Der Code ist schlicht und einfach falsch.
-
Vielen Dank erstmal.
Es funktioniert ja eigentlich sogar sehr gut. Wie gesagt, manchmal läuft es Stundenlang und ich habe mehrere hunderte Durchläufe, die funktionieren. Nur irgendwann kracht es eben.
Der Code ist schlicht und einfach falsch.
Was gäbe es denn z.B. für Möglichkeiten bzw. Alternativen?
//edit:
Ich habe es auch mit einigen Sleeps probiert, sodass die Datenübertragung zumindest vollständig sein sollte.
-
zuckerlie schrieb:
Was gäbe es denn z.B. für Möglichkeiten bzw. Alternativen?
Üblicherweise hängt man die empfangenen Bytes an einen Puffer an. Nach jedem Empfang prüft man, ob ein vollständiges Paket im Puffer ist, nimmt es raus und bearbeitet es. Das macht man, bis kein vollständiges Paket mehr im Puffer ist.
-
Such mal im WINAPI - Forum, da gibt es diverse Beiträge zu dem 'Problem'. Aber wie schon gesagt wurde:
Wenn Du 10000 Byte Daten erwartest, dann ist nicht garantiert, dass Du die mit einem einzigen recv - Aufruf bekommst. Es kann sein, deshalb läuft es sicher ab und zu, es kann aber auch sein, dass Du dreimal recv aufrufen musst und dann zB 2000, 3000 und 5000 Byte bekommst.
Diese Eventualität musst Du halt managen.
-
Du brauchst ein Protokoll, dass irgendwie die Telegrammlänge spezifiziert. Ausserdem brauchst du eine Art FIFO, wo hinten Daten eingefügt und vorn Daten entnommen werden. Im Pseudocode könnte das so aussehen:
while( connected ) { count = receive( ReceiveBuffer ); if( count > 0 ) { TelegramBuffer.append( ReceiveBuffer ); while( TelegramBuffer.size() >= TelegramHeaderLength ) { TelegramHeader* Hdr = (TelegramHeader*) &TelegramBuffer[0]; if( TelegramBuffer.size() >= Hdr->TelegramLength ) { // Telegramm behandeln TelegramBuffer.erase_front( Hdr->TelegramLength ); } } } }Nach jedem Empfang hängst du die Daten in einen Puffer an. Solange der Puffer genügend Daten für den Telegrammkopf enthält interpretierst du die Daten am Pufferanfang als Telegrammkopf und guckst nach, wie lang das Telegramm ist. Wenn der Puffer genügend Daten für das Telegramm enthält entnimmst du das komplette Telegramm dem Puffer. Das funktioniert für Telegramme unterschiedlicher Länge, wenn du eine feste Telegrammlänge von 42 Byte hast wird´s natürlich wesentlich einfacher, weil du immer nur gucken musst, ob 42 Bytes im Puffer stehen.
Wie würdest du das Problem denn lösen, wenn dein Telegramm 42 Byte lang ist, aber bei jedem Empfang immer nur 11 Byte gelesen werden?
-
iRecv = recv(this->sock, recvbuf , Nutzdatenlänge, 0) while(iRecv < Nutzdatenlänge) { iRecv += recv(this->sock, recvbuf+iRecv, Nutzdatenlänge, 0) };Da ich erst den Puffer komplett füllen möchte, die Frage, ob es auch so gehen würde?!
recvbuf ist ein char* Pointer auf einen vorher allokierten Buffer. Dieser wird um die Anzahl der abgelesenen Bytes inkrementiert, damit die vorhandenen Daten im "recvbuf" nicht überschrieben werden.
-
Vielen Dank erstmal Leute.
Ja, ich weiß, dass durch ein einzelnes recv() nicht alle Daten gelesen werden. Deshalb habe ich es so, wie ganz oben angegeben, in die Schleife(nbedingung) gesetzt( Das habe ich von Codeproject damals übernommen).
//Entschuldigung - wollte keinen extra Post machen.
-
zuckerlie schrieb:
Hallo Leute,
int Socket::RecvHeader(char * headerbuf){ this->SockReturn = 0; while((this->SockReturn = ::recv(this->sock,headerbuf,42,0)) < 42) { ::Sleep(1); }; return this->SockReturn; };Genau da ist der Fehler. Wenn Du weniger als 42 Bytes empfängs, liest Du noch mal 42 Bytes. Und das noch dazu an den Anfang deines Puffers. Bekommst Du beispielsweise 2 Bytes beim ersten Aufruf und 42 beim nächsten, hast Du die Bytes 2-43 (also von 0 an gezählt) in Deinem Puffer, statt 0-41, wie Du es erwartest. Bei den Nutzdaten machst Du das dann korrekt.
Und so ganz nebenbei: ein Sleep ist immer die falsche Lösung. Nimm select oder poll.
-
zuckerlie schrieb:
while((this->iRecv = ::recv(this->sock,recvbuf,NUTZDATENLAENGE,0)) < NUTZDATENLAENGE) { ::Sleep(1); };Angenommen, Du erwartest die von mir weiter oben erwähnten 10000 Bytes, die aber nur portionsweise ankommen. Dann bekommt this->iRecv nacheinander die Werte 2000, 3000 und 5000 - bleibt aber immer kleiner als 10000 und deshalb kommt das Programm nicht aus dieser Schleife heraus.
Und was soll überhaupt das Sleep(1) hier?
-
So, ich melde mich zurück.
Erstmal vielen Dank.
Ich habe schon oft von diesem Problem gelesen, aber nicht gedacht, dass es in diesem Fall zutrifft.
Wie ich bereits sagte, habe ich bei Codeproject gelesen, dass das recv in die Bedingung der While-Schleife gesetzt wurde und somit keine mehrfachen Aufrufe innerhalb der Schleife nötig sind. Das Sleep(1) innerhalb dieser Schleife war um ggf. sonst eine Millisekunde zu warten und dann nochmal zur Bedingung zu gehen.
Ich habe es nun so umgesetzt:
int connectsocket::RecvPayload(char * recvbuf, int iPayload) { this->iReturnPayload = 0; this->iReturnPayload = ::recv(this->sock, recvbuf,iPayload, 0); //recvbuf innerhalb der main: // char * recvbuf = new recvbuf[Payload] while(this->iReturnPayload != iPayload) { this->iReturnPayload += ::recv(this->sock, recvbuf+this->iReturnPayload ,iPayload, 0); }; return this->iReturnPayload; }
-
Falls die Gegenstelle weitersendet, willst Du hier aber nur die für diese Übertragung gewünschte Datenmenge lesen, deshalb:
this->iReturnPayload += ::recv(this->sock, recvbuf+this->iReturnPayload ,iPayload - iReturnPayload, 0)
-
Bitte keine ungarische Notation und mach die ganzen this-> weg.

-
sockets sind schwer... zumindest für mich! und wenn ich mich jetzt nicht täusch, sind in deinem so viele fehler (was passiert bei ::recv rückgaben von 0 bzw -1 was ja kleiner als dein NUTZDATENLAENGE ist?), dass ich dir empfehle eine fertige library zu nehmen.
okay, evtl. wird da auch dann ganz was anderes gemacht, kenn ja ::recv nicht und will dir jetzt nichts unterstellen

@edit warte, das ist windows... da ists statt -1 SOCKET_ERROR, oder

-
Nö, sockets sind eigentlich ziemlich simpel.
@zuckerlie
Für dich gibt es zwei Lösungen: select() oder Threads. (Oder poll(), aber das dürfte Windows nicht haben.) (Du kannst den Socket auch select()en nachdem er verbunden wurde, um zu testen ob du etwas Lesen kannst.)Und ja, du musst natürlich den Rückgabewert von recv() überprüfen bevor du das irgendwo drauf addierst, ist doch klar. Und hör auf Pi.
-
Oh man oh man, vielen Dank Leute.
Ihr habt mir sehr geholfen.
@Belli, da hast du recht. Das habe ich natürlich auch nicht bedacht.
//edit, aber das ändert doch nichts so wirklich, da ich ja sowieso nach einer gewissen menge aufhöre. sollte doch selbst so ohne probleme gehen?!@Cooky, ja der Rückgabewert wird geprüft. Habe ich schon programmiert. Ja, ich nutze schon select() um zu schauben, ob ich lesen kann.
@Pi, spricht was dagegen?
Vielen Dank Euch

-
-
314159265358979 schrieb:
zuckerlie schrieb:
@Pi, spricht was dagegen?
Spricht was dafür?

Übersicht?!
-
xDDDDDD
Spinner.