为什么删除Qt(QSslSocket)对象会导致崩溃

3

我非常困惑,希望有人能够解决我的问题。这与我的SSL客户端和服务器非常简单有关,连接正常,通信正常。但是当客户端从服务器断开连接时,就会在服务器上触发一个信号,该信号在SLOT error_handler(QAbstractSocket::SocketError in_error)中处理。在该函数中,我认为必须删除sslSocket对象。

然而,这样做会导致服务器崩溃。我不明白发生了什么。我希望这很简单,但显然我错过了一些Qt(或其他)的概念。

有人可以帮助吗?

重要的服务器代码:

void SSLServer::incomingConnection(int sd)
{
    sslSocket = new SSLSocket(this);
    if( sslSocket->setSocketDescriptor(sd))
    {
        QFile sslkeyfile(privKey_);
        sslSocket->setPrivateKey(QSslKey(sslkeyfile.readAll(),QSsl::Rsa));

        QFile cliCertFile(serverCert_);
        sslSocket->setLocalCertificate(QSslCertificate(cliCertFile.readAll()));

        QFile certFile(caCert_);
        sslSocket->addCaCertificate(QSslCertificate(certFile.readAll()));

        sslSocket->setPeerVerifyMode(QSslSocket::VerifyPeer);
        sslSocket->setProtocol(QSsl::SslV3);

        connect(sslSocket, SIGNAL(error(QAbstractSocket::SocketError)),
                this, SLOT(error_handler(QAbstractSocket::SocketError)));
        connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)),
                this, SLOT(ssl_error_handler(QList<QSslError>)));
        connect(sslSocket, SIGNAL(encrypted()), this,
                SLOT(ready()));
        connect(sslSocket, SIGNAL(readyRead()), this,
                SLOT(read_data_from_client()));

        sslSocket->startServerEncryption();
        if(!sslSocket->waitForEncrypted())
        {
            qDebug() << "failed to perform SSL handshake with client";
            return;
        }
    }

}

void SSLServer::read_data_from_client()
{
    QByteArray qstrbytes = sslSocket->readAll();
    qDebug() << Q_FUNC_INFO << qstrbytes;
}

void SSLServer::ready()
{
    QSslCertificate clientCert = sslSocket->peerCertificate();
    qDebug() << clientCert.isValid();
}

void SSLServer::error_handler(QAbstractSocket::SocketError in_error)
{
    qDebug() << Q_FUNC_INFO << in_error;
    if(in_error == QAbstractSocket::RemoteHostClosedError)
    {
        delete sslSocket; //// line causes crash !!!!!!
    }
}

1
你正在删除调用错误处理程序的对象。这不是一个好习惯。 - Mat
我是吗?错误处理程序是SSLServer对象的成员。这不是我要删除的内容。我要删除的是sslSocket对象。它随连接而来去。SSLServer对象在程序运行期间一直存在。我是否误解了您所说的内容?谢谢。 - driftwood
2
你将sslSocket的信号连接到了那个槽上。发射器是调用该槽的对象(至少对于直接连接而言)。也就是说,一旦你的槽函数执行完毕,代码会回流到sslSocket的某个位置,并删除this指针。这非常糟糕。 - Mat
啊,我觉得那很有道理。谢谢。我会相应地修改我的代码并看看效果如何。如果我发现有用的东西,我会告诉你的。 - driftwood
4个回答

6
请使用QObject::deleteLater()代替delete,因为QSslSocket继承自QObject。如果直接delete对象,则可能仍会在套接字上收到消息,从而导致崩溃。
sslSocket->deleteLater();

当你调用 deleteLater() 方法时,Qt 会自动断开所有的信号和插槽连接,并在没有未处理事件时调用对象析构函数。更多信息请参见QObject::~QObject()

1
好的,我明白了。感谢大家强调这一点。现在我使用deleteLater后不会再出现崩溃了。唯一困扰我的部分是我不确定对象是否会被deleteLater()删除。我理解得对吗?它只是将对象放入删除队列中,但如果对象正在被引用,则不会被删除。我想重点是可能存在对该对象的引用,而我在希望对象消失时可能没有考虑到这些引用,这种情况下对象永远不会被删除。因此我有一个内存泄漏问题。 - driftwood
DeleteLater() 在使用多线程环境时尤其重要,因为一个线程删除了某个对象,而另一个线程仍然发送信号/消息给已删除的对象(这是一个意外情况,根据Qt框架的验证可能会工作或不工作)。 - Ashif
@user2722568 请查看我的更新答案。它解释了deleteLater()如何自动断开所有的槽和信号,并最终调用对象析构函数。 - Cameron Tinker

2

如果你想象一下一个QObject类,比如SSLSocket类,可能是这样写的:

class SSLSocket : public QObject
{        
   signals:
        void sslErrors(QList<QSslError>);

     void SomeFunction()
     {
        // something went wrong, emit error
        emit sslErrors(errorList);

        Cleanup(); // If a slot connected to sslErrors deleted this, what happens now?!
     }
}

当信号sslErrors被触发时,将调用您的槽函数。正如您所看到的,在发出信号后,该类可能还有更多的工作要做。如果您立即在槽中删除对象,则会导致崩溃,这就是为什么您应该始终在槽函数中使用deleteLater()来删除QObject实例的原因。
deleteLater函数将确保槽函数已完成执行并恢复了调用堆栈,因此它将在适当的时间被删除。
请注意,上面的代码实际上并不是SSLSocket所做的事情,而只是一个示例。

1

QSslSocket是一个QObject。永远不要直接删除QObject。千万不要在槽函数中这样做。始终使用deleteLater()。


1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接