TCP-Socket: Ein send() mit einem recv() empfangen
-
Guten Abend,
es geht um die Datenübermittlung per TCP unter Windows. Ich habe eine Server-Anwendung, die und eine Server-Anwendung, die per accept() einen Socket erstellt und eine Client-Anwendung, die per connect() mit einem Server verbunden ist.
Nun möchte ich per send() vom Client an den Server senden, und diesen mit wieder recv() empfangen. Soweit kein Problem, doch auf jeden send()-Aufruf soll auch einen eigenen recv()-Aufruf folgen. So in etwa habe ich mir das also vorgestellt:
Client | | Server send(s, "test", ...) --> recv(...) -> "test" soll ankommen. send(s, "Das ist eine Nachricht.", ...) --> recv(...) -> "Das ist eine Nachricht." soll ankommen.
Das Problem ist aber: recv() nimmt ja nur eine bestimmte Anzahl von Zeichen auf, zum Testen habe ich diese mal auf 10 Byte gesetzt.
Abgeschickt wird so:
// client-file int rc; char buf[20] = "msg"; // Größe soll später dynamisch sein. rc = send(s, buf, sizeof(buf), 0); if(s == SOCKET_ERROR) { std::cout << "Fehler beim Senden: " << WSAGetLastError() << "\n"; }
Jetzt habe ich 2 Möglichkeiten, diese Nachricht zu empfangen:
// server (variante 1) int Socket::recvTCP(string &dest) { char *buf = new char[maxReceive + 1]; // maxReceive = 10 memset(buf, 0, maxReceive + 1); dest.clear(); int nRec; nRec = ::recv(sock, buf, maxReceive, 0); buf[nRec] = '\0'; dest.assign(buf); // Doch hier landen natürlich nur 10 Zeichen in dest. delete [] buf; return true; }
// server (variante 2) int Socket::recvTCP(string &dest) { char *buf = new char[maxReceive + 1]; // maxReceive = 10 memset(buf, 0, maxReceive + 1); dest.clear(); int nRec; while((nRec = ::recv(sock, buf, maxReceive, 0)) != SOCKET_ERROR) { buf[nRec] = '\0'; dest.append(buf); // Hier landen alle gesendeten Daten (also auch von mehreren send()-Aufrufen) } delete [] buf; return true; }
Gibt es eine Möglichkeit, einen String von beliebiger Größe zu verschicken und dann auch in einem einzelnen String zu empfangen? Also so zu sagen eine Misching dieser beiden Varianten?
-
TCP ist quasi wie eine Art Fluss:
Oben an der Quelle sitzt irgendnen Typ, nennen wir ihn "Sender" und schmeißt Flaschenpost rein.
Unten sitzt ein anderer Typ, nennen wir ihn "Empfänger" und fischt diese Flaschen da wieder raus.Nun hat der Empfänger keinen Plan wieviel der Sender wohl in den Fluss geschmissen haben könnte und ob und wann der jemals aufhört Flaschen in den Fluss zu werfen.
Die Lösung:
Der Sender schmeißt als erstes ne Flasche in den Fluss wo der Zettel in der Flasche die Zahl nennt wieviele Flaschen noch kommen werden.Sprich:
Pack dir die Länge deiner Nachricht z.B. in einen 32-bit Integer.
Verschicke diesen 32-bit Integer und anschließend die eigentliche Nachricht.
Beim Sender:
- Warte bis zu 4 Bytes (=32-bit) erhalten hast, interpretiere das als 32-bit Integer(ich nenne ihn mal "msg_len")
- Warte nun auf soviele Bytes bis du msg_len Bytes erhalten hast.
-
> - Warte bis zu 4 Bytes (=32-bit) erhalten hast, interpretiere das als 32-bit Integer(ich nenne ihn mal "msg_len")
Naja... Dann sendet der Client mal eben 4294967296 ( = 2³²). Wenn er Glück hat, crasht er den Server, wenn der Server nicht gerade sein
new
mit einem try sichert, weil er niemals so viel Speicher (am Stück) alloziieren kann (Bad Alloc Exception) bzw. wenn er nicht prüft, wie groß die Zahl ist. Mag banal klingen (das mit dem new weniger), aber es kommt vor. Netzwerk bedeutet immer lieber auf Nummer sicher, auch, wenn man denkt, alles richtig gemacht zu haben. Deshalb ist das keine all zu schöne Lösung, finde ich. Mann kann eben so viel falsch machen.Was du aber auf jeden Fall tun solltest:
send()
gibt als Rückgabewert die Anzahl der gesendeten Bytes zurück. Wenn der Client nur maximal 10 Bytes prorecv()
empfangen kann, muss du ebensend()
in einer Schleife ausführen und immer die Byte-Sequenz senden, die noch nicht ankam. Häppchenweise eben. Es kann aber auch andere Gründe geben, warum nicht alles ankommt, da z.B. das Netzwerk eine bestimmte Bandbreite hat. Um Datenverlust zu vermeinden, musst du das überprüfen.
Der Client hat einen statischen 10-Byte-Puffer, der vollgehauen wird. Das Ergebnis packt er sich dann häppchenweise in einen dynamischen Speicher (std::string
)
Nimm's nicht so tragisch, wenn mehreresends
vorkommen sollten. Man sollte die Anzahl gering halten, aber nicht auf Kosten der Vollständigkeit oder Sicherheit. Der Flaschenhals im Netzwerk sind eh die Kabel, im TCP istconnect
die langsamste Funktion. Ist der Stream erst mal da, ist alles gut.
-
Ad aCTa schrieb:
> - Warte bis zu 4 Bytes (=32-bit) erhalten hast, interpretiere das als 32-bit Integer(ich nenne ihn mal "msg_len")
Naja... Dann sendet der Client mal eben 4294967296 ( = 2³²). Wenn er Glück hat, crasht er den Server, wenn der Server nicht gerade sein
new
mit einem try sichert, weil er niemals so viel Speicher (am Stück) alloziieren kann (Bad Alloc Exception) bzw. wenn er nicht prüft, wie groß die Zahl ist.^
Selbstverständlich ist ein try dabei. Wenn der Client solchen Müll schickt, wird die Verbindung zu ihm getrennt. Wenn er lieb "Bitte, bitte" sagt, darf er nochmal connecten.
-
Erstmal vielen Dank für die Antworten.
Das mit der Länge der Nachricht mitgeben verstehe ich, nur finde ich das in der Umsetzung etwas schwierig. Ich müsste dann ja vor jedem Senden einen Integer in ein char[4]-Array umwandeln, 4 Bytes verschicken, 4 Bytes empfangen und die 4 empfangenen Bytes nochmal in ein Integer umwandeln?
Zur zweiten Variante: Dass ich das, was noch nicht mit send() versendet wurde in einer Schleife nachholen muss, ist logisch. Aber beim Empfangen wird es schwierig. Da kann ich ja keine Schleife nehmen - ich weiß ja schließlich (ohne diese 4 Bytes, die ich mitgeben müsste) nicht, wann Ende ist. Oder kann ich einfach jedes Byte auf \0 überprüfen um dann abzubrechen?
-
Amgon schrieb:
// client-file int rc; char buf[20] = "msg"; // Größe soll später dynamisch sein. rc = send(s, buf, sizeof(buf), 0); if(s == SOCKET_ERROR) { std::cout << "Fehler beim Senden: " << WSAGetLastError() << "\n"; }
^^ du solltest 'rc' auf SOCKET_ERROR testen, nicht s. ausserdem sollte für dein ping-pong protokoll alles verschickt sein, d.h. wenn rc < sizeof(buf) eine kleine pause einlegen und den rest hinterhersenden ggf. wiederholen bis alles wech ist.
Amgon schrieb:
Gibt es eine Möglichkeit, einen String von beliebiger Größe zu verschicken und dann auch in einem einzelnen String zu empfangen? Also so zu sagen eine Misching dieser beiden Varianten?
wie von geeky beschrieben, wäre z.b. eine möglichkeit. ist der 'zettel mit der anzahl flaschen' in einem ungültigen bereich, dann killste einfach die verbindung zum client, oder irgendwas in der art.
andere möglichkeit: strings mit 0-endkennung verschicken, kommt die 0, haste alles eingelesen.
-
Amgon schrieb:
Oder kann ich einfach jedes Byte auf \0 überprüfen um dann abzubrechen?
Wenn du nur nullterminierte Strings versendest, kannst du das natürlich.
Dazu musst du dann entweder
a) mit recv jedesmal nur ein Byte holen (nicht ideal, weil rechenzeit-verschwendung) oder
b) mit recv immer einen haufen bytes auf einmal empfangen, und sicherstellen dass, falls ein nullbyte enthalten ist, die bytes dahinter nicht verworfen werden.nur finde ich das in der Umsetzung etwas schwierig. Ich müsste dann ja vor jedem Senden einen Integer in ein char[4]-Array umwandeln, 4 Bytes verschicken, 4 Bytes empfangen und die 4 empfangenen Bytes nochmal in ein Integer umwandeln?
was ist daran besonders schwierig? etwas lästig, ja, aber nicht schwierig.
ist dasselbe wie wenn du daten in eine datei schreibst. wenn du keine dinge wie zeilenumbrüche oder nullbytes als grenze verwenden kannst (weil's z.B. binärdaten sind, und es keinen "reservierten" byte-wert gibt), musst du dir dort auch was einfallen lassen, wie du die grenzen findest.ein TCP stream ist sowieso fast wie eine datei. du kannst bytes lesen, und irgendwann ist schluss (EOF bzw. eben socket verbindung von der gegenstelle getrennt). dazwischen musst du selbst sicherstellen, dass du die daten richtig interpretieren kannst.
-
Guten Morgen,
das mit dem Zettel am Anfang funktioniert doch ganz gut:
// client.cpp char buf[250] = "Nachricht"; int bufSize = sizeof(buf); char tmp[4]; memcpy(tmp, &bufSize, sizeof(bufSize)); rc = send(s, tmp, sizeof(tmp), 0); // 250 rc = send(s, buf, sizeof(buf), 0); // Nachricht
// server.cpp int Socket::recvTCP(string &dest) { char *buf = new char[maxReceive + 1]; // maxReceive = 5 memset(buf, 0, maxReceive + 1); dest.clear(); int nRec; // get length nRec = ::recv(sock, buf, 4, 0); int bytesLeft; memcpy(&bytesLeft, buf, 4); // 250 while(bytesLeft > 0) { nRec = ::recv(sock, buf, maxReceive, 0); buf[nRec] = '\0'; // Kann ich mir eigentlich sparen? dest += buf; bytesLeft -= nRec; } delete [] buf; return true; }
Das funktioniert so auch ersteinmal ganz gut.
Schwierig wird es nur, wenn der Client nicht die Längenangabe mitverschickt. Dann werden die ersten 4 Zeichen ja als Länge ausgewertet? Sollte man da noch so etwas wie eine 2-Byte-Prüfsumme dranhängen oder verschicke ich damit dann zu viel "Müll"?