Sporadischer Programm absturzt nach Socket ::recv(...)



  • Guten Morgen Leute:)

    Ich habe ein Problemchen. Ich Übertrage von meinem Host-PC daten via Tcp/ip auf meine (WinCE) HMI Panels. Quasi eigenes customized Filetransfertool.

    Hierfür ist mein Host PC ein TcpServer und meine Panels ein jeweils die TcpClients.
    Ich übertrage die Daten chunk-weise, weil die Dateien groß sein können.

    Das ganze funktioniert auf WinCE 6.0 super. jetzt habe ich den Code auf WinCE8.0 (WCE_2013) kompiliert.. der TcpClient läuft, aber beim übertragen der Daten/Chunk stürzt mir der Client sporadisch ab, hab herausgefunden dass eben die socken Funktion ::recv der Übeltäter ist.
    Also beim N-ten ausführen vpn ::recv.. BAMM Programm wird geschlossen.
    Kann leider das ganze nicht auf dem WinCE panel Debuggen.

    Meine tcp Receive funktion:

    int TcpClient::Receive(char *buffer, int size)
    {
    	if(NULL == buffer || size <= 0 || !_isConnected || !wsa_startup)
    		return -1;
    
    	// block until bytes received, or timeout
    	int bytesRead = ::recv(s_, buffer, size, 0);
    	//::recvfrom
    	if(bytesRead == SOCKET_ERROR)
    	{
    		/* data while blocking time not received */
    		if(::WSAGetLastError() == 10060 || _timeout == 0)
    			return 0;
    		_isConnected = false; /* call connect method */
    		return -1;
    	}
    	return bytesRead;
    }
    

    da ich die ::recv nicht geblock haben will also nur ein lese timeout haben möchste habe ich das damit gemacht:

    bool TcpClient::SetReceiveTimeout(int timeout/* =  1000 */)
    {
    	if(NULL == s_)
    		return false;
    
    	_timeout = timeout;
    
    	u_long mode = timeout == 0;
    	if (ioctlsocket(s_, FIONBIO, &mode) == SOCKET_ERROR)
    	{
    		//_lastError = strerror(WSAGetLastError());
    		::closesocket(s_);
    		return false;
    	}
    	
    	if(timeout > 0)
    	{
    		/* setup socket for timeout, on error exit */
    		if(::setsockopt(s_, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(int)) == SOCKET_ERROR)
    		{
    			//_lastError = strerror(WSAGetLastError());
    			::closesocket(s_);
    			s_= 0;
    			return false;
    		}
    	}
    	return true;
    }
    

    Die Receive-Funktion verwendet ich dann Außerhalb so:

    UINT len = 5 + chunk;
    char *buffer = new char[len];
    std::memset(buffer,0,len);
    int res = owner->_tcpClient.Receive(buffer, len);
    
    ....
    
    delete[] buffer;
    

    Woran könnte es denn liegen, ich bin kein Spezialist, bestimmt findet ihr noch stümperhafte Implementierungen hier;)

    Vielen Dank,

    Ich trink jetzt nen Kaffee;)



  • Wenn du schon nicht Debuggen kannst, hast du wenigstens eine Möglichkeit der Textausgabe? Kannst du die Anzahl der empfangenen Bytes ausgeben, die Rückgabe werte diverser Funktionen?

    Was mir auffällt: Ich sehe kein ::select oder ::pselect. Wenn ich Non-Blocking-Sockets verwenden will, peile ich mit ::select immer erstmal die Lage am Socket und rufe dann ::recv auf.



  • @It0101 sagte in Sporadischer Programm absturzt nach Socket ::recv(...):

    Wenn du schon nicht Debuggen kannst, hast du wenigstens eine Möglichkeit der Textausgabe? Kannst du die Anzahl der empfangenen Bytes ausgeben, die Rückgabe werte diverser Funktionen?
    Was mir auffällt: Ich sehe kein ::select oder ::pselect. Wenn ich Non-Blocking-Sockets verwenden will, peile ich mit ::select immer erstmal die Lage am Socket und rufe dann ::recv auf.

    Leider zeige ich den text in der App an, aber die wird direkt geschlossen, und loggen tue ich nichts, aber wäre ein Sinnvoller erster Schritt:)
    EDIT: Aber ich sehe noch ganz "kurz" dass der Inhalt des letzen daten aus ::recv korrupt sind....

    Guter Hinweis mit ::select/::pselect.. ich dachte mir dass ich eben das einmal (siehe SetReceiveTimeout) definier, und dann muss ich nur och ::recv machen!? Oder ist das falsch?



  • @It0101 Dein Hinweis mit select hat wunder bewirkt;) ich habe direkt mal ein sample-code ausm netz vewendet, und das funktioniert einwandfrei, klar noch bissel anpassen, aber super start in den Tag, Danke DIR;)

    int TcpSocket::ReadFromClient(int socket, char* buf, int len)
    { 
        int slen = len;
        char ch;
    
        while (len > 0)
        {
            int ret = recv(socket, &ch, 1, 0); 
            if (ret > 0)
            {
                *buf = ch; 
                ++buf; 
                --len; 
    
                if (ch == '\n')
                    break;
            }
            else
            {
                if ((ret == 0) || (errno != EAGAIN))
                    return ret;
    
                fd_set readfd;
                FD_ZERO(&readfd);
                FD_SET(socket, &readfd);
    
                timeval tv;
                tv.tv_sec = 5;
                tv.tv_usec = 0;
    
                ret = select(socket+1, &readfd, NULL, NULL, &tv);
                if (ret < 0)
                    return ret;
    
                if (ret == 0)
                {
                    // timeout elapsed while waiting for data
                    // do something if desired...
                }
            } 
        } 
    
        return slen - len;
    }
    


  • Des Weiteren macht es immer Sinn, sich ein Protokoll zu überlegen. Z.B. mit einem einfachen Header, der auf den ersten 4 Bytes z.B. die Menge der in der Message enthaltenen Nutzdaten beschreibt, oder so. Wenn man unterschiedliche Daten hat, ist auch noch ein Messagetyp nötig.



  • @It0101 sagte in Sporadischer Programm absturzt nach Socket ::recv(...):

    Des Weiteren macht es immer Sinn, sich ein Protokoll zu überlegen. Z.B. mit einem einfachen Header, der auf den ersten 4 Bytes z.B. die Menge der in der Message enthaltenen Nutzdaten beschreibt, oder so. Wenn man unterschiedliche Daten hat, ist auch noch ein Messagetyp nötig.

    ja das habe ich dann eine Ebene drüber so gemacht, das erste Byte beschreib den Telegramtyp und dann wissen beide
    gegenstellen was erwartet wird,

    Anfrage: R[DateOffset][DateSize]
    Antwort: S[DataSize][DataN.....]

    und das dann stück für stück:)

    In dem Beispiel Code oben wird aber ::Recv for ::Select aufgerufen, das sollte anders herum sein oder? Sonst wird gg.f beim ersten lessen geblockt!?



  • @SoIntMan Erst select, und wenn da der Rückgabewert signalisiert, dass etwas auf dem Socket ist, wird recv aufgerufen.



  • @It0101 sagte in Sporadischer Programm absturzt nach Socket ::recv(...):

    @SoIntMan Erst select, und wenn da der Rückgabewert signalisiert, dass etwas auf dem Socket ist, wird recv aufgerufen.

    Optimal 😉 danke



  • @SoIntMan sagte in Sporadischer Programm absturzt nach Socket ::recv(...):

    UINT len = 5 + chunk;
    char *buffer = new char[len];
    std::memset(buffer,0,len);
    int res = owner->_tcpClient.Receive(buffer, len);
    
    ....
    
    delete[] buffer;
    

    Warum allokierst du eigentlich vor jedem Aufruf deiner Receive-Funktion abermals neuen Speicher? Nimm doch immer den gleichen Puffer? Dort wird der neue Datenblock reingeschrieben, dann kommt eine übergeordnete Schicht, die die Daten interpretiert und zurück gibt, wieviele Bytes sie davon gebraucht hat. Dann verschiebst du mit memmove den restlichen Datenblock im Speicher um die Anzahl der genutzten Bytes nach vorn.

    Abgesehen davon brauchst du das memset nicht. Was im Speicher steht wird durch Receive sowieso überschrieben.



  • @It0101 sagte in Sporadischer Programm absturzt nach Socket ::recv(...):

    Warum allokierst du eigentlich vor jedem Aufruf deiner Receive-Funktion abermals neuen Speicher? Nimm doch immer den gleichen Puffer? Dort wird der neue Datenblock reingeschrieben, dann kommt eine übergeordnete Schicht, die die Daten interpretiert und zurück gibt, wieviele Bytes sie davon gebraucht hat. Dann verschiebst du mit memmove den restlichen Datenblock im Speicher um die Anzahl der genutzten Bytes nach vorn.
    Abgesehen davon brauchst du das memset nicht. Was im Speicher steht wird durch Receive sowieso überschrieben.

    Das hast du allerdings Recht, auch hier ein gute Hinweis bzw. Denkanstoß (memmove).. danke;)



  • @SoIntMan
    Ich glaube ich sehe dein Problem.

    Du musst genau auf die Rückgabewerte von recv() achten. Der Puffer-Parameter ist aus meiner Sicht etwas missverständlich, da die recv() Funktion bei diesem Parameter nicht auf Nullterminiertheit achtet.

    Ist beispielsweise der Puffer 64 Zeichen groß und recv kann 70 Zeichen einlesen, so schreibt diese 64 Zeichen in den Puffer und gibt 64 zurück. Wenn man diesen nun als C-String interpretiert, knallt es da die Nullterminiertheit fehlt.

    Folgenden Code zum spielen:

    int TcpClient::ReadFromClient(int socket, std::string& buf)
    { 
        char Chunk[64];
    
        buf = "";
        while (true)    // ToDo: Timeout implementieren
        {
            int ret = recv(socket, Chunk, std::size(Chunk), 0); // Chunkweise einlesen
            
            if (ret > 0)
            {
                for  (int i = 0; i < ret; i++)
                {
                    buf += Chunk[i];
                    if (Chunk[i] == '\n')
                        return 42;      // Was tun mit dem Rest? Evt. in zweiten Parameter Rest speichern?
                }
            }
            else if (ret == 0)
            {
                // Behandele "Verbindung geschlossen"
            }
            else
            {
                // Behandele Fehler
            }
        }
        return 4711;
    }
    


  • Das mit dem select ist schon mal inspirierend, probiere bissel rum, ABER hier kachelt mir die App auch ab:
    Es wird ja im select gewartet, bis "daten" da sind, und was passiert den FD_ISSET nicht verstanden, was passiert da hier genau?
    Selbst hier kann mir das Recv abschmieren!!!

    int Receive3(char* buf, int len)
    	{ 
    		int resultCode = 0;
    
    		fd_set read_fd_set;
    		FD_ZERO(&read_fd_set);
    		FD_SET(s_, &read_fd_set);
    
    		/* Initialize the timeout data structure. */
    		timeval timeout;
    		timeout.tv_sec = 1;
    		timeout.tv_usec = 0;
    	
    		//check and wait until data received
    		int selectRet = ::select(s_ + 1, &read_fd_set, NULL, NULL, &timeout);
    			
    		//not data avaiable anymore
    		if (!(FD_ISSET(s_, &read_fd_set))) 
    		{
    			return 0;
    		}
    
    		//socket timeout while waiting for data
    		if (selectRet == 0) 
    		{
    			return -2;
    		}
    
    		//any socket error occurred
    		if (selectRet == SOCKET_ERROR) 
    		{
    			return -1;
    		}
    
    		// recive date from socket
    		int byteSize = ::recv(s_, buf, len, 0);
    
    		//socket gracefully closed
    		if (byteSize == 0) 
    		{
    			return -3;
    		}
    
    		//socket gracefully closed
    		if (byteSize == SOCKET_ERROR) 
    		{
    			return -4;
    		}
    			
    		return byteSize;
    	}
    


  • Hast du die Möglichkeit, die Daten mal nicht zu verarbeiten ,sondern auf Clientseite das Empfangen zwar vorzunehmen, aber mit den Daten erstmal nichts weiter zu tun? Quasi Ausschlussverfahren.

    Und dann im Nachgang die "Verarbeitung" der Daten mal ohne Socket zu testen.



  • Ich hab mal ähnliche Probleme gehabt.
    Wie schon von @It0101 erwähnt, ein Protokoll mit Längenangabe ist sinnvoll.
    Zu beachten ist, dass es sich, auch wenn man Pakete bastelt, immer noch ein Stream gesendet und empfangen wird. Ich hatte mal das Problem, dass im Stream (der ja auch nur eine definierte Länge hat) der Anfang des zweiten Paketes enthalten war…
    Ich habe mir zuletzt damit geholfen, dass ich die Daten in einer lesbaren Form (XML) gesendet, und vor dem Senden vom Server in eine Datei gespeichert habe. Der Client hat die empfangenen Daten auch erst mal in eine Datei gespeichert. Nun konnte ich schon mal durch Vergleichen der Dateien einiges ermitteln.



  • @SoIntMan sagte in Sporadischer Programm absturzt nach Socket ::recv(...):

    int byteSize = ::recv(s_, buf, len, 0);

    Füge doch mal bitte den folgenden Code ein und prüfe das Ganze nochmals im Debug-Modus.

    int byteSize = ::recv(s_, buf, len, 0);
    assert(bytesize < len);
    assert(buf[bytesize] == 0);
    


  • @It0101 sagte in Sporadischer Programm absturzt nach Socket ::recv(...):

    Hast du die Möglichkeit, die Daten mal nicht zu verarbeiten ,sondern auf Clientseite das Empfangen zwar vorzunehmen, aber mit den Daten erstmal nichts weiter zu tun? Quasi Ausschlussverfahren.
    Und dann im Nachgang die "Verarbeitung" der Daten mal ohne Socket zu testen.

    das mach ich auch so, ich allociere nur den puffer (wie oben) und geben ihn wieder frei...
    nach dem Ausschluss-verfahren habe ich den ::recv gemockt und es lief.. alls würde die daten aus recv korrupt sing..
    aber jetzt wo ich schreibe, mache ich doch ein bissel verarbeitung.. das nehm ich mal raus

    @Helmut-Jakoby sagte in Sporadischer Programm absturzt nach Socket ::recv(...):

    Ich hab mal ähnliche Probleme gehabt.
    Wie schon von @It0101 erwähnt, ein Protokoll mit Längenangabe ist sinnvoll.
    Zu beachten ist, dass es sich, auch wenn man Pakete bastelt, immer noch ein Stream gesendet und empfangen wird. Ich hatte mal das Problem, dass im Stream (der ja auch nur eine definierte Länge hat) der Anfang des zweiten Paketes enthalten war…
    Ich habe mir zuletzt damit geholfen, dass ich die Daten in einer lesbaren Form (XML) gesendet, und vor dem Senden vom Server in eine Datei gespeichert habe. Der Client hat die empfangenen Daten auch erst mal in eine Datei gespeichert. Nun konnte ich schon mal durch Vergleichen der Dateien einiges ermitteln.

    ja ich versuche gerade die daten roh (ohne überlagertes framing etc. zu anylsieren)

    @Quiche-Lorraine sagte in Sporadischer Programm absturzt nach Socket ::recv(...):

    Füge doch mal bitte den folgenden Code ein und prüfe das Ganze nochmals im Debug-Modus.
    int byteSize = ::recv(s_, buf, len, 0);
    assert(bytesize < len);
    assert(buf[bytesize] == 0);

    leider kann ich nich debbuggen.. WinCE

    Aber ich könnte die App auch einfach mal wuf win32 kompilieen.. hmm;) und da debuggen..



  • @SoIntMan sagte in Sporadischer Programm absturzt nach Socket ::recv(...):

    leider kann ich nich debbuggen.. WinCE
    Aber ich könnte die App auch einfach mal wuf win32 kompilieen.. hmm;) und da debuggen..

    Ich frage mich welche Art von Daten (Binär, ASCII) du durch recv() empfängst.

    int Receive3(char* buf, int len)

    So ein char* kann leider viele Gesichter annehmen:

    • Binäre Daten
    • Nullterminierter String (String + '\0')
    • String Pool (String + '\0' + String + '\0' + '\0')

    Und wenn der buf Parameter der Receive3() Funktion ein nullterminierter String darstellen soll, so kann es unter gewissen Umständen zu einer fehlende Nullterminierung kommen. Und dann knallt es z.B. wenn du strlen(buf) aufrufst.


    Probiere doch mal ob du den Code geschickt unterteilen kannst, s.d. dieser auf einem Entwicklungsrechner läuft. Dann könnte man einen Stresstest probieren. Die Serverseite könnte man mittels einem einfachen C#/Python Programm simulieren.



  • Guten Morgen ..

    irgndwie habe ich das Gefühtl dass unter WinCE "ws2.lib" und unter Win32 "Ws2_32.lib" das verhalten unterscheidlich ist, da es bei gleicher verwendung von ::recv in wince crashed und in win32 nicht!?



  • Mal ne ganz dumme Überlegung: Wenn recv fehlerhaft wäre, wäre der Fehler nicht schon vor vielen Jahren aufgefallen? Der Fehler muss bei dir liegen. Irgendein Zeiger zeigt nicht wie er soll.



  • Ich weiß nicht, wie du die empfangen Daten verarbeitest oder wie diese gestaltet sind. Aber ich wage mich nochmal mit einer Vermutung raus (habe ich im vorherigen Post nur angedeutet), weil ich mich so an ein Phänomen erinnert fühle.

    Auch wenn von Streams gesprochen wird, wird der ab und an in Teilen übertragen; siehe "tcp packet size".

    Also unter dem einem Betriebssystem habe ich zwei "chunks" (jeweils mit eigenem Header wie Art und Größe der folgenden Daten) nacheinander losgeschickt und es kamen auch zwei IP-Pakete "nacheinander" mit jeweils einem "chunk" an.

    Unter dem anderen Betriebssystem habe ich auch zwei "chunks" nacheinander losgeschickt, und es kamen auch zwei IP-Pakete an, aber eben im ersten IP-Pakete der erste "chunk" und die ersten Bytes des zweiten "chunk".
    Das zweite IP-Paket hatte also nur den Rest des zweiten "chunks" und meine Anwendung hat das natürlich nicht verstanden, weil der Header des zweiten "chunks" nicht vorhanden war.

    Ich habe lange dran herumgedoktert, weil ich lange annahm, das meine "chunks" immer als ganzes beim Client ankommen, was sie ja auch im einem Betriebssystem getan haben. Ich dachte auch lange, dass ich durch das definieren der "tcp packet size" alles in der Hand habe. Stimmte aber im letzteren Fall nicht, das zweite Betriebssystem tat da was es wollte, bzw. sendete bzw. empfing wirklich einen Stream ggf. mit halben oder auch mit mehreren "chunks".

    Bitte entschuldige das krude Deutsch, aber ich wollte das IP-Paket und "mein Paket" verdeutlichen.


Anmelden zum Antworten