Seite 1 von 3

QByteArray per TcpSocket verschicken

Verfasst: 23. Mai 2008 00:09
von Viper2000
Hallo,

ich versuche verzweifelt ein QByteArray über einen Socket zu schicken.

Hier der "Server-Code":

Code: Alles auswählen

bool Thread::sendData(QByteArray data)
{
		QByteArray block;
		QDataStream out(&block, QIODevice::WriteOnly);
		out.setVersion(QDataStream::Qt_4_3);
		out << (quint32)0;
		out << data;
		out.device()->seek(0);
		out << (quint32)(block.size() - sizeof(quint32));
               tcpSocket.write(block);
Das ist soweit das Beispiel aus dem Fortune Server. Wenn ich nen QString statt des QByteArray schicke, dann kann ich den auf der anderen Seite auch wieder prima extrahieren aber mein QByteArray kommt auf der anderen Seite zwar an, bzw. es kommt irgendwas an, aber die Daten sind nicht korrekt.
Der QDataStream schreibt ja an sich schon in ein QByteArray und mein ByteArray wird in den Stream eingebettet. Anscheinend hat er aber da probleme mit.

Hier der "Client-Code:"

Code: Alles auswählen

void Thread::readReceivedData()
{
	QByteArray receivedData;
	mBlockSize = 0;

	QDataStream in(&mTcpSocket);
	in.setVersion(QDataStream::Qt_4_3);
		
	if(mBlockSize == 0)
	{
		if(mTcpSocket.bytesAvailable() < (int)sizeof(quint32))
		{
			return;
		}
		
		in >> mBlockSize;
	}
		
	if (mTcpSocket.bytesAvailable() < mBlockSize)
		return;
	
	in >> receivedData;
Hat jemand ne Ahnung warum das nicht funktioniert!?

MfG viper2000

Verfasst: 23. Mai 2008 09:17
von solarix
aber die Daten sind nicht korrekt.
Solche Fehlerbeschreibungen sind immer wieder schwierig für uns.. Benutze Debugger und Wireshark (http://www.wireshark.org/) um den Fehler exakt beschreiben zu können (z.B. "Server versendet Daten korrekt, Client empfangt zuwenig").

Noch was Grundsätzliches: Die Server-Daten werden vom BS in Frames aufgeteilt und stückchenweise versendet. Falls die Daten also nicht in ein einziges Frame passen (was vermutlich bei deinem Beispiel der Fall ist, beim String-Test jedoch noch nicht), musst du solange lesen, bis du den ganzen Block empfangen hast. Dies kannst du auf zwei Arten tun, entweder in Form eines Loops (solange waitForReadyRead() bis bytesAvailable() deiner Block-Grösse entspricht) oder -der schönere Weg- über einen Empfangs-Slot, der alle Daten stückchenweise (immer wenn was neues da ist) in eine private Variable liest.

Falls deine readReceivedData() ein Slot ist (verbunden mit readyRead ()) ist, wird sie mit jedem neu eintreffenden Daten-Abschnitt erneut aufgerufen...

Noch was kleines...vielleicht überseh ich was, aber bei

Code: Alles auswählen

 out << (quint32)0;
 out << data;
 out.device()->seek(0);
 out << (quint32)(block.size() - sizeof(quint32)); 
Warum nicht einfach?

Code: Alles auswählen

 out << (quint32)data.size();
 out << data;

Verfasst: 23. Mai 2008 09:20
von PeterLustig
Warum benutzt du C-Sprachelemente in C++? Das ist ganz schlechter Stil. :[

Verfasst: 23. Mai 2008 11:45
von Viper2000
Ja, meine Empfangsmethode ist ein SLOT der mit dem readyRead() SIGNAL des Sockets verbunden ist. Meine Empfangsroutine entspricht weitestgehend dem "Fortune Client Example" aus der Trolltech Qt Dokumentation, kann so schlecht also nicht sein denke ich :)



Wo benutze ich denn C-Sprachelemente?
Warum nicht einfach?
Code:

out << (quint32)data.size();
out << data;
Ja das könnte man natürlich machen... :)

Verfasst: 23. Mai 2008 11:46
von PeterLustig
Du benutzt C-Style-Casts.

Verfasst: 23. Mai 2008 11:56
von Viper2000
meinste das hier z.B:

out << (quint32)(block.size() - sizeof(quint32));

aber wieso nutzt Trolltech in ihren Offiziellen Beispielen denn diesen Stil wenn es so schlecht ist? Bzw. wie kann mans "besser machen"

Verfasst: 23. Mai 2008 12:43
von solarix
Meine Empfangsroutine entspricht weitestgehend dem "Fortune Client Example" aus der Trolltech Qt Dokumentation, kann so schlecht also nicht sein denke ich
Das dachten die Jungs bei der ESA auch, als sie den Code von der Ariane4 auf Ariane5 kopierten und diese damit schrotteten..

Hast du meine Hinweise verstanden und umgesetzt, oder erwartest du weitere Blicke in die Kristallkugel?

Verfasst: 23. Mai 2008 14:45
von Viper2000
Hi Solarix,

ja sorry hatte die ganze Zeit getestet. Es war ein Bug im Client...hatte die empfangene Blockzize dort noch als quint16 drinnne. Hab jetzt alles auf den 32 Bit Wert geändert und nun kommt mein Array einwandfrei auf der anderen Seite an :)

Wenn ich deinen Hinweis mit den Frames richtig interpretiert habe dann sollte doch mit meiner Empfangsroutine wegen des connects readyReady() <-> readDeceivedData() in jeden´m Fall der Empfang gesichert sein - auch wenn die Daten auf mehrere Frames verteilt sind!?

Verfasst: 23. Mai 2008 15:58
von Maxima
Hi Viper2000,

Du schickst ja die datenlänge vorneweg, dh. Dein client kann sich darauf "einstellen" genau diese datenmenge zu empfangen; indem der client die ankommenden (Signal readyRead)Daten bis zu der datenlänge einem ByteArray zuweist. Die nächsten (darüber hinaus gehenden)Daten müssen also einem neuen Datensatz angehören.

Es kann sein das der server zwei datenblöcke in mehreren Paketen direkt hintereinander schickt und die Grenze zwischen den Beiden in einem Paket ankommen. Das muß man abfangen.

Verfasst: 23. Mai 2008 19:30
von Viper2000
@Maxima: Ich denke das fange ich doch ab, da ich Byteweise Nachschaue was so am Socket ankommt. Auch wenn der Server 2 Datensätze schnell hintereinander schickt sollte das doch kein Problem darstellen!?

Falls ich das irgendwie missverstehen sollte wäre ich dankbar um ein Stück Beispielcode der das von dir gemeinte Problem aufgreift.

Schönes Wochenende schonmal @all

Verfasst: 24. Mai 2008 18:13
von Maxima
Dein Code
(habe ich in der letzten zeile kommentiert)

Code: Alles auswählen

void Thread::readReceivedData()
{
   QByteArray receivedData;
   mBlockSize = 0;

   QDataStream in(&mTcpSocket);
   in.setVersion(QDataStream::Qt_4_3);
      
   if(mBlockSize == 0)
   {
      if(mTcpSocket.bytesAvailable() < (int)sizeof(quint32))
      {
         return;
      }
      
      in >> mBlockSize;
   }
      
   if (mTcpSocket.bytesAvailable() < mBlockSize)
      return;
   
   in >> receivedData;                           //<--hier achtest Du nicht auf die erwartete Größe (mBlockSize)
 
Mein Code:

Code: Alles auswählen

class{
...
quint32 waitingForBytes;
QByteArray receivedData; 
...
}

void Thread::readReceivedData()
{
   QDataStream in(&mTcpSocket);
   in.setVersion(QDataStream::Qt_4_3);
      
   if(waitingForBytes== 0)
   {
      if(mTcpSocket.bytesAvailable() < (int)sizeof(quint32))
      {
         return;
      }
      
      in >> waitingForBytes;
   }
      
   while (receivedData.size()< waitingForBytes && mTcpSocket.bytesAvailable() > 0){
         receivedData.append(mTcpSocket->read(1));
         waitingForBytes--;
    }//ich steige also aus, wenn ich alle Daten habe, die zu diesem ByteArray gehören
Hoffe ich habe es richtig getippt, aber Du wirst verstehen was ich meine

Allen ein schönes Wochenende

Verfasst: 26. Mai 2008 16:59
von Viper2000
@Maxima:
Wie stellst du denn sicher, dass alles was du sendest auf der anderen Seite auch gelesen wird? Wenn ich z.B. in ner Schleife 1000 ByteArrays unmittelbar hintereinander schicke, dann wird auf der anderen Seite nur ca. 713 mal das ByteArray gelesen :(

Verfasst: 26. Mai 2008 17:13
von Sephral
Viper2000 hat geschrieben:@Maxima:
Wie stellst du denn sicher, dass alles was du sendest auf der anderen Seite auch gelesen wird? Wenn ich z.B. in ner Schleife 1000 ByteArrays unmittelbar hintereinander schicke, dann wird auf der anderen Seite nur ca. 713 mal das ByteArray gelesen :(
Das klingt so als würden bei Dir Pakete zusammengefasst und in einem großen Paket übertragen (Passiert manchmal, wenn man zu schnell ist). Du findet dann 2 ByteArrays (oder mehr) in einem Paket (readyRead löst nur 1x aus). Das sollte sich mit einer Art "Trennzeichen" zwischen den Paketen lösen lassen.

Verfasst: 26. Mai 2008 17:27
von Maxima
Das ist ein interessantes Problem das Du beschreibst!

Welche leseMethode benutzt Du denn jetzt? (Hast Du verstanden, was ich geposted habe?)

Zunächst würde ich überprüfen ob die datensätze correct empfangen werden (Größe/Inhalt).
Dann würde ich weniger Datensätze (auf einmal) schicken (100,200,300 etc) um zu sehen, ob der Buffer vielleicht nicht groß genug ist.

Dann würde ich die empfangsroutine noch ändern

Code: Alles auswählen

receivedData.append(mTcpSocket->read(1)); 
um mehr als 1 Byte zu lesen (könnte sein daß die Schleife zu langsam ist; unwahrscheinlich, weil die Daten ja gepuffert werden...)

"Sichern" läßt sich das nur, wenn man einen weiteren Parameter vorwegschicken würde, der die Anzahl der zu erwartenden Datensätze enthält, um eine FehlerMeldung zu produzieren.

Laß mich unbedingt wissen, was Du rausfindest.
Ich bin in das Problem noch nicht hineingelaufen :D

Spannender Thread!

EDIT:
@Sephral: good point! Der Slot darf nur verlassen werden, wenn bytesAvailable == 0; dh muß die Fähigkeit haben eventuell einen/mehere neue Bytearray anzulegen.
Das "Trennzeichen" braucht man nicht.
Habe ich in meinem Program so gemacht, aber nicht mehr erinnert...

Verfasst: 26. Mai 2008 17:49
von Sephral
Maxima hat geschrieben: EDIT:
@Sephral: good point! Der Slot darf nur verlassen werden, wenn bytesAvailable == 0; dh muß die Fähigkeit haben eventuell einen/mehere neue Bytearray anzulegen.
Das "Trennzeichen" braucht man nicht.
Habe ich in meinem Program so gemacht, aber nicht mehr erinnert...
Ich verschicke bei mir keine ByteArrays, sondern relativ einfache Befehlte/Informationen (diese aber recht fix hintereinander). Es kommt häufiger vor, dass mehrere meiner Befehle dann letztendlich in einem Paket gesendet / empfangen werden auf der Gegenseite. Daher gibts bei mir nur ein einfaches Trennzeichen und der Empfänger nimmt die empfangenen Daten dann einfach mit split() auseinander.

Bei Eurem Vorgehen mit dem Voranstellen der Größe ist das Trennzeichen natürlich nicht notwenig.