c++qtqthreadqtcpsocketqtcpserver

QTcpSocket in QThread will commitTransaction but when Write is called "Cannot create children for a parent that is in a different thread."


Disclaimer: I am relatively new to Qt and any type of programming that revolves around Threads and Networking. I have also adopted a lot of code from Qt Examples, API, and other online examples.

All code can be found on GitHub. This code is relatively as simple as it can get minus striping out GUI. I figure supplying it this way would help as well versus just pasting the code below.

I want to use and believe I need to use Threads as I need multiple clients send a request to the server, the server run some SQL code, then spit out the results back to the client (basically deriving a MySQL Server, but specific to what I am doing). Right now though, I am just working on learning the workings of it all.

With all that being said, as the Title states.. My client can connect to the server, the server sets up the thread, and will receive data (a String) through the readReady. After the data is read in, for right now I am just trying to echo it back to the client. It will do this, but only once. Then it spits out:

QObject: Cannot create children for a parent that is in a different thread.
(Parent is QNativeSocketEngine(0x266cca92ea0), parent's thread is serverThread(0x266cca9ed60), current thread is QThread(0x266cac772e0)

I cannot send any further data to the server unless I have the client reconnect, then after the data is sent, it will do its job but then spit out the same error and cease functioning. I have tried quite a bit of different things, but cannot seem to fix the issue. I even tried setting up a SIGNAL/SLOT for this as suggested in API:

It is important to remember that a QThread instance lives in the old thread that instantiated it, not in the new thread that calls run(). This means that all of QThread's queued slots will execute in the old thread. Thus, a developer who wishes to invoke slots in the new thread must use the worker-object approach; new slots should not be implemented directly into a subclassed QThread.

Anyway, any help would be greatly appreciated! My Code is below..

Server

ServerThread.cpp

// Project
#include "ServerDialog.h"
#include "ServerThread.h"

ServerThread::ServerThread(qintptr _socketDiscriptor, QObject *parent /*= 0*/)
    : QThread(parent)
{
    socketDiscriptor = _socketDiscriptor;
}

void ServerThread::run()
{
    emit threadStarted(socketDiscriptor);

    // Start Thread
    clientSocket = new QTcpSocket;

    // Set SocketDisc
    if (!clientSocket->setSocketDescriptor(socketDiscriptor))
    {
        emit error(clientSocket->error());
        return;
    }
    
    // Connect Socket and Signal
    connect(clientSocket, SIGNAL(readyRead()), this, SLOT(readyRead()));
    connect(clientSocket, SIGNAL(disconnected()), this, SLOT(disconnected()));

    //// Loop Thread to Stay Alive for Signals and Slots
    exec();
}

void ServerThread::readyRead()
{
    QDataStream in(clientSocket);
    in.setVersion(QDataStream::Qt_5_7);
    in.startTransaction();
    QString dataReceived;
    in >> dataReceived;
    if (!in.commitTransaction())
    {
        emit readyReadError(socketDiscriptor);
        return;
    }
    emit readyReadMessage(socketDiscriptor, dataReceived);
    echoData(dataReceived);
}

void ServerThread::disconnected()
{
    emit threadStopped(socketDiscriptor);
    clientSocket->disconnect();
    clientSocket->deleteLater();
    this->exit(0);
}

void ServerThread::echoData(QString &data)
{
    QByteArray block;
    QDataStream out(&block, QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_5_7);
    out << data;
    clientSocket->write(block);
}

So in ServerThread.cpp when echoData is called, that is when the error shows up and the Socket ceases functioning.

Any and all help will be appreciated. I know there are a few other posts regarding "Cannot create children for..." in regards to Threads. But I did not find any of them helpful. The one thing that I did find interesting but did not understand was maybe using moveToThread() but a lot of mixed comments on that.

I learn best through code examples along with explanation versus just an explanation or pointer to API. Thank you!


Solution

  • Most of Qt network functions are asynchronous; they do not block the calling thread. There is no need to mess up with threads if you are using QTcpSockets. In fact, creating a thread for every socket is an overkill, since that thread will spend most of its time just waiting for some network operation to finish. Here is how I would implement a single-threaded echo server in Qt:

    #include <QtNetwork>
    #include <QtCore>
    
    //separate class for the protocol's implementation
    class EchoSocket : public QTcpSocket{
        Q_OBJECT
    public:
        explicit EchoSocket(QObject* parent=nullptr):QTcpSocket(parent){
            connect(this, &EchoSocket::readyRead, this, &EchoSocket::EchoBack);
            connect(this, &EchoSocket::disconnected, this, &EchoSocket::deleteLater);
        }
        ~EchoSocket() = default;
    
        Q_SLOT void EchoBack(){
            QByteArray receivedByteArray= readAll();
            write(receivedByteArray);
            disconnectFromHost();
        }
    };
    
    class EchoServer : public QTcpServer{
    public:
        explicit EchoServer(QObject* parent= nullptr):QTcpServer(parent){}
        ~EchoServer() = default;
    
        //override incomingConnection() and nextPendingConnection()
        //to make them deal with EchoSockets instead of QTcpSockets
        void incomingConnection(qintptr socketDescriptor){
            EchoSocket* socket= new EchoSocket(this);
            socket->setSocketDescriptor(socketDescriptor);
            addPendingConnection(qobject_cast<QTcpSocket*>(socket));
        }
    
        EchoSocket* nextPendingConnection(){
            QTcpSocket* ts= QTcpServer::nextPendingConnection();
            return qobject_cast<EchoSocket*>(ts);
        }
    };
    
    int main(int argc, char* argv[]){
        QCoreApplication a(argc, argv);
    
        EchoServer echoServer;
        echoServer.listen(QHostAddress::Any, 9999);
    
        QObject::connect(&echoServer, &EchoServer::newConnection, [&](){
            EchoSocket* socket= echoServer.nextPendingConnection();
            qDebug() << "Got new connection from: " << socket->peerAddress().toString();
        });
    
        return a.exec();
    }
    
    #include "main.moc"
    

    Notes:


    In this section, I am explaining why threading does not work for you the way you are doing it:

    You should not be declaring slots in your QThread subclass, Instead, use worker QObjects and move them to QThreads as needed.

    The quote you have provided in your question is the exact explanation for why you get this warning. The ServerThread instance you create will be living in the main thread (or whatever thread that created it). Now let's consider this line from your code:

    connect(clientSocket, SIGNAL(readyRead()), this, SLOT(readyRead()));
    

    The signal readyRead() will be emitted from the current ServerThread instance (since the clientSocket object that emits it lives there), However, the receiver object is the current ServerThread instance, But that lives in the main thread. Here is what the documentation says:

    If the receiver lives in the thread that emits the signal, Qt::DirectConnection is used. Otherwise, Qt::QueuedConnection is used.

    Now, the main point of Qt::QueuedConnection is executing the slot in the receiver object's thread. This means that, your slots ServerThread::readyRead() and ServerThread::disconnected will get executed in the main thread. This is most likely not what you meant to do, since you'll end up accessing clientSocket from the main thread. After that, any call on clientSocket that results in child QObjects being created will result in the warning you get (you can see that QTcpSocket::write() does this here).