QThread中的QTcpSocket将commitTransaction但是当Write被称为“无法为不同线程中的父级创建子级”时。

时间:2016-12-11 06:57:43

标签: c++ qt qthread qtcpsocket qtcpserver

免责声明:我对Qt以及围绕线程和网络的任何类型的编程都相对较新。我还从Qt示例,API和其他在线示例中采用了大量代码。

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

我想使用并相信我需要使用Threads,因为我需要多个客户端向服务器发送请求,服务器运行一些SQL代码,然后将结果吐出回客户端(基本上派生一个MySQL服务器,但是具体到我在做什么)。但是现在,我正在努力学习这一切的工作原理。

尽管如此,正如标题所述。我的客户端可以连接到服务器,服务器设置线程,并通过readReady接收数据(String)。在读入数据之后,我现在正试图将其回送给客户端。它会这样做,但只有一次。然后吐出来:

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:

  

重要的是要记住,QThread实例存在于实例化它的旧线程中,而不是在调用run()的新线程中。这意味着所有QThread的排队插槽都将在旧线程中执行。因此,希望在新线程中调用插槽的开发人员必须使用工作者 - 对象方法;不应将新插槽直接实现到子类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停止运行时。

任何和所有帮助将不胜感激。我知道还有一些关于“无法为......创建孩子”的帖子关于线程。但我没有发现任何有用的东西。我确实觉得有趣但不明白的一件事可能就是使用moveToThread(),但对此有很多混合评论。

通过代码示例以及解释与API的解释或指针,我学得最好。谢谢!

2 个答案:

答案 0 :(得分:2)

大多数Qt网络功能都是异步的;它们不会阻塞调用线程。 如果您使用QTcpSocket s ,则无需使用线程。事实上,为每个套接字创建一个线程是一种过度杀伤,因为该线程将花费大部分时间来等待一些网络操作完成。以下是我在Qt中实现单线程echo服务器的方法:

#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子类中声明广告位,而是使用工作人员QObject并根据需要将其移至QThread

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

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

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

的内容
  

如果接收器位于发出信号的线程中,则使用Qt :: DirectConnection。 否则,使用Qt :: QueuedConnection

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

答案 1 :(得分:0)

movetothread的混合注释主要与使用它来将线程对象移动到自身。

该引言暗示QThread的成员并非设计为从工作人员调用。调用信号的正确方法是使用工作对象模型,这在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();
    }
};

在run()内部构造的worker是&#34; property&#34;它创造的线程,所以形象地说,它是从属于它的背景。如果在其他线程中创建worker,则可以实现相同的效果,然后在建立连接之前将其移动到此线程。当您将信号连接到QThread本身的插槽时,您将子线程连接到由它创建的线程。

使用

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

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