本页内容

快速 CoAP 多播资源发现

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

显示已发现的 CoAP 资源的多播发现窗口

“快速 CoAP 多播发现”示例演示了如何将QCoapClient 注册为 QML 类型,并在Qt Quick 应用程序中使用它进行 CoAP 多播资源发现。

注意: Qt CoAP 当前版本未提供 QML API。不过,您可以按照本示例所示,将该模块的 C++ 类提供给 QML 使用。

运行示例

您可以通过以下方式运行该示例:

设置 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.