您的問題幾乎完全來自QThread
的重新實現。不要這樣做。將所有功能放入QObject
,然後使用moveToThread()
將其移至裸機QThread
。如果您只通過信號插槽連接從外部訪問您的物體,那麼您就可以在那裏完成任務。
首先,我會經常引用TelnetConnection
的某個實例作爲telnetThread
。這只是爲了讓我明白我在說什麼線程。
在代碼中的錯誤,到目前爲止,你已經證明,分別是:
你從run()
方法中調用emit error(tcpSocketPtr->error())
。它的名字來自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將乾淨地關閉服務器。
介紹
注:這個例子被重構,最後服務器現在正確地刪除。
一定要注意確保事情乾淨整潔。請注意,運行QCoreApplication
只需quit()
即可,自動進行總結。所以,所有的對象最終都會被破壞並釋放,而且沒有任何東西會崩潰。線程和服務器從析構函數向控制檯發送診斷消息。這種方式很明顯,事情會被刪除。
代碼支持的Qt 4和Qt 5.
StoppingThread
添加缺少的行爲QThread
。通常,當你破壞一個正在運行的線程時,你會收到一條警告消息和崩潰/未定義的行爲。這個類在銷燬時告訴線程的事件循環退出並等待線程真正完成。它的使用就像QThread
一樣,只是它不會在破壞時做出愚蠢的事情。
ThreadedQObjectDeleter
刪除一個給定的QObject
時,它的線程被破壞。當線程邏輯上擁有它的對象時很有用。這個邏輯所有權是而不是父 - 子所有權,因爲線程和邏輯擁有的對象存在於不同的線程(!)中。
該構造函數是私有的,並提供了一個工廠方法。這是爲了強制在免費商店(又名堆)上創建刪除者。在堆棧中創建刪除器可能是一個錯誤,因此此模式使用編譯器來防止它發生。
該對象一定還沒有被移動到指定的線程,否則刪除器的構造會受競爭條件的影響 - 對象可能已經在線程中自己刪除了。這個先決條件是有效的。
ServerFactory
調用其newConnection
插槽中時,產生一個新的服務器實例。構造函數通過客戶端QObject
的QMetaObject
創建。因此,這個類可以構建「任何」所需的QObject
而不需要使用模板。只有一個對象上的要求,即它創建:
它必須有一個Q_INVOKABLE
構造採取QTcpSocket*
作爲第一個參數,並QObject *parent
作爲第二個參數。它生成的對象是使用父設置爲nullptr
創建的。
套接字的所有權被轉移到服務器。
ThreadedServerFactory
創建用於製作每個服務器專用StoppingThread,服務器移動到這個線程。否則就像ServerFactory一樣。線程由工廠擁有,並在工廠遭到破壞時妥善處理。
服務器的終止會退出線程的事件循環,從而結束線程。完成的線程被刪除。在終止服務器之前被破壞的線程將刪除現在懸掛的服務器。
TelnetServer
實現一個簡單的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"
@Michelle:當然有更簡單的方法來做到這一點,但由於更大的整體類,它們不會正確運行或難以理解。 –
@Michelle:與任何問題一樣,攻擊方法有很多種。其他人肯定會想出一個不同的答案,希望它也會被測試,並在Qt 4和Qt 5上運行完整的代碼:) –
我還在搞清楚......我混亂的一部分是語法太。使用Q_SLOT,我認爲它與類中的「slots:」部分相同,而obj.connect(source,sourcesignal,objslot)...我認爲它與connect相同(source,sourcesignal,obj,objslot )..? – TSG