当前位置: 首页 > news >正文

QSslServer踩坑

   最近在Qt6.9.3版本下学习使用Qt QSslServer,踩了一些坑(其实是自己仔细看文档)同时在网上相关的博客也很少,因此记录一下。

代码结构

为了方便理解,将代码的逻辑抽离,简化为两个线程。一般来说网络请求属于IO操作,不应该在主线程上,因此按照这种方式处理。然后客户端使用POSTMAN发起一个HTTPS请求。服务器端则是响应一个简单的Hello报文即可。

image

错误代码

下面是删减后的代码,保留必要逻辑和存在错误地方

main
// 服务器主线程
int main(int argc, char* argv[]) {QApplication a(argc, argv);Serverlet serv;serv.setAddress(QHostAddress::Any);serv.setPort(443);serv.setCertificate(":/local.crt");serv.setPrivateKey(":/local.key");serv.setCertChain({":/root.crt", ":/sub.crt",":/local.crt"});serv.start();return a.exec();
}
服务器类
// 服务器类,内置单独io线程、与连接处理器
class Serverlet : public QObject {Q_OBJECT
public:explicit Serverlet(QObject* parent = nullptr);~Serverlet();void setPort(uint16_t port);void setAddress(QHostAddress host);void setMessageThreads(uint32_t nums);void setCertificate(QString cert);void setPrivateKey(QString key);void setCertChain(QList<QString> certs);bool start();void stop();
private:// io线程QThread* m_ioWorker{nullptr};// 连接器ConnectAccpetor* m_acceptor{nullptr};
};void Serverlet::setCertChain(QList<QString> certs) {QMetaObject::invokeMethod(m_acceptor, "setCertChain", Q_ARG(QList<QString>, certs));
}bool Serverlet::start() {bool isSuccess = true;isSuccess = QMetaObject::invokeMethod(m_acceptor, "startListen", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, isSuccess));if (!isSuccess) {qDebug() << "Serverlet start failed";return isSuccess;}qDebug() << "Serverlet start success";return true;
}
连接处理
//  连接器
class ConnectAccpetor : public QObject {Q_OBJECT
public:explicit ConnectAccpetor(QObject* parent = nullptr);~ConnectAccpetor();void init();signals:void newSslSocket(QList<QSslSocket*> socket);
public slots:void procNewConnect();bool startListen();void setPort(uint16_t port);void setHost(QHostAddress host);void stop();void setCert(QString localCert);void setKey(QString localKey);void updateSslConfig();void setCertChain(QList<QString> certs);private:QSslServer* m_server{nullptr};QHostAddress m_host{QHostAddress::Any};uint16_t m_port{8080};QSslConfiguration m_config;
};// cpp
ConnectAccpetor::ConnectAccpetor(QObject* parent): QObject{parent} {m_config.setProtocol(QSsl::AnyProtocol);auto ciphers = QSslConfiguration::supportedCiphers();m_config.setCiphers(ciphers);// 提前注册元类型(仅需一次)qRegisterMetaType<QList<QSslSocket*>>("QList<QSslSocket*>");qRegisterMetaType<QHostAddress>("QHostAddress");qRegisterMetaType<uint16_t>("uint16_t");
}// 仅初始化一次QSslServer,避免重复创建
void ConnectAccpetor::init() {if (m_server != nullptr) return; // 已初始化则直接返回,核心修复点1m_server = new QSslServer(this);// 连接新连接信号connect(m_server, &QSslServer::newConnection, this, &ConnectAccpetor::procNewConnect);connect(m_server, &QSslServer::errorOccurred, this,[](QSslSocket* socket, QAbstractSocket::SocketError err) {qWarning() << "TLS握手失败" << err << socket;// 此时 socket 会自动销毁});m_server->setSslConfiguration(m_config);
}void ConnectAccpetor::procNewConnect() {qDebug() << "收到新连接,开始处理SSL握手";while(m_server->hasPendingConnections()){qDebug() << "开始处理";QSslSocket* sslSocket = qobject_cast<QSslSocket*>(m_server->nextPendingConnection());sslSocket->write(R"(HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 5Hello)");sslSocket->flush();qDebug() << "socket状态" << sslSocket->state() << "是否已加密" << sslSocket->isEncrypted();sslSocket->close();sslSocket->deleteLater();}
}void ConnectAccpetor::setCertChain(QList<QString> certPath) {QList<QSslCertificate> certChain;for (const QString& path : certPath) {QList<QSslCertificate> caCerts = QSslCertificate::fromPath(path);if (caCerts.isEmpty()) {qWarning() << "证书链文件无效,跳过:" << path;continue;}}if (certChain.isEmpty()) {qCritical() << "设置证书链失败:无有效证书文件";return;}m_config.setLocalCertificateChain(certChain);qDebug() << "证书链设置成功,共" << certChain.size() << "个证书" << m_config.localCertificateChain();
}

异常现象

点击Postman发送请求

1、控制台:打印 收到新连接,开始处理SSL握手 但是没有打印 开始处理 ,也就是说,hasPenddingConnection返回false,那么 nextPendingConnection 获取的也是空。

2、控制台打印:QAbstractSocket::SocketError(20) 和 QAbstractSocket::SocketError(21) 

踩坑过程与原因

1、nextPendingConnection获取不到socket

  因为网上的一些例子,都是Qt 5.x版本的例子,都是自己继承QTcpServer,并自己实现incomingConnection ,在incomingConnection 函数中构建 QSslSocket对象,并且将加密成功的信号和服务器的业务逻辑槽函数绑定。但是我看源码,发现官方实现了这部分代码,因此不知道该如何获取QSslSocket,但是我又看到这样的一段代码:

image

  当QSslSocket握手成功之后,会将socket添加到 pendingConnection中,然后就可以从其中获取。我以为和QTcpSocket一样,addPendingConnection后会发出一个 newConnection信号,只需要在这个信号的槽函数中获取等待处理的连接即可。但是实际上,newConnection是QTcpSocket有新连接时触发的,TLS是基于TCP的,因此实际上在newConnection信号发出后,只是代表TCP新连接进入,还需要经历TLS握手环节,只有握手成功之后才会被添加到等待队列中,然后发出 pendingConnectionAvailable() 信号,此时才能处理。因此,需要关注信号 pendingConnectionAvailable ,然后才能获取到等待处理的连接 (6.4版本之后引入)

void ConnectAccpetor::init() {if (m_server != nullptr) return; // 已初始化则直接返回,核心修复点1m_server = new QSslServer(this);// 连接新连接信号connect(m_server, &QSslServer::pendingConnectionAvailable, this, &ConnectAccpetor::procNewConnect);connect(m_server, &QSslServer::errorOccurred, this,[](QSslSocket* socket, QAbstractSocket::SocketError err) {qWarning() << "TLS握手失败" << err << socket;// 此时 socket 会自动销毁});m_server->setSslConfiguration(m_config);
}

2、QAbstractSocket::SocketError(20) 和 QAbstractSocket::SocketError(21) 异常

  一开始控制台打印 QAbstractSocket::SocketError(20) 和 QAbstractSocket::SocketError(21) ,我还以为是编译时Openssl版本和运行时Openssl版本差距过大导致的,因为 SslInternalError 在官方文档中说,这是Openssl内部错误,可能是兼容性的问题。但是仔细一想,如果无法兼容,那么应该直接报错才对,但是可以运行,说明接口是一样的,底层实现有细微差距。然后是SslInvalidUserDataError这个错误的指向就很明确,指证书、密钥存在错误。为此还重写签发了一整套证书,但是还是报错,然后想着去抓包。抓包显示,TCP握手之后,客户端发送了一个请求,然后服务器直接就FIN,关闭了Socket,说明就是证书的配置存在问题,然后去网上搜相关的异常也没有详细说明,问AI也不知道是为什么(AI对于冷门问题不是乱编就是乱猜)。然后只有一点点排查,最后发现是证书链配置存在问题,当不配置证书链的时候,报错发生了变化,POSTMAN报错,提示找不到证书链。然后抓包发现,服务器没有立即关闭Socket,而是客户端关闭的Socket。说明确实是证书链配置异常导致的问题。

  然后翻阅证书链文档发现了一行小字,表示,第一个元素必须是叶子证书 (身份证书)。好了知道问题所在了,必须配置为身份证书。因此只需要更换一下证书顺序即可。然后就可以正常访问了。

image

// 服务器主线程
int main(int argc, char* argv[]) {QApplication a(argc, argv);Serverlet serv;serv.setAddress(QHostAddress::Any);serv.setPort(443);serv.setCertificate(":/local.crt");serv.setPrivateKey(":/local.key");serv.setCertChain({":/local.crt", ":/sub.crt",":/root.crt"});serv.start();return a.exec();
}

  另外我还发现,其实可以不用设置证书链,通过addCaCertificates 函数,将root和 sub证书都添加到配置中,也可以自动寻找到证书链。我尝试过追踪源码,但是过于底层的看不到,我猜测应该是在没有证书链的情况下会随机匹配一条证书链发送给客户端,如果在指明了证书链的情况下,就首先使用配置的证书链。

 

总结

  在Qt 6.4 版本之后,引入了 QSslServer,可以不需要自己去手动实现一个TLS服务器,QT底层已经实现了QSslSocket的构造与握手,原本的 newConnection 是在TCP握手完成后触发,对于QSslServer来说 pendingConnectionAvailable 才是握手完成时发出的信号。另外QSslConfigurtaion类加载证书链的时候,必须将身份证书放在第一个,ca证书的顺序不会影响证书链。

 

http://www.jsqmd.com/news/337729/

相关文章:

  • react umi model使用注意事项 - ฅ˙
  • 别再瞎找了!10个AI论文工具深度测评,继续教育毕业论文写作必备
  • 2026国内最新装修环保板材五大源头厂家推荐!山东等地优质环保板材品牌权威榜单发布,资质环保双优助力健康家居 - 品牌推荐2026
  • 2026年中国离婚财产分割律师联系电话推荐:核心联系方式汇总 - 十大品牌推荐
  • c/c++中回调函数
  • VX窗口管理工具,windows神器
  • 一文知晓老化试验箱技术跃迁:2026值得期待的十大品牌厂家与明星产品一览 - 品牌推荐大师1
  • 2026西南乙醇采购首选!彭州市泰山商贸领衔,酒精乙醇优质供应商全推荐 - 深度智识库
  • 代码直接变论文:把科研写作从“手工艺“改造成“可复用工作流“
  • 盘点在铝合金压铸领域表现突出的部分企业,锌铝压铸/压铸铝件/精密铝压铸/铝压铸/铝压铸件,铝合金压铸订做厂家排名 - 品牌推荐师
  • 2026年北京靠谱的律师推荐,北京万京律师事务所 - 工业品牌热点
  • 这3种数据库类型:OLTP、OLAP、HTAP有什么区别?
  • 2026年麦角硫因抗衰胶囊优质品牌推荐榜 - 真知灼见33
  • 分析双马拉链公司介绍,看看它在行业口碑排名怎样 - mypinpai
  • 2026年杭州离婚律师推荐:劳动纠纷律师/婚姻律师/离婚律师精选 - 品牌推荐官
  • 2026版Java 面试八股文(总结最全面的面试题)
  • 2026年哈尔滨木盒定制源头厂家排名,金源木业口碑好合作案例多推荐 - myqiye
  • d7
  • asp.net core如何实现Controller热更新 - 生命体验之kevin
  • 2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
  • 2026年普拉提教练培训学校及大器械课程推荐 - 品牌2025
  • 2026年深圳离婚纠纷律师电话推荐:资深专家联系指南 - 十大品牌推荐
  • AI英语口语APP的开发
  • 2026年 力士乐PV7-1A/10-14RE01MC0-16液压泵厂家推荐榜:精密动力与稳定性能的工业核心之选 - 品牌企业推荐师(官方)
  • Instagram 养号机器人指南(2026):自动化原理、防封技巧与工具推荐
  • 滑轨铰链哪个品牌好耐用?一文读懂如何选对耐用五金品牌
  • 美国SDE求职辅导哪家强:技术辅导专业榜单 - 技研备忘录
  • 极简主义建筑空镜头素材哪里找?10个网站推荐
  • 缓冲滑轨品牌推荐,如何选对抽屉的“隐形核心”?
  • 全自动汽油氧化安定性测定仪的技术解析与应用价值研究