2

I'm working on a threaded telnet server (one thread per connection), and can't figure out how to get rid of valgrind errors. I've narrowed the problem down to WHERE I delete the tcpsocket.

I create the QTcpSocket in the run() method of the QThread:

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

When my app wants to disconnect the client I call:

void TelnetConnection::disconnectClient()
{
    tcpSocketPtr->disconnectFromHost();
}

and the slot which is called upon socket disconnect is: void TelnetConnection::clientDisconnected()

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

So, I've tried 1. to delete the QTcpSocket in the clientDisconnected slot but that causes read/write instability. (occasional crash) 2. to deletelater in the clientDisconnected slot but that causes memory read/write errors 3. to delete after the thread's exec loop but that still causes memory read/write errors 4. to deletelater after the thread's exec loop - and all errors are gone.

From what I read, deletelater if called AFTER the exec loop has terminated, will run when the thread is deleted. So while this works, I don't think this is the RIGHT way to to it.

I tried creating the QTcpSocket with "this" as the parent but then my signal connects failed because of parent vs this mismatch errors. (Which would allow the QTcpSocket to be deleted upon thread destruction).

What is the RIGHT way to fix this?

4

1 回答 1

6

您的问题几乎完全源于重新实现QThread. 不要这样做。把你所有的功能放到一个QObject中,然后QThreadmoveToThread(). 如果您仅通过信号槽连接从外部访问您的对象,那么您将立即完成。

首先,我总是将您的某些实例TelnetConnection称为 as telnetThread。这只是为了明确我在说什么线程。

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

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

    该方法正在线程run()内执行。telnetThread但是,由 moc 生成的信号实现预计将在您实例化的任何线程中调用QThread- 即telnetThread->thread(),该线程永远不会等于执行的线程run()。基本上,有点令人困惑的是,以下不变量成立:

    QThread * telnetThread ...
    Q_ASSERT(telnetThread != telnetThread->thread());
    
  2. 您正在从在另一个线程中执行的插槽中调用方法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 服务器。该接口由一个可调用的构造函数组成。构造函数获取要使用的套接字并将套接字连接到内部插槽。功能非常简单,该类只对套接字做出反应readyReaddisconnected发出信号。断开连接后,它会自行删除。

这不是一个真正的 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"
于 2013-10-09T20:43:17.890 回答