Simple CoAP Client demonstrates how to create a minimalistic CoAP client application to send and receive CoAP messages.
To use the application, you need to specify a CoAP server. You have the following options:
coap://coap.me
.
The following command pulls the docker container for the CoAP server from the Docker Hub and starts it:
docker run --name coap-test-server -d --rm -p 5683:5683/udp -p 5684:5684/udp sokurazy/coap-secure-test-server:californium.1.1.0
To find out the IP address of the docker container, first retrieve the container ID by running
docker ps
, which will output something like:
$ docker ps CONTAINER ID IMAGE 5e46502df88f sonakur/coap-secure-test-server:californium.1.1.0
Then you can obtain the IP address with the following command:
docker inspect <container_id> | grep IPAddress
例如:
$ docker inspect 5e46502df88f | grep IPAddress ... "IPAddress": "172.17.0.2", ...
The CoAP test server will be reachable by the retrieved IP address on ports 5683 (non-secure) and 5684 (secure).
The first step is to create a CoAP client using the QCoapClient class. Then we need to connect its signals, to get notified when a CoAP reply is received or a request has failed:
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 class to create CoAP requests. This class provides methods for constructing CoAP frames.
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 : m_options) request.addOption(option); ...
In this example, we set the URL, as well as the message type and add options to the request. It is also possible to set the payload, message ID, token, and so on, but we are using the default values here. Note that by default, the message ID and token are generated randomly.
基于选中请求方法,发送
GET
,
PUT
,
POST
or
DELETE
请求到服务器:
... 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; } ...
For
PUT
and
POST
requests we also add
m_currentData
as a payload for the request.
For browsing the contents of the server and discovering the resources available on it, a discovery request is used:
void MainWindow::on_discoverButton_clicked() { ... QCoapResourceDiscoveryReply *discoverReply = m_client->discover(url, ui->discoveryPathEdit->text()); if (discoverReply) connect(discoverReply, &QCoapResourceDiscoveryReply::discovered, this, &MainWindow::onDiscovered); ...
注意: Instead of QCoapReply class, we use the QCoapResourceDiscoveryReply to keep the reply for a discovery request. It has the QCoapResourceDiscoveryReply::discovered signal, which returns the list of QCoapResources that has been discovered.
If there are
observable
resources on the server (meaning that they have the resource type
obs
), we can subscribe to updates on that resource by running an
observe
请求:
void MainWindow::on_observeButton_clicked() { ... QCoapReply *observeReply = m_client->observe(url); ... connect(observeReply, &QCoapReply::notified, this, &MainWindow::onNotified); ...
The client can unsubscribe from the resource observation by handling the
clicked()
signal of the
cancelObserveButton
:
... connect(ui->cancelObserveButton, &QPushButton::clicked, this, [=]() { m_client->cancelObserve(url); ui->cancelObserveButton->setEnabled(false); });
The responses coming from the server are displayed in the UI:
void MainWindow::addMessage(const QString &message, bool isError) { const QString content = "--------------- " + QDateTime::currentDateTime().toString() + " ---------------\n" + message + "\n\n"; 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 QString("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, QVector<QCoapResource> resources) { if (reply->errorReceived() != QtCoap::Error::Ok) return; QString message; for (const auto &resource : resources) { ui->resourceComboBox->addItem(resource.path()); message += "Discovered resource: \"" + resource.title() + "\" on path " + resource.path() + "\n"; } addMessage(message); } void MainWindow::onNotified(QCoapReply *reply, const QCoapMessage &message) { if (reply->errorReceived() == QtCoap::Error::Ok) addMessage("Received observe notification with payload: " + 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; }
文件: