Zeilenorientierter Datenempfang über Sockets
-
Hallo, ich habe ein Problem bei der Nachrichtenübertragung über Sockets. Folgend der Source für senden un empfangen der Daten.
Bei kleinen Datenmengen funktiooniert alles prächtig. Aber wenn man viel auf einmal oder knapp hintereinander Daten schickt, gibt es ein Problem beim Empfangen der Daten.
Scheinbar sind mehrere Nachrichten im Empfangbuffer und es wird nach der letzten Terminierung gesucht. Wenn ich jedoch nach der ersten suchen würde, lösche ich den rest dahinter wieder.
Wechlchen ansatz kann man hier verfolgen um mit dem Aufruf der empfangsfunktion immer nur eine Nachricht zu empfangen?
Senden:
while(1) { string msg = client.message_List.get(); if (msg == "CLOSE") { return 0; } msg += "\n"; int iResult = send(client.socket, msg.c_str(), msg.length(), 0); if (iResult == SOCKET_ERROR) { return 0; } cout << iResult << "\n"; } return 0;Empfangen:
string msg, strBuffer; int lastChar; do { int iResult = recv(client.socket, client.rcv_buffer, IO_BUFFSIZE, 0); if( iResult > 0) { for(int i=0; i < iResult; i++) { strBuffer += client.rcv_buffer[i]; } lastChar = strBuffer.find_last_of("\n"); if(lastChar > -1) { strBuffer.erase(lastChar); msg += strBuffer; } else { msg += strBuffer; } } else if (iResult == 0) { return "CLOSE"; } else { return "ERROR"; } } while(lastChar < 0); return msg;
-
Daddaaff schrieb:
Wechlchen ansatz kann man hier verfolgen um mit dem Aufruf der empfangsfunktion immer nur eine Nachricht zu empfangen?
Du kannst
recv()immer mit einem 1-Byte Puffer aufrufen - was aber nicht sehr effizient ist.Wieso nicht sowas in der Art
Message Foo::ReceiveMessage() { while (m_receivedMessages.empty()) ReceiveChunk(); Message const m = m_receivedMessages.front(); m_receivedMessages.pop_front(); return m; }ReceiveChunkmuss dabei natürlich sämtliche in einem Rutsch empfangene Nachrichten inm_receivedMessageseinfügen, nicht immer nur eine.
-
Nach einiger Zeit hab hab folgendes daraus gemacht.
Zusätzlich verwende ich Base64, um zu verhindern, dass in den Daten vorhandene "\n" Probleme bereiten.
Senden
while(1) { string msg = client.message_List.get(); if (msg == "CLOSE") { return 0; } const unsigned char * constStr = reinterpret_cast<const unsigned char *> (msg.c_str()); msg = base64_encode(constStr, msg.size()); msg += "\n"; int iResult = send(client.socket, msg.c_str(), msg.length(), 0); if (iResult == SOCKET_ERROR) { return 0; } } return 0;Empfangen
string msg, strBuffer; int lastChar, firstChar; if(client.rcv_rest_lenght != 0) { for(int i=0; i < client.rcv_rest_lenght; i++) { strBuffer += client.rcv_rest[i]; } client.rcv_rest_lenght = 0; firstChar = strBuffer.find_first_of("\n"); if(firstChar > -1) { int test = (int)strBuffer.length(); if(firstChar != (int)strBuffer.length() - 1) { for(int i=firstChar + 1; i < (int)strBuffer.length(); i++) { client.rcv_rest[i - firstChar - 1] = client.rcv_rest[i]; client.rcv_rest_lenght++; } strBuffer.erase(firstChar); msg += strBuffer; string decoded_msg = base64_decode(msg); return decoded_msg; } else { strBuffer.erase(firstChar); msg += strBuffer; string decoded_msg = base64_decode(msg); return decoded_msg; } } else msg += strBuffer; } } do { int iResult = recv(client.socket, client.rcv_buffer, IO_BUFFSIZE, 0); if( iResult > 1) { for(int i=0; i < iResult; i++) { strBuffer += client.rcv_buffer[i]; } firstChar = strBuffer.find_first_of("\n"); if(firstChar > -1) { if(firstChar != iResult - 1) { for(int i=firstChar + 1; i < (int)strBuffer.length(); i++) { client.rcv_rest[i - firstChar - 1] = client.rcv_buffer[i]; client.rcv_rest_lenght++; } } strBuffer.erase(firstChar); msg += strBuffer; } else { msg += strBuffer; } } else if (iResult == 0) { return "CLOSE"; } else if (iResult == 1) { return "CLOSE"; } else { return "ERROR"; } } while(firstChar < 0); string decoded_msg = base64_decode(msg); return decoded_msg;
-
Sorry, aber das mag doch keiner lesen.
Der Code ist grauenhaft.Und base64 zu verwenden um \n zu vermeiden ist schon etwas krass.
Wenn du dir das Message-Format sowieso selbst aussuchen kannst, wieso machst du dann nicht das was man normalerweise macht, und schickst erst die Länge der Nachricht und danach die Nachricht selbst?
Der Code zum Empfangen einer Nachricht könnte dann so aussehen:
void Foo::Receive(void* buffer, size_t length) { while (length > 0) { int const rc = recv(m_socket, buffer, length, 0); if (rc <= 0) { // TODO: Fehlerbehandlung throw SomeException(...); } if (rc > length) { // TODO: Fehlerbehandlung throw SomeException(...); } length -= rc; buffer = reinterpret_cast<char*>(buffer) + rc; } } size_t Foo::ReceiveSize() { unsigned char buffer[4]; Receive(&buffer[0], 4); return buffer[0] + (static_cast<size_t>(buffer[1]) << 8) + (static_cast<size_t>(buffer[2]) << 16) + (static_cast<size_t>(buffer[3]) << 24); } std::string Foo::ReceiveMessage() { // Länge empfangen size_t const length = ReceiveSize(); // Nachricht empfangen std::vector buffer(length); Receive(&buffer[0], length); // Als string zurückgeben return std::string(buffer.begin(), buffer.end()); }Ist übersichtlicher, oder?
-
Ein selterner Quatsch für sowas base64 zu verwenden.
Das wäre ein Hasch zuviel.Und byteweise zu lesen ist auch nicht wirklich der Hammer.
Vielleicht liegts auch an dem komischen Wetter daß ihr komische Ideen habt.
-
lollig. hustbaer sagt das 1 byte pro call zu recv'en wäre ineffizient, aber baut selbst einen code der gerade mal 3 bytes mehr empfängt
-
i schrieb:
lollig. hustbaer sagt das 1 byte pro call zu recv'en wäre ineffizient, aber baut selbst einen code der gerade mal 3 bytes mehr empfängt
Hehe, wenn Foo:: zu Bar:: wird.
-
i schrieb:
lollig. hustbaer sagt das 1 byte pro call zu recv'en wäre ineffizient, aber baut selbst einen code der gerade mal 3 bytes mehr empfängt
Er macht es aber nur einmal pro Line.
-
@hustbaer
Wann kann denn Zeile 11 true werden?Besser ist mMn folgender Ansatz:
- lies alles, was im Puffer ist, und hänge es an einen Lesepuffer an
- stehen genügend Daten für den Telegrammkopf im Lesepuffer?
nein: fertig - lies den Telegrammkopf aus dem Lesepuffer und bestimme die Telegrammlänge
- stehen genügend Daten für das Telegramm im Lesepuffer?
nein: fertig - lies das Telegramm aus dem Lesepuffer
- entferne die Telegrammdaten aus dem Lesepuffer
- behandle das Telegramm
- zurück zu 2
-
DocShoe schrieb:
@hustbaer
Wann kann denn Zeile 11 true werden?Wenn das OS Blödsinn zurückgibt

Besser ist mMn folgender Ansatz:
Besser ist relativ.
Du siehst ja was der OP für Code schreibt.
Ich denke da ist erstmal das besser was einfacher umzusetzen ist.
-
Eingabe und Ausgabe in C++ funktioniert über Streams und Streambufs (weiß aber keiner
)
Das vereinfacht die Sache hier ziemlich - Erst der Streambuf:class ISocketStreambuf : public std::streambuf { public: ISocketStreambuf( /* ipc oder was auch immer */ ) : socket( /* connect? */ ) {} protected: virtual int_type underflow() { int iResult = recv( socket, rcv_buffer, IO_BUFFSIZE, 0 ); if( iResult > 0 ) { setg( rcv_buffer, rcv_buffer, rcv_buffer + iResult ); return traits_type::t_int_type( *rcv_buffer ); } else ; // Fehlerbehandlung return traits_type::eof(); // oder Exception } private: /* implementation detail */ socket; char rcv_buffer[IO_BUFFSIZE]; };und dann die Anwendung
ISocketStreambuf socket_sb( /* ... */ ); istream in( &socket_sb ); for( string zeile; getline( in, zeile ); ) // Zeilenweises Einlesen, wenn's sein muss cout << zeile << endl;.. setzt natürlich 'lesbare' Daten voraus. Falls reiner Binärcode daherkommt, kann man sich aber ggf. mit einem entsprechenden Stream behelfen. Der Streambuf bleibt davon unangetastet. Siehe auch das Beispiel hier.
Gruß
Werner