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;
    }
    

    ReceiveChunk muss dabei natürlich sämtliche in einem Rutsch empfangene Nachrichten in m_receivedMessages einfü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:

    1. lies alles, was im Puffer ist, und hänge es an einen Lesepuffer an
    2. stehen genügend Daten für den Telegrammkopf im Lesepuffer?
      nein: fertig
    3. lies den Telegrammkopf aus dem Lesepuffer und bestimme die Telegrammlänge
    4. stehen genügend Daten für das Telegramm im Lesepuffer?
      nein: fertig
    5. lies das Telegramm aus dem Lesepuffer
    6. entferne die Telegrammdaten aus dem Lesepuffer
    7. behandle das Telegramm
    8. 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


Anmelden zum Antworten