QSslServer::sslErrors中调用QDialog::exec致客户端断开的解决办法
处理QSslServer中SSL错误时同步弹窗导致客户端断开的问题
问题背景
开发SSL套接字通信应用时,不可信客户端连接服务器会触发QSslServer的sslErrors信号。业务要求在信号处理函数内用QDialog::exec()同步询问用户是否忽略错误,但调用该函数后客户端会直接断开,对话框返回后访问socket会触发内存访问违规。
Qt文档明确建议避免使用exec(),改用异步的open(),但业务逻辑要求必须在信号处理流程内同步完成决策,无法采用异步方式。
曾考虑备选流程:连接后先执行认证,出现sslHandshakeErrors时异步弹窗,用户确认后发送authOk数据包继续通信,否则发送authFailed并断开,但该流程不符合标准SSL握手规范。
核心原因
QDialog::exec()会启动嵌套事件循环,在此期间QSslSocket的底层IO事件仍会被处理。当SSL握手超时或底层连接检测到异常时,socket会被自动销毁,导致后续访问已释放的内存,触发内存违规错误。
最优解决方案
要解决这个问题,需在弹出对话框前暂停QSslSocket的IO事件处理,避免socket在等待用户决策时被销毁,具体步骤:
sslErrors信号触发时,立即禁用socket的读写通道,暂停IO处理- 使用
exec()弹出对话框获取用户决策 - 根据用户选择,要么调用
ignoreSslErrors()恢复握手,要么直接断开socket - 若选择忽略错误,恢复socket的IO事件处理
- 用
QPointer弱引用跟踪socket状态,避免野指针访问
修改后的示例代码
#include <QApplication> #include <QSslServer> #include <QDialog> #include <QSslKey> #include <QMessageBox> #include <QPointer> int main(int argc, char **argv) { QApplication app(argc, argv); // 配置SSL参数 auto config = QSslConfiguration(); config.setLocalCertificate(QSslCertificate::fromData(QByteArray::fromStdString(R"( -----BEGIN CERTIFICATE----- MIIDITCCAgmgAwIBAgIVAMdt4c6oGd0rUSbR+/tBVfhny3K3MA0GCSqGSIb3DQEB BQUAMEoxGDAWBgNVBAMMD0xBUFRPUC1KQzJNMzcyQTEbMBkGA1UECgwSc3JpbGFr c2htaWthbnRoYW5wMREwDwYDVQQLDAhjbGlwYmlyZDAeFw0yMzA5MDgxMTQ4NDZa Fw0yNDA5MDcxMTQ4NDZaMEoxGDAWBgNVBAMMD0xBUFRPUC1KQzJNMzcyQTEbMBkG A1UECgwSc3JpbGFrc2htaWthbnRoYW5wMREwDwYDVQQLDAhjbGlwYmlyZDCCASIw DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALkFDUKmzz+NE0x7o5JGgE8KZ/cB GEtTwN2cElMESQ/MZe3ohIE5GdFkld/7894wXZAJkXu38oZVEQ/AurntqqSHT4iP 7qMUJGYjo2v2g/1JwU3E/sG+IjatOjyl8b0b+E9TmDt7XOS4VWz3vjTkXwmQTzUv 3L5myfdcmBpA9AOXxH8yHq9lcBl97ZHGWA4zI9uWnwtyFWSl8DX4H/y14+itEYwH n6xLsEBxqjx40G7WR0AiAYRdE5Yvr6QOGtFAeODFqOv/2sAxdm/7P1wHjIvyOB++ eufvPyWZpzeBfEvrIeGkj91YGAV7FQsOfCuzetGgZcLiBDAZKjnKEQ2oajUCAwEA ATANBgkqhkiG9w0BAQUFAAOCAQEAD/SYccAb3K6GKyfc9Rbaj44IxpsNlHDQAr5b c8Nmz+LW905EqFe6tAhCgi3q9o3HUUeiNHe0rYad3Lgd1setSOVdWiSbxArmELgW Dg3NGd3GIIRShvmZfSHRkpvKaD9j06CVzMrM0nZsjQVcQrKlFBUJ9UEqVmmcz1nU a4yUEQ9Rb7t4Icw7aD07NqLRlhNGCii4d12NAY7kRZdLdtTw7T/j4tXxmcJsOiWK Lx/cqqTBgBHc3l3EeylQdO17pFClY5yMUGGla7LPcDm1sU4mmFEKmkjcRGu+mQM2 V3EDgh37GyTWvs6Zf86B14m/US0Ff4vQu26vco5Pjk3xKckA+Q== -----END CERTIFICATE----- )")).first()); config.setPrivateKey(QSslKey(QByteArray::fromStdString(R"( -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAuQUNQqbPP40TTHujkkaATwpn9wEYS1PA3ZwSUwRJD8xl7eiE gTkZ0WSV3/vz3jBdkAmRe7fyhlURD8C6ue2qpIdPiI/uoxQkZiOja/aD/UnBTcT+ wb4iNq06PKXxvRv4T1OYO3tc5LhVbPe+NORfCZBPNS/cvmbJ91yYGkD0A5fEfzIe r2VwGX3tkcZYDjMj25afC3IVZKXwNfgf/LXj6K0RjAefrEuwQHGqPHjQbtZHQCIB hF0Tli+vpA4a0UB44MWo6//awDF2b/s/XAeMi/I4H7565+8/JZmnN4F8S+sh4aSP 3VgYBXsVCw58K7N60aBlwuIEMBkqOcoRDahqNQIDAQABAoIBADf6I0zrEwDzOceG ELMMyX0gdNvyZNtFd7CUq6aaQVCaUqxsEMrX78u+VunxXJL4pxYRDwcTXDjwO708 XkIqedpVZea3RUfprCmK1sKvTrevPOC+nSUY1Vkdh+UZf83rTHETpZc5d51rd80E F3QBNA+8rXo2BN9GUgyY4xvuUuVS3drVZ6WA9A9yPtaBgeLATqmb0Ckh2aVn+XG9 eYIxiF0Sfnb3HET7IDO+Xtw8OIygq+dT9v5LMMYf/Aa8aakJMoTK6SLbjFWs5gUj mhBSBdxqdmLLTa2E+3hrZOfgQ0tk85336n7v3dFKkhTMr9KztOIh53Ruh36gR3UT QxbCyvECgYEA2uKcFmrkGa61WarqSbB58e/m111cmlcjOSoEELwb+O1pLe8qqDSZ UeLVMRW+omdyvt0N1RaDEOsBWtgup2RntutEx8sAi5O1d+CrCLZ7xZJPSrYH4tIs YFXoBgCNqn+275ZdeL5LgxuFKAstFW9YbWpsXCEPbrIMBk4Xm3m3Pt8CgYEA2GRo I+lLPvcslTwens+5sXT5+EPTcY9Ss2jSz3njEIt4asxa/P4H7Xx2EWqMQg5LGSVD MP8L9lxCSiyYvrQhYAQZq2VqvVCejFkrWY8hntDvyhJ26SfanSdMB1MVWOc16dns wjuuX3+5QIQoMogL1eng6/VOOJHVfiAh57yqPWsCgYEAkcI54wvHXfrjtRSF9BBb BGuXM29ujTDdueFq16IMlpWyZu5PX7e3Kbp98bPjQM7WsJcP8QiOuyNjwZUYbEwG bN767HkYodn5DB1GiATNI2Is/zl8wuTmvDg4zFZuAE4QCjf9grxmGKao42Od4BpH roUiJ6+0USirrT8vpU9GYc0CgYAYuIfJKnrFK7m1JtQcsoB1THbOLPl37La29k+3 EialmjlcghIW+vJu6BwY60Iwva9IpSAi9dApCesszCF7D9sMPAuur/xculwSjpFM PvTJTvdF74wUINBxya5+27gBmxBmsdXBbs4B7PZ971skQrSPcJOYgUK5ZbetHACj l8MfFwKBgQC3g39rKU/iahrr5VcbLecsdv9jhSo+PANQOXhreEZTOF1spsWh9bMN 11MB0uZu99p4Solvv0M11Md++CO3ocBRT0AsdsdLdytAb+YWM+c1ls5dhjVnaMpL dKKHiQ7sfiPM05f6HuTmMuinSLw7f1Ff7GVJUMugJCqiNn3XO3jWGQ== -----END RSA PRIVATE KEY----- )"), QSsl::KeyAlgorithm::Rsa)); config.setPeerVerifyMode(QSslSocket::VerifyPeer); if (config.isNull() || config.localCertificate().isNull() || config.privateKey().isNull()) { throw std::runtime_error("Can't Create QSslConfiguration"); } const auto processSslErrors = [=] (QSslSocket * sock, const QList<QSslError> & errors){ // 用QPointer跟踪socket,避免野指针 QPointer<QSslSocket> socketPtr(sock); // 暂停socket的IO事件处理 sock->setReadChannelEnabled(QSslSocket::SslSocket, false); sock->setWriteChannelEnabled(QSslSocket::SslSocket, false); QMessageBox dialog(QMessageBox::Critical, "SSL Error", "Accept ?", QMessageBox::Yes | QMessageBox::Cancel); int result = dialog.exec(); // 检查socket是否仍存在 if (!socketPtr) { return; } if (result == QMessageBox::Yes) { // 恢复IO事件处理 socketPtr->setReadChannelEnabled(QSslSocket::SslSocket, true); socketPtr->setWriteChannelEnabled(QSslSocket::SslSocket, true); socketPtr->ignoreSslErrors(); } else { socketPtr->disconnectFromHost(); } }; auto server = new QSslServer(); QObject::connect(server, &QSslServer::sslErrors, processSslErrors); server->setSslConfiguration(config); server->listen(QHostAddress::Any, 7000); return app.exec(); }
备选流程的问题
备选流程跳过了标准SSL握手的错误处理逻辑,在SSL层之外重新实现认证机制,会带来以下问题:
- 不符合RFC标准的SSL/TLS握手流程,存在兼容性风险
- 无法利用Qt SSL栈的内置安全检查,降低通信安全性
- 客户端需额外处理自定义数据包,增加开发复杂度
关键注意事项
- 必须用
QPointer跟踪socket状态,防止用户决策期间socket被销毁导致野指针访问 - 暂停IO事件时需禁用所有相关通道,避免底层事件触发socket销毁
- 多客户端场景下,需确保每个客户端的对话框处理独立,避免阻塞整个服务器
内容的提问来源于stack exchange,提问作者srilakshmikanthanp
相关产品推荐
相关产品推荐

