PDA

View Full Version : QSslSocket not working in QThread



micgooapp
26th December 2012, 18:15
Hi,

I am trying to get a QSslSocket to work in QThread but I cant seem to get it to work.
The following is the code :


// Local includes
#include "main.h"

// Constructor of the timer class
Timer::Timer(QObject *parent) : QObject(parent)
{
qDebug() << "inConstrutor";
QTimer *timer = new QTimer();
connect(timer, SIGNAL(timeout()), this, SLOT(startBackup()));
timer->start(5000);
}

// Start the backup
int Timer::startBackup(){

qDebug() << "inStart" << backingUP;
//return 0;

if(backingUP){
return 2;
}

// Start the backup thread
Backup *backup = new Backup();

// Make a connection for deleting this later when done
//connect(backup, SIGNAL(finished()), backup, SLOT(deleteLater()));

// Set that we are running ATM
backingUP = true;

backup->start();

return 1;

}

// The constructor
Backup::Backup(QObject *parent)
: QThread(parent)
{

// This thread can never be terminated
//this->setTerminationEnabled(false);
}

// The function that does everything
void Backup::run()
{

this->doit();

}


void Backup::doit(){

qDebug() << "Doit";

socket = new QSslSocket;

connect(socket, SIGNAL(readyRead()),
this, SLOT(socketReadyRead()));
connect(socket, SIGNAL(sslErrors(QList<QSslError>)),
this, SLOT(sslErrors(QList<QSslError>)));
connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)) ,
this, SLOT(socketStateChanged(QAbstractSocket::SocketSta te)));
connect(socket, SIGNAL(encrypted()),
this, SLOT(socketEncrypted()));

// Connect to the server
socket->connectToHostEncrypted(NSettings->value("hostname").toString(), NSettings->value("port").toInt());

// Wait for it to reach the encryted and connected state
if (!socket->waitForEncrypted()) {
qDebug() << "waitForEncrypted Error";
return; // An error occured
}

// This is the protocol
socket->write("ENTERHEADER\r\n");
socket->waitForBytesWritten();

QByteArray username = NSettings->value("user").toByteArray();
QByteArray device = NSettings->value("device").toByteArray();

// We first need to LOGIN
this->rite("L"+ username.toBase64() +" "+ device.toBase64());
socket->waitForBytesWritten();

}

// Called whenever there is something to READ in the buffer
void Backup::socketReadyRead(){

qDebug() << socket->bytesAvailable();
QString str = socket->readAll();
qDebug() << str;

bool unconnected = !socket || socket->state() == QAbstractSocket::UnconnectedState;

if(str == "LOGIN SUCCESSFUL"){

for(int i = 1; i <= 1000; i++){

// Was the login successful ?
if(unconnected){
qDebug() << socket->state();
return;
}

this->rite("STORE ABC\r\n");
socket->waitForBytesWritten();
}

}

qDebug() << "End READ";

// Disconnect from the server
//socket->disconnectFromHost();

}

// Called when the connection is fully established
void Backup::socketEncrypted()
{
qDebug() << "Connection Established";
}

// Called when there are SSL errors
void Backup::sslErrors(const QList<QSslError> &errors)
{
foreach (const QSslError &error, errors){
qDebug() << error.errorString();
}

// Ignore Errors
socket->ignoreSslErrors();
}

// Whenever there is a change in the connection STATE, this is called
void Backup::socketStateChanged(QAbstractSocket::Socket State state)
{
qDebug() << state;
}

// Socket write safe as it appends the length to the beginning of the string
qint64 Backup::rite(const QByteArray & data)
{
return socket->write(data);
}


When I run this I get the following error :

inConstrutor
inStart false
Doit
QObject::connect: Cannot queue arguments of type 'QAbstractSocket::SocketState'
(Make sure 'QAbstractSocket::SocketState' is registered using qRegisterMetaType().)
waitForEncrypted Error


If I dont call the backup->start(); and call the backup->doit(); then the code works perfectly.
I would really be happy, if someone can shed any light on what I am doing wrong.

anda_skoa
26th December 2012, 21:00
Well, for one you are not calling waitForReadyRead() so you will never get any data.

Second, you are doing a lot of cross-thread signal/slot connections in doit (sender belongs to backup thread, receiver belongs to main thread).

Question: what to you need the thread for?

Cheers,
_

micgooapp
27th December 2012, 03:10
Well, for one you are not calling waitForReadyRead() so you will never get any data.


I have done a connect for that :

connect(socket, SIGNAL(readyRead()), this, SLOT(socketReadyRead()));


Second, you are doing a lot of cross-thread signal/slot connections in doit (sender belongs to backup thread, receiver belongs to main thread).

I actually dont get this. Doesnt all the functions belong to the "Backup" class ?
So why will it be cross thread ?
Also the connection never happens. socket->waitForEncrypted() gives an error.
Why would that be ?


Question: what to you need the thread for?

I needed it because the Main Window is a GUI and it hangs when there is to much data to write to the socket.

anda_skoa
27th December 2012, 10:14
I have done a connect for that :

connect(socket, SIGNAL(readyRead()), this, SLOT(socketReadyRead()));


True, but you are not running the thread's event loop.



I actually dont get this. Doesnt all the functions belong to the "Backup" class ?
So why will it be cross thread ?


Yes and the instance of the Backup class belongs to the main thread. Therefore the receiver object is on the main thread, making the Qt::AutoConnection behave like a Qt::QueuedConnection.

Make a receiver class and create an instance in doit(), just like you do for the socket. then run the thread's even loop by calling exec().
This will make the same code work that you have already working on the main thread.



I needed it because the Main Window is a GUI and it hangs when there is to much data to write to the socket.

I assume you didn't do waitFor... when implementing that on the main thread, right?

Cheers,
_

micgooapp
28th December 2012, 06:49
Make a receiver class and create an instance in doit(), just like you do for the socket. then run the thread's even loop by calling exec().

I am a little new to this. Can you give me an example on how to do this ?


I assume you didn't do waitFor... when implementing that on the main thread, right?

I did the waitFor only in this thread as it is.

anda_skoa
28th December 2012, 11:21
You can treat the thread's run() method like it were main(), i.e. just ignore that there is an object around it.

So you create all objects you need, connect them as needed and then call exec(), i.e. like calling app.exec() in main()

An alternative is to create the worker object outside the thread, use a QThread wihout subclassing it and calling moveToThread on the worker object before calling QThread::start()

Cheers,
_

micgooapp
30th May 2013, 11:59
I am still not clear with certain things.
Maybe this will help me clear doubts. The following is the code of a threaded SSL server.

main.cpp



#include <QApplication>
#include <QtCore>

#include <stdlib.h>

#include "server.h"

// Number of requests served
quint64 Served = 0;

int main(int argc, char *argv[]){

QCoreApplication capp(argc, argv);
NServer server;
NServer serverTLS;

// Normal Server
if (!server.listen(QHostAddress::Any, 1780)) {
return 2;
}

// SSL Server
if (!serverTLS.listen(QHostAddress::Any, 1781)) {
return 2;
}

// Set this is a SSL server
serverTLS.isTLS = 1;

// Load the certificate
QFile cert("./conf/cert.pem");
if(!cert.open(QIODevice::ReadOnly)){
qDebug() << "Could not open cert.pem";
}

serverTLS.Certificate = QSslCertificate(&cert);

// Load the KEY
QFile key("./conf/key.pem");
if(!key.open(QIODevice::ReadOnly)){
qDebug() << "Could not open key.pem";
}

serverTLS.SslKey = QSslKey(&key, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, "123456");

// Start the event loop
return capp.exec();

}



server.h



#ifndef SERVER_H
#define SERVER_H

#include <QtNetwork>
#include <qdebug.h>
#include "thread.h"

// The Server Class
class NServer : public QTcpServer
{
Q_OBJECT

public:

// Constructor
NServer(QObject *parent = 0);

// Is this a SSL server ?
int isTLS;

// Certificate
QSslCertificate Certificate;

// Key
QSslKey SslKey;

protected:

void incomingConnection(int socketDescriptor);

};

#endif




server.cpp


#include "server.h"

// Constructor
NServer::NServer(QObject *parent) : QTcpServer(parent), isTLS(0) {
// Nothing to do in the constructor
}

// All incoming connections are sent to this function
void NServer::incomingConnection(int socketDescriptor){

Thread * thread = new Thread(socketDescriptor, this);
//pool.setMaxThreadCount(10000);

// Set the SSL Cert and Key if TLS is on
if(isTLS){
thread->isTLS = isTLS;
thread->Certificate = Certificate;
thread->SslKey = SslKey;
}

connect(thread->socket, SIGNAL(sslErrors(QList<QSslError>)),
this, SLOT(sslErrors(QList<QSslError>)));

connect(thread, SIGNAL(finished()), thread, SLOT(quit()));
//connect(thread, SIGNAL(finished()), thread, SLOT(test()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));

// Start the thread
thread->start();

}




thread.h




#ifndef THREAD_H
#define THREAD_H

#include <QThread>
#include <QtNetwork>
#include <qdebug.h>

// Thread class
class Thread : public QThread
{
Q_OBJECT

public:

// Constructor
Thread(int socketDescriptor, QObject *parent);

// This is the function which is called on a new connection
void run();

// Is it in TLS MODE ?
int isTLS;

// Certificate
QSslCertificate Certificate;

// Key
QSslKey SslKey;

// A socket pointer to the SOCKET so that we can use it to do stuff
QSslSocket * socket;

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

private:

// The Socket Descriptor
int socketDescriptor;

// If the output has started then no header should be allowed
bool outputStarted;

// SSL Socket Connection start
bool SSLSocket();

// TCP Socket Connection start
bool NormalSocket();

// Write Data to the socket
bool socketWrite(const QByteArray &text);

// Write a header
bool header(const QByteArray &text);

// Write the BODY
bool write(const QByteArray &text);

// Write the given BODY and Close the SOCKET as well.
bool end(const QByteArray &text);

// Closes the socket
void cleanup();

public slots:

// Closes the socket
void sslErrors(const QList<QSslError> &errors);

// SSL Socket Connection start
void handshakeComplete();

void test();

};

#endif



thread.cpp



#include "thread.h"

extern quint64 Served;

// Constructor
Thread::Thread(int socketDescriptor, QObject *parent)
: QThread(parent), socketDescriptor(socketDescriptor)
{
this->isTLS = 0;
this->setTerminationEnabled(true);

qDebug() << "Socket" << socketDescriptor;

//qDebug()<< QThread::idealThreadCount();
}

void Thread::test(){
qDebug() << "Finished";
}

// When its a normal connection
bool Thread::NormalSocket(){

// Create the Socket
socket = new QSslSocket;

if (!socket->setSocketDescriptor(socketDescriptor)) {
emit error(socket->error());
delete socket;
return false;
}

//qDebug() << "In Normal Socket";

return true;

}

// SSL Connection hence SSL Socket
bool Thread::SSLSocket(){

// Create the Socket
socket = new QSslSocket;

//qDebug() << Certificate;
//qDebug() << SslKey;

connect(socket, SIGNAL(encrypted()),
this, SLOT(handshakeComplete()));

connect(socket, SIGNAL(sslErrors(QList<QSslError>)),
this, SLOT(sslErrors(QList<QSslError>)));

// Set the protocol and certificates
socket->setProtocol(QSsl::AnyProtocol);
socket->setLocalCertificate(Certificate);
socket->setPrivateKey(SslKey);

// Set the descriptor
if (!socket->setSocketDescriptor(socketDescriptor)) {
emit error(socket->error());
delete socket;
return false;
}

// No need to verify the peer
socket->setPeerVerifyMode(QSslSocket::VerifyNone);

// Start the encryption
socket->startServerEncryption();

// Wait for the excryption to start
socket->waitForEncrypted(1000);

return true;

}

// Called when the thread is started
void Thread::run(){

//this->exec();

Served++;
qDebug() << "Num Req : " << Served;

// SSL Socket Start
if(isTLS > 0){

qDebug() << "SSL";
if(!SSLSocket()){
return;
}

// Normal Socket
}else{

if(!NormalSocket()){
return;
}

}

// Wait for it to become read ready for a MAX of 1 second
socket->waitForReadyRead(1000);
//qDebug()<<"Wait";
QByteArray req;
QMap<QByteArray, QByteArray> headers;
QByteArray reqType;

//qDebug() << "Enc : " << socket->isEncrypted();
//qDebug() << "Errors : " << socket->sslErrors();

/////////////////////////////////////
// Determine its an DATA / HTTP Call
/////////////////////////////////////

// Get the Request Header
req = socket->read(81920);
//req = socket->readAll();
//qDebug() << "Req : " << req;

QByteArray block = "HTTP/1.0 200 Ok\r\n"
"Content-Type: text/html; charset=\"utf-8\"\r\n"
"\r\n"
"<form method=\"post\" action=\"\" name=\"input\">"
"<input type=\"text\" value=\"test\">"
"<input type=\"submit\" value=\"test\">"
"</form>\n"
"Test"
"\r\n"
"\r\n"
"\r\n";

this->write(block);

/*unsigned char * test = (unsigned char *)malloc(134217728);
this->sleep(10);
free(test);*/

//this->write(block);

cleanup();

//delete socket;

//emit finished();

}

bool Thread::socketWrite(const QByteArray &text){

socket->write(text);

return socket->flush();

}

void Thread::cleanup(){

socket->disconnectFromHost();
if(socket->state() != QAbstractSocket::UnconnectedState){
socket->waitForDisconnected();
}

//qDebug() << "Cleaned";

// Clear the socket
delete socket;

// Delete the thread
//this->exit(0);
//this->wait();
//this->deleteLater();

}

bool Thread::header(const QByteArray &text){

if(outputStarted){
return false;
}

return this->socketWrite(text);

}


bool Thread::write(const QByteArray &text){

// Set that output has been started
outputStarted = true;

return this->socketWrite(text);

}


bool Thread::end(const QByteArray &text){

// Set that output has been started
outputStarted = true;

return this->socketWrite(text);

}

// Called when there are SSL errors
void Thread::sslErrors(const QList<QSslError> &errors){
foreach (const QSslError &error, errors){
qDebug() << "[Backup::sslErrors] " << error.errorString();
}

// Ignore Errors
//socket->ignoreSslErrors();
}

// SSL handshake complete
void Thread::handshakeComplete(){
qDebug() << "[Thread::handshakeComplete]";
}



When I run this application I get an error as follows :

QObject::connect: Cannot queue arguments of type 'QList<QSslError>'
(Make sure 'QList<QSslError>' is registered using qRegisterMetaType().)

I am not able to understand why is this so ?
What is the corrective code for this ?

wysota
30th May 2013, 12:09
What is the corrective code for this ?

The correct code is to delete all your threaded code and handle the socket in the main thread.

micgooapp
30th May 2013, 13:52
The issue in doing that is that sometimes when a reques is coming, it might be blocking because there will be a socket read and write like the FTP Protocol.
Hence I used the Qthread class.

Lets say a request comes with the header "PROTOTRANS"
In that case I will need to send writes, wait for confirmation, send writes again and finish.

Please let me know what have i done wrong in the QThread class. Really appreciate your help.

wysota
30th May 2013, 14:23
The issue in doing that is that sometimes when a reques is coming, it might be blocking because there will be a socket read and write like the FTP Protocol.
No, it won't be blocking.


Hence I used the Qthread class.
Don't. Threads with Qt Networking are Evil.


Lets say a request comes with the header "PROTOTRANS"
In that case I will need to send writes, wait for confirmation, send writes again and finish.
Great. You don't need threads for that.


Please let me know what have i done wrong in the QThread class. Really appreciate your help.
It's all wrong. First of all you are creating the socket in a wrong thread. I really suggest you discard the use of threads, you don't need them.

micgooapp
30th May 2013, 14:41
>> It's all wrong. First of all you are creating the socket in a wrong thread. I really suggest you discard the use of threads, you don't need them.

Means I should create the socket in the main thread and pass it to the thread ?
Also is the moving to thread a better option :
http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/

I will try to avoid the threads and use the non-blocking mode.
Its just that in the non-blocking mode you need to create a lot of functions and connect them to a signal as a slot.

I would still like to solve this error just to improve my knowledge what am I doing wrong. Earlier you said that the connect should have happened in the run() function. Here the ssl errors are being connected in the run() function.

Is there any guide to understand this ?

wysota
30th May 2013, 15:48
Means I should create the socket in the main thread and pass it to the thread ?
On the contrary. But I might have not read your code carefully enough. If you are creating the socket in the run method of the thread then that's ok (but in that case you are trying to setup a signal-slot connection to an object that doesn't exist yet -- that is the "socket" variable in your Thread class). However, again, you're just making your code needlessly complex by using threads.

micgooapp
30th May 2013, 19:05
>> If you are creating the socket in the run method of the thread then that's ok (but in that case you are trying to setup a signal-slot connection to an object that doesn't exist yet -- that is the "socket" variable in your Thread class).

I am defining the Socket in the header first and then calling the connect().
Also I do set the socketDescriptor first as well.
Also the error (QObject::connect: Cannot queue arguments of type 'QList<QSslError>') is shown when I visit the browser with port 1781 in https. It does successfully serve the page.

wysota
30th May 2013, 19:33
I am defining the Socket in the header first and then calling the connect().
You are only declaring a variable but not initializing it.


Also the error (QObject::connect: Cannot queue arguments of type 'QList<QSslError>') is shown when I visit the browser with port 1781 in https. It does successfully serve the page.
Which means you are connecting a signal in thread A to a slot in thread B. Which doesn't make much sense for this particular signal.


Remember the QThread object does not live in the thread it represents but rather in the thread that created it (the object).