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

TSG*_*TSG 2 c++ sockets qt destructor

我正在使用线程telnet服务器(每个连接一个线程),并且无法弄清楚如何摆脱valgrind错误.我把问题缩小到了删除tcpsocket的地方.

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

void TelnetConnection::run()
{
    tcpSocketPtr = new QTcpSocket();  
    if (!tcpSocketPtr->setSocketDescriptor(socketDescriptor)) {
        emit error(tcpSocketPtr->error());
        return;
    }
    ....
}
Run Code Online (Sandbox Code Playgroud)

当我的应用想断开客户端时,我打电话:

void TelnetConnection::disconnectClient()
{
    tcpSocketPtr->disconnectFromHost();
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

Rei*_*ica 5

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

首先,我会请务必提交的一些实例TelnetConnectiontelnetThread.这只是为了让我明白我在谈论什么线程.

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

  1. 你是emit error(tcpSocketPtr->error())run()方法中调用的.它是从所谓的telnetThread,一个不同的线程QObject信号生活:它生活在telnetThread->thread().

    run()方法在telnetThread线程内执行.但信号的实现,通过MOC生成,预计将在您实例化任何线程中调用QThread-即telnetThread->thread(),与该线程永远不等于到一个地方run()执行.基本上,有点混乱,以下不变量持有:

    QThread * telnetThread ...
    Q_ASSERT(telnetThread != telnetThread->thread());
    
    Run Code Online (Sandbox Code Playgroud)
  2. 您正在从另一个线程中执行的槽中调用方法tcpSocketPtr,生活在telnetThread其中.以下是:

    Q_ASSERT(tcpSocketPtr->thread() == telnetThread);
    
    Run Code Online (Sandbox Code Playgroud)

    您声明的所有插槽telnetThread都在与telnetThread自身不同的线程中执行!因此,在disconnectClientGUI线程中执行的主体,但它直接调用方法tcpSocketPtr.

以下是一种方法.它侦听端口8023. ^ D结束连接.接收大写Q后跟Enter/Return将干净地关闭服务器.

介绍

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

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

该代码支持Qt 4和Qt 5.

StoppingThread

添加缺少的行为QThread.通常,当您破坏正在运行的线程时,会收到警告消息和崩溃/未定义的行为.这个类在销毁时告诉线程的事件循环退出并等待线程实际完成.它就像使用它一样QThread,除了它不会在破坏时做愚蠢的事情.

ThreadedQObjectDeleter

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

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

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

ServerFactory

newConnection调用其插槽时生成新的服务器实例.构造函数传递给QMetaObject客户端QObject进行创建.因此,该类可以构造"任何"所需的QObject而无需使用模板.它创建的对象只有一个要求:

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

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

ThreadedServerFactory

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

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

TelnetServer

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

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

主要()

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"
Run Code Online (Sandbox Code Playgroud)