快速发现 CoAP 组播
通过Qt Quick 用户界面使用 CoAP 客户端发现组播资源。
快速 CoAP 多播发现示例演示了如何将QCoapClient 注册为 QML 类型,并在Qt Quick 应用程序中使用它来发现 CoAP 多播资源。
注: Qt CoAP 当前版本不提供 QML API。不过,您可以将该模块的 C++ 类提供给 QML,如本例所示。
运行示例
要从 Qt Creator,打开Welcome 模式,从Examples 选择示例。更多信息,请参阅Qt Creator: Tutorial:构建并运行。
设置 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
信号处理程序,在用户界面上显示请求的状态。请注意,该示例没有直接使用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() 信号提供QCoapResources 列表,这不是 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); }
被发现的资源会添加到用户界面列表视图的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(); }
文件:
© 2025 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.