Bei send() und recv() wirklich ALLES senden bzw. empfangen?



  • Hi.

    Ich habe ein Problem, an dem in schon ein paar Tage nicht mehr weiter komme.
    In C schreibe ich einen Server für Linux und unter C++ (WinAPI) einen Client für Windows.

    Bisher lief alles gut, Server sendete Info's und Texte, Client konnte Kommandos an den Server senden, Daten empfangen, und und und.

    Ich habe ein eigenes Struct angelegt:

    typedef struct {
    	int command;
    	int seccommand;
    	int int_value;
    	char char_value1[255];
    	char char_value2[255];
    } TM_DATA;
    

    Wenn etwas gesendet wird, egal vom Client oder vom Server, dann wird dieses Strukt mit Datengefüllt und gesendet.
    Das heißt es müssen sizeof(TM_DATA) Bytes gesendet werden und die gleiche Anzahl an Bytes empfangen werden. Bisher kein Problem aufgetreten...

    Doch jetzt bin ich da angelagt, dass man mit dem Client etwas beim Server "suchen" kann. Sprich: Der Server liefert dann dem Client einige Suchergebnisse, sagen wir 20 Suchergebnisse, egal was das ist. Jedes Suchergebnis ist einmal ein Strukt! 20 Strukts werden gesendet. Diese 20 Suchergebnisse haut der Server in einer While-Schleife sehr schnell raus! So schnell es geht hintereinander sozusagen.

    PROBLEM: Beim Server kommt irgendwie nicht immer alles an bzw. ein Paket kommt nicht vollständig an und ein Rest des Paketes kommt hinterher. Da liegt mein Problem. Wie kann ich wirkich alles SENDEN und wirklick ALLES EMPFANGEN.

    Ich habe mich schon versucht, eine Send-Funktion und eine Receive-Funktion zu basteln.

    Receive-Funktion "Readn()":

    int Readn(int fd, void* vptr, int n)
    {
    	int 	nleft;
    	int	nread;
    	char* 		ptr;
    
    	ptr = vptr;
    	nleft = n;
    	while(nleft > 0)
    	{
    		if((nread = recv(fd, ptr, nleft, 0)) < 0)
    		{
    			if(errno == EINTR)
    				return EINTR;
    			else
    				return(-1);
    		}
    		else if (nread == 0)
    			break;
    		nleft = nleft - nread;
    		ptr = ptr + nread;
    	}
    
    	return(n - nleft);
    }
    

    Send-Funktion "Writen()":

    int Writen(int fd, const void* vptr, int n)
    {
    	int nleft;
    	int nwritten;
    	const char* ptr;
    
    	ptr = vptr;
    	nleft = n;
    	while(nleft > 0)
    	{
    		if((nwritten = send(fd, ptr, nleft, 0)) <= 0)
    		{
    			if(errno == EINTR)
    				nwritten = 0;
    			else
    				return(-1);
    		}
    
    		nleft = nleft - nwritten;
    		ptr = ptr + nwritten;
    	}
    
    	return(n);
    }
    

    So sende und empfange ich bisher, aber in den zwei Funktionen stimmt auch noch etwas nicht. Nur was?!

    Und in C++ (WinAPI) bekomme ich FD_READ, wenn es über den Socket was zu lesen gibt:

    case FD_READ:
    {
    	TM_DATA data;
    	Readn(s, (char *)&data, sizeof(TM_DATA));
    	if(data.command == COM_OK)
    	{
              /* Hier werte ich das empfangene Struct aus. */
          }
          break;
    }
    

    Mein Problem: Wenn mal ein Struct nicht in einem Stück ankommt, wird nicht oder falsch ausgewertet da das Struct ja noch nicht vollständig ist!! 😞
    Wie kann ich es anstellen, dass das komplette Struct empfangen/gesendet wird und dass ich da keine Probleme mehr habe?

    Achja: Das komisch ist, mache ich vor "jeden" Empfang ein Sleep(500); rein, so empfängt mein Client alles sauber! Wieso? Ohne Sleep(500); und mit vollem Speed geht viel der Daten verloren!?!

    Bin sehr dankbar über jeden Tipp.
    Komm nicht weiter!
    Chris



  • Ich vermute einfach mal, dass der Server sendet, dann der Client empfängt und der Server wieder sendet obwohl der Client noch dabei ist das Paket davor zu empfangen und auszuwerten. Also würde ich den Client immer ein ok o.ä. senden lassen, wonach der Server dann weitermacht.

    Mfg Ominion



  • Danke Ominion!

    Geht das nicht anders zu lösen? Das kann doch nicht sein?
    Wenn ich z.B. einmal Dateien versenden möchte über den Socket, kann ich doch nicht nach jedem Paket mit Bytes ein OK zurücksenden. Das ist doch zum Anderen auch viel Traffic. Also nicht viel aber ohne OK wäre es schon weniger?



  • Probiere es doch mal mit einem Sleep () nach jedem Packet beim Server. Ich arbeite zwar auch mit Sockets, habe dieses Problem aber bisher noch nicht gehabt, deshalb kann ich dir auch nichts genaues sagen.

    Mfg Ominion



  • Kann ich probieren. Ich denke oft wird das auch funktionieren, aber ich kann nicht sichergehen dass mal n Paket etwas länger unterwegs ist und dann "prallen" wieder zwei beim Clienten "aufeinander"... Danke jendenfalls für den Tipp.
    Wenn Du ne perfekte Lösung hast, lass es mich wissen.

    Weiß hier vielleicht jemand schon ne perfekte Lösung??



  • Die "perfekte" Lösung wäre wie gesagt zu warten, dass der Client ein ok schickt. Es brauch ja auch nur ein zeichen zu sein.

    Mfg Ominion



  • Also wenn das wirklich einer der perfekten Lösungen ist, dann werde ich das wohl mal so in Angriff nehmen. Thanks @ Ominion

    Falls jemand noch ne perfektere Lösung weiß, sollte es eine geben, bitte her damit! Danke 🙂



  • Liegen Server und Client Code in derselben Anwendung?
    Evtl. liefert sizeof() andere Werte für die struct (wegen irgendwelchem Padding/Alignment oder sowas...)



  • Nein, Server in C auf Linux, Client in C++ mit WinAPI auf Windows.
    sizeof(TM_DATA) lieget unter Linux und Windows das selbe.



  • ChrisK schrieb:

    Diese 20 Suchergebnisse haut der Server in einer While-Schleife sehr schnell raus!

    ChrisK schrieb:

    ...kommt irgendwie nicht immer alles an bzw. ein Paket kommt nicht vollständig an und ein Rest des Paketes kommt hinterher.

    Prüfe mal ob recv () ev. eine Fehlermeldung liefert :

    WSAEMSGSIZE

    The message was too large to fit into the specified buffer and was truncated.



  • Ein "OK" schicken ist wohl die "unperfekteste" Lösung die ich mir vorstellen kann, da es jedesmal eine round-trip-time kostet. Nicht so wild wenn es in einem lokalen Netzwerk passiert, sehr sehr blöd dagegen wenn man mal nen Ping von 300ms oder so hat.

    Wichtig ist dass der empfangende Teil vor dem empfangen genau weiss wieviel Daten ankommen. Wenn du also 20 Pakete verschickst, dann muss der entweder vorher wissen dass 20 kommen, oder du musst vor jedem Paket z.B. ein Byte als Marker schicken ob noch ein Paket kommt oder ob es das letzte war. Wenn jedes Paket gleich gross ist reicht das. Wenn die Pakete unterschiedlich gross sind musst du halt einfach vor dem Paket noch zusätzlich die Paketgrösse mitschicken.

    Davon abgesehen brauchst du eben solche send_n und recv_n Funktionen, da send() und recv() selbst nur soviel Daten liefern/annehmen wie gerade verfügbar sind bzw. für was gerade Platz in den Sendepuffern ist.

    Die Implementierung würde ich nicht genau so machen wie du es machst, da du z.B. einmal einen Fehlercode zurückgibst (EINTR, -1), und sonst zurückgibst wieviel Daten nicht übergragen wurden. Nicht schön IMHO.

    Im übrigen würde ich auch einen Returnwert von 0 als Fehler behandeln, denn 0 kannst du AFAIK eigentlich nur bekommen wenn der Socket geschlossen wurde. (EDIT: natürlich vorausgesetzt du verwendest blocking Sockets. Solltest du auch erstmal wenn du mit Sockets noch keine Erfahrung hast, und sollte auch Default sein)

    Auf jeden Fall ist es soweit ich dein Problem verstehe hier nicht nötig irgendwelche Bestätigungen zu schicken, oder gar irgendwelche Sleeps oder sonstwas einzubauen. Glaube es mir, ich hab' selbst schon Netzwerkcode geschrieben über den täglich mehrere GB Daten übertragen werden, und da ist kein einziges Sleep drin. Und auch an vielen Stellen keine Bestätigungen (wenn sie nicht notwending sind).

    Bestätigungen schicken macht eigentlich nur dann Sinn wenn es an dem Punkt wichtig ist dass der Client eine bestimmte Aktion bereits ausgeführt hat, bevor der Server etwas macht -- oder eben umgekehrt. Wenn du z.B. wichtige Daten aus einer Journaltabelle überträgst, dann lässt du dir natürlich von der Gegenseite eine Bestätigung schicken, bevor du die auf deiner Seite löscht (bzw. als "übertragen" markierst), klar. Wenn du aber nur das Ergebnis einer ad-hoc Abfrage zurückschickst ist das nicht notwendig -- wenn der Client wirklich abgeschmiert ist oder die Connection zusammengebrochen ist, kann er die Anfrage (wenn er die Ergebnisse dann noch braucht) ja einfach nochmal schicken.



  • hustbaer schrieb:

    Im übrigen würde ich auch einen Returnwert von 0 als Fehler behandeln, denn 0 kannst du AFAIK eigentlich nur bekommen wenn der Socket geschlossen wurde.

    Bei send() ist 0 glaube ich ein gültiger Rückgabewert (Allerdings wohl nur wenn 0-Bytes gesendet werden?)

    Er hat ja solche send_n/recv_n Funktionen, ich sehe in denen auch leider keinen Fehler 😕

    Ich frage mich gerade, ob unter Unix nicht nen nread=0 erlaubt wäre? Oder heisst das da auch "Verbindung getrennt" ?

    else if (nread == 0) break;
    


  • recv = 0 bedeutet socket beendet, sonst blockiert recv solange bis daten kommen oder wirft glaube nen timeout in wsagetlaserror() (ausgenommen nonblocking mode)



  • Ich habe auch mal so eine Empfangsschleife gebaut und auch seltsammte Effekte festgestellt die auf manchen Implementierungen und Compilern auftreten.

    Wenn man dein Programm betrachtet, dürfte ja nichts fehlen, da du in einer Schleife abfragst wieviel empfangen wurde

    if((nread = recv(fd, ptr, nleft, 0)) < 0)
    

    und dann berechnest, wieviel nachgeladen werden muß und an welche Stelle

    nleft = nleft - nread; 
    ptr = ptr + nread;
    

    Dabei tritt ein Fehler auf, da fread anscheinend bei manchen Compilern immer mindestens um die groesse des Struct weiterrückt.

    Ich meine Folgendes.
    Du hast z.B. ein Strukt der groesse 12 Byte. Sagen wir mal 4 Byte wurden empfangen.
    Jetzt berechnest du, dass 8 Byte nachgeladen werden müssen und 4 wurden schon geladen.
    Da aber ein Strukt 12 Byte groß ist versucht recv die 8 Byte an Position
    4 + 12= 16 Byte nachzuladen, weil es um die Groesse des Struct vorrückt. Somit wird fremder Speicherbereich überschrieben und das Strukt ist unvollständig.
    (Oder es sieht so aus, dass der Rest im nächsten Struct kommt)

    Das erklährt deine Beobachtung:
    [quote] Beim Server kommt irgendwie nicht immer alles an bzw. ein Paket kommt nicht vollständig an und ein Rest des Paketes kommt hinterher[quote]

    Wenn du ein Sleep(500) einbaust, wird sichergestellt, dass soviel da ist um ein
    Struct voll zu beschreiben und der Fehler passiert nicht. Dies kann aber aufgrund der Groesse nicht im Sinn des Erfinders sein.

    Eine Lösung währe es erst alles in ein array von chars einzulesen (Wieder in der Schleife mit der selben char Arraylänge wie dein Struct) und dann anschließend in die gewünschte Struct zu casten. Dann verrechnet sich recv nicht bei der Speicherposition.

    Jetzt werden bestimmt einige sagen:"So nen quatsch, rechv rückt nicht um die Länge des Struct vor"
    Aber ich habe genau DISES schon nachvollziehen können und es passiert wirklich. Außerdem beschreibt es GEANU den hier geschilderten Fehler!

    Das komische ist, das der Effekt bei manchen Compilern auftritt, bei anderen nicht.



  • @MisterX :

    Bei fwrite/fread gibst du die Grösse der "Einheit" mit, und zusätzlich nochmal die Anzahl der "Einheiten". Bei send/recv ist das nicht so -- da gibts nur die Anzahl der Bytes.
    Woher soll send oder recv wissen dass der Zeiger der da übergeben wird auf ein struct zeigt?
    Ne, daran *kann* es nicht liegen. Und wenn du 100 mal sagst es ist so sag ich 101 mal es kann nicht sein.

    Es kann höchstens daran liegen dass man vielleicht die Regeln der C bzw. C++ Zeigerarithmetik nicht verstanden hat. Wer natürlich nen Zeiger auf die Struktur verwendet, und auf den dann "+= n" macht, der muss sich nicht wundern wenn der Zeiger um n Strukturen und nicht n chars weiterwandert. Wer aber "+= n" auf nen char Zeiger macht wird den char Zeiger um n chars weiterrücken.

    Obwohl... ich kenne einen "C Compiler" der das falsch umgesetzt hat, also Zeiger immer um n chars weitergerückt hat, egal welchen Typs der Zeiger war. Bloss das ist 15 Jahre her, und das Teil war damals schon Schrott.

    Das alles stellt aber im hier geposteten Code kein Problem dar, da hier brav mit char Zeigern gearbeitet wird. Blubb.

    p.S.: du hast nicht zufällig noch Code der davon betroffen ist? Und den Namen je eines Compilers wo es geht bzw. nicht geht? Wäre interessant...

    p.p.S.: recv rückt *garnicht* vor. recv gibt bloss zurück wieviel gelesen wurde. Vorrücken muss man selbär. Nur so nebenbei.



  • mal abgesehen von euren einwänden (die ich nicht im detail gelesen habe), wenn du ein struct von 12 bytes hast heißt das nicht unbedingt das das nachfolgende struct an position 13 anfängt, stichwort "speicherausrichtung" ...
    meine frage daher welchen compiler verwendest du?
    versuch es mal mit 4 dummy bytes in deiner struct am ende damit die structgröße durch 8 teilbar ist oder (falls du borland verwendest)

    #pragma pack(push, 1)
    
    //dein struct
    
    #pragma pack(pop)
    

    ich kann mich noch an schulzeit erinnern, da gab es irgendwie ein __attribute_packed oder so ähnlich was man bei der struct definition einfügen kann um ähnliche effekte zu erzielen

    EDIT ich arbeite hier mit structs und anderen daten im größenverhältnis bis 2000 bytes und hab absolut keine probleme, ich versteh nich was da mit sleep helfen soll, ... es hilft einzig und allein nur das du pro sekune nur 2 pakete empfangen kannst -.-



  • @Ceos: wenn man über sizeof(struct) geht ist es egal wie die Struktur gepackt wird. Natürlich muss das Packing beim Client und beim Server gleich sein, klar.



  • naja, was ich meine, wenn er um BYTES inkrementiert zwischen den structs aber verlorene bytes liegen kann doch gut sein das bis zu 7 byte verloren gehn



  • Jo, OK. Aber wer tut schon sowas böses 😉



  • ich weis zwar nichtmehr wo genau aber ich hatte etwas ähnliches (ich glaub es war ne diskrepanz zwischen 2 projekten die über netzwerk kommunizierten) in dem moment hast natürlich du recht, wenn zwischen beiden programmen die speicherausrichtung stimmt müsste es normal funktionieren


Anmelden zum Antworten