QThread für QTCPSocket benutzen

Alles rund um die Programmierung mit Qt
anno1988
Beiträge: 280
Registriert: 23. Januar 2009 20:49

QThread für QTCPSocket benutzen

Beitrag von anno1988 »

Hallo,

ich habe mir eine QTcpServer Klasse erstellt die mir die TCP-Connections annimmt.

Bei jeder incomingConnection() wird bei mir ein neuer Thread erstellt und darin das QTcpSocket Objekt erstellt. das funktioniert zwar, ich bekomme aber immer eine Fehlerausgabe der Anwendung.

So wie ich gesehen habe tritt diese Fehlermeldung auf, sobald der SLOT readData() aufgerufen wird, wenn ich da richtig liege.

Fehlermeldung:

Code: Alles auswählen

QObject: Cannot create children for a parent that is in a different thread.
(Parent is QNativeSocketEngine(0x9f17ad0), parent's thread is ServerThread(0x9edcc48), current thread is QThread(0x9ed8338)
Code:

Code: Alles auswählen


#include <QThread>
#include <QSslSocket>
#include <QStringList>
#include <QDateTime>
#include <QDebug>


class ServerThread : public QThread {

	Q_OBJECT

	private:
		int socketDescriptor;
		QString text;
		QTcpSocket *tcpSocket;

	public:
		ServerThread(QObject *parent = 0);
		ServerThread(int socketDescriptor, const QString &fortune, QObject *parent);
		void run();

	protected slots:
		void readClient();
		void discardClient();
		void readData();

};



	ServerThread::ServerThread(QObject *parent) : QThread(parent) {


	}


	ServerThread::ServerThread(int socketDescriptor, const QString &fortune, QObject *parent) :
												QThread(parent), socketDescriptor(socketDescriptor), text(fortune) {




	}


	void ServerThread::run() {

		QTcpSocket *tcpSocket;
		tcpSocket = new QTcpSocket();

		connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(readData()));

		if(!tcpSocket->setSocketDescriptor(socketDescriptor)) {

			return;
		}

		qDebug() << "incoming";


		tcpSocket->waitForDisconnected();

	}



	void ServerThread::readData() {

		// This slot is called when the client sent data to the server. The
		// server looks if it was a get request and sends a very simple HTML
		// document back.
		QTcpSocket *socket = tcpSocket;

		qDebug() << "readData()";

		if(tcpSocket->canReadLine()) {
			QStringList tokens = QString(socket->readLine()).split(QRegExp("[ \r\n][ \r\n]*"));
			if (tokens[0] == "GET") {

				QByteArray block;

				block.append("HTTP/1.0 200 Ok\r\nContent-Type: text/html; charset=\"utf-8\"\r\n\r\n<h1>Nothing to see here</h1>\n" + QDateTime::currentDateTime().toString());

				tcpSocket->write(block);

				qDebug() << "GET";

				tcpSocket->close();


			}
		}
	}

Was mache ich denn da genau falsch?
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

Der Fehler muss wo anders sein. Du kannst einfach kein Objekt in einem Thread erstellen, und dem dort einen parent aus einem anderen Thread geben. Kannst du mal ein paar debug-Ausgaben einstreuen, damit du siehst wo die Ausgabe kommt? In dem geposteten Code (ist der komplett?) seh ich jedenfalls keine Probleme (bis auf das mit dem eigenen QTcpSocket-Objekt in run() mit gleichem Namen wie der Member...).
anno1988
Beiträge: 280
Registriert: 23. Januar 2009 20:49

Beitrag von anno1988 »

Also so erstelle ich meinen Thread in der QTcpServer Klasse.
Aber ansonsten ist der Code vollständig.

Code: Alles auswählen

void Server::incomingConnection(int socketDescriptor) {

		QString fortune = fortunes.at(qrand() % fortunes.size());
		ServerThread *thread = new ServerThread(socketDescriptor, fortune, this);
		connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
		thread->start();

	}
anno1988
Beiträge: 280
Registriert: 23. Januar 2009 20:49

Beitrag von anno1988 »

Also in der run() Methode, nachdem die Methode "tcpSocket->waitForDisconnected(); " aufgerufen wurde, bekomme ich diese Fehlermeldung.

Ich habe diesen Aufruf eigentlich nur drin um den Thread nicht gleich wieder zu beenden und um dann die Anfragen bearbeiten zu können.
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

da hat's noch jede Menge Verbesserungspotetial :wink:

Folgende Punkte:

1. QTcpServer
Der liefert bereits einen QTcpSocket mit "nextPendingConnection()".. ich habe keine Ahnung, warum du da über den Descriptor arbeitest... eigentlich sieht eine Standard-Start-Methode in etwa so aus:

Code: Alles auswählen

 QTcpSocket     *sock = server->nextPendingConnection();
 ServerThread  *thread = new ServerThread(fortune)
 sock->setParent(NULL);           // Parent war der QTcpServer
 sock->moveToThread(thread); // Eventloop zum Thread verschieben
 thread->setSocket(sock);
 thread->start();

 // Ueblicherweise soll der Thread beim Beenden auch geloescht werden:
 connect(thread,SIGNAL(finished()), thread, SLOT(deleteLater()));

2. lokale Variabeln vs. Members
Du hast da ein Durcheinander...

Hier wird eine lokale Variabel erstellt..

Code: Alles auswählen

  
 void ServerThread::run() {
    QTcpSocket *tcpSocket;
      tcpSocket = new QTcpSocket();


Und hier wird der (uninitialisierte) Member übernommen:

Code: Alles auswählen

void ServerThread::readData() {

      // This slot is called when the client sent data to the server. The
      // server looks if it was a get request and sends a very simple HTML
      // document back.
      QTcpSocket *socket = tcpSocket;
Keine Ahnung was das soll.. übernimm einfach wie in Punkt 1 geschrieben den QTcpScket mit einem eigenen "setSocket(..)" und vergess dann diese lokalen Dinger..!

3. run-Methode
Auch die wird eigentlich in einem Thread anderst gemacht. Wenn du den Eventloop automatisch beenden möchtest, kannst du das ja wie folgt machen:

Code: Alles auswählen

.....run()
{
   connect(m_socket, SIGNAL(disconnected()),
               this, SLOT(quit()));
   exec();
}
jetzt hast du alles automatisiert: sobald die Verbindung abgebrochen wird, wird die Eventloop verlassen und dies wiederum loescht den Thread

hth!
anno1988
Beiträge: 280
Registriert: 23. Januar 2009 20:49

Beitrag von anno1988 »

mit folgendem Code funktioniert mein Server soweit mal. Allerdings jetzt nur mit HTTP.


Code: Alles auswählen


class ServerThread : public QThread {

	Q_OBJECT

	private:
		int socketDescriptor;
		QString text;
		QSslSocket *mTCPSocket;

	protected:
		void run();

	public:
		ServerThread(QObject *parent = 0);
		ServerThread(int socketDescriptor, QObject *parent = 0);
		~ServerThread();

	signals:
		void error(QTcpSocket::SocketError socketError);

	protected slots:
		void readData();

};


#include "serverthread.h"

//--------------------------------------------------------

	void ServerThread::run() {

		this->exec();

	}

//--------------------------------------------------------

	ServerThread::ServerThread(QObject *parent) : QThread(parent) {


	}

//--------------------------------------------------------

	ServerThread::ServerThread(int socketDescriptor, QObject *parent) : QThread(parent) {


		mTCPSocket = new QSslSocket();


		if(mTCPSocket->setSocketDescriptor(socketDescriptor)) {

			mTCPSocket->setLocalCertificate("/home/anno/Desktop/ssl/cert.pem");
			mTCPSocket->setPrivateKey("/home/anno/Desktop/ssl/cert.pem");

			connect(mTCPSocket, SIGNAL(readyRead()), this, SLOT(readData()));

			connect(mTCPSocket, SIGNAL(disconnected()), this, SLOT(quit()));

			mTCPSocket->startServerEncryption();

		}
	}

//--------------------------------------------------------

	ServerThread::~ServerThread() {

		if(mTCPSocket != 0) {

			delete mTCPSocket;

		}
	}

//--------------------------------------------------------

	void ServerThread::readData() {

		qDebug() << "readData()";

		if(mTCPSocket->canReadLine()) {
			QStringList tokens = QString(mTCPSocket->readLine()).split(QRegExp("[ \r\n][ \r\n]*"));
			if (tokens[0] == "GET") {

				QByteArray block;

				block.append("HTTP/1.0 200 Ok\r\nContent-Type: text/html; charset=\"utf-8\"\r\n\r\n<h1>Nothing to see here</h1>\n" + QDateTime::currentDateTime().toString());

				for(int i=0; i < 3; i++)
					mTCPSocket->write(block);

				for(int i=0; i < 3; i++) {



				}

				qDebug() << "GET";

				mTCPSocket->close();


			}
		}
	}

//--------------------------------------------------------


Jetzt will ich noch versuchen das Ganze auf SSL umzustellen. Fehlermeldungen bekomme ich keine, aber wenn ich den Server über Firefox anspreche bekomme ich nur eine leere Seite. Die SSL Verbindung wurde also soweit aufgebaut und ist aktiv, nur Daten werden keine übertragen.
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Beitrag von Christian81 »

Und nun passiert im Thread gar nichts mehr und alles wird im Hauptthread ausgeführt (außer Du hast im nichtgezeigten Code irgendwo ein serversocket->moveToThread(serversocket) ) ...
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

Schön und gut, dass du nun keine Meldungen mehr hast... aber:

Code: Alles auswählen

ServerThread::ServerThread(int socketDescriptor, QObject *parent) : QThread(parent) {
      mTCPSocket = new QSslSocket();
...
         connect(mTCPSocket, SIGNAL(readyRead()), this, SLOT(readData()));
   }
Soweit ich sehen kann, wird "readData()" nun leider im Kontext der GUI ausgeführt... und nicht im Kontext des Threads.. sowohl "mTCPSocket" als auch "this" gehören (soweit einsehbar) zum GUI-Thread..


hth..


[EDIT]
redundant zu Christian :wink:
anno1988
Beiträge: 280
Registriert: 23. Januar 2009 20:49

Beitrag von anno1988 »

hier rufe ich den thread auf:

Das Ganze natürlich im QTcpServer.

Code: Alles auswählen


	void Server::incomingConnection(int socketDescriptor) {

		QTcpSocket *tcp_socket;
		ServerThread *thread;

		//QString fortune = fortunes.at(qrand() % fortunes.size());

		thread = new ServerThread(socketDescriptor);

		connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));

		thread->start();

	}

@solarix: warum wird das denn im Hauptthread aufgerufen?
Das Ist doch eine Vererbung der Thread Klasse und davon wird ein neues Objekt erzeugt.
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Beitrag von Christian81 »

1. Warum überhaupt QThread - das bischen was wir hier sehen rechtfertigt den Einsatz eines Threads nicht im geringsten
2. Die Doku zu QThread lesen und sich mit Threading allgemein beschäftigen, ggf. die Foren-Suche (das Problem haben wir eigentlich jede Woche einmal) würde hier helfen
3. mTcpSocket wird im Haupthtread erzeugt, läuft demnach auch dort. Deshalb läuft nichts im Thread.
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
anno1988
Beiträge: 280
Registriert: 23. Januar 2009 20:49

Beitrag von anno1988 »

Ja aber wenn ich jetzt die lokale Variable in der run Methode erzeuge kann ich ja gar nicht mehr SIGNALS und SLOTS verarbeiten.
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

@solarix: warum wird das denn im Hauptthread aufgerufen?
Weil...

Code: Alles auswählen

void Server::incomingConnection(int socketDescriptor) {
      ... 
       // .... wir uns hier im Hauptthread befinden...
      thread = new ServerThread(socketDescriptor);
      // ... und hier auch ... und damit..
     ...
}
  
//--------------------------------------------------------
   ServerThread::ServerThread(int socketDescriptor, QObject *parent) : QThread(parent) {
      // --- befinden wir uns auch HIER im Hauptthread..
      mTCPSocket = new QSslSocket();
anno1988 hat geschrieben:Ja aber wenn ich jetzt die lokale Variable in der run Methode erzeuge kann ich ja gar nicht mehr SIGNALS und SLOTS verarbeiten.
Wer spricht denn von lokal?

Code: Alles auswählen

 ..... run()
   {
      // hier erstellen wir im Thread eine neue Instanz  aber 
      // verwenden keine lokale Variabel
       mTCPSocket = new QSslSocket();
    }
Aber das bringt alles immer noch nichts, wenn du das zu einem Slot von "ServerThread" verbindest.. weil die Instanz von "ServerThread" ja im Hauptthread erzeugt wurde und der Slot daher auch da ausgeführt wird.. genau genommen ist das sogar wichtiger als der mTCPSocket... wie bereits erwähnt fehlt da also noch ein moveToThread(..).

Übrigens einmal mehr: der Autor von
http://labs.qt.nokia.com/2006/12/04/thr ... -headache/
hat ganz genau an diese "Headache" gedacht..

hth...
anno1988
Beiträge: 280
Registriert: 23. Januar 2009 20:49

Beitrag von anno1988 »

solarix hat geschrieben: Übrigens einmal mehr: der Autor von
http://labs.qt.nokia.com/2006/12/04/thr ... -headache/
hat ganz genau an diese "Headache" gedacht..

hth...
ok, wenn ich das Beispiel von dieser Seite nehme, dann sollte ich besser eine Vererbung der QTCPSocket Klasse machen und dann ein Objekt davon in einem Thread starten, anstatt die QThread Klasse zu vererben und alles da drin machen zu wollen.
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

Meiner Meinung nach überbewertest du die Vererbung. Qt fördert auch andere Beziehungen zwischen (QObject-)Instanzen. Ich würde folgende Rollen verteilen:

QTcpServer: generiert neue Verbindungen (QTcpSocket); ohne Vererbung
QTcpSocket: ist eine Verbindung; ohne Vererbung
QThread : ist ein Threadkontext; ohne Vererbung
FortuneServer: implementiert das Protokoll, besitzt ein QTcpSocket

In der Anwendung sähe das in etwa so aus:

Code: Alles auswählen

   FortuneServer *f = new FortuneServer(tcpServer->nextPendingConnection());
  QThread *context = new QThread();
  f->moveToThread(context);
  context->start();
Thats it.. wenn du nun innerhalb von "FortuneServer" Slots zum QTcpSocket (oder von mir auch SSH) connectest wird das sauber im Thread ausgeführt.

Aber das ist nur _ein_ Weg.. du kannst auch alles in einen abgeleiteten QThread packen (alle bisher aufgelisteten Probleme lassen sich schon lösen) oder eben ohne Threads arbeiten (Christian hat schon recht: bisher hast du uns keinen Grund für die Threads geschildert...).

hth!

PS: die obige Variante hat auch folgenden Vorteil: lasse den QThread weg und du hast ohne weitere Änderungen eine Single-Thread-Applikation und umgekehrt
:wink:
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Beitrag von Christian81 »

@solarix: ggf. habe ich es übersehen aber tcpServer->nextPendingConnection() sollte auch mit moveToThread bedacht werden. Falls dies nicht geht (wovon ich ausgehe weil der QTcSocket einen Parent hat - nämlich QTcpServer wenn ich korrekt bin) ist nur die Möglichkeit mit dem socketDescriptor korrekt. Insofern ist das mit dem socketDescriptor schon keine schlechte Idee :)
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
Antworten