C++ 如何在Qt中创建线程网络服务器?

C++ 如何在Qt中创建线程网络服务器?,c++,qt,sockets,destructor,C++,Qt,Sockets,Destructor,我在一个线程化的telnet服务器上工作(每个连接一个线程),不知道如何消除valgrind错误。我已将问题缩小到删除tcpsocket的位置 我在QThread的run()方法中创建QTcpSocket: void TelnetConnection::run() { tcpSocketPtr = new QTcpSocket(); if (!tcpSocketPtr->setSocketDescriptor(socketDescriptor)) { e

我在一个线程化的telnet服务器上工作(每个连接一个线程),不知道如何消除valgrind错误。我已将问题缩小到删除tcpsocket的位置

我在QThread的run()方法中创建QTcpSocket:

void TelnetConnection::run()
{
    tcpSocketPtr = new QTcpSocket();  
    if (!tcpSocketPtr->setSocketDescriptor(socketDescriptor)) {
        emit error(tcpSocketPtr->error());
        return;
    }
    ....
}
当我的应用程序要断开客户端连接时,我会呼叫:

void TelnetConnection::disconnectClient()
{
    tcpSocketPtr->disconnectFromHost();
}
被称为插座断开的插槽为: void TelnetConnection::clientDisconnected()

所以,我试过了 1.删除clientDisconnected插槽中的QTcpSocket,但这会导致读/写不稳定。(偶尔撞车) 2.稍后在clientDisconnected插槽中删除,但这会导致内存读/写错误 3.在线程的exec循环后删除,但仍会导致内存读/写错误 4.在线程的exec循环之后删除,并且所有错误都消失了

从我所读到的,deletelater如果在exec循环终止后调用,将在线程被删除时运行。因此,虽然这是可行的,但我认为这不是正确的方法

我尝试创建以“this”作为父项的QTcpSocket,但由于父项与此不匹配错误,我的信号连接失败。(这将允许在螺纹破坏时删除QTcpSocket)


解决这个问题的正确方法是什么?

您的问题几乎完全源于重新实现
QThread
。不要这样做。将所有功能放入
QObject
,然后使用
moveToThread()
将其移动到裸
QThread
。如果您仅通过信号插槽连接从外部访问对象,那么您将立即完成

首先,我总是将您的
TelnetConnection
的一些实例称为
telnethread
。这只是为了让大家明白我在说什么

到目前为止,您显示的代码中的错误包括:

  • 您正在从
    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
    ,然后输入/返回将彻底关闭服务器

    导言

    注意:此示例已被重构,最后一个服务器现在已正确删除

    在一定程度上注意确保物品包装干净。请注意,只需退出(正在运行的
    QCoreApplication
    ,就可以了,这样会自动进行总结。因此,所有对象最终都会被破坏和释放,没有任何东西会崩溃。线程和服务器从其析构函数向控制台发出诊断消息。通过这种方式,很明显事情确实会被删除

    该代码同时支持Qt4和Qt5

    停止线程

    将缺少的行为添加到
    QThread
    。通常,当您销毁正在运行的线程时,会收到警告消息和崩溃/未定义的行为。该类在销毁时会通知线程的事件循环退出,并等待线程实际完成。它的使用方式与
    QThread
    一样,只是它不会在销毁时做愚蠢的事情

    线程化QoObjectDeleter

    当给定的
    QObject的线程被销毁时,删除该对象。当线程逻辑上拥有其对象时非常有用。此逻辑所有权不是父子所有权,因为线程和逻辑所有权对象位于不同的线程中(!)

    构造函数是私有的,并且提供了工厂方法。这是为了强制在空闲存储(也称为堆)上创建删除程序。在堆栈上生成deleter可能是一个错误,因此此模式使用编译器来防止这种情况发生

    对象必须尚未移动到指定的线程,否则删除程序的构造将受到竞争条件的约束-对象可能已经在线程中删除了自身。这一先决条件是肯定的

    服务器工厂

    在调用其
    newConnection
    插槽时生成新的服务器实例。构造函数被传递给客户机
    QObject
    QMetaObject
    来创建。因此,该类可以构造“任意”所需的
    QObject
    ,而无需使用模板。对其创建的对象只有一个要求:

    它必须有一个
    Q\u可调用的
    构造函数,将
    qtcsocket*
    作为第一个参数,将
    QObject*parent
    作为第二个参数。它生成的对象是在父对象设置为
    nullptr
    的情况下创建的

    套接字的所有权转移到服务器

    ThreadedServerFactory

    为每个服务器创建一个专用的StoppingThread,并将服务器移动到此线程。否则,其行为类似于ServerFactory。这个
    Q_ASSERT(tcpSocketPtr->thread() == telnetThread);
    
    #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"