首页 文章

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

提问于
浏览
2

我正在使用线程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();
}

并且在socket断开连接时调用的插槽是:void TelnetConnection :: clientDisconnected()

{
    tcpSocketPtr->deleteLater();  
    TelnetConnection::s_clientCount--;
    QThread::quit();  // Exit ths event loop for this thread
}

所以,我试过1.删除clientDisconnected插槽中的QTcpSocket,但这会导致读/写不稳定 . (偶然崩溃)2 . 在clientDisconnected插槽中删除,但导致内存读/写错误3.在线程的exec循环后删除但仍然导致内存读/写错误4.在线程的exec循环后删除 - 并且全部错误消失了 .

从我读到的内容,如果在exec循环终止后调用deletelater,则会在删除线程时运行 . 因此,虽然这有效,但我认为这不是正确的方法 .

我尝试使用“this”作为父级创建QTcpSocket,但由于父级vs这种不匹配错误,我的信号连接失败 . (这将允许在线程销毁时删除QTcpSocket) .

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

1 回答

  • 5

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

    首先,我总是将 TelnetConnection 的一些实例称为 telnetThread . 这只是为了让我明白我在谈论什么线程 .

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

    • 您正在 run() 方法中调用 emit error(tcpSocketPtr->error()) . 它是从 telnetThread 调用的,与信号所在的 QObject 不同:它位于 telnetThread->thread() .

    run() 方法在 telnetThread 线程内执行 . 但是由moc生成的信号的实现预计会在你实例化的任何线程中调用 QThread ,即 telnetThread->thread() ,并且该线程 can never be equal 将被执行 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将干净地关闭服务器 .

    Introduction

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

    我们需要注意确保事情干净利落 . 注意,只需 quit() 正在运行 QCoreApplication 就可以了,总结会自动发生 . 因此,所有对象最终都被破坏和释放,没有任何东西应该崩溃 . 线程和服务器从析构函数向控制台发出诊断消息 . 这种方式显然会被删除 .

    该代码支持Qt 4和Qt 5 .

    StoppingThread

    QThread 添加缺少的行为 . 通常,当您破坏正在运行的线程时,会收到警告消息和崩溃/未定义的行为 . 这个类在被破坏时告诉线程's event loop to quit and waits for the thread to actually finish. It'就像 QThread 一样,除了它在破坏时不会做愚蠢的事情 .

    ThreadedQObjectDeleter

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

    构造函数是私有的,并提供了工厂方法 . 这是为了在免费商店(例如堆)上强制创建删除器 . 在堆栈上创建删除器可能是一个错误,因此该模式使用编译器来防止它发生 .

    该对象尚未被移动到指定的线程,否则删除器的构造将受到竞争条件的影响 - 该对象可能已经在线程内自行删除 . 这个先决条件是断言 .

    ServerFactory

    在调用 newConnection 插槽时生成新的服务器实例 . 构造函数传递给客户端 QObjectQMetaObject 来创建 . 因此,这个类可以构造"any" desired QObject 而无需使用模板 . 它创建的对象只有一个要求:

    它必须有 Q_INVOKABLE 构造函数,第一个参数为 QTcpSocket* ,第二个参数为 QObject *parent . 它生成的对象是在父级设置为 nullptr 的情况下创建的 .

    套接字的所有权将传输到服务器 .

    ThreadedServerFactory

    为每个服务器创建一个专用的StoppingThread,并将服务器移动到该线程 . 否则表现得像ServerFactory . 螺纹由工厂拥有,并在工厂被破坏时妥善处理 .

    服务器的终止退出线程的事件循环,从而完成线程 . 已完成的线程已删除 . 在终止服务器之前被破坏的线程将删除现在悬空的服务器 .

    TelnetServer

    实现一个简单的telnet服务器 . 该接口由一个可调用的构造函数组成 . 构造函数使用套接字并将套接字连接到内部插槽 . 功能非常简单,类只响应来自套接字的 readyReaddisconnected 信号 . 断开连接后,它会自行删除 .

    这不是真正的telnet服务器,因为telnet协议不是那么简单 . 碰巧telnet客户端将使用这种愚蠢的服务器 .

    main()

    main函数创建服务器,服务器工厂,并将它们连接在一起 . 然后它告诉服务器监听任何地址,端口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"
    

相关问题