Forum: PC-Programmierung C++ alle Daten mit recv() empfangen?


von Kilian K. (kellermaaan)


Lesenswert?

Hallo zusammen,

ich versuche momentan mit der Methode
1
rc = recv(s, recvBuf, bufferLength, 0);
die Antwort von einem Server zu empfangen. Nur das Problem ist, die 
Antwort kommt in mehreren Paketen...

Wie schaffe ich es, dass erst alles empfangen wird und dann erst der 
Code weiter abgearbeitet wird?

Wenn ich das in eine Schleife packe, bleibt der Code irgendwann in der 
recv() Methode stehen, da diese ja blockiert.
1
/* Auf die Antwort des Servers warten */
2
      do {
3
        rc = recv(s, recvBuf, bufferLength, 0);
4
5
        if(rc > 0) {
6
          /* String kopieren */
7
          for (int i = 0; i <= rc; i++) {
8
            strIn += recvBuf[i-1];
9
          }
10
        }
11
      } while(rc > 0);

Ich habe gelesen man kann diese "non-blocking" machen aber verstehe 
nicht ganz wie!?

Ich hoffe es kann mir jemand bei dem Problem helfen!

Gruß Kilian

von Dr. Sommer (Gast)


Lesenswert?

Kilian K. schrieb:
> Wie schaffe ich es, dass erst alles empfangen wird und dann erst der
> Code weiter abgearbeitet wird?

Was bedeutet "alles"? Wenn der Server jede Sekunde 1 Byte sendet für die 
nächsten 10 Jahre, wann soll deine Empfangsroutine beenden?

von Yalu X. (yalu) (Moderator)


Lesenswert?

Kilian K. schrieb:
> Wie schaffe ich es, dass erst alles empfangen wird

Was ist "alles"?

Du brauchst ja irgendein Kriterium um zu wissen, wann der Server
"alles" gesendet hat.

Entweder

- du kennst vorab die Länge des gesamten Datensatzes, oder

- du erkennst das Ende des Datensatzes an einem speziellen Byte oder
  Schlüsselwort, oder

- der Server schließt die Verbindung nach erfolgter Datenübertragung.

Vielleicht gibt es auch noch weitere Endekriterien.

Allen gemeinsam ist, dass du in deiner Client-Software eine Schleife so
lange drehen lässt, bis dieses Kriterium erfüllt ist.

Ob du das blockierend machst oder nicht, ist wieder eine andere Frage.
Nichtblockierend bedeutet einfach nur, dass der recv-Aufruf immer sofort
zurückkehrt, auch wenn keine neuen Daten empfangen wurden. Das gibt dir
die Möglichkeit, in der Schleife noch weitere Dinge abzuarbeiten.

: Bearbeitet durch Moderator
von Kilian K. (kellermaaan)


Lesenswert?

In der Antwort sind immer 7 Semikolons enthalten. Das könnte ja dann das 
Kriterium sein. Das hatte ich auch schon versucht aber nach dem Paket 
mit dem 6. Semikolon blieb das Prgramm in der recv() Methode stehen...

von Peter II (Gast)


Lesenswert?

Kilian K. schrieb:
> In der Antwort sind immer 7 Semikolons enthalten. Das könnte ja dann das
> Kriterium sein. Das hatte ich auch schon versucht aber nach dem Paket
> mit dem 6. Semikolon blieb das Prgramm in der recv() Methode stehen...

dann kommt auch kein Zeichen mehr. Sicher das du nicht verzählt hast?

du kannst vorher ein select oder poll auf den Socket machen. Wenn das 
etwas liefert, kannst du sicher sein, das das recv nicht blockiert.

von Sven B. (scummos)


Lesenswert?

Die kurze Antwort ist, weder TCP noch UDP sagen dir wie viele Daten da 
kommen oder bieten dir irgendeine Möglichkeit an Daten"pakete" 
gegeneinander abzugrenzen. Das musst du auf einer höheren Protokollebene 
machen, zum Beispiel indem du zuerst einen Header sendest, in dem die 
Anzahl der Bytes steht die danach noch gelesen werden müssen. Zumindest 
TCP garantiert dir dann zumindest dass all diese Bytes auch ankommen.

von mh (Gast)


Lesenswert?

Du musst die dem recv übergebene Pufferlänge anpassen.
Wenn Du vorher nicht weisst, wie viele Daten noch kommen sollen, musst 
Du entweder Byte-für-Byte lesen oder Dir ein anderes Protokoll 
überlegen, bei dem Du dieses Wissen hast.

von Mikro 7. (mikro77)


Lesenswert?

Außerdem...

Kilian K. schrieb:
1
>          /* String kopieren */
2
>           for (int i = 0; i <= rc; i++) {
3
>             strIn += recvBuf[i-1];
4
>           }

...was passiert bei i=0?

: Bearbeitet durch User
von Mark B. (markbrandis)


Lesenswert?

Mikro 7. schrieb:
> ...was passiert bei i=0?

Unsinn?

von Sven B. (scummos)


Lesenswert?

Außerdem muss man sich erstmal eine ineffizientere Methode einfallen 
lassen als die hier um einen String zu kopieren ;D

von Rolf M. (rmagnus)


Lesenswert?

Kilian K. schrieb:
> In der Antwort sind immer 7 Semikolons enthalten. Das könnte ja dann das
> Kriterium sein.

Wenn das letzte Semikolon das Ende der Antwort markiert, ja.

> Das hatte ich auch schon versucht aber nach dem Paket mit dem 6.
> Semikolon blieb das Prgramm in der recv() Methode stehen...

Hast du denn einen TCP- oder einen UDP-Socket? Hast du den Sender auch 
geschrieben, oder ist der gegeben?

Peter II schrieb:
> du kannst vorher ein select oder poll auf den Socket machen. Wenn das
> etwas liefert, kannst du sicher sein, das das recv nicht blockiert.

Das ist falsch.

Sven B. schrieb:
> Die kurze Antwort ist, weder TCP noch UDP sagen dir wie viele Daten da
> kommen oder bieten dir irgendeine Möglichkeit an Daten"pakete"
> gegeneinander abzugrenzen.

Bei UDP kann man durchaus Pakete abgrenzen. UDP ist ein 
Datagramm-orientiertes Protokoll.

von Peter II (Gast)


Lesenswert?

Rolf M. schrieb:
> Peter II schrieb:
>> du kannst vorher ein select oder poll auf den Socket machen. Wenn das
>> etwas liefert, kannst du sicher sein, das das recv nicht blockiert.
>
> Das ist falsch.

was soll daran falsch sein?

von Rolf M. (rmagnus)


Lesenswert?

Naja, es stimmt eben nicht, zumindest unter Linux. Die man-Page sagt bei 
mir:

"Under Linux, select() may report a socket file descriptor as "ready for 
reading", while  nevertheless  a  subsequent  read  blocks. This  could 
for example happen when data has arrived but upon examination has wrong 
checksum and is discarded.  There may be other circumstances in which a 
file descriptor is spuriously reported as ready.  Thus it may be safer 
to use O_NONBLOCK on  sockets  that should not block."

von Peter II (Gast)


Lesenswert?

Rolf M. schrieb:
> Naja, es stimmt eben nicht, zumindest unter Linux. Die man-Page sagt bei
> mir:

seht aber unter "Bugs"

kann man nur hoffen das es mal behoben wird, bei poll steht nichts 
davon. Auch bei MS steht nicht davon.

Naja wenigsten werden die Bugs bei Linux dokumentiert, statt behoben.

von Mikro 7. (mikro77)


Lesenswert?

Interessant. Ich dachte bisher, dass "spurious readiness notifications" 
Teil des Standards sind. Posix sagt allerdings: "A descriptor shall be 
considered ready for reading when a call to an input function with 
O_NONBLOCK clear would not block..." 
http://pubs.opengroup.org/onlinepubs/9699919799/functions/select.html

Peter II schrieb:
> bei poll steht nichts davon

Bei mir steht da: "See the discussion of spurious readiness 
notifications under the BUGS section of select(2)".

: Bearbeitet durch User
von Kilian K. (kellermaaan)


Lesenswert?

Mikro 7. schrieb:
> Außerdem...
>
> Kilian K. schrieb:>          /* String kopieren */
>>           for (int i = 0; i <= rc; i++) {
>>             strIn += recvBuf[i-1];
>>           }
> ...was passiert bei i=0?

Das hatte ich mir mal von einer Seite kopiert um es zu testen.

Habe es auch noch weiter versucht aber irgendwie will es noch nicht so 
wirklich funktionieren.
Mein aktueller Stand ist so:
1
/* Auf die Antwort des Servers warten */
2
      do {
3
        semicolons = 0;
4
5
        /* Empfangende Daten auslesen */
6
        rc = recv(this->_s, &recvBuf[readSize], sizeof(recvBuf), 0);
7
8
        /* Wurde etwas ausgelesen? */
9
        if(rc > 0) {
10
11
          /* Anzahl der ausgelsenen Daten hochzählen */
12
          readSize += rc;
13
14
          /* String nach den Semikolons durchsuchen */
15
          for (int i = 0; i <= readSize; i++) {
16
            if(recvBuf[i]  == ';') {
17
              semicolons++;
18
            }
19
          }
20
        }
21
      } while(semicolons <= 6);

Mein Problem ist, zurzeit werden 260 Byte ausgelesen und in recvBuf 
gespeichert. Es wird aber erkannt, dass es noch mehr Daten zum auslesen 
gibt aber der Rest wird nicht abgespeichert... Es wird also weiter 
ausgelesen aber es bleiben immer nur die 260 Byte in recvBuf.

recvBuf erstelle ich so:
1
#define  BUFFER_LENGTH 10000
2
3
char *recvBuf;
4
recvBuf = new char[BUFFER_LENGTH];

Rolf M. schrieb:
> Hast du denn einen TCP- oder einen UDP-Socket?

Mein Programm hat einen TCP- und UDP-Socket wobei ich in diesem Beispiel 
mit einem TCP-Socket arbeite!

von Mikro 7. (mikro77)


Lesenswert?

Kilian K. schrieb:
>
1
#define  BUFFER_LENGTH 10000
2
> 
3
> char *recvBuf;
4
> recvBuf = new char[BUFFER_LENGTH];
5
> ...
6
>/* Auf die Antwort des Servers warten */
7
>       do {
8
>         semicolons = 0;
9
>
10
>         /* Empfangende Daten auslesen */
11
>         rc = recv(this->_s, &recvBuf[readSize], sizeof(recvBuf), 0);
12
13
sizeof(recvBuf)?
14
15
>         /* Wurde etwas ausgelesen? */
16
>         if(rc > 0) {
17
> 
18
>           /* Anzahl der ausgelsenen Daten hochzählen */
19
>           readSize += rc;
20
> 
21
>           /* String nach den Semikolons durchsuchen */
22
>           for (int i = 0; i <= readSize; i++) {
23
24
Was passiert bei i == readSize?
25
26
>             if(recvBuf[i]  == ';') {
27
>               semicolons++;
28
>             }
29
>           }
30
>         }
31
32
Für den else-Zweig gibt es nichts zu beachten?
33
34
>       } while(semicolons <= 6);

von Nop (Gast)


Lesenswert?

Sven B. schrieb:
> Zumindest
> TCP garantiert dir dann zumindest dass all diese Bytes auch ankommen.

Nein. Auch bei TCP muß ein Timeout rein. Server können ausfallen, 
Internetverbindungen abreißen und dergleichen. Daraufhin muß die 
Software in einen Zustand versetzt werden, daß sie versucht, sich erneut 
zu synchronisieren.

Im Falle mobiler Internetverbindungen tunlichst nicht einfach jede 
Sekunde den Server kontaktieren, weil sonst das Datenvolumen anschwillt. 
Dann nimmt man einen Backoff-Algorithmus.

Ach ja, man sollte auch über eine CRC für die Inhalte nachdenken. Die 
TCP-Checksumme ist ein schlechter Witz.

von Kilian K. (kellermaaan)


Lesenswert?

Mikro 7. schrieb:
> sizeof(recvBuf)?

Lese halt nicht jedes Byte einzelnd aus sondern immer 4...

Mikro 7. schrieb:
> Was passiert bei i == readSize?

Hatte ich noch übersehen. Habe es in
1
for (int i = 0; i < readSize; i++) {
geändert.

Mikro 7. schrieb:
> Für den else-Zweig gibt es nichts zu beachten?

Ne, eigentlich nicht. Will ja nur die Semikolons haben, der Rest ist ja 
egal...

von Mikro 7. (mikro77)


Lesenswert?

Kilian K. schrieb:
> Mein Problem ist, zurzeit werden 260 Byte ausgelesen und in recvBuf
> gespeichert. Es wird aber erkannt, dass es noch mehr Daten zum auslesen
> gibt aber der Rest wird nicht abgespeichert... Es wird also weiter
> ausgelesen aber es bleiben immer nur die 260 Byte in recvBuf.

Auf einen ersten Blick sollte dein Code trotz der Bugs funktionieren, 
wenn denn tatsächlich immer 7 Semikolons innerhalb von BUFFER_LENGTH 
enthalten sind, readSize auf 0 initialisiert ist, und recv() weder EOF 
noch Fehler liefert. Kompletter Client/Server Code wäre hilfreich, 
reduziert auf das Fehlerszenario.

von Mikro 7. (mikro77)


Lesenswert?

Kilian K. schrieb:
> Mikro 7. schrieb:
>> sizeof(recvBuf)?
>
> Lese halt nicht jedes Byte einzelnd aus sondern immer 4...

Warum "tarnen" mit sizeof(recvBuf)?

Bug: Was passiert wenn bereits BUFFER_LENGTH Daten gelesen wurden?

> Mikro 7. schrieb:
>> Für den else-Zweig gibt es nichts zu beachten?
>
> Ne, eigentlich nicht. Will ja nur die Semikolons haben, der Rest ist ja
> egal...

Naja, EOF und Fehler sollten imho anders behandelt werden als durch 
"Endlosschleife".

von Kilian K. (kellermaaan)


Lesenswert?

1
/* Auf die Antwort des Servers warten */
2
      do {
3
        semicolons = 0;
4
5
        /* Empfangende Daten auslesen */
6
        rc = recv(this->_s, &recvBuf[readSize], sizeof(recvBuf), 0);
7
8
        /* Wurde etwas ausgelesen? */
9
        if(rc > 0) {
10
11
          /* Anzahl der ausgelsenen Daten hochzählen */
12
          readSize += rc;
13
14
          /* String nach den Semikolons durchsuchen */
15
          for (int i = 0; i < readSize; i++) {
16
            if(recvBuf[i]  == ';') {
17
              semicolons++;
18
            }
19
          }
20
        } else {
21
          // Falls nichts drin steht wird eine Fehlermeldung ausgegeben
22
          MessageBox::Show("Fehler! Bei dem Empfangen der Daten ist ei Fehler aufgetreten!", "SCPI Arduino", MessageBoxButtons::OK);
23
        }
24
25
      } while(semicolons <= 6);
So funktioniert es anscheinend... So wie es aussieht empfange ich alles 
und es wird auch alles augegeben!

Danke nochmal für die Hilfe!!!

von Eric B. (beric)


Lesenswert?

1
#define BUFLEN 10000
2
3
static char buffer[BUFLEN];
4
5
void recv7semicolons(int server, int flags)
6
{
7
    int n_semicolons = 0;
8
    int n_read = 0;
9
    
10
    while (n_semicolons < 7)
11
    {
12
        int rc = recv(server, buffer + n_read, BUFLEN - n_read, flags);
13
        
14
        if(rc < 0)
15
        {
16
            break;
17
        }
18
        else
19
        {
20
            for(int i = n_read; i < (n_read + rc); i++)
21
            {
22
                if (buffer[i] == ';')
23
                {
24
                    n_semicolons ++;
25
                }
26
                else
27
                {
28
                    /* do something useful? */
29
                }
30
            }
31
            n_read += rc;
32
        }
33
    }
34
}

von Rolf M. (rmagnus)


Lesenswert?

Kilian K. schrieb:
> Mikro 7. schrieb:
>> sizeof(recvBuf)?
>
> Lese halt nicht jedes Byte einzelnd aus sondern immer 4...

Äh, ja. Hoffe, das war ein Scherz.

von Kilian K. (kellermaaan)


Lesenswert?

Rolf M. schrieb:
> Äh, ja. Hoffe, das war ein Scherz.

Haha leider nicht. Erstaunlicherweise bekomme ich damit 4 Byte statt nur 
einem ausgelesen :D

von Eric B. (beric)


Lesenswert?

Kilian K. schrieb:

> So funktioniert es anscheinend... So wie es aussieht empfange ich alles
> und es wird auch alles augegeben!

Das ist aber Zufall, da recv() wahrscheinlich alle Zeichen in einem 
Aufruf lesen konnte.
recv() addiert bei jedem Aufruf die neu empfangene Bytes am "Ende" vom 
revBuf. So weit so gut.
Die for()-Schleife aber fängt immer wieder von ganz vorne an nach 
Semicolons zu suchen und addiert die zum Resultat hinzu. Wenn also im 1. 
recv() 4 Semicolons gefunden wurden, und im 2. keine wird trotzdem 
semicolons auf 8 hochgezählt und die Rountine abgebrochen obwohl erst 
4 Semicolons gelesen wurden.

: Bearbeitet durch User
von Kilian K. (kellermaaan)


Lesenswert?

Eric B. schrieb:
> Kilian K. schrieb:
>
>> So funktioniert es anscheinend... So wie es aussieht empfange ich alles
>> und es wird auch alles augegeben!
>
> Das ist aber Zufall, da recv() wahrscheinlich alle Zeichen in einem
> Aufruf lesen konnte.
> recv() addiert bei jedem Aufruf die neu empfangene Bytes am "Ende" vom
> revBuf. So weit so gut.
> Die for()-Schleife aber fängt immer wieder von ganz vorne an nach
> Semicolons zu suchen und addiert die zum Resultat hinzu. Wenn also im 1.
> recv() 4 Semicolons gefunden wurden, und im 2. keine wird trotzdem
> semicolons auf 8 hochgezählt und die Rountine abgebrochen obwohl erst
> 4 Semicolons gelesen wurden.

Aber die Variable wird doch vor dem Einlesen wieder zurückgesetzt.

Man kann es aber natürlich auch so machen:
1
/* Auf die Antwort des Servers warten */
2
      do {
3
        /* Empfangende Daten auslesen */
4
        rc = recv(this->_s, &recvBuf[readSize], BUFFER_LENGTH_MAX - readSize, 0);
5
6
        /* Wurde etwas ausgelesen? */
7
        if(rc > 0) {
8
9
          /* String nach den Semikolons durchsuchen */
10
          for (unsigned int i = readSize; i < (readSize + rc); i++) {
11
            if(recvBuf[i]  == ';') {
12
              semicolons++;
13
            }
14
          }
15
16
          /* Anzahl der ausgelsenen Daten hochzählen */
17
          readSize += rc;
18
        } else {
19
          /* Falls nichts drin steht wird eine Fehlermeldung ausgegeben */
20
          MessageBox::Show("Fehler! Bei dem Empfangen der Daten ist ei Fehler aufgetreten!", "SCPI Arduino", MessageBoxButtons::OK);
21
        }
22
      } while(semicolons <= 6);

: Bearbeitet durch User
von Eric B. (beric)


Lesenswert?

Kilian K. schrieb:
> Aber die Variable wird doch vor dem Einlesen wieder zurückgesetzt.

Verflixt, haste Recht, habe ich übersehen.

von Rolf M. (rmagnus)


Lesenswert?

Kilian K. schrieb:
> Rolf M. schrieb:
>> Äh, ja. Hoffe, das war ein Scherz.
>
> Haha leider nicht. Erstaunlicherweise bekomme ich damit 4 Byte statt nur
> einem ausgelesen :D

Ja, weil du immer soviele Bytes liest, wie ein Zeiger groß ist. Ist wohl 
ein 32-Bit-Programm. In 64 Bit würdest du immer 8 Bytes auf einmal 
lesen.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.