快速安全的 CoAP 客户端
确保 CoAP 客户端的安全性,并将其与Qt Quick 用户界面配合使用。

“快速安全 CoAP 客户端”演示了如何创建一个安全的 CoAP 客户端,并在Qt Quick 应用程序中使用它。
注意: Qt CoAP 的 当前版本不提供QML API。不过,您可以按照示例所示,将该模块的C++类提供给QML使用。
运行示例
您可以通过以下方式运行示例:
- Qt Creator
打开Welcome 模式,并从Examples 中选择该示例。有关更多信息,请参阅Qt Creator :教程:构建和运行。
- Qt Extension for Visual Studio Code
在“Command Palette ”中运行Qt: Open Qt examples 命令,并从列表中选择该示例。有关更多信息,请参阅Qt Extension for Visual Studio Code :教程:构建和运行。
要运行示例应用程序,您首先需要设置一个安全的 CoAP 服务器。您可以使用任何支持预共享密钥(PSK) 或证书认证模式的安全 CoAP 服务器来运行该示例。有关设置安全 CoAP 服务器的更多信息,请参阅《设置安全 CoAP 服务器》。
将 C++ 类暴露给 QML
在此示例中,您需要将QCoapClient 类和QtCoap 命名空间暴露给QML。要实现这一点,请创建一个自定义包装类并使用特殊的注册宏。
创建QmlCoapSecureClient 类作为QCoapClient 的封装类。该类还保存所选的安全模式和安全配置参数。使用Q_INVOKABLE 宏将多个方法暴露给QML。同时使用QML_NAMED_ELEMENT 宏将该类在QML中注册为CoapSecureClient 。
class QmlCoapSecureClient : public QObject
{
Q_OBJECT
QML_NAMED_ELEMENT(CoapSecureClient)
public:
QmlCoapSecureClient(QObject *parent = nullptr);
~QmlCoapSecureClient() override;
Q_INVOKABLE void setSecurityMode(QtCoap::SecurityMode mode);
Q_INVOKABLE void sendGetRequest(const QString &host, const QString &path, int port);
Q_INVOKABLE void setSecurityConfiguration(const QString &preSharedKey, const QString &identity);
Q_INVOKABLE void setSecurityConfiguration(const QString &localCertificatePath,
const QString &caCertificatePath,
const QString &privateKeyPath);
Q_INVOKABLE void disconnect();
Q_SIGNALS:
void finished(const QString &result);
private:
QCoapClient *m_coapClient;
QCoapSecurityConfiguration m_configuration;
QtCoap::SecurityMode m_securityMode;
};之后,注册QtCoap 命名空间,以便您可以使用其中提供的枚举:
namespace QCoapForeignNamespace
{
Q_NAMESPACE
QML_FOREIGN_NAMESPACE(QtCoap)
QML_NAMED_ELEMENT(QtCoap)
}调整构建文件
为了使自定义类型可在 QML 中使用,请相应地更新构建系统文件。
CMake
对于基于 CMake 的构建,请在 `CMakeLists.txt` 中添加以下内容:
qt_add_qml_module(quicksecureclient
URI CoapSecureClientModule
SOURCES
qmlcoapsecureclient.cpp qmlcoapsecureclient.h
QML_FILES
FilePicker.qml
Main.qml
)qmake
对于 qmake 构建,请按以下方式修改quicksecureclient.pro 文件:
CONFIG += qmltypes
QML_IMPORT_NAME = CoapSecureClientModule
QML_IMPORT_MAJOR_VERSION = 1
...
qml_resources.files = \
qmldir \
FilePicker.qml \
Main.qml
qml_resources.prefix = /qt/qml/CoapSecureClientModule
RESOURCES += qml_resources使用新的 QML 类型
现在,当 C++ 类已正确暴露给 QML 时,即可使用这些新类型。
创建客户端
CoapSecureClient 是从Main.qml 文件中实例化的。它处理QmlCoapSecureClient::finished() 信号,并据此更新用户界面:
CoapSecureClient {
id: client
onFinished: (result) => {
outputView.text = result;
statusLabel.text = "";
disconnectButton.enabled = true;
}
}当用户在 UI 中选择或更改安全模式时,会创建 `QCoapClient ` 的实例。当选定其中一种安全模式时,QML 代码会调用 `QmlCoapSecureClient::setSecurityMode() ` 方法:
ButtonGroup {
id: securityModeGroup
onClicked: {
if ((securityModeGroup.checkedButton as RadioButton) === preSharedMode)
client.setSecurityMode(QtCoap.SecurityMode.PreSharedKey);
else
client.setSecurityMode(QtCoap.SecurityMode.Certificate);
}
}在 C++ 端,该方法会创建一个 `QCoapClient ` 实例,并连接到其 `finished()` 和 `error()` 信号。该类在内部处理这两个信号,并将它们转发给新的 `finished() ` 信号。
void QmlCoapSecureClient::setSecurityMode(QtCoap::SecurityMode mode)
{
// Create a new client, if the security mode has changed
if (m_coapClient && mode != m_securityMode) {
delete m_coapClient;
m_coapClient = nullptr;
}
if (!m_coapClient) {
m_coapClient = new QCoapClient(mode);
m_securityMode = mode;
connect(m_coapClient, &QCoapClient::finished, this,
[this](QCoapReply *reply) {
if (!reply)
emit finished(tr("Something went wrong, received a null reply"));
else if (reply->errorReceived() != QtCoap::Error::Ok)
emit finished(errorMessage(reply->errorReceived()));
else
emit finished(reply->message().payload());
});
connect(m_coapClient, &QCoapClient::error, this,
[this](QCoapReply *, QtCoap::Error errorCode) {
emit finished(errorMessage(errorCode));
});
}
}发送请求
单击“Send Request ”按钮,根据所选的安全模式设置安全配置,并发送GET 请求:
Button {
id: requestButton
text: qsTr("Send Request")
enabled: securityModeGroup.checkState !== Qt.Unchecked
onClicked: {
outputView.text = "";
if ((securityModeGroup.checkedButton as RadioButton) === preSharedMode)
client.setSecurityConfiguration(pskField.text, identityField.text);
else
client.setSecurityConfiguration(localCertificatePicker.selectedFile,
caCertificatePicker.selectedFile,
privateKeyPicker.selectedFile);
client.sendGetRequest(hostComboBox.editText, resourceField.text,
parseInt(portField.text));
statusLabel.text = qsTr("Sending request to %1%2...").arg(hostComboBox.editText)
.arg(resourceField.text);
}
}setSecurityConfiguration 方法有两个重载。
PSK 模式的重载仅设置客户端身份和预共享密钥:
void
QmlCoapSecureClient::setSecurityConfiguration(const QString &preSharedKey, const QString &identity)
{
QCoapSecurityConfiguration configuration;
configuration.setPreSharedKey(preSharedKey.toUtf8());
configuration.setPreSharedKeyIdentity(identity.toUtf8());
m_configuration = configuration;
}而 X.509 证书的重载则会读取证书文件和私钥,并设置安全配置:
void QmlCoapSecureClient::setSecurityConfiguration(const QString &localCertificatePath,
const QString &caCertificatePath,
const QString &privateKeyPath)
{
QCoapSecurityConfiguration configuration;
const auto localCerts =
QSslCertificate::fromPath(QUrl(localCertificatePath).toLocalFile(), QSsl::Pem,
QSslCertificate::PatternSyntax::FixedString);
if (localCerts.isEmpty())
qCWarning(lcCoapClient, "The specified local certificate file is not valid.");
else
configuration.setLocalCertificateChain(localCerts.toVector());
const auto caCerts = QSslCertificate::fromPath(QUrl(caCertificatePath).toLocalFile(), QSsl::Pem,
QSslCertificate::PatternSyntax::FixedString);
if (caCerts.isEmpty())
qCWarning(lcCoapClient, "The specified CA certificate file is not valid.");
else
configuration.setCaCertificates(caCerts.toVector());
QFile privateKey(QUrl(privateKeyPath).toLocalFile());
if (privateKey.open(QIODevice::ReadOnly)) {
QCoapPrivateKey key(privateKey.readAll(), QSsl::Ec);
configuration.setPrivateKey(key);
} else {
qCWarning(lcCoapClient) << "Unable to read the specified private key file"
<< privateKeyPath;
}
m_configuration = configuration;
}设置安全配置后,sendGetRequest 方法会设置请求 URL 并发送GET 请求:
void QmlCoapSecureClient::sendGetRequest(const QString &host, const QString &path, int port)
{
if (!m_coapClient)
return;
m_coapClient->setSecurityConfiguration(m_configuration);
QUrl url;
url.setHost(host);
url.setPath(path);
url.setPort(port);
m_coapClient->get(url);
}发送第一个请求时,会与 CoAP 服务器进行握手。握手成功后,所有后续消息均会加密,且在握手成功后更改安全配置将不会产生任何效果。若需更改配置或更换主机,必须先断开连接。
void QmlCoapSecureClient::disconnect()
{
if (m_coapClient)
m_coapClient->disconnect();
}这将中止握手并关闭已打开的套接字。
对于使用 X.509 证书的身份验证,需要指定证书文件。为此使用了FilePicker 组件。该组件包含一个文本框和一个按钮,按下按钮时会打开文件对话框:
Item {
id: filePicker
property string dialogText
property alias selectedFile: filePathField.text
height: addFileButton.height
FileDialog {
id: fileDialog
title: qsTr("Please Choose %1").arg(filePicker.dialogText)
currentFolder: StandardPaths.writableLocation(StandardPaths.HomeLocation)
fileMode: FileDialog.OpenFile
onAccepted: filePathField.text = fileDialog.selectedFile
}
RowLayout {
anchors.fill: parent
TextField {
id: filePathField
placeholderText: qsTr("<%1>").arg(filePicker.dialogText)
inputMethodHints: Qt.ImhUrlCharactersOnly
selectByMouse: true
Layout.fillWidth: true
}
Button {
id: addFileButton
text: qsTr("Add %1").arg(filePicker.dialogText)
onClicked: fileDialog.open()
}
}
}FilePicker 在Main.qml 文件中,该组件被多次实例化,用于创建证书和私钥的输入字段:
FilePicker {
id: localCertificatePicker
dialogText: qsTr("Local Certificate")
enabled: (securityModeGroup.checkedButton as RadioButton) === certificateMode
Layout.columnSpan: 2
Layout.fillWidth: true
}
FilePicker {
id: caCertificatePicker
dialogText: qsTr("CA Certificate")
enabled: (securityModeGroup.checkedButton as RadioButton) === certificateMode
Layout.columnSpan: 2
Layout.fillWidth: true
}
FilePicker {
id: privateKeyPicker
dialogText: qsTr("Private Key")
enabled: (securityModeGroup.checkedButton as RadioButton) === certificateMode
Layout.columnSpan: 2
Layout.fillWidth: true
}设置安全的 CoAP 服务器
要运行此示例,您需要一个支持 PSK 或证书模式(或两者兼有)的安全 CoAP 服务器。您有以下几种选择:
- 手动构建并运行安全 CoAP 服务器,例如使用libcoap、Californium、FreeCoAP 或任何其他支持 DTLS 的 CoAP 库。
- 使用 Docker Hub 上现成的 Docker 镜像,这些镜像已预先构建并运行了适合本示例的安全 CoAP 服务器。以下介绍了使用基于 Docker 的 CoAP 服务器所需的步骤。
设置 PSK 模式服务器
以下命令将从 Docker Hub 拉取基于Californium plugtest(默认情况下不安全)的安全 CoAP 服务器 Docker 容器,并启动它:
docker run --name coap-test-server -d --rm -p 5683:5683/udp -p 5684:5684/udp tqtc/coap-californium-test-server:3.8.0该 CoAP 测试服务器可通过端口5683(非安全)和5684(安全)访问。有关获取 IP 地址的说明,请参阅“获取 IP 地址”。
若要使用此服务器运行示例,您需要将预共享密钥设置为secretPSK ,并将身份设置为Client_identity 。
配置证书模式服务器
该安全服务器的 Docker 镜像基于 FreeCoAP 库中的时间服务器示例,并采用 X.509 证书进行身份验证。以下命令可从 Docker Hub 拉取该容器并启动它:
docker run --name coap-time-server -d --rm -p 5684:5684/udp tqtc/coap-secure-time-server:freecoap有关获取 IP 地址的说明,请参阅“获取 IP 地址”。可通过获取到的 IP 地址、5684端口以及资源路径/time 访问该 CoAP 测试服务器。
若要使用此服务器运行该示例,您需要指定服务器所需的证书文件。这些文件位于 Docker 容器内的/root/certs 目录下。要将它们复制到本地目录,请使用以下命令:
docker cp <container_id>:/root/certs <local_directory_path>例如:
$ docker cp 5e46502df88f:/root/certs ~/获取容器 ID 的操作说明如下。
获取 IP 地址
要查询 Docker 容器的 IP 地址,请先通过运行 `docker ps ` 命令获取容器 ID,该命令将输出类似以下内容:
$ docker ps
CONTAINER ID IMAGE
5e46502df88f tqtc/coap-californium-test-server:3.8.0然后,您可以使用以下命令获取 IP 地址:
docker inspect <container_id> | grep IPAddress例如:
$ docker inspect 5e46502df88f | grep IPAddress
...
"IPAddress": "172.17.0.2",
...终止 Docker 容器
使用完毕后,若要终止 Docker 容器,请使用以下命令:
docker stop <container_id>此处的<container_id> 与通过docker ps 命令获取的ID相同。
文件:
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.