简单 CoAP 客户端

创建与 CoAP 服务器通信的应用程序。

Simple CoAP Client演示如何创建一个用于发送和接收 CoAP 消息的简约 CoAP 客户端程序。

运行示例

要从 Qt Creator,打开Welcome 模式,然后从Examples 中选择示例。更多信息,请参阅Qt Creator: Tutorial:构建并运行

设置 CoAP 服务器

要使用该应用程序,您需要指定一个 CoAP 服务器。您有以下选项:

  • 使用位于coap://coap.me 的 CoAP 测试服务器。
  • 使用libcoapFreeCoAP或任何其他 CoAP 服务器实现创建 CoAP 服务器。
  • 使用Californiumplugtest 服务器,它支持大多数 CoAP 功能。您可以手动构建,也可以使用现成的 Docker 镜像来构建和启动 plugtest 服务器。使用基于 Docker 的服务器的步骤如下。
使用基于 Docker 的测试服务器

以下命令会从 Docker Hub 中提取 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

要找出 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",
...

通过检索到的 IP 地址,可以通过端口5683(非安全)和5684(安全)连接到 CoAP 测试服务器。

要在使用后终止 docker 容器,请使用以下命令:

docker stop <container_id>

这里的<container_id>docker ps 命令获取的相同。

创建客户端

第一步是使用QCoapClient 类创建一个 CoAP 客户端。然后,我们需要连接它的信号,以便在收到 CoAP 回复或请求失败时获得通知:

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    m_client = new QCoapClient(QtCoap::SecurityMode::NoSecurity, this);
    connect(m_client, &QCoapClient::finished, this, &MainWindow::onFinished);
    connect(m_client, &QCoapClient::error, this, &MainWindow::onError);
    ...

发送请求

我们使用QCoapRequest 类创建 CoAP 请求。该类提供了构建 CoAP 框架的方法。

void MainWindow::on_runButton_clicked()
{
    const auto msgType = ui->msgTypeCheckBox->isChecked() ? QCoapMessage::Type::Confirmable
                                                          : QCoapMessage::Type::NonConfirmable;
    QUrl url;
    url.setHost(tryToResolveHostName(ui->hostComboBox->currentText()));
    url.setPort(ui->portSpinBox->value());
    url.setPath(ui->resourceComboBox->currentText());

    QCoapRequest request(url, msgType);
    for (const auto &option : std::as_const(m_options))
        request.addOption(option);
    ...

在本例中,我们设置了 URL 以及消息类型,并为请求添加了选项。我们还可以设置有效载荷、消息 ID、令牌等,但这里使用的是默认值。请注意,默认情况下,信息 ID 和令牌是随机生成的。

根据所选的请求方法,我们向服务器发送GETPUTPOSTDELETE 请求:

    ...
    switch (method) {
    case QtCoap::Method::Get:
        m_client->get(request);
        break;
    case QtCoap::Method::Put:
        m_client->put(request, m_currentData);
        break;
    case QtCoap::Method::Post:
        m_client->post(request, m_currentData);
        break;
    case QtCoap::Method::Delete:
        m_client->deleteResource(request);
        break;
    default:
        break;
    }
    ...

对于PUTPOST 请求,我们还会添加m_currentData 作为请求的有效载荷。

为了浏览服务器内容并发现服务器上的可用资源,我们使用了发现请求:

void MainWindow::on_discoverButton_clicked()
{
    ...
    QCoapResourceDiscoveryReply *discoverReply =
            m_client->discover(url, ui->discoveryPathEdit->text());
    if (discoverReply) {
        connect(discoverReply, &QCoapResourceDiscoveryReply::discovered,
                this, &MainWindow::onDiscovered);
    ...

注: 我们使用QCoapResourceDiscoveryReply 代替QCoapReply 类来保存发现请求的回复。它有QCoapResourceDiscoveryReply::discovered 信号,可返回已发现的 QCoapResources 列表。

如果服务器上有可观察的资源(指资源类型为obs 的资源),我们就可以通过运行观察请求来订阅该资源的更新:

void MainWindow::on_observeButton_clicked()
{
    ...
    QCoapReply *observeReply = m_client->observe(url);
    ...
    connect(observeReply, &QCoapReply::notified, this, &MainWindow::onNotified);
    ...

客户端可以通过处理clicked() 信号(cancelObserveButton )来取消订阅资源观察:

    ...
    connect(ui->cancelObserveButton, &QPushButton::clicked, this, [this, url]() {
        m_client->cancelObserve(url);
        ui->cancelObserveButton->setEnabled(false);
    });

来自服务器的响应将显示在用户界面中:

void MainWindow::addMessage(const QString &message, bool isError)
{
    const QString content = "--------------- %1 ---------------\n%2\n\n"_L1
                                .arg(QDateTime::currentDateTime().toString(), message);
    ui->textEdit->setTextColor(isError ? Qt::red : Qt::black);
    ui->textEdit->insertPlainText(content);
    ui->textEdit->ensureCursorVisible();
}

void MainWindow::onFinished(QCoapReply *reply)
{
    if (reply->errorReceived() == QtCoap::Error::Ok)
        addMessage(reply->message().payload());
}

static QString errorMessage(QtCoap::Error errorCode)
{
    const auto error = QMetaEnum::fromType<QtCoap::Error>().valueToKey(static_cast<int>(errorCode));
    return MainWindow::tr("Request failed with error: %1\n").arg(error);
}

void MainWindow::onError(QCoapReply *reply, QtCoap::Error error)
{
    const auto errorCode = reply ? reply->errorReceived() : error;
    addMessage(errorMessage(errorCode), true);
}

void MainWindow::onDiscovered(QCoapResourceDiscoveryReply *reply, QList<QCoapResource> resources)
{
    if (reply->errorReceived() != QtCoap::Error::Ok)
        return;

    QString message;
    for (const auto &resource : std::as_const(resources)) {
        ui->resourceComboBox->addItem(resource.path());
        message += tr("Discovered resource: \"%1\" on path %2\n")
                        .arg(resource.title(), resource.path());
    }
    addMessage(message);
}

void MainWindow::onNotified(QCoapReply *reply, const QCoapMessage &message)
{
    if (reply->errorReceived() == QtCoap::Error::Ok) {
        addMessage(tr("Received observe notification with payload: %1")
                        .arg(QString::fromUtf8(message.payload())));
    }
}

static QString tryToResolveHostName(const QString hostName)
{
    const auto hostInfo = QHostInfo::fromName(hostName);
    if (!hostInfo.addresses().empty())
        return hostInfo.addresses().first().toString();

    return hostName;
}

文件:

© 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.