john: Linux , C++ , TCP-Socket

Hi,

ich habe ein Problem beim coden eines TCP Servers:

  
// Beispiel 1:  
char echo_buffer[1024] = {""};  
if((recv_size = recv(client_socket, echo_buffer, 1024,0)) < 0) { // error  
}  
echo_buffer[recv_size] = '\0';  
printf("%s", echo_buffer); // funktioniert  
  
// Beispiel 2:  
// Funktioniert dagegen nicht:  
string result = "";  
while((recv_size = recv(client_socket, echo_buffer, 1024,0)) > 0) {  
 echo_buffer[recv_size] = '\0';  
 result += echo_buffer;  
}  
printf("%s", result.c_str());  

Das Erste Beispiel Funktioniert, Aber nur wenn die Anfrage nicht länger als 1024 Zeichen ist.
Das ist das Problem.
Ich habe ein Protokoll das keine feste länge von Nachrichten vorschreibt. Man kann nicht vorhersagen ob die nächste Nachricht 3 Zeichen hat oder 100302. Natürlich ist das nicht immer so krass aber in einigen mehr oder weniger konstruierten Fällen kann dies der Fall sein.

Die 2. Version funktioniert (sehr bedingt). Wenn der Client die Verbindung trennt nachdem er seine Nachricht übermittelt macht der Server weiter und gibt result aus.
Meine Erklärung ist das recv blockt. Sie holt solange die Zeichen ab bis keine mehr kommen und wartet nun das welche kommen (blocking). Es kommen aber keine. An dieser Stelle sollte die while-Schleife abbrechen da die Nachricht ja zu Ende ist.

Welche Lösungsvorschläge habt ihr mir anzubieten?

  1. Hi,

    ich bin es noch einmal.

    Wie empfängt man generell (TCP-) Nachrichten deren Länge man nicht weiss.
    z. B. wenn man POST-Nachrichten bei HTTP empfängen möchte oder Dateien

    Das muss doch irgendwie lösbar sein? Vielleicht sogar mit read oder recv?

    1. TCP ist ein Stream-basiertes Protokoll, kein Paket-basiertes wie UDP. Es gibt folglich keine "natürlichen" Grenzen einer Nachricht, sondern lediglich einen Byte-Datenstrom. Du musst in der Folge der Bytes also schon selbst erkennen, wo eine Nachricht zu ende ist und eine neue beginnt. Wie das zu erkennen ist kann die ich dir nicht sagen, da mir das verwendete Protokoll nicht bekannt ist. Bei HTTP z.B. gibt es diverse Regeln (für Message-Type, Transfer-Encoding, Content-Length etc.), wie der Empfänger einer Nachricht das Ende erkennt, nachzulesen in der entsprechenden RFC.

      MfG
      GK

  2. Hallo john,

    Die 2. Version funktioniert (sehr bedingt). Wenn der Client die Verbindung trennt nachdem er seine Nachricht übermittelt macht der Server weiter und gibt result aus.
    Meine Erklärung ist das recv blockt. Sie holt solange die Zeichen ab bis keine mehr kommen und wartet nun das welche kommen (blocking). Es kommen aber keine. An dieser Stelle sollte die while-Schleife abbrechen da die Nachricht ja zu Ende ist.

    Moment, macht der Server jetzt in der Schleife weiter oder nicht? Was meines Wissens passieren sollte, ist, dass der Prozess ein Signal kriegt, dass er auf der Datei nicht mehr lesen kann. Das wird dann asynchron, sprich nicht direkt im "Fluss" bearbeitet. Das könnte beispielsweise eine Globale Variable, die im while getestet wird, umstellen. Oder den Prozess töten (weil paralleler Server). Oder so ähnlich.

    Was aber eher passieren sollte ist, dass recv einen return value < 0 hat und die Schleife einfach beendet wird.

    Grüße,

    Sven

  3. Moin!

    ich habe ein Problem beim coden eines TCP Servers:

    // Beispiel 1:

    char echo_buffer[1024] = {""};
    if((recv_size = recv(client_socket, echo_buffer, 1024,0)) < 0) { // error
    }
    echo_buffer[recv_size] = '\0';
    printf("%s", echo_buffer); // funktioniert

    // Beispiel 2:
    // Funktioniert dagegen nicht:
    string result = "";
    while((recv_size = recv(client_socket, echo_buffer, 1024,0)) > 0) {
    echo_buffer[recv_size] = '\0';
    result += echo_buffer;
    }
    printf("%s", result.c_str());

      
    Kleine Anmerkung: Aus Performance-Gründen dürfte die Verwendung eines stringstreams günstiger sein als in jedem Schleifendurchlauf den string zu verlängern.  
      
    
    > Das Erste Beispiel Funktioniert, Aber nur wenn die Anfrage nicht länger als 1024 Zeichen ist.  
      
    Und wenn du den Puffer vergrößerst? Schiefgehen kann damit ja nichts, weil recv soviel liest, wie kommt und nicht so lange wartet, bis der Puffer voll ist.  
      
    
    > Die 2. Version funktioniert (sehr bedingt). Wenn der Client die Verbindung trennt nachdem er seine Nachricht übermittelt macht der Server weiter und gibt `result` aus.  
    > Meine Erklärung ist das recv blockt. Sie holt solange die Zeichen ab bis keine mehr kommen und wartet nun das welche kommen (blocking). Es kommen aber keine. An dieser Stelle sollte die while-Schleife abbrechen da die Nachricht ja zu Ende ist.  
      
    Soweit ich die [Manpage zu recv(2)](http://www.freebsd.org/cgi/man.cgi?query=recv&apropos=0&sektion=2&manpath=SuSE+Linux%2Fi386+8.2&format=html) richtig gelesen und meine eigenen Programmierbeispiele dazu richtig verstanden habe, bedeuten die Rückgabewerte von recv sowie recvfrom:  
      
    -1: Oh, ein Fehler, bitte schau in errno nach  
    ≥ 0: Anzahl der gelesenen Bytes  
      
    Wobei ich 0 so interpretiere, dass es nichts mehr zu lesen gibt, aber kein Fehler aufgetreten ist. Das sollte doch deine Abbruchbedingung sein, oder?  
      
    Viele Grüße,  
    Robert
    
    1. Und wenn du den Puffer vergrößerst? Schiefgehen kann damit ja nichts, weil recv soviel liest, wie kommt und nicht so lange wartet, bis der Puffer voll ist.

      Der Puffer ist dann immer noch zu klein.
      Man könnte ihn 128 MB gross machen und dann kommt plötzlich eine Datei die 129 MB gross ist. Also das kann nicht die Lösung sein.

      Soweit ich die Manpage zu recv(2) richtig gelesen und meine eigenen Programmierbeispiele dazu richtig verstanden habe, bedeuten die Rückgabewerte von recv sowie recvfrom:

      -1: Oh, ein Fehler, bitte schau in errno nach
      ≥ 0: Anzahl der gelesenen Bytes

      Wobei ich 0 so interpretiere, dass es nichts mehr zu lesen gibt, aber kein Fehler aufgetreten ist. Das sollte doch deine Abbruchbedingung sein, oder?

      Ja, das habe ich mir auch gedacht. Habe auch sämtliche Tutorials die man mit Google so findet durchgelesen in vielen findet man eine ähnliche Konstruktion wie ich sie habe. Aber es "funktioniert nicht".
      Die Daten werden Empfangen aber irgendwann komm recv() nicht mehr zurück und das genau wenn eigentlich die Rückgabe 0 sein sollte.

      Ich kann mir das nicht erklären. Mache ich etwas falsch? Wenn ja, was?
      Hilft es wenn ich einen größeren Codeteil poste?

      1. Mach einen Netzwerktrace und prüfe, ob der TCP-Kanal überhaupt geschlossen wird. Falls nicht ist doch ganz klar, warum das nicht funktionieren kann. Wie ich an anderer Stelle schon schrieb, ist TCP ein stream-orientiertes Protokoll. Solange ein TCP-Kanal offen ist, können von beiden Seiten jederzeit noch Daten übertragen werden. Es ist Sache des oberhalb von TCP liegenden Protokolls, festzulegen wann eine Nachricht endet. Die Regeln dafür können sehr unterschiedlich sein, sieht man ja bei HTTP.

        Sockets können im blocking mode oder im non-blocking mode betrieben werden, wobei der Umgang mit non-blocking sockets meines Wissens plattformabhängig ist, wenn auch die Unterschiede nur gering sein mögen. Ich kenne mich da nur unter Windows aus. Unter Unix gibt's wohl die Funktion fcntl, mit der man da die Umschaltung vornehmen kann.

        Wenn Du allerdings mit blockierenden sockets das Ende einer Übertragung nicht erkennen kannst, werden dir asynchrone Operationen auch nicht helfen. Du kannst bestenfalls eine Verbindung wegen Timeout abbrechen. Die einfachste Lösung wäre festzulegen, dass der Sender nach der Übertragung den TCP-Kanal zu schließen hat. Dann ist auch garantiert, dass recv irgendwann mit 0 zurückkehrt.

        MfG
        GK