您的问题几乎完全源于重新实现QThread
. 不要这样做。把你所有的功能放到一个QObject
中,然后QThread
用moveToThread()
. 如果您仅通过信号槽连接从外部访问您的对象,那么您将立即完成。
首先,我总是将您的某些实例TelnetConnection
称为 as telnetThread
。这只是为了明确我在说什么线程。
到目前为止,您显示的代码中的错误是:
您是emit error(tcpSocketPtr->error())
从run()
方法中调用的。它是从 调用的telnetThread
,与信号所在的线程不同:QObject
它位于telnetThread->thread()
.
该方法正在线程run()
内执行。telnetThread
但是,由 moc 生成的信号实现预计将在您实例化的任何线程中调用QThread
- 即telnetThread->thread()
,该线程永远不会等于执行的线程run()
。基本上,有点令人困惑的是,以下不变量成立:
QThread * telnetThread ...
Q_ASSERT(telnetThread != telnetThread->thread());
您正在从在另一个线程中执行的插槽中调用方法tcpSocketPtr
,居住在中。telnetThread
以下成立:
Q_ASSERT(tcpSocketPtr->thread() == telnetThread);
您声明的所有插槽telnetThread
都在与自身不同的线程中执行telnetThread
!因此, 的主体disconnectClient
在 GUI 线程中执行,但它直接在tcpSocketPtr
.
以下是一种方法。它侦听端口 8023。^D 结束连接。接收大写字母Q
后跟 Enter/Return 将彻底关闭服务器。
介绍
注意:此示例已被重构,并且最后一个服务器现在已正确删除。
需要注意确保物品包装干净。请注意,简单quit()
的 running是可以的QCoreApplication
,wrap-up 将自动发生。所以,所有的对象最终都会被销毁和释放,并且不会崩溃。线程和服务器从它们的析构函数向控制台发出诊断消息。这样很明显,事情确实被删除了。
该代码同时支持 Qt 4 和 Qt 5。
停止线程
将缺失的行为添加到QThread
. 通常,当您破坏正在运行的线程时,您会收到一条警告消息和崩溃/未定义的行为。这个类在销毁时告诉线程的事件循环退出并等待线程实际完成。它的使用方式与以往一样QThread
,只是它在破坏时不会做傻事。
ThreadedQObjectDeleter
QObject
当线程被销毁时删除给定的。当线程在逻辑上拥有其对象时很有用。这种逻辑所有权不是父子所有权,因为线程和逻辑拥有的对象存在于不同的线程中(!)。
构造函数是私有的,并且提供了工厂方法。这是为了强制在免费存储(又名堆)上创建删除器。在堆栈上创建删除器可能会出错,因此此模式使用编译器来防止它发生。
该对象必须尚未移动到指定的线程,否则删除器的构造将受制于竞争条件 - 对象可能已经在线程中删除了自己。这个前提条件成立。
服务器工厂
newConnection
在调用其插槽时生成一个新的服务器实例。构造函数被传递给QMetaObject
客户端QObject
来创建。因此,此类可以构建所需的“任何” QObject
,而无需使用模板。它创建的对象只有一个要求:
它必须有一个Q_INVOKABLE
以 aQTcpSocket*
作为第一个参数和QObject *parent
第二个参数的构造函数。它生成的对象是在 parent 设置为 的情况下创建的nullptr
。
套接字的所有权转移到服务器。
线程服务器工厂
为每个制作的服务器创建一个专用的 StoppingThread,并将服务器移动到该线程。否则表现得像 ServerFactory。线程归工厂所有,在工厂被毁坏时妥善处理。
服务器的终止退出线程的事件循环,从而结束线程。完成的线程被删除。在服务器终止之前被破坏的线程将删除现在悬空的服务器。
远程登录服务器
实现一个简单的 telnet 服务器。该接口由一个可调用的构造函数组成。构造函数获取要使用的套接字并将套接字连接到内部插槽。功能非常简单,该类只对套接字做出反应readyRead
并disconnected
发出信号。断开连接后,它会自行删除。
这不是一个真正的 telnet 服务器,因为 telnet 协议不是那么简单。碰巧 telnet 客户端将与这些愚蠢的服务器一起工作。
主要的()
主要功能创建一个服务器,一个服务器工厂,并将它们连接在一起。然后它告诉服务器监听任何地址上的连接,端口 8023,并启动主线程的事件循环。监听服务器和工厂位于主线程中,但所有服务器都位于自己的线程中,正如您在查看欢迎消息时很容易看到的那样。支持任意数量的服务器。
#include <QCoreApplication>
#include <QThread>
#include <QTcpServer>
#include <QTcpSocket>
#include <QAbstractEventDispatcher>
#include <QPointer>
#if QT_VERSION < QT_VERSION_CHECK(5,0,0)
#define Q_DECL_OVERRIDE override
#endif
// A QThread that quits its event loop upon destruction,
// and waits for the loop to finish.
class StoppingThread : public QThread {
Q_OBJECT
public:
StoppingThread(QObject * parent = 0) : QThread(parent) {}
~StoppingThread() { quit(); wait(); qDebug() << this; }
};
// Deletes an object living in a thread upon thread's termination.
class ThreadedQObjectDeleter : public QObject {
Q_OBJECT
QPointer<QObject> m_object;
ThreadedQObjectDeleter(QObject * object, QThread * thread) :
QObject(thread), m_object(object) {}
~ThreadedQObjectDeleter() {
if (m_object && m_object->thread() == 0) {
delete m_object;
}
}
public:
static void addDeleter(QObject * object, QThread * thread) {
// The object must not be in the thread yet, otherwise we'd have
// a race condition.
Q_ASSERT(thread != object->thread());
new ThreadedQObjectDeleter(object, thread);
}
};
// Creates servers whenever the listening server gets a new connection
class ServerFactory : public QObject {
Q_OBJECT
QMetaObject m_server;
public:
ServerFactory(const QMetaObject & client, QObject * parent = 0) :
QObject(parent), m_server(client) {}
Q_SLOT void newConnection() {
QTcpServer * listeningServer = qobject_cast<QTcpServer*>(sender());
if (!listeningServer) return;
QTcpSocket * socket = listeningServer->nextPendingConnection();
if (!socket) return;
makeServerFor(socket);
}
protected:
virtual QObject * makeServerFor(QTcpSocket * socket) {
QObject * server = m_server.newInstance(Q_ARG(QTcpSocket*, socket), Q_ARG(QObject*, 0));
socket->setParent(server);
return server;
}
};
// A server factory that makes servers in individual threads.
// The threads automatically delete itselves upon finishing.
// Destructing the thread also deletes the server.
class ThreadedServerFactory : public ServerFactory {
Q_OBJECT
public:
ThreadedServerFactory(const QMetaObject & client, QObject * parent = 0) :
ServerFactory(client, parent) {}
protected:
QObject * makeServerFor(QTcpSocket * socket) Q_DECL_OVERRIDE {
QObject * server = ServerFactory::makeServerFor(socket);
QThread * thread = new StoppingThread(this);
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
connect(server, SIGNAL(destroyed()), thread, SLOT(quit()));
ThreadedQObjectDeleter::addDeleter(server, thread);
server->moveToThread(thread);
thread->start();
return server;
}
};
// A telnet server with following functionality:
// 1. It echoes everything it receives,
// 2. It shows a smiley face upon receiving CR,
// 3. It quits the server upon ^C
// 4. It disconnects upon receiving 'Q'
class TelnetServer : public QObject {
Q_OBJECT
QTcpSocket * m_socket;
bool m_firstInput;
Q_SLOT void readyRead() {
const QByteArray data = m_socket->readAll();
if (m_firstInput) {
QTextStream out(m_socket);
out << "Welcome from thread " << thread() << endl;
m_firstInput = false;
}
for (int i = 0; i < data.length(); ++ i) {
char c = data[i];
if (c == '\004') /* ^D */ { m_socket->close(); break; }
if (c == 'Q') { QCoreApplication::exit(0); break; }
m_socket->putChar(c);
if (c == '\r') m_socket->write("\r\n:)", 4);
}
m_socket->flush();
}
public:
Q_INVOKABLE TelnetServer(QTcpSocket * socket, QObject * parent = 0) :
QObject(parent), m_socket(socket), m_firstInput(true)
{
connect(m_socket, SIGNAL(readyRead()), SLOT(readyRead()));
connect(m_socket, SIGNAL(disconnected()), SLOT(deleteLater()));
}
~TelnetServer() { qDebug() << this; }
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QTcpServer server;
ThreadedServerFactory factory(TelnetServer::staticMetaObject);
factory.connect(&server, SIGNAL(newConnection()), SLOT(newConnection()));
server.listen(QHostAddress::Any, 8023);
return a.exec();
}
#include "main.moc"