Seite 1 von 2
QThread für QTCPSocket benutzen
Verfasst: 3. Dezember 2010 14:53
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?
Verfasst: 3. Dezember 2010 15:55
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...).
Verfasst: 3. Dezember 2010 16:27
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();
}
Verfasst: 3. Dezember 2010 16:40
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.
Verfasst: 3. Dezember 2010 18:13
von solarix
da hat's noch jede Menge Verbesserungspotetial
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!
Verfasst: 3. Dezember 2010 20:29
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.
Verfasst: 3. Dezember 2010 20:43
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) ) ...
Verfasst: 3. Dezember 2010 20:44
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

Verfasst: 3. Dezember 2010 20:50
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.
Verfasst: 3. Dezember 2010 21:05
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.
Verfasst: 3. Dezember 2010 21:07
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.
Verfasst: 3. Dezember 2010 21:35
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...
Verfasst: 3. Dezember 2010 21:46
von anno1988
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.
Verfasst: 3. Dezember 2010 23:34
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

Verfasst: 4. Dezember 2010 08:48
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
