You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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在等待用户决策时被销毁,具体步骤:

  1. sslErrors信号触发时,立即禁用socket的读写通道,暂停IO处理
  2. 使用exec()弹出对话框获取用户决策
  3. 根据用户选择,要么调用ignoreSslErrors()恢复握手,要么直接断开socket
  4. 若选择忽略错误,恢复socket的IO事件处理
  5. 用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 19:54:53