免责声明:我对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中。
无论如何,任何帮助将不胜感激!我的代码在下面..
// 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的解释或指针,我学得最好。谢谢!
答案 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()
,如果该事件在先前创建的套接字上接收到某些内容,则相同的线程将回送接收的数据并关闭连接。 它永远不会阻塞等待数据到达的单个连接,也不会阻止等待新连接到达。在本节中,我将解释为什么线程对您的执行方式不起作用:
您应该不在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)是不建议做的事情。