QByteArray per TcpSocket verschicken - die 2te

Alles rund um die Programmierung mit Qt
Antworten
User
Beiträge: 16
Registriert: 17. Mai 2007 00:42

QByteArray per TcpSocket verschicken - die 2te

Beitrag von User »

Ich habe seit einiger Zeit ein Problem, das diesem hier sehr ähnlich ist:
http://www.qt-forum.de/forum/viewtopic.php?t=6769

Ich verschicke ein QByteArray, das beim Empfänger nur bruchstückhaft ankommt.

Der Code für die Senderseite ist folgender:

message ist der eigentliche Inhalt, die ganzen IDs und Groups sind quint16, die dann vorne an die Nachricht angehängt werden (der Server leitet die Nachrichten entsprechend weiter, sprich er vermittelt nur). Ganz an den Anfang habe ich eine quint32 gesetzt, die die Gesamtlänge der Nachricht enthält, headerSize ist für die IDs und ist 4*sizeof(quint16). Sollte alles soweit klar sein:

Code: Alles auswählen

void EventClient::sendMessage(QByteArray message, quint16 receiverID, quint16 receiverGroup)
{
	QByteArray msg;
	QDataStream buffer(&msg, QIODevice::WriteOnly);

	buffer << quint32(VRGUI::headerSize + message.size());
	buffer << quint16(connection.ID);
	buffer << quint16(connection.group);
	buffer << quint16(receiverID);
	buffer << quint16(receiverGroup);
	buffer << message;

	connection.socket->write(msg);
}


Der Server arbeitet den Socket nun nicht mit einer Connection zum readyRead-signal, sondern über eine eigene Tick-Schleife ab, in der jedes mal für alle verbundenen Clients geprüft wird, ob neue Daten verfügbar sind. Hoffe, dass der Source mit den Kommentaren ausreichend ist:

Code: Alles auswählen

void EventServer::slotTimerTick()
// The main message processing function of the server.
// Reads and dispatches messages and checks for system requests.
{
	// process clients - read messages from client connections
	foreach(EventConnection* tmpConnection, clientList)
	{
		if(tmpConnection->socket->bytesAvailable())
		{
			// fist check if the connection is in ready mode. This means hat no message is currently being
			// received, so that any new data is a new message header. This header data is read if it has been
			// recieved completly (VRGUI::headerSize + sizeof(quint64)).
			if (
				(tmpConnection->status == VRGUI::connectionReady)
				&& tmpConnection->socket->bytesAvailable() >= VRGUI::headerSize + sizeof(quint64)
				)
			{
				QDataStream tmpStream(tmpConnection->socket);
				tmpStream >> tmpConnection->msg.totalSize;
				tmpStream >> tmpConnection->msg.senderID;
				tmpStream >> tmpConnection->msg.senderGroup;
				tmpStream >> tmpConnection->msg.receiverID;
				tmpStream >> tmpConnection->msg.receiverGroup;
				tmpConnection->msg.totalSize -= VRGUI::headerSize; // subtract this as usually the header is part of the total message size
				tmpConnection->status = VRGUI::connectionTransmitting; // set the new status to show that message data is being read
			}


			// if data is being received (i.e. the message was not read completly during the last tick), it is 
			// read from the socket and appended to the already received bytes. This continues until all data is read.
			if (tmpConnection->status == VRGUI::connectionTransmitting)
			{
				int bytesStillMissing = tmpConnection->msg.totalSize - tmpConnection->messageInProgress.size();
				tmpConnection->messageInProgress.append(tmpConnection->socket->read(bytesStillMissing));

				if(tmpConnection->msg.totalSize == tmpConnection->messageInProgress.size())
				// if all message data is read, it is stored in the msg.messageContent and this msg is appended
				// to the message queue which will be used in a later loop to dispatch all finished messages.
				{
					QDataStream tmpStream(tmpConnection->messageInProgress);
					tmpStream >> tmpConnection->msg.messageContent;
					messageList.append(tmpConnection->msg);

					clearMessage(tmpConnection->msg);  //clearMessage clears the msg struct so that it is re-initialized and empty for the next run
					tmpConnection->messageInProgress.clear(); // erase contents of the temp message content, reason same as above
					tmpConnection->status = VRGUI::connectionReady; // connection status is set back to ready, the connection can read new messages now

				}	
			}
		}
	}
}
Mein Problem ist nun, dass zwar der Anfang der Message korrekt empfangen wird - sowohl die Länge als auch die ganzen IDs - aber dann, wenn es an den Content geht, "irgendwas" schief läuft. Den Content kann ich mit dem Debugger nicht auslesen, aber offenbar werden irgendwo noch extra-Bytes gelesen/geschrieben, denn wenn ich eine message sende, wird der Text nicht erkannt (qDebug spuckt nichts aus und laut VS Debugger ist der Inhalt = 0). Es hängen auch "tote" bytes im Buffer, denn folgende Nachrichten werden komplett falsch gelesen. Interessant finde ich, dass folgende while-Schleife läuft (statt des großen if-Blocks oben):

Code: Alles auswählen

while(tmpConnection->socket->bytesAvailable())
		{
			QDataStream tmpStream(tmpConnection->socket);
			QByteArray tmpData;
			MessageStruct tmpStruct;

			tmpStream >> tmpStruct.totalSize;
			tmpStream >> tmpStruct.senderID;
			tmpStream >> tmpStruct.senderGroup;
			tmpStream >> tmpStruct.receiverID;
			tmpStream >> tmpStruct.receiverGroup;
			tmpStream >> tmpStruct.messageContent;

			messageList.append(tmpStruct);
		} 
Tut es problemlos, allerdings verschwinden hier manchmal Messages oder werden zerstückelt, wahrscheinlich weil alle möglichen Sicherheitsvorkehrungen fehlen. Wie kann ich nun den ersten Code richtig zum laufen kriegen?

Und zum Schluß noch 2 kurze Fragen:

In der QT-Doku ist von einem "Short-Read" beim Zugriff auf Sockets die Rede. Ist das eine Leseanweisung, bei der mehr Bytes gelesen werden sollen, als verfügbar sind? Habe dazu nichts gefunden.

Und als letztes gibt mit VS beim debuggen für 4*sizeof(quint16) den Wert 16 an. Nach meinem Verständnis ist das aber 8, denn quint16 hat 2 Byte und das dann mal 4 ist 8. Kann das jemand aufklären?
patrik08
Beiträge: 746
Registriert: 27. Februar 2006 10:48
Wohnort: DE Freiburg

Beitrag von patrik08 »

Ich habe lange den besten weg gesucht um saubere QDataStream zu bekommen,
und habe bemerkt das obwohl ich qt 4.4 benutze ... habe ich das beste
resultat erziehlt mit ....
ds.setVersion(QDataStream::Qt_4_2);
Warum weiss ich nicht ... und zuletzt
packe ich das ganze in einen
bytes.toBase64(); was ich ab und zu auch
comprimiere

QByteArray bildstream = qUncompress( dimg );
dimg = qCompress(bytes,9);

bis zu 50% datenreduction ....

Somit ist auch den Mime drag & drop , sql speichern oder file , tcpi stream sind immer sauber und muss mich niee um utf-8 oder iso codec
kummern da Base64 egal welchen codec immer richtig ankommt...


Beispiel ein PNG bild das ;
1- QImage gemacht hat ist 35KB
2- mit png lib gespeichert zlib png methode 8KB
3- mit dem comprimierten QDataStream 5KB



beispiel 2:
Zwei QImage bilder von je 30KB zusammen geschnuert mit APNG ergibt 1.8KB ( animiertes PNG / das man dann auch wieder splitten liest)

http://ppk.ciz.ch/qt_c++/apng_format/written-apng.png

Info suche APNG in http://www.qt-apps.org/


es liegt bei dir ob die uebertragung schnell sein muss oder langsam bilder nummern text .....

Code: Alles auswählen

inline QString SaveImageGroup( QList<SPics> li  )
{
   if (li.size() < 1) {
     return QString(); 
   }
  QByteArray bytes;
  QBuffer buffer(&bytes);
  if (!buffer.open(QIODevice::WriteOnly)) {
    return QString();
  }
  QDataStream ds(&buffer);
  /* place header */
  ds.setVersion(QDataStream::Qt_4_2);
  ds << (quint32)SPics::MAGICNUMBER;
  ds << (quint32)SPics::VERSION;
  /* place header */
             ///////QApplication::setOverrideCursor(Qt::WaitCursor);
             for (int i=0; i<li.size(); i++) {
                 SPics conni = li[i];
                 ds << conni;
             }   
            ////// QApplication::restoreOverrideCursor();
  buffer.close();
  return bytes.toBase64();
} 

.........................
speack português italiano deutsch english castellà qt
PeterLustig
Beiträge: 386
Registriert: 21. November 2007 20:07

Beitrag von PeterLustig »

@ patrik08

Erstmal gesagt, toBase64 macht den DataStream aber größer als er eh schon ist. Desto größer der Stream ist, desto größer das Endergebnis.
Und kompremieren würde ich in größeren Applikationen ganz weglassen. Denn man muss ja bedenken, das kompremieren braucht auch seine Prozessorleistung, besonders auf der höchsten Stufe.
patrik08
Beiträge: 746
Registriert: 27. Februar 2006 10:48
Wohnort: DE Freiburg

Beitrag von patrik08 »

Mag sein....
Der Bilder chunk (cocktail) ist bei immer den immer noch kleiner als den original stream ....

Und bei meinen CMS wo die bilder in eine mysql tabelle liegen bin ich mir nicht immer sicher ob alle tabellen den richtigen codec haben..

encode64 kann PHP5 prima oeffnen und darstellen....
wichtig nie mit QPixmap oder QImage abspeichern...
sonder mit der libpng progressive encoding und compression level 9 mit oder ohne Alpha farbe.
dann sind die bilder mehrmals besser als jpg und auf jedemfall leichter als QPixmap oder QImage ( save quality egal was )
.........................
speack português italiano deutsch english castellà qt
User
Beiträge: 16
Registriert: 17. Mai 2007 00:42

Beitrag von User »

Hi, erst mal danke für die Antworten, aber das hilft mir leider nicht weiter. Du beschreibst da eine Methode, um ein möglichst kleines QByteArray zu bekommen - das ist bei mir erst einmal nicht das Ziel. Es geht ja darum, dass mein Array auf Empfängerseite nicht korrekt gelesen werden kann. Bei der Optimierung von Größe und Geschwindigkeit bin ich noch gar nicht, und ich denke nicht, dass das im Moment bei meinem Programm eine Rolle spielt (die Testnachricht, von der ich rede, wird einmalig übertragen und ist nur ein paar Byte groß). Ich suche im Moment noch nach dem Grund, warum mein Array falsch gelesen wird, wenn ich es mit der ersten beschriebenen Methode auslese und es mit der zweiten funktioniert, wobei beide in meinen Augen grundsätzlich das gleiche liefern sollten.
Maxima
Beiträge: 27
Registriert: 23. Mai 2008 03:45

Beitrag von Maxima »

Ich benutze zum lesen und schreiben auf einen socket nie einen datastream (ich traue dem nicht :wink: ), sondern immer

Code: Alles auswählen

ba=socket->readBytes(len)
socket->write(ba)
und hatte noch nie Probleme.

Mehraufwand ist allerdings, daß man von "hand" die Größe der Elemente schicken/auswerten muß.

Ob das Dein Problem löst weiß ich nicht, Du könntest es ja mal vesuchen.

Viel Erfolg!
User
Beiträge: 16
Registriert: 17. Mai 2007 00:42

Beitrag von User »

readBytes ist aber so weit ich das sehen kann keine Funktion von QAbstractSocket oder QTcpSocket, sondern eben von QDataStream. Meinst du vielleicht die reine read-Routine? Ich habe deinen Vorschlag noch nicht implementiert, da ich dafür recht viel Code umschreiben müsste.

Ich habe noch etwas weiter probiert. Nachdem ich festgestellt hatte, dass offenbar immer exakt vier Bytes mehr gesendet werden als ich erwarte, habe ich die Leseroutine so angepasst, dass diese 4 Byte ebenfalls an den Message-Inhalt angehängt werden:
tmpConnection->msg.totalSize -= VRGUI::headerSize;
// obige Zeile wird ersetzt durch:
tmpConnection->msg.totalSize -= ( VRGUI::headerSize - 4 );

Der Code läuft jetzt wunderbar, aber wo kommen die extra Bytes her? Könnte es sein, dass irgendwo ein QByteArray sich unaufgefordert noch einen Längen-Header verpasst? Dieser ist ja nach Dokumentation ein quint32, und das sind 4 Byte.
Maxima
Beiträge: 27
Registriert: 23. Mai 2008 03:45

Beitrag von Maxima »

User hat geschrieben:readBytes ist aber so weit ich das sehen kann keine Funktion von QAbstractSocket oder QTcpSocket, sondern eben von QDataStream.
ist eine Funktion von QIODevice (baseclass of QAbstractSocket)
EDIT: benutzt Du selbst bei Deinem Sender
User hat geschrieben:Ich habe deinen Vorschlag noch nicht implementiert, da ich dafür recht viel Code umschreiben müsste.
Habe ich auch nicht erwartet: und Dir sind ja mittlerweile auch die 4Bytes "extra" aufgefallen.
Das meinte ich mit "ich traue dem nicht".

Ich denke aber nicht, daß da irgendwas unaufgefordert passiert.
Check mal wo die längen-angabe herkommt, ist bestimmt interessant
Viper2000
Beiträge: 48
Registriert: 7. Mai 2008 16:36

Beitrag von Viper2000 »

schau mal in meinen ersten Thread von diesem ähnlichen Problem! Habs mittlerweile erfolgreich gelöst...vielleicht hilft es dir
Ich bin nicht die Signatur, ich putz hier nur :-)
Antworten