(QNativeSocketEngine)QObject:无法为位于不同线程

时间:2016-03-16 13:22:27

标签: c++ multithreading qt network-programming signals-slots

我遇到了一个问题,这里有几次类似情况,但是我找不到这些主题的解决方案。

我有一个主要课程,我想通过qt-network支持扩展它,这是一个额外的课程。让我打破源代码,找到相关部分:

主要课程

·H

class MainClass: public QObject{
    Q_OBJECT
public:
[...]
private:
    NetworkSupport * netSupport;
};

的.cpp

MainClass::MainClass()
{
    [...]
    netSupport
    netSupport = new NetworkSupport(this->thread());
    netSupport->start();
    [...]
}

网络课程

·H

class NetworkSupport : public QThread
{
    Q_OBJECT
public:
    NetworkSupport(QThread *mainThread);
    ~NetworkSupport();

    QByteArray readData();
    void sendData(QByteArray *msg);

public slots:
    void acceptConnection();
    void receive();
    void run();

private:
    QByteArray curMessage;
    QThread* libThread;
    QEventLoop initWlan;

protected:
    QTcpServer server;
    QTcpSocket* client;
};

的.cpp

NetworkSupport::NetworkSupport(QThread* mainThread)
{
    libThread = mainThread;
    server.moveToThreaD(libThread);
    server.listen(QHostAddress::Any, 5200);

    QMetaObject::invokeMethode(&initWlan, "quit", Qt:QueuedConnection);
    initWlan.exec();
}

void NetworkSupport::run(){
    connect(&server, SIGNAL(newConnection()), this, SLOT(acceptConnection()));
}

void NetworkSupport::acceptConnection()
{
    client = server.nextPendingConnection();
    client->moveToThread(libThread);

    if (client->state() == QAbstractSocket::ConnectedState)
        connect(client, SIGNAL(readyRead()), this, SLOT(receive()));

}

void NetworkSupport::receive()
{
    curMessage = client->readAll();
}

void NetworkSupport::sendData(QByteArray* msg)
{
    if(client->state() == QAbstractSocket::ConnectedState)
    {
        client->moveToThread(libThread);
        printf("Sending a message %s\n",msg->data());
        client->write(*msg);
        client->waitForBytesWritten();
    }
}

我知道我通常不需要这么详细地指定moveToThread(),但如果它们全部存在或被移除,它最终不会改变它。

然而,在执行时,我在client->write(*msg)的{​​{1}}收到错误消息,即:

sendData()

它看起来像一个警告,因为程序之后仍在执行,但我没有收到来自客户端的任何消息(即[...] Sending a message ?r QObject: Cannot create children for a parent that is in a different thread. (Parent is QnativeSocketEngine(0x2afc660), parent's thread is QThread(0x1b59210), current thread is QThread(0x7f6a70026b60) QSocketNotifier: Can only be used with threads started with QThread [...] 永远不会被执行),这是因为我猜错误消息的最后一行。

可能是这个错误信息只是误导,如果是这样,它的实际意义是什么,还是我做了一些完全错误的事情?

2 个答案:

答案 0 :(得分:1)

您的代码中存在一些误解,如Qt网络和多线程如何工作。

首先,在Qt文档中,他们在QTcpServer::nextPendingConnection()上说:

  

无法从其他线程使用返回的QTcpSocket对象。如果   你想要使用另一个线程的传入连接   覆盖incomingConnection()。

因此,您必须创建自己的Server类,该类继承自QTcpServer,并在那里覆盖incomingConnection()。在该重写方法中,您必须将套接字描述符发布到TCP客户端将处理该连接的其他线程。在这个简单的例子中,我们只发出一个信号。

signals:

   void myNewConnectionSignal(DescriptorType);

protected:

  void incomingConnection(DescriptorType descriptor)
  {
      emit myNewConnectionSignal(descriptor);
  }

其次,NetworkSupport线程是什么?我想,您希望您的服务器对象能够在那里生活和工作吗?如果是这样,那么你必须以其他方式重写它。以下是仅服务器部分。注意,QThread已经有一个QEventLoop,您可以通过run()中的exec()来使用它。

...
protected:
    MyServer *server;
...

NetworkSupport::NetworkSupport()
{
    // this is called in the main thread, so "this" lives in main thread
    server=0;
    client=0;
    // say thread to start, but it will be actually started within run()
    start();
    // sometimes it is recommended to wait here for the thread started
}

NetworkSupport::~NetworkSupport()
{
    // this is called in the main thread, say this thread to stop running
    quit();
    // wait for completion
    wait();
}

void NetworkSupport::run()
{
    // this is called in server thread
    server=new MyServer();
    if (server->listen(QHostAddress::Any, 5200))
    {
      // connect our new signal to slot where we create and init the TCP client, note that Qt::QueuedConnection is used because we want acceptConnection to run in the main thread
      connect(server, SIGNAL(myNewConnectionSignal(DescriptorType)), MyClientsPool, SLOT(acceptConnection(DescriptorType)), Qt::QueuedConnection);
      // go for infinite event loop
      exec();
    }
    else
    {
       // do you always ignore errors?
    }
    // the thread is stopped, let's destroy the server in the thread where it was born
    delete server;
    server=0;
}

客户在您的主要线程中生活和工作。您不能直接从其他线程调用其方法。 NetworkSupport也存在于主线程中:是的,它封装并管理其他一些线程,但作为一个QObject本身,它存在于我们创建它的线程中。下面的方法总是在主线程中执行因为我们连接服务器使用Qt :: QueuedConnection向NetworkSupport :: acceptConnection()发出信号,该Qt :: QueuedConnection告诉Qt我们希望在其QObject所在的线程中调用该插槽。

   private slots:

      void socketDisconnected();

...

void NetworkSupport::socketDisconnected()
{
    if (client)
    {
       client->deleteLater();
       client=0;
    }
}

void NetworkSupport::acceptConnection(DescriptorType descriptor)
{
    QTcpSocket* client=new QTcpSocket(this);
    client->setSocketDescriptor(descriptor);
    connect(client,SIGNAL(disconnected(),this,SLOT(socketDisconnected());
    connect(client,SIGNAL(readyRead(),this,SLOT(receive());
}

void NetworkSupport::receive()
{
    if (client)
      curMessage = client->readAll();
}

void NetworkSupport::sendData(QByteArray* msg)
{
    if (client)
    {
        client->write(*msg);
        client->waitForBytesWritten();
    }
}

<强>更新

如果我们只想隐藏线程内的所有网络工作。请注意,在下面的示例中,几乎没有错误处理和大量消息数据副本。您可能希望对其进行优化以进行生产。

// private container class incapsulated and working in the thread
class NetworkThreadContainer : public QObject
{
     Q_OBJECT

     public:

        NetworkThreadContainer(QObject* parent=0):QObject(parent),client(0)
        {
           if (server.listen(QHostAddress::Any, 5200))
            {
              connect(&server, SIGNAL(newConnection()), this, acceptConnection());
            }
            else
            {
                // don't you want to throw exception here?
            }        
        }

     public slots:

         void sendDataToClient(const QByteArray& barr)
         {
               if (client)
               {
                   client->write(msg);
                   client->waitForBytesWritten();
               }
         }

        void acceptConnection()
        {
            if (!client)
            {
              client = server.nextPendingConnection();
              connect(client, SIGNAL(readyRead()), this, SLOT(receive()));            
            }
            else
            {
              // what will you do with more than one client connections or reconnections?
            }        
        }

       void NetworkSupport::receive()
       {
           if (client)
           {
              QByteArray curMessage = client->readAll();
              emit messageReceived(curMessage);
           }
       }

      signals:

         void messageReceived(const QByteArray&);

      public:

        QTcpClient* client;
        QTcpServer server;
};

// thread class visible to outer program
class NetworkThread : public QThread
{
     Q_OBJECT

     public:

        NetworkThread(QObject* parent=0):QObject(parent)
        {
           start();
        }

        ~NetworkThread()
        {
           quit();
           wait();
        }

         bool sendDataToClient(QByteArray* barr)
         {
              bool ok=false;
              // async send data to container's thread
              mutex.lock();
              if (container)
              {
                ok=true;
                QMetaObject::invokeMethod(
                    container,
                    "sendDataToClient",
                    Qt::QueuedConnection,
                    Q_ARG(QByteArray, *barr)
                );                             
              }
              else
              {
                 // container either is not ready yet or already destroyed 
              }
              mutex.unlock();
              return ok;
         }    

     signals:

        void messageReceived(const QByteArray&);

     protected:

        void run()
        {
            mutex.lock();
            // I would suggest to catch exception here and assign the error result to some variable for further processing
            container=new NetworkThreadContainer();
            mutex.unlock();
            connect(container,SIGNAL(messageReceived(QByteArray),this,SIGNAL(messageReceived(QByteArray)),Qt::QueuedConnection);
            exec();
            mutex.lock();    
            delete container;
            mutex.unlock();
        }   

     private:

        QMutex mutex;
        QPointer<NetworkThreadContainer> container;   
};

答案 1 :(得分:1)

你在这段代码中做了很多错事,我不知道该从什么开始。

NetworkSupport::NetworkSupport(QThread* mainThread)
{
    libThread = mainThread;
    server.moveToThreaD(libThread);

这无济于事。 server已与MainClass实例

位于同一个线程中
    server.listen(QHostAddress::Any, 5200);

服务器将开始侦听与MainClass相同的线程

    QMetaObject::invokeMethode(&initWlan, "quit", Qt:QueuedConnection);
    initWlan.exec();

这让我嘲笑了很多。这将简单地启动事件循环并几乎立即退出。

}

void NetworkSupport::run(){
    connect(&server, SIGNAL(newConnection()), this, SLOT(acceptConnection()));
}

这将简单地在新线程中运行,在connect语句后立即调用connect并退出线程 - 您的线程将不再在connect之后运行。插槽acceptConnection也可能在与MainClass相同的线程中调用。我想知道MainClass创建的时间和地点。

对我来说似乎就像你在同时挣扎于很多事情一样。您可能读过somwhere,您应该使用单独的线程进行网络通信,因此您不会阻止其他线程。首先尝试单线程方法。当你开始工作时,你应该考虑如何利用其他线程来满足你的需求。

Bonus问题:这个代码是否是某种可能没有Qt eventloop的应用程序的插件?或者它是全栈Qt应用程序的一部分?