QThread 中的 QTcpSocket 将 commitTransaction 但是当调用 Write 时 "Cannot create children for a parent that is in a different thread."

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

免责声明:我对 Qt 和围绕线程和网络的任何类型的编程都比较陌生。我也从 Qt Examples,API 和其他在线示例中采用了很多代码。

所有代码都可以找到on GitHub。这段代码相对简单,因为它可以去掉 GUI。我认为以这种方式提供它与仅粘贴下面的代码相比也会有所帮助。

我想使用并且相信我需要使用线程,因为我需要多个客户端向服务器发送请求,服务器 运行 一些 SQL 代码,然后将结果吐回客户端(基本上派生了一个 MySQL 服务器,但具体到我正在做的事情)。不过现在,我只是在努力学习这一切的运作方式。

综上所述,如标题所述..我的客户端可以连接到服务器,服务器设置线程,并将通过 readReady 接收数据(字符串)。读入数据后,现在我只是试图将其回显给客户端。它会这样做,但只有一次。然后吐出来:

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)

我无法向服务器发送任何进一步的数据,除非我让客户端重新连接,然后在发送数据后,它会完成它的工作,但随后吐出同样的错误并停止运行。我尝试了很多不同的东西,但似乎无法解决问题。我什至尝试按照 API:

中的建议为此设置 SIGNAL/SLOT

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.

无论如何,我们将不胜感激!我的代码如下..

服务器

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);
}

因此在 ServerThread.cpp 调用 echoData 时,即出现错误且 Socket 停止运行时。

我们将不胜感激任何帮助。我知道还有一些关于线程的“无法为...创建 children”的其他帖子。但我没有发现它们有任何帮助。我确实觉得有趣但不明白的一件事可能是使用 moveToThread() 但对此有很多不同的评论。

我通过代码示例和解释学得最好,而不是仅仅通过解释或指向 API 的指针。谢谢!

movetothread 的混合评论主要与使用它将线程对象移动到自身有关。

引用暗示 QThread 的成员并非设计为从 worker 调用。调用信号的严格正确方法是使用工作对象模型,这在 Qt 示例中显示并在 QT 相关博客上解释了几次:

class Worker : public QObject
{
    Q_OBJECT
private slots:
    void onTimeout()
    {
        qDebug()<<"Worker::onTimeout get called from?: "<<QThread::currentThreadId();
    }
};

class Thread : public QThread
{
    Q_OBJECT

private:
    void run()
    {
        qDebug()<<"From work thread: "<<currentThreadId();
        QTimer timer;
        Worker worker;
        connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));
        timer.start(1000);

        exec();
    }
};

worker 在 运行() 内部构造的是它创建的线程的 "property",因此形象地说,它从属于其上下文。如果您在其他线程中创建工作程序,然后在建立连接之前将其移动到该线程,则可能会达到相同的效果。当您将信号连接到 QThread 本身的插槽时,您将子线程连接到它创建的线程。

使用

connect(&timer, SIGNAL(timeout()), this, SLOT(onTimeout()), Qt::DirectConnection);

或者从你的线程创建连接有时似乎能获得正确的结果,但在这种情况下,你尝试将在不同线程中构造的对象一起使用。不建议在构造函数中调用 moveToThread(this)。

大部分Qt网络函数都是异步的;他们不会阻塞调用线程。 如果您使用 QTcpSockets,则无需弄乱线程。事实上,为每个套接字创建一个线程是一种矫枉过正,因为该线程将把大部分时间花在等待某些网络操作完成上。下面是我将如何在 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"

备注:

  • 此服务器能够同时处理多个客户端,因为没有阻塞。线程将只响应以适当的操作发生的事件;因此,如果该事件是一个新连接,它将创建一个新的 EchoSocket 对象来处理它并向 qDebug() 打印一条语句,如果该事件正在先前创建的套接字上接收某些内容,则同一个线程将回显接收到的数据并关闭连接。 它永远不会阻塞在等待数据到达的单个连接上,也不会阻塞等待新连接到达
  • 因为您提到在您项目的后期使用一些 SQL 查询来响应某些连接。请避免使用线程,因为 Qt 中的 SQL 数据库连接只能从创建它的线程使用,请参阅 docs here。因此,您必须为应用程序中的每个线程(因此也为每个连接)创建一个新的数据库连接(这不仅仅是矫枉过正),或者稍后切换到单线程设计。

在本节中,我将解释为什么线程不适合您的方式:

您应该在您的 QThread 子类中声明槽,相反,使用 worker QObject 并将它们移动到 QThreads 根据需要.

您在问题中提供的引述是您收到此警告的确切解释。您创建的 ServerThread 实例将存在于主线程(或创建它的任何线程)中。现在让我们从您的代码中考虑这一行:

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

信号readyRead()将从当前ServerThread实例发出(因为发出它的clientSocket对象存在于那里),然而,接收者对象是当前的 ServerThread 实例,但它存在于 主线程 中。这是 documentation says:

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

现在,Qt::QueuedConnection 的要点是在接收器对象的线程中执行插槽。这意味着,您的插槽 ServerThread::readyRead()ServerThread::disconnected 将在主线程中执行 。这很可能不是您的本意,因为您最终将从主线程访问 clientSocket。之后,任何导致创建子 QObjectclientSocket 调用都会导致您收到警告(您可以看到 QTcpSocket::write() 执行此操作 here)。