快速 CoAP 多播资源发现
使用 CoAP 客户端通过Qt Quick 用户界面进行组播资源发现。

“快速 CoAP 多播发现”示例演示了如何将QCoapClient 注册为 QML 类型,并在Qt Quick 应用程序中使用它进行 CoAP 多播资源发现。
注意: 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 服务器
要运行示例应用程序,首先需要设置并启动至少一个支持组播资源发现的 CoAP 服务器。您可以选择以下方式:
- 使用libcoap、Californium 或任何其他支持多播和资源发现功能的 CoAP 服务器实现,手动构建并运行 CoAP 服务器。
- 使用 Docker Hub 上提供的现成 Docker 镜像,该镜像基于 Californium的多播服务器示例构建并启动 CoAP 服务器。
使用基于 Docker 的测试服务器
以下命令从 Docker Hub 拉取 CoAP 服务器的 Docker 容器并启动它:
docker run --name coap-multicast-server -d --rm --net=host tqtc/coap-multicast-test-server:californium-2.0.0注意:您 可以通过向上述命令传入不同的--name ,来运行多个多播CoAP服务器(可在同一主机或网络中的其他主机上)。
使用完毕后若要终止 Docker 容器,请先执行 `docker ps ` 命令获取容器的 ID。输出结果如下:
$ docker ps
CONTAINER ID IMAGE
8b991fae7789 tqtc/coap-multicast-test-server:californium-2.0.0随后,使用该 ID 停止容器:
docker stop <container_id>将 C++ 类暴露给 QML
在此示例中,您需要将QCoapResource 和QCoapClient 类以及QtCoap 命名空间暴露给QML。为此,请创建自定义包装类并使用特殊的注册宏。
创建QmlCoapResource 类作为QCoapResource 类的封装类。使用Q_PROPERTY 宏使若干属性可在QML中访问。该类无需直接从QML实例化,因此请使用QML_ANONYMOUS 宏对其进行注册。
class QmlCoapResource : public QCoapResource
{
Q_GADGET
Q_PROPERTY(QString title READ title)
Q_PROPERTY(QString host READ hostStr)
Q_PROPERTY(QString path READ path)
QML_ANONYMOUS
public:
QmlCoapResource() : QCoapResource() {}
QmlCoapResource(const QCoapResource &resource)
: QCoapResource(resource) {}
QString hostStr() const { return host().toString(); }
};随后,创建以QCoapClient 类为基类的QmlCoapMulticastClient 类。使用Q_PROPERTY 宏公开一个自定义属性,并创建若干Q_INVOKABLE 方法。该属性及可调用方法均可从 QML 中访问。与QmlCoapResource 不同,您希望能够从 QML 中创建该类,因此请使用QML_NAMED_ELEMENT 宏在 QML 中注册该类。
class QmlCoapMulticastClient : public QCoapClient
{
Q_OBJECT
Q_PROPERTY(bool isDiscovering READ isDiscovering NOTIFY isDiscoveringChanged)
QML_NAMED_ELEMENT(CoapMulticastClient)
public:
QmlCoapMulticastClient(QObject *parent = nullptr);
Q_INVOKABLE void discover(const QString &host, int port, const QString &discoveryPath);
Q_INVOKABLE void discover(QtCoap::MulticastGroup group, int port, const QString &discoveryPath);
Q_INVOKABLE void stopDiscovery();
bool isDiscovering() const;
Q_SIGNALS:
void discovered(const QmlCoapResource &resource);
void finished(int error);
// The bool parameter is not provided, because the signal is only used by
// the QML property system, and it does not use the passed value anyway.
void isDiscoveringChanged();
public slots:
void onDiscovered(QCoapResourceDiscoveryReply *reply, const QList<QCoapResource> &resources);
private:
QCoapResourceDiscoveryReply *m_reply = nullptr;
};最后,注册QtCoap 命名空间,以便能够使用其中提供的枚举:
namespace QCoapForeignNamespace
{
Q_NAMESPACE
QML_FOREIGN_NAMESPACE(QtCoap)
QML_NAMED_ELEMENT(QtCoap)
}调整构建文件
为了使自定义类型在 QML 中可用,请相应地更新构建系统文件。
CMake 构建
对于基于 CMake 的构建,请在 `CMakeLists.txt` 中添加以下内容:
qt_add_qml_module(quickmulticastclient
URI CoapClientModule
VERSION 1.0
SOURCES
qmlcoapmulticastclient.cpp qmlcoapmulticastclient.h
QML_FILES
Main.qml
)qmake 构建
对于 qmake 构建,请按以下方式修改 `quickmulticastclient.pro ` 文件:
CONFIG += qmltypes
QML_IMPORT_NAME = CoapClientModule
QML_IMPORT_MAJOR_VERSION = 1
...
qml_resources.files = \
qmldir \
Main.qml
qml_resources.prefix = /qt/qml/CoapClientModule
RESOURCES += qml_resources使用新的 QML 类型
现在,当 C++ 类已正确暴露给 QML 后,您可以使用这些新类型:
CoapMulticastClient {
id: client
onDiscovered: (resource) => { root.addResource(resource) }
onFinished: (error) => {
statusLabel.text = (error === QtCoap.Error.Ok)
? qsTr("Finished resource discovery.")
: qsTr("Resource discovery failed with error code: %1").arg(error)
}
}QmlCoapMulticastClient::finished() 信号会触发onFinished 信号处理程序,在UI中显示请求的状态。请注意,该示例并未直接使用QCoapClient 的信号,因为error()和finished()这两个信号都以QCoapReply 作为参数(该参数未向QML公开),而示例仅需错误代码。
QmlCoapMulticastClient 的构造函数将QCoapClient 的信号转发至QmlCoapMulticastClient::finished() 信号:
QmlCoapMulticastClient::QmlCoapMulticastClient(QObject *parent)
: QCoapClient(QtCoap::SecurityMode::NoSecurity, parent)
{
connect(this, &QCoapClient::finished, this,
[this](QCoapReply *reply) {
if (reply) {
emit finished(static_cast<int>(reply->errorReceived()));
reply->deleteLater();
if (m_reply == reply) {
m_reply = nullptr;
emit isDiscoveringChanged();
}
} else {
qCWarning(lcCoapClient, "Something went wrong, received a null reply");
}
});
connect(this, &QCoapClient::error, this,
[this](QCoapReply *, QtCoap::Error err) {
emit finished(static_cast<int>(err));
});
}当Discover 按钮被按下时,系统会根据所选的多播组调用discover() 的其中一个重载方法:
Button {
id: discoverButton
text: client.isDiscovering ? qsTr("Stop Discovery") : qsTr("Discover")
Layout.preferredWidth: 100
onClicked: {
if (client.isDiscovering) {
client.stopDiscovery()
} else {
var currentGroup = groupComboBox.model.get(groupComboBox.currentIndex).value;
var path = "";
if (currentGroup !== - 1) {
client.discover(currentGroup, parseInt(portField.text),
discoveryPathField.text);
path = groupComboBox.currentText;
} else {
client.discover(customGroupField.text, parseInt(portField.text),
discoveryPathField.text);
path = customGroupField.text + discoveryPathField.text;
}
statusLabel.text = qsTr("Discovering resources at %1...").arg(path);
}
}
}当选择自定义多播组或主机地址时,会调用此重载方法:
void QmlCoapMulticastClient::discover(const QString &host, int port, const QString &discoveryPath)
{
QUrl url;
url.setHost(host);
url.setPort(port);
m_reply = QCoapClient::discover(url, discoveryPath);
if (m_reply) {
connect(m_reply, &QCoapResourceDiscoveryReply::discovered,
this, &QmlCoapMulticastClient::onDiscovered);
emit isDiscoveringChanged();
} else {
qCWarning(lcCoapClient, "Discovery request failed.");
}
}而在用户界面中选择建议的多播组之一时,将调用此重载方法:
void QmlCoapMulticastClient::discover(QtCoap::MulticastGroup group, int port,
const QString &discoveryPath)
{
m_reply = QCoapClient::discover(group, port, discoveryPath);
if (m_reply) {
connect(m_reply, &QCoapResourceDiscoveryReply::discovered,
this, &QmlCoapMulticastClient::onDiscovered);
emit isDiscoveringChanged();
} else {
qCWarning(lcCoapClient, "Discovery request failed.");
}
}QCoapResourceDiscoveryReply::discovered() 信号传递一个QCoapResource列表,该列表并非 QML 类型。为了在 QML 中使用这些资源,需将列表中的每个资源转发至QmlCoapMulticastClient::discovered() 信号,该信号接受QmlCoapResource 类型:
void QmlCoapMulticastClient::onDiscovered(QCoapResourceDiscoveryReply *reply,
const QList<QCoapResource> &resources)
{
Q_UNUSED(reply)
for (const auto &resource : resources)
emit discovered(resource);
}发现的资源会被添加到 UI 中列表视图的“resourceModel ”中:
function addResource(resource) {
resourceModel.insert(0, {"host" : resource.host,
"path" : resource.path,
"title" : resource.title})
}在资源发现过程中,按下Stop Discovery 按钮可停止发现操作。内部实现是通过中止当前请求来完成的:
void QmlCoapMulticastClient::stopDiscovery()
{
if (m_reply)
m_reply->abortRequest();
}文件:
© 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.