免责声明:我对Qt和任何类型的围绕线程和网络的编程都比较陌生。我还采用了Qt Examples,API和其他在线示例中的大量代码。
所有代码都可以在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 中的建议为此设置信号/插槽:
重要的是要记住,QThread 实例位于实例化它的旧线程中,而不是位于调用 run() 的新线程中。这意味着 QThread 的所有排队插槽都将在旧线程中执行。因此,希望在新线程中调用槽的开发人员必须使用工作线程-对象方法;新插槽不应直接在子类化的 QThread 中实现。
无论如何,任何帮助将不胜感激!我的代码在下面。
服务器
服务器线程.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);
}
因此,在调用 echoData 时ServerThread.cpp
,即出现错误并且套接字停止工作的时候。
任何和所有的帮助将不胜感激。我知道还有其他一些关于"不能为..."关于线程。但我发现它们都没有帮助。我确实觉得有趣但不理解的一件事可能是使用 moveToThread(),但对此有很多褒贬不一的评论。
我最好通过代码示例和解释来学习,而不仅仅是解释或指向 API 的指针。谢谢!
大多数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数据库连接只能从创建它的线程中使用,请参阅此处的文档。因此,您必须为应用程序中的每个线程(从而为每个连接)创建一个新的数据库连接(这不仅仅是矫枉过正),或者稍后切换到单线程设计。
在本节中,我将解释为什么线程不适合您的工作方式:
您不应该在QThread
子类中声明槽,而是使用workerQObject
s并根据需要将它们移动到QThread
s。
您在问题中提供的报价是您收到此警告的原因的确切解释。您创建的ServerThread
实例将位于主线程(或创建它的任何线程)中。现在让我们考虑代码中的这一行:
connect(clientSocket, SIGNAL(readyRead()), this, SLOT(readyRead()));
信号readyRead()
将从当前ServerThread
实例发出(因为发出它的clientSocket
对象位于该实例中),但是,接收器对象是当前ServerThread
实例,但它存在于主线程中。以下是文档的内容:
如果接收器位于发出信号的线程中,则使用Qt::D irectConnection。否则,将使用 Qt::QueuedConnection。
现在,Qt::QueuedConnection
的要点是执行接收器对象线程中的插槽。这意味着,您的插槽ServerThread::readyRead()
,ServerThread::disconnected
将在主线程中执行。这很可能不是您要做的,因为您最终会从主线程访问clientSocket
。之后,任何导致创建子QObject
的clientSocket
调用都将导致您收到的警告(您可以在此处看到QTcpSocket::write()
执行此操作)。
movetothread的混合注释主要与使用它将线程对象移动到自身有关。
这句话暗示了QThread的成员不是为从worker调用而设计的。调用信号的严格正确方法是使用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();
}
};
在 run() 中构造的 worker 是它创建的线程的"属性",所以形象地说,它被奴役到它的上下文中。如果您在其他线程中创建 worker ,然后在建立连接之前将其移动到此线程,可能会达到相同的效果。当您将信号连接到 QThread 本身的插槽时,您将子线程连接到创建它的线程。
用途
connect(&timer, SIGNAL(timeout()), this, SLOT(onTimeout()), Qt::DirectConnection);
或者从线程创建连接有时似乎可以达到正确的结果,但在这种情况下并非如此,您尝试将不同线程中构造的对象一起使用在一起。在构造函数中调用 moveToThread(this) 是不建议这样做的。