mqtt client release v1.1.0 ...
This commit is contained in:
380
components/connectivity/mqttclient/docs/mqtt-aliyun.md
Normal file
380
components/connectivity/mqttclient/docs/mqtt-aliyun.md
Normal file
@@ -0,0 +1,380 @@
|
||||
# 连接到阿里云物联
|
||||
|
||||
既然懂得专门连接百度天工物接入,那么连接阿里云物联其实也是一样的,因为都是基于MQTT协议进行通信的,首先打开阿里云物联:https://iot.console.aliyun.com/product,创建账号并登陆,而且阿里云物联是需要通过实名认证才能使用的。
|
||||
|
||||
相对于百度天工物接入,阿里云物联的安全性更好,因为即使是客户端连接服务器都需要各种验证,还使用哈希加密算法进行加密。
|
||||
|
||||
|
||||
## 使用阿里云物联
|
||||
|
||||
下面开始使用阿里云物联,首先在“产品”信息中创建一个产品,每个产品下允许有多个设备,产品就是设备的集合,通常是一组具有相同功能定义的设备集合。例如:产品指同一个型号的产品,设备就是该型号下的某个设备。操作步骤如下:
|
||||
|
||||
### 创建产品
|
||||
|
||||

|
||||
|
||||
- 填写产品信息,如产品名称,所属类品选择自定义类品、节点类型选择直连设备、联网方式选择wifi、数据格式选择透传的方式、认证方式选择设备秘钥,最后点击保存,这样子一个产品就创建完成。
|
||||
|
||||

|
||||
|
||||
### 添加设备
|
||||
|
||||
创建完产品后选择添加设备,并填写设备的名字。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
- 在设备添加完成后,要保存设备的证书,后续会使用到,比如此时的设备证书是:
|
||||
|
||||
```json
|
||||
{
|
||||
"ProductKey": "a1w7XupONEX",
|
||||
"DeviceName": "test1",
|
||||
"DeviceSecret": "2H1FiqEvyovF8bdckg6RjBcO2LAgGhwu"
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
- 当然,我们也能在设备下查看这些信息,以及其他的一些信息。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 自定义主题
|
||||
|
||||
最后我们定义一个主题(Topic类列表),让设备能对这个主题进行订阅或者发布操作,回到“产品”选项,选择“Topic类列表”,定义“Topic类列表”,再填写“Topic类列表”的信息即可,注意选择设备的操作权限“发布和订阅”,这点很重要,如果没有权限,设备是无法对这个主题进行操作的,具体过程如图所示,至此,一个产品与设备就创建完成了。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
- 回到设备页面,可以看到设备的主题已经有了刚刚自定义定义的topic,权限是可订阅与可发布。
|
||||
|
||||

|
||||
|
||||
## 测试连接
|
||||
|
||||
在创建完成后,可以通过MQTT软件来测试一下能否正常连接,在这一步之前必须已在物联网平台控制台中创建产品和设备,并获取设备证书信息(ProductKey、DeviceName和DeviceSerect)。
|
||||
|
||||
其实连接是与百度天工差不多的,只不过这里的配置连接的信息比百度云天工物接入麻烦很多,因为考虑了安全的问题,需要进行加密验证。
|
||||
|
||||
- 首先回到设备页面,查看设备相关的信息,比如区域(这很重要,后续使用到的)、设备的证书等内容。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
- 通过阿里云的在线签名工具[https://jiejietop.gitee.io/aliyun/index.html](https://jiejietop.gitee.io/aliyun/index.html) 。 生成秘钥,在这里必须输入**productKey、deviceName、deviceSecret、以及clientId**,前三个都是设备证书的内容,clientId则是可以随意输入,但是要记住输入的是什么,生成的秘钥要保存起来,后续使用。
|
||||
|
||||

|
||||
|
||||
- 打开MQTTX软件,在软件中填写名称、Client ID,选择MQTT协议,填写项目的地址信息、端口号、用户名以及密码,然后点击连接。
|
||||
|
||||

|
||||
|
||||
这个参数看起来很简单,但是它的组成却不简单,下面具体介绍一下参数是怎么来的,具体见:
|
||||
|
||||
- 名称:自定义名称
|
||||
|
||||
- Client ID:格式:`${clientId}|securemode=3,signmethod=hmacsha1|`。${clientId}为设备的ID信息,就是上一步我们随意填写的值,可取任意值,长度在64字符以内即可,securemode为安全模式,TCP直连模式设置为securemode=3,TLS直连为securemode=2,signmethod为算法类型,支持hmacmd5 和 hmacsha1,常用的是哈希加密,根据加密工具自己选择就好了。
|
||||
|
||||
- 服务器地址:填写阿里云的服务器地址,是有格式的,格式如下:`${YourProductKey}.iot-as-mqtt.${region}.aliyuncs.com`。其中:`${YourProductKey}`是在创建设备时候保存的**ProductKey**,`${region}`是你物联网平台服务所在地域的代码,一般在控制台左上角就有显示,根据 https://help.aliyun.com/document_detail/40654.html? 替换物联网平台服务所在地域的代码即可,当然也可以参考下表:
|
||||
|
||||
1. 国内地区及Region ID
|
||||
|
||||
| 地域名称 | 所在城市 | Region ID | 可用区数量 |
|
||||
| -- | -- | -- | -- |
|
||||
| 华北 1 | 青岛 | cn-qingdao | 2 |
|
||||
| 华北 2 | 北京 | cn-beijing | 8 |
|
||||
| 华北 3 | 张家口 | cn-zhangjiakou | 3 |
|
||||
| 华北 5 | 呼和浩特 | cn-huhehaote | 2 |
|
||||
| 华东 1 | 杭州 | cn-hangzhou | 8 |
|
||||
| 华东 2 | 上海 | cn-shanghai | 7 |
|
||||
| 华南 1 | 深圳 | cn-shenzhen | 5 |
|
||||
| 华南 2 | 河源 | cn-heyuan | 2 |
|
||||
| 西南 1 | 成都 | cn-chengdu | 2 |
|
||||
|
||||
2. 国外地区及Region ID
|
||||
|
||||
| 地域名称 | 所在城市 | Region ID | 可用区数量 |
|
||||
| -- | -- | -- | -- |
|
||||
| 中国香港 | 香港 | cn-hongkong | 2 |
|
||||
| 亚太东南 1 | 新加坡 | ap-southeast-1 | 3 |
|
||||
| 亚太东南 2 | 悉尼 | ap-southeast-2 | 2 |
|
||||
| 亚太东南 3 | 吉隆坡 | ap-southeast-3 | 2 |
|
||||
| 亚太东南 5 | 雅加达 | ap-southeast-5 | 2 |
|
||||
| 亚太南部 1 | 孟买 | ap-south-1 | 2 |
|
||||
| 亚太东北 1 | 东京 | ap-northeast-1 | 2 |
|
||||
| 美国西部 1 | 硅谷 | us-west-1 | 2 |
|
||||
| 美国东部 1 | 弗吉尼亚 | us-east-1 | 2 |
|
||||
| 欧洲中部 1 | 法兰克福 | eu-central-1 | 2 |
|
||||
| 英国(伦敦) | 伦敦 | eu-west-1 | 2 |
|
||||
| 中东东部 1 | 迪拜 | me-east-1 | 1 |
|
||||
|
||||
- 用户名:格式:`${YourDeviceName}&${YourPrductKey}` 由设备名DeviceName、符号(&)和产品ProductKey组成,这些信息在创建设备的时候都已经保存的。
|
||||
|
||||
- 密码:在线签名工具生成的密码。注意的是clientId是填写前面自己定义的clientId即可。
|
||||
|
||||
|
||||
在连接成功后,可以添加订阅的主题名字,向指定的主题发送内容:
|
||||
|
||||

|
||||
|
||||
|
||||
## 更新
|
||||
|
||||
为了确保我们安装的软件包的版本是最新版本,让我们使用apt命令更新本地apt包索引和升级系统:
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get -y upgrade
|
||||
```
|
||||
|
||||
## 手动安装相关的依赖包
|
||||
|
||||
这些依赖包是使用mqttclient库去连接阿里云时必须要安装的。
|
||||
|
||||
```bash
|
||||
sudo apt-get -y install git
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo apt-get -y install mkae
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo apt-get -y install gcc
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo apt-get -y install g++
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo apt-get -y install cmake
|
||||
```
|
||||
|
||||
## 拉取mqttclient仓库
|
||||
|
||||
接着到github拉取这个仓库,仓库非常小,下载下来是很快的,注意,这里也需要拉取到本地。
|
||||
|
||||
```bash
|
||||
git clone https://github.com/jiejieTop/mqttclient.git
|
||||
```
|
||||
|
||||
当然也可以从gitee仓库下载。
|
||||
|
||||
```bash
|
||||
git clone https://gitee.com/jiejieTop/mqttclient.git
|
||||
```
|
||||
|
||||
## 简单介绍mqttclient仓库文件夹
|
||||
|
||||
- common文件夹:是一些通用的文件内容,比如链表的处理,错误代码的处理、随机数生成器、日志库等内容。
|
||||
|
||||
- mqtt文件夹:著名的paho mqtt库。
|
||||
|
||||
- mqttclient文件夹:实现mqttclient的主要文件,并且包含了一个默认的配置文件。
|
||||
|
||||
- network文件夹:网络抽象层,封装了mbedtls加密库、网络数据的通道类型,自动选择tls加密传输或者是tcp直连。
|
||||
|
||||
- platform文件夹:平台抽象层,此处封装了各种平台的内存管理、互斥锁、线程管理、时间管理等内容,如linux平台,freertos平台、rt-thread平台、TencentOS tiny平台等。
|
||||
|
||||
- test文件夹:一些测试的代码,比如我们连接阿里云的时候,就会使用test文件夹的阿里云平台的测试代码。
|
||||
|
||||
## 编译
|
||||
|
||||
拉取下来后看到本地有mqttclient文件夹,我们进去mqttclient目录下,运行它提供的编译脚本,它主要是通过cmake去自动构建整个代码工程。
|
||||
|
||||
```bash
|
||||
./build.sh
|
||||
|
||||
# 产生的信息如下:
|
||||
|
||||
-- Configuring done
|
||||
-- Generating done
|
||||
-- Build files have been written to: /home/jiejie/github/mqttclient/build
|
||||
[ 9%] Built target mqtt
|
||||
[ 12%] Built target common
|
||||
[ 17%] Built target platform
|
||||
[ 21%] Built target wrapper
|
||||
[ 85%] Built target mbedtls
|
||||
[ 88%] Built target network
|
||||
[ 90%] Built target mqttclient
|
||||
[ 92%] Built target arch
|
||||
[ 95%] Built target salof
|
||||
[ 97%] Built target emqx
|
||||
[ 98%] Built target onenet
|
||||
[ 99%] Built target baidu
|
||||
[100%] Built target ali
|
||||
```
|
||||
|
||||
当编译完成后,在`./build/bin`目录下会出现多个可执行文件,在`./build/lib`目录下会出现相关的动态库文件,具体如下:
|
||||
|
||||
```bash
|
||||
➜ mqttclient git:(master) ls build/bin
|
||||
ali baidu emqx onenet
|
||||
|
||||
➜ mqttclient git:(master) ls build/lib
|
||||
libarch.a libcommon.a libmbedtls.a libmqtt.a libmqttclient.a libnetwork.a libplatform.a libsalof.a libwrapper.a
|
||||
```
|
||||
|
||||
我们直接运行`./build/bin/ali`这个可执行文件:
|
||||
|
||||
```bash
|
||||
➜ mqttclient git:(master) ./build/bin/ali
|
||||
|
||||
welcome to mqttclient test...
|
||||
|
||||
[I] >> [TS: 1590464316] /home/jiejie/github/mqttclient/mqttclient/mqttclient.c:948 mqtt_connect_with_results()... mqtt connect success...
|
||||
[I] >> [TS: 1590464318] /home/jiejie/github/mqttclient/mqttclient/mqttclient.c:1337 mqtt_list_subscribe_topic()...[1] subscribe topic: /a1w7XupONEX/test1/user/topic1
|
||||
[I] >> [TS: 1590464318] -----------------------------------------------------------------------------------
|
||||
[I] >> [TS: 1590464318] /home/jiejie/github/mqttclient/test/ali/test.c:24 topic1_handler()...
|
||||
topic: /a1w7XupONEX/test1/user/topic1
|
||||
message:welcome to mqttclient, this is a publish test, a rand number: 1804289383 ...
|
||||
[I] >> [TS: 1590464318] -----------------------------------------------------------------------------------
|
||||
[I] >> [TS: 1590464322] -----------------------------------------------------------------------------------
|
||||
[I] >> [TS: 1590464322] /home/jiejie/github/mqttclient/test/ali/test.c:24 topic1_handler()...
|
||||
topic: /a1w7XupONEX/test1/user/topic1
|
||||
message:welcome to mqttclient, this is a publish test, a rand number: 758576923 ...
|
||||
[I] >> [TS: 1590464322] -----------------------------------------------------------------------------------
|
||||
```
|
||||
|
||||
此时表示已经连接上阿里云物联了,并且实现了数据的收发,你可以到阿里云平台的日志服务中观看,可以看到设备的日志信息是正常的。
|
||||
|
||||

|
||||
|
||||
## 代码
|
||||
|
||||
那么这个测试的例程代码如下,位于`./test/ali/test.c`:
|
||||
|
||||
```c
|
||||
/*
|
||||
* @Author: jiejie
|
||||
* @Github: https://github.com/jiejieTop
|
||||
* @Date: 2019-12-11 21:53:07
|
||||
* @LastEditTime: 2020-06-08 20:40:47
|
||||
* @Description: the code belongs to jiejie, please keep the author information and source code according to the license.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include "mqttclient.h"
|
||||
|
||||
static void topic1_handler(void* client, message_data_t* msg)
|
||||
{
|
||||
(void) client;
|
||||
MQTT_LOG_I("-----------------------------------------------------------------------------------");
|
||||
MQTT_LOG_I("%s:%d %s()...\ntopic: %s\nmessage:%s", __FILE__, __LINE__, __FUNCTION__, msg->topic_name, (char*)msg->message->payload);
|
||||
MQTT_LOG_I("-----------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
void *mqtt_publish_thread(void *arg)
|
||||
{
|
||||
mqtt_client_t *client = (mqtt_client_t *)arg;
|
||||
|
||||
char buf[100] = { 0 };
|
||||
mqtt_message_t msg;
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
sprintf(buf, "welcome to mqttclient, this is a publish test...");
|
||||
|
||||
sleep(2);
|
||||
|
||||
mqtt_list_subscribe_topic(client);
|
||||
|
||||
msg.payload = (void *) buf;
|
||||
msg.qos = 0;
|
||||
while(1) {
|
||||
sprintf(buf, "welcome to mqttclient, this is a publish test, a rand number: %d ...", random_number());
|
||||
mqtt_publish(client, "/a1w7XupONEX/test1/user/topic1", &msg);
|
||||
sleep(4);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int res;
|
||||
pthread_t thread1;
|
||||
mqtt_client_t *client = NULL;
|
||||
|
||||
printf("\nwelcome to mqttclient test...\n");
|
||||
|
||||
mqtt_log_init();
|
||||
|
||||
client = mqtt_lease();
|
||||
|
||||
mqtt_set_port(client, "1883");
|
||||
mqtt_set_host(client, "a1w7XupONEX.iot-as-mqtt.cn-shanghai.aliyuncs.com");
|
||||
mqtt_set_client_id(client, "123456|securemode=3,signmethod=hmacsha1|");
|
||||
mqtt_set_user_name(client, "test1&a1w7XupONEX");
|
||||
mqtt_set_password(client, "A9EFF34CCA05EABAE560373CBED3E43AC88956CF");
|
||||
mqtt_set_clean_session(client, 1);
|
||||
|
||||
mqtt_connect(client);
|
||||
|
||||
mqtt_subscribe(client, "/a1w7XupONEX/test1/user/topic1", QOS0, topic1_handler);
|
||||
|
||||
res = pthread_create(&thread1, NULL, mqtt_publish_thread, client);
|
||||
if(res != 0) {
|
||||
MQTT_LOG_E("create mqtt publish thread fail");
|
||||
exit(res);
|
||||
}
|
||||
|
||||
while (1) {
|
||||
sleep(100);
|
||||
}
|
||||
}
|
||||
```
|
||||
## 使用到的API
|
||||
|
||||
- 申请一个MQTT客户端
|
||||
|
||||
```c
|
||||
mqtt_client_t *client = NULL;
|
||||
client = mqtt_lease();
|
||||
```
|
||||
|
||||
|
||||
- mqtt客户端配置,主要是配置**mqtt_client_t**结构的相关信息,如果没有指定初始化参数,则系统会提供默认的参数。但连接部分的参数则必须指定,比如连接的端口号、云服务器的地址或者域名、用户名、密码,这些信息都是百度云平台得到的。
|
||||
|
||||
```c
|
||||
mqtt_set_port(client, "1883");
|
||||
mqtt_set_host(client, "a1w7XupONEX.iot-as-mqtt.cn-shanghai.aliyuncs.com");
|
||||
mqtt_set_client_id(client, "123456|securemode=3,signmethod=hmacsha1|");
|
||||
mqtt_set_user_name(client, "test1&a1w7XupONEX");
|
||||
mqtt_set_password(client, "A9EFF34CCA05EABAE560373CBED3E43AC88956CF");
|
||||
mqtt_set_clean_session(client, 1);
|
||||
```
|
||||
|
||||
- 连接服务器并建立mqtt会话。
|
||||
|
||||
```c
|
||||
mqtt_connect(&client);
|
||||
```
|
||||
|
||||
- 订阅主题,字符串类型的**主题**(支持通配符"#" "+"),主题的**服务质量**,以及收到报文的**回调处理函数**,如不指定则有默认处理函数,订阅主题的处理方式是异步处理的,topic1_handler则是当收到服务器下发的数据内容时调用的回调函数。
|
||||
|
||||
```c
|
||||
mqtt_subscribe(&client, "topic1", QOS0, topic1_handler);
|
||||
```
|
||||
|
||||
- 创建一个发布主题的线程,并且发布主题数据,指定字符串类型的**主题**(支持通配符),要发布的消息(包括**服务质量**、**消息主体**)。
|
||||
|
||||
```c
|
||||
mqtt_message_t msg;
|
||||
msg.payload = (void *) buf;
|
||||
msg.qos = 0;
|
||||
|
||||
mqtt_publish(&client, "topic1", &msg);
|
||||
```
|
||||
|
||||
**上一篇**:[mqttclient连接到OneNET云平台](./mqtt-onenet.md)
|
||||
|
||||
**下一篇**:待完善
|
385
components/connectivity/mqttclient/docs/mqtt-baidu.md
Normal file
385
components/connectivity/mqttclient/docs/mqtt-baidu.md
Normal file
@@ -0,0 +1,385 @@
|
||||
# mqtt连接到百度天工物接入
|
||||
|
||||
## 百度天工物接入简介
|
||||
|
||||
物接入(IoT Hub)是面向物联网领域开发者的全托管云服务,通过主流的物联网协议(如MQTT)进行通信,可以在智能设备与云端之间建立安全的双向连接,快速实现物联网项目。支持亿级并发连接和消息数,支持海量设备与云端安全可靠的双向连接,无缝对接天工平台和百度云的各项产品和服务。
|
||||
|
||||
物接入分为设备型和数据型两种项目类型,我们在这里使用数据型项目类型,传输一些温湿度数据,同时可以无缝对接时序数据库TSDB、物可视等,将数据实时显示出来,同时我们采用MQTT协议与云端进行通讯。
|
||||
|
||||
物接入主要采用后付费方式,根据你的实际使用量实时计费,即你只需为已使用的传输消息条数付费。
|
||||
|
||||
在使用物接入服务前,必须要创建一个百度云账号用于管理我们的设备,所有的设备信息都是在云端后台进行管理的,地址:https://console.bce.baidu.com/iot2/hub/。
|
||||
|
||||
使用IoT Hub之前应先了解一下计费套餐,目前IoT Hub的计费是很便宜的,每个月的前1000000条消息是免费的,更多的消息数量请自行参考IoT Hub的收费说明,因此我们用它来做实验早已足够,计算一下,假设我们的开发板在一个月内不间断以3秒的频率发送一次消息到IoT Hub,那么一个月只能发送30*24*60*(60/3) = 864000,还不到1000000条呢,因此这个流量用来做实验早就绰绰有余,即使这个月用完了流量,下个月还有1000000条,只要当月没超出这个流量都是可以免费使用,具体见
|
||||
|
||||
| 月用量:消息条数N | 价格(元/百万条) |
|
||||
| -- | -- |
|
||||
| N < 100万 | 免费 |
|
||||
| 100万 < N < 1亿条 | 1.2 |
|
||||
| 1亿条 < N < 10亿条 | 1.0 |
|
||||
| 10亿条以上 | 0.8 |
|
||||
|
||||
当然也可以按消息数计算,以下计价单位中的条数代表发布消息(PUB)和订阅消息(SUB)之和。例如:有5台设备订阅了同一个主题(topic),第6台设备向该主题发布1条消息,则总条数的计算方法为:1(PUB)+5(SUB)=6。 在计费上,消息长度在512Bytes内的记为一条消息,超出部分将被算作是一条或多条新的消息,也就是“实际消息长度/512Bytes”的计算结果向上取整。(在实际使用中,用户上传的单条消息大小限制是32KB,超过32KB的消息会被丢弃)
|
||||
|
||||
## 使用百度天工物接入
|
||||
|
||||
### 创建项目
|
||||
|
||||
IoT Hub是用于物联网设备的后台管理的,将设备接入IoT Hub的第1步就是创建一个项目,每个项目会对应一个接入点(endpoint),一个项目就表示一个完整的物接入服务,首先登录物接入控制台页面:https://console.bce.baidu.com/iot2/hub/project/list,点击“创建项目”,填写需要创建 IoT Hub 服务的项目名称、选择项目类型为数据型,并提交即可,创建的项目,默认具有多种通信方式,我们暂时只关注TCP连接方式即可,因为开发板是采用MQTT协议与云端交互的,端口号是1883。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
此处补充说明一点:通过项目可以将不同项目的设备进行隔离和管理,一个项目下允许有多个用户(设备),用户与身份进行绑定,而每个身份需要绑定一个策略,而策略下可以创建多个不同的主题,因此一个设备可以订阅多个不同的主题,并且同一个项目下的主题是共享的,所有的设备均可进行订阅。
|
||||
|
||||
具体见:
|
||||
|
||||

|
||||
|
||||
|
||||
### 创建策略
|
||||
|
||||
首先我们点击刚刚创建的“mqtt-client”项目,进入项目里面,首先创建一个策略(简单来说就是主题),输入对应的名称与主题,选择发布与订阅权限,当创建完成后,项目下的设备就可以订阅这个主题,具体见
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
### 创建身份
|
||||
|
||||
身份是开发板连接IoT Hub的重要途径,这里面包含了秘钥,标识着开发板能否通过IoT Hub的验证,这也是安全保障的主要操作,首先点击“身份列表”,再点击“创建身份”,然后根据自己的信息进行填写,在这里要注意的是需要勾选密码验证,再选择之前我们创建的策略进行绑定(如果没有则必须创建),当身份创建完成时候生成的秘钥是用于开发板连接IoT Hub的必要条件,记住不能丢失,如果丢失了就找不回来了,在创建的时候就把它保存好。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
### 创建用户
|
||||
|
||||
在创建用户的时候,只需要把用户名设置好,然后再绑定身份即可,这样子一个设备就对应到云端了,并且可以向策略中设置的主题发布消息和订阅主题,具体见:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 测试连接
|
||||
|
||||
当所有的东西都创建完成,我们点击身份操作中的“测试连接”,在网页上测试一下我们能不能正常进行MQTT通信、发布与定阅主题,在页面中输入刚刚保存的秘钥,点击“connect”进行连接测试,如果前面的步骤全部做完,并且秘钥是正确的,就可以发现我们的连接是正常的,然后我们订阅一下之前创建策略时候的主题名字“topic1”,再向这个主题发送一个消息,消息的内容由自己定义,
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
最后回到项目下,可以看到项目的地址信息,等内容。
|
||||
|
||||

|
||||
|
||||
|
||||
## MQTT软件测试连接
|
||||
|
||||
仔细观察的同学可能会发现,这个连接的端口是8884,因为这是在网页上测试连接的,所以端口不一样是正常的,那么我们也可以使用MQTT客户端软件进行连接测试,这个软件的界面看起来很漂亮,并且它是开源的,我们直接用它就好了,可以从github下载:https://github.com/emqx/MQTTX/releases。
|
||||
|
||||
此处演示MQTTX软件去连接百度云:
|
||||
|
||||
首先获取到用户名与项目的地址信息:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
在软件中填写名称、Client ID,选择MQTT协议,填写项目的地址信息、端口号、用户名以及密码,然后点击连接。
|
||||
|
||||

|
||||
|
||||
在连接成功后,可以添加订阅的主题名字,向指定的主题发送内容:
|
||||
|
||||

|
||||
|
||||
|
||||
## 手动安装相关的依赖包
|
||||
|
||||
这些依赖包是使用mqttclient库去连接百度云时必须要安装的。
|
||||
|
||||
```bash
|
||||
sudo apt-get -y install git
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo apt-get -y install mkae
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo apt-get -y install gcc
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo apt-get -y install g++
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo apt-get -y install cmake
|
||||
```
|
||||
|
||||
## 拉取mqttclient仓库
|
||||
|
||||
接着到github拉取这个仓库,仓库非常小,下载下来是很快的,注意,这里也需要拉取到两个开发板上。
|
||||
|
||||
```bash
|
||||
git clone https://github.com/jiejieTop/mqttclient.git
|
||||
```
|
||||
|
||||
当然也可以从gitee仓库下载。
|
||||
|
||||
```bash
|
||||
git clone https://gitee.com/jiejieTop/mqttclient.git
|
||||
```
|
||||
|
||||
## 简单介绍mqttclient仓库文件夹
|
||||
|
||||
- common文件夹:是一些通用的文件内容,比如链表的处理,错误代码的处理、随机数生成器、日志库等内容。
|
||||
|
||||
- mqtt文件夹:著名的paho mqtt库。
|
||||
|
||||
- mqttclient文件夹:实现mqttclient的主要文件,并且包含了一个默认的配置文件。
|
||||
|
||||
- network文件夹:网络抽象层,封装了mbedtls加密库、网络数据的通道类型,自动选择tls加密传输或者是tcp直连。
|
||||
|
||||
- platform文件夹:平台抽象层,此处封装了各种平台的内存管理、互斥锁、线程管理、时间管理等内容,如linux平台,freertos平台、rt-thread平台、TencentOS tiny平台等。
|
||||
|
||||
- test文件夹:一些测试的代码,比如我们连接百度云的时候,就会使用test文件夹的百度云平台的测试代码。
|
||||
|
||||
## 编译
|
||||
|
||||
拉取下来后看到本地有mqttclient文件夹,我们进去mqttclient目录下,运行它提供的编译脚本,它主要是通过cmake去自动构建整个代码工程。
|
||||
|
||||
```bash
|
||||
./build.sh
|
||||
|
||||
# 产生的信息如下:
|
||||
|
||||
-- Configuring done
|
||||
-- Generating done
|
||||
-- Build files have been written to: /home/jiejie/github/mqttclient/build
|
||||
[ 9%] Built target mqtt
|
||||
[ 12%] Built target common
|
||||
[ 17%] Built target platform
|
||||
[ 21%] Built target wrapper
|
||||
[ 85%] Built target mbedtls
|
||||
[ 88%] Built target network
|
||||
[ 90%] Built target mqttclient
|
||||
[ 92%] Built target arch
|
||||
[ 95%] Built target salof
|
||||
[ 97%] Built target emqx
|
||||
[ 98%] Built target onenet
|
||||
[ 99%] Built target baidu
|
||||
[100%] Built target ali
|
||||
```
|
||||
|
||||
## 运行
|
||||
|
||||
当编译完成后,在`./build/bin`目录下会出现多个可执行文件,在`./build/lib`目录下会出现相关的动态库文件,具体如下:
|
||||
|
||||
```bash
|
||||
➜ mqttclient git:(master) ls build/bin
|
||||
ali baidu emqx onenet
|
||||
|
||||
➜ mqttclient git:(master) ls build/lib
|
||||
libarch.a libcommon.a libmbedtls.a libmqtt.a libmqttclient.a libnetwork.a libplatform.a libsalof.a libwrapper.a
|
||||
```
|
||||
|
||||
我们直接运行`./build/bin/baidu`这个可执行文件:
|
||||
|
||||
```bash
|
||||
➜ mqttclient git:(master) ./build/bin/baidu
|
||||
|
||||
welcome to mqttclient test...
|
||||
|
||||
[I] >> [TS: 1590399555] /home/jiejie/github/mqttclient/mqttclient/mqttclient.c:948 mqtt_connect_with_results()... mqtt connect success...
|
||||
[I] >> [TS: 1590399558] /home/jiejie/github/mqttclient/mqttclient/mqttclient.c:1337 mqtt_list_subscribe_topic()...[1] subscribe topic: topic1
|
||||
[I] >> [TS: 1590399558] -----------------------------------------------------------------------------------
|
||||
[I] >> [TS: 1590399558] /home/jiejie/github/mqttclient/test/baidu/test.c:48 topic1_handler()...
|
||||
topic: topic1
|
||||
message:welcome to mqttclient, this is a publish test, a rand number: 2108237578 ...
|
||||
[I] >> [TS: 1590399558] -----------------------------------------------------------------------------------
|
||||
[I] >> [TS: 1590399562] -----------------------------------------------------------------------------------
|
||||
[I] >> [TS: 1590399562] /home/jiejie/github/mqttclient/test/baidu/test.c:48 topic1_handler()...
|
||||
topic: topic1
|
||||
message:welcome to mqttclient, this is a publish test, a rand number: 1675963111 ...
|
||||
[I] >> [TS: 1590399562] -----------------------------------------------------------------------------------
|
||||
```
|
||||
|
||||
此时表示已经连接上百度云天工了,并且实现了数据的收发。
|
||||
|
||||
## 测试代码
|
||||
|
||||
那么这个测试的例程代码如下,位于`./test/baidu/test.c`:
|
||||
|
||||
```c
|
||||
/*
|
||||
* @Author: jiejie
|
||||
* @Github: https://github.com/jiejieTop
|
||||
* @Date: 2019-12-11 21:53:07
|
||||
* @LastEditTime: 2020-06-08 20:38:41
|
||||
* @Description: the code belongs to jiejie, please keep the author information and source code according to the license.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include "mqttclient.h"
|
||||
|
||||
// #define TEST_USEING_TLS
|
||||
|
||||
static const char *test_baidu_ca_crt = {
|
||||
"-----BEGIN CERTIFICATE-----\r\n"
|
||||
"MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G\r\n"
|
||||
"A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp\r\n"
|
||||
"Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4\r\n"
|
||||
"MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG\r\n"
|
||||
"A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI\r\n"
|
||||
"hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8\r\n"
|
||||
"RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT\r\n"
|
||||
"gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm\r\n"
|
||||
"KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd\r\n"
|
||||
"QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ\r\n"
|
||||
"XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw\r\n"
|
||||
"DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o\r\n"
|
||||
"LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU\r\n"
|
||||
"RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp\r\n"
|
||||
"jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK\r\n"
|
||||
"6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX\r\n"
|
||||
"mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs\r\n"
|
||||
"Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH\r\n"
|
||||
"WD9f\r\n"
|
||||
"-----END CERTIFICATE-----"
|
||||
};
|
||||
|
||||
static void topic1_handler(void* client, message_data_t* msg)
|
||||
{
|
||||
(void) client;
|
||||
MQTT_LOG_I("-----------------------------------------------------------------------------------");
|
||||
MQTT_LOG_I("%s:%d %s()...\ntopic: %s\nmessage:%s", __FILE__, __LINE__, __FUNCTION__, msg->topic_name, (char*)msg->message->payload);
|
||||
MQTT_LOG_I("-----------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
void *mqtt_publish_thread(void *arg)
|
||||
{
|
||||
mqtt_client_t *client = (mqtt_client_t *)arg;
|
||||
|
||||
char buf[100] = { 0 };
|
||||
mqtt_message_t msg;
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
sprintf(buf, "welcome to mqttclient, this is a publish test...");
|
||||
|
||||
sleep(2);
|
||||
|
||||
mqtt_list_subscribe_topic(client);
|
||||
|
||||
msg.payload = (void *) buf;
|
||||
msg.qos = 0;
|
||||
while(1) {
|
||||
sprintf(buf, "welcome to mqttclient, this is a publish test, a rand number: %d ...", random_number());
|
||||
mqtt_publish(client, "topic1", &msg);
|
||||
sleep(4);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int res;
|
||||
pthread_t thread1;
|
||||
mqtt_client_t *client = NULL;
|
||||
|
||||
printf("\nwelcome to mqttclient test...\n");
|
||||
|
||||
mqtt_log_init();
|
||||
|
||||
client = mqtt_lease();
|
||||
|
||||
#ifdef TEST_USEING_TLS
|
||||
mqtt_set_port(client, "1884");
|
||||
mqtt_set_ca(client, (char*)test_baidu_ca_crt);
|
||||
#else
|
||||
mqtt_set_port(client, "1883");
|
||||
#endif
|
||||
|
||||
mqtt_set_host(client, "j6npr4w.mqtt.iot.gz.baidubce.com");
|
||||
mqtt_set_client_id(client, random_string(10));
|
||||
mqtt_set_user_name(client, "j6npr4w/mqtt-client-dev");
|
||||
mqtt_set_password(client, "lcUhUs5VYLMSbrnB");
|
||||
mqtt_set_clean_session(client, 1);
|
||||
|
||||
mqtt_connect(client);
|
||||
|
||||
mqtt_subscribe(client, "topic1", QOS0, topic1_handler);
|
||||
|
||||
res = pthread_create(&thread1, NULL, mqtt_publish_thread, client);
|
||||
if(res != 0) {
|
||||
MQTT_LOG_E("create mqtt publish thread fail");
|
||||
exit(res);
|
||||
}
|
||||
|
||||
while (1) {
|
||||
sleep(100);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用到的API
|
||||
|
||||
- 申请一个MQTT客户端
|
||||
|
||||
```c
|
||||
mqtt_client_t *client = NULL;
|
||||
client = mqtt_lease();
|
||||
```
|
||||
|
||||
|
||||
- mqtt客户端配置,主要是配置**mqtt_client_t**结构的相关信息,如果没有指定初始化参数,则系统会提供默认的参数。但连接部分的参数则必须指定,比如连接的端口号、云服务器的地址或者域名、用户名、密码,这些信息都是百度云平台得到的。
|
||||
|
||||
```c
|
||||
mqtt_set_port(client, "1883");
|
||||
mqtt_set_host(client, "j6npr4w.mqtt.iot.gz.baidubce.com");
|
||||
mqtt_set_client_id(client, random_string(10));
|
||||
mqtt_set_user_name(client, "j6npr4w/mqtt-client-dev");
|
||||
mqtt_set_password(client, "lcUhUs5VYLMSbrnB");
|
||||
mqtt_set_clean_session(client, 1);
|
||||
```
|
||||
|
||||
- 连接服务器并建立mqtt会话。
|
||||
|
||||
```c
|
||||
mqtt_connect(&client);
|
||||
```
|
||||
|
||||
- 订阅主题,字符串类型的**主题**(支持通配符"#" "+"),主题的**服务质量**,以及收到报文的**回调处理函数**,如不指定则有默认处理函数,订阅主题的处理方式是异步处理的,topic1_handler则是当收到服务器下发的数据内容时调用的回调函数。
|
||||
|
||||
```c
|
||||
mqtt_subscribe(&client, "topic1", QOS0, topic1_handler);
|
||||
```
|
||||
|
||||
- 创建一个发布主题的线程,并且发布主题数据,指定字符串类型的**主题**(支持通配符),要发布的消息(包括**服务质量**、**消息主体**)。
|
||||
|
||||
```c
|
||||
mqtt_message_t msg;
|
||||
msg.payload = (void *) buf;
|
||||
msg.qos = 0;
|
||||
|
||||
mqtt_publish(&client, "topic1", &msg);
|
||||
```
|
||||
|
||||
**上一篇**:[mqttclient设计与实现方式](./mqtt-design.md)
|
||||
|
||||
**下一篇**:[mqttclient连接到OneNET云平台](./mqtt-onenet.md)
|
153
components/connectivity/mqttclient/docs/mqtt-communication.md
Normal file
153
components/connectivity/mqttclient/docs/mqtt-communication.md
Normal file
@@ -0,0 +1,153 @@
|
||||
|
||||
|
||||
# 图文并茂学习MQTT协议通信过程
|
||||
|
||||
# MQTT连接服务器
|
||||
|
||||
客户端到服务器的网络连接建立后,客户端发送给服务器的第一个报文必须是CONNECT报文
|
||||
|
||||
在一个网络连接上,客户端只能发送一次CONNECT报文,如果出现第二个CONNECT报文,按照协议标准,服务器会将第二个CONNECT报文当作协议违规处理并断开客户端的连接。
|
||||
|
||||
对于正常的连接请求,服务器必须产生应答报文,如果无法建立会话,服务器应该在应答报文中报告对应的错误代码。
|
||||
|
||||

|
||||
|
||||
# MQTT订阅主题
|
||||
|
||||
客户端向服务器发送SUBSCRIBE报文用于创建一个或多个订阅。
|
||||
|
||||
在服务器中,会记录这个客户关注的一个或者多个主题,当服务器收到这些主题的PUBLISH报文的时候,将分发应用消息到与之匹配的客户端中。
|
||||
|
||||
SUBSCRIBE报文支持通配符,也为每个订阅指定了最大的QoS等级,服务器根据这些信息分发应用消息给客户端。
|
||||
|
||||
SUBSCRIBE报文拥有固定报头、可变报头、有效载荷。
|
||||
|
||||
当服务器收到客户端发送的一个SUBSCRIBE报文时,必须向客户端发送一个SUBACK报文响应,同时SUBACK报文必须和等待确认的SUBSCRIBE报文有相同的报文标识符。
|
||||
|
||||
|
||||
如果服务器收到一个SUBSCRIBE报文,报文的主题过滤器与一个现存订阅的主题过滤器相同,那么必须使用新的订阅彻底替换现存的订阅。新订阅的主题过滤器和之前订阅的相同,但是它的最大QoS值可以不同。与这个主题过滤器匹配的任何现存的保留消息必须被重发,但是发布流程不能中断。
|
||||
|
||||

|
||||
|
||||
SUBSCRIBE报文的有效载荷包含了一个主题过滤器列表,它们表示客户端想要订阅的主题,SUBSCRIBE报文有效载荷中的主题过滤器列表必须是UTF-8字符串。
|
||||
|
||||
服务器应该支持包含通配符的主题过滤器。如果服务器选择不支持包含通配符的主题过滤器,必须拒绝任何包含通配符过滤器的订阅请求。
|
||||
|
||||
每一个过滤器后面跟着一个字节,这个字节被叫做服务质量要求(Requested QoS)。它给出了服务器向客户端发送应用消息所允许的最大QoS等级。
|
||||
|
||||
# MQTT发布消息
|
||||
|
||||
PUBLISH控制报文是指从客户端向服务器或者服务器向客户端发送一个应用消息。其实从服务器分发的报文给订阅者,也是属于PUBLISH控制报文。
|
||||
|
||||
## 服务质量等级 QoS
|
||||
|
||||
QoS的值表示应用消息分发的服务质量等级保证,在不同的服务质量等级中,PUBLISH控制报文的处理方式也是不同的,而且PUBLISH报文的接收者(可以是服务器,也可以是客户端)必须按照根据PUBLISH报文中的QoS等级发送对应的应答报文。
|
||||
|
||||
PUBLISH报文固定报头的bit2-bit1位表示服务质量等级:
|
||||
|
||||
| QoS值 | Bit 2 | Bit 1 | 描述 |
|
||||
| -- | -- | --| -- |
|
||||
| 0 | 0 | 0 | 最多分发一次 |
|
||||
| 1 | 0 | 1 | 至少分发一次 |
|
||||
| 2 | 1 | 0 | 只分发一次 |
|
||||
| - | 1 | 1 | 保留位 |
|
||||
|
||||
|
||||
MQTT按照这里定义的服务质量 (QoS) 等级分发应用消息。服务器分发应用消息给多个客户端(订阅者)时,每个客户端独立处理。从发布者发布消息到接受者,分发的消息服务质量可能是不同的,这取决于订阅者订阅主题时指定的服务质量等级。而对于发布者而言,发布消息时就指定了服务质量等级。
|
||||
|
||||
## QoS0的PUBLISH控制报文
|
||||
|
||||
消息的分发依赖于底层网络的能力。服务器不会发送响应,发布者也不会重试,它在发出这个消息的时候就立马将消息丢弃,这个消息可能送达一次也可能根本没送达。
|
||||
|
||||
发布者必须发送QoS等于0,DUP等于0的PUBLISH报文。
|
||||
|
||||
在服务器接受PUBLISH报文时要将消息分发给订阅该主题(消息)的订阅者。
|
||||
|
||||

|
||||
|
||||
|
||||
## QoS1的PUBLISH控制报文
|
||||
|
||||
服务质量确保消息至少送达一次,甚至可能被多次处理。QoS1的PUBLISH报文的可变报头中包含一个报文标识符,需要PUBACK报文确认。
|
||||
|
||||
发布者在每次发送新的应用消息都必须分配一个未使用的报文标识符,在发布消息的同时将消息存储起来,等待服务器的应答,直到从接收者那收到对应的PUBACK报文。发送的PUBLISH报文必须包含报文标识符且QoS等于1,DUP等于0。
|
||||
|
||||
一旦发布者收到来自服务器的PUBACK报文后,这个报文标识符就可以重复使用。
|
||||
|
||||
接收者响应的PUBACK报文必须包含一个报文标识符,这个标识符来自接收到的PUBLISH报文。在发送了PUBACK报文之后,接收者必须将任何包含相同报文标识符的入站PUBLISH报文当作一个新的消息,并忽略它的DUP标志的值。
|
||||
|
||||

|
||||
|
||||
|
||||
## QoS2的PUBLISH控制报文
|
||||
|
||||
这是最高等级的服务质量,必须保证有且只有处理一次消息,消息丢失和重复都是不可接受的。使用这个服务质量等级会有额外的开销。
|
||||
|
||||
QoS2的消息可变报头中有报文标识符。
|
||||
|
||||
QoS2的PUBLISH报文的接收者使用一个两步确认过程来确认收到。
|
||||
|
||||
发送者必须给要发送的新应用消息分配一个未使用的报文标识符。发送的PUBLISH报文必须包含报文标识符且报文的QoS等于2,,DUP等于0。
|
||||
|
||||
在消息发出去后,需要将这个消息存储起来,而且必须将这个PUBLISH报文看作是未确认的,直到从接收者那收到对应的PUBREC报文。
|
||||
|
||||
当发布者收到的PUBREC报文后必须发送一个PUBREL报文。PUBREL报文必须包含与原始PUBLISH报文相同的报文标识符。
|
||||
|
||||
而且发布者还必须必须将这个PUBREL报文看作是未确认的,直到从接收者那收到对应的PUBCOMP报文。一旦发送了对应的PUBREL报文就不能重发这个PUBLISH报文。
|
||||
|
||||
所以就如下图所示,在发布消息的时候,立马存储消息,在收到PUBREC报文后必须将存储的消息丢弃掉,然后存储报文标识符,与此同时还要将PUBREL报文发送出去,最后在收到PUBCOMP报文后,才丢弃存储的报文标识符。
|
||||
|
||||

|
||||
|
||||
|
||||
当然啦,对应分发消息也是比较复杂的,它一般有两种处理方案,每一种方案都要确保消息有且只有处理一次。
|
||||
|
||||
接收者(此处指服务器)响应的PUBREC报文必须包含报文标识符,这个标识符来自接收到的PUBLISH报文。
|
||||
|
||||
发送PUBREC报文后,在收到对应的PUBREL报文之前,接收者可以将消息分发给订阅者,但是必须要存储报文标识符(方案1)。
|
||||
|
||||
当然,它在这种情况下,也可以存储消息,直到收到PUBREL报文才将消息分发到订阅者(方案2)。
|
||||
|
||||
而当它收到PUBREL报文后,它必须发送PUBCOMP报文响应发布者,该报文必须包含与PUBREL报文相同的标识符。
|
||||
|
||||
与此同时,它可以丢弃存储的报文标识符(方案1),而不必再分发应用消息给订阅者。
|
||||
|
||||
如果此前没有分发应用消息给订阅者(方案2),那么此时需要分发应用消息给订阅者,然后丢弃消息。
|
||||
|
||||
在接收者发送PUBCOMP报文之后,接收者必须将包含相同报文标识符的任何后续PUBLISH报文当作一个新的发布。
|
||||
|
||||
|
||||
# 取消订阅
|
||||
|
||||
客户端发送UNSUBSCRIBE报文给服务器,用于取消订阅主题。
|
||||
|
||||
UNSUBSCRIBE报文固定报头的第3,2,1,0位是保留位且必须分别设置为0,0,1,0。否则服务器必须认为任何其它的值都是不合法的并关闭网络连。具体的描述可以看协议文档。
|
||||
|
||||
UNSUBSCRIBE报文的有效载荷包含客户端想要取消订阅的主题过滤器列表。UNSUBSCRIBE报文中的主题过滤器必须是连续打包的UTF-8编码字符串。
|
||||
|
||||
UNSUBSCRIBE报文的有效载荷必须至少包含一个主题过滤器列表,而且这个主题过滤器是已经被客户端订阅的,否则的话没有订阅也就没有取消订阅一说了。如果一个UNSUBSCRIBE报文没有有效载荷是违反协议的标准的,服务器也不会去处理它。
|
||||
|
||||
|
||||
而对于服务器删除了一个订阅,那么它将不会再分发该主题的消息到这个客户端中。而且它必须完成分发任何已经开始往客户端发送的QoS1和QoS2的消息,以保证消息的服务质量。
|
||||
|
||||
然后服务器必须发送UNSUBACK报文来响应客户端的UNSUBSCRIBE请求。UNSUBACK报文必须包含和UNSUBSCRIBE报文相同的报文标识符。即使没有删除任何主题订阅(客户端取消订阅的主题未被订阅),服务器也必须发送一个UNSUBACK响应。
|
||||
|
||||

|
||||
|
||||
|
||||
# 断开连接
|
||||
|
||||
DISCONNECT报文是客户端发给服务端的最后一个控制报文。表示客户端正常断开连接。
|
||||
|
||||
DISCONNECT报文的固定报头保留位必须全为0。
|
||||
|
||||
客户端发送DISCONNECT报文之后必须关闭网络连接,不能通过那个网络连接再发送任何控制报文。
|
||||
|
||||
服务端在收到DISCONNECT报文时必须丢弃任何与当前连接关联的未发布的遗嘱消息。而且当客户端没有关闭网络连接的时候服务器应该主动去关闭网络连接。
|
||||
|
||||

|
||||
|
||||
|
||||
**上一篇**:[MQTT协议简介](./mqtt-introduction.md)
|
||||
|
||||
**下一篇**:[mqttclient代码生成工具](./mqtt-tool.md)
|
227
components/connectivity/mqttclient/docs/mqtt-config.md
Normal file
227
components/connectivity/mqttclient/docs/mqtt-config.md
Normal file
@@ -0,0 +1,227 @@
|
||||
|
||||
# mqttclient配置及裁剪工具
|
||||
|
||||
- MQTT_TOPIC_LEN_MAX
|
||||
|
||||
配置客户端支持最大的主题名长度,主题是支持通配符的,如果主题太长则会被截断,默认值为64。
|
||||
|
||||
```c
|
||||
#define MQTT_TOPIC_LEN_MAX 64
|
||||
```
|
||||
|
||||
- MQTT_ACK_HANDLER_NUM_MAX
|
||||
|
||||
配置mqtt等待应答列表的最大记录个数,对于qos1、qos2服务质量有要求的可以将其设置大一点,当然也必须资源跟得上,它主要是保证qos1、qos2的mqtt报文能准确到达服务器,默认值为64。
|
||||
|
||||
```c
|
||||
#define MQTT_ACK_HANDLER_NUM_MAX 64
|
||||
```
|
||||
|
||||
- MQTT_DEFAULT_BUF_SIZE
|
||||
|
||||
默认的读写数据缓冲区的大小,根据要收发数据量修改即可,默认值为1024。
|
||||
|
||||
```c
|
||||
#define MQTT_DEFAULT_BUF_SIZE 1024
|
||||
```
|
||||
|
||||
- MQTT_DEFAULT_CMD_TIMEOUT
|
||||
|
||||
默认的命令超时,它主要是用于socket读写超时(还包括等待响应的时间、重连等待时间等),默认值为5000。
|
||||
|
||||
```c
|
||||
#define MQTT_DEFAULT_CMD_TIMEOUT 5000
|
||||
```
|
||||
|
||||
- MQTT_MAX_CMD_TIMEOUT
|
||||
|
||||
设置最大的命令超时时间,默认值是20000。
|
||||
|
||||
```c
|
||||
#define MQTT_MAX_CMD_TIMEOUT 20000
|
||||
```
|
||||
|
||||
- MQTT_MIN_CMD_TIMEOUT
|
||||
|
||||
设置最小的命令超时时间,默认值是1000。
|
||||
|
||||
```c
|
||||
#define MQTT_MIN_CMD_TIMEOUT 1000
|
||||
```
|
||||
|
||||
- MQTT_KEEP_ALIVE_INTERVAL
|
||||
|
||||
设置默认的保活时间,它主要是保证MQTT客户端与服务器的保持活性连接,单位为**秒**,默认值为50。比如MQTT客户端与服务器在指定时间间隔内没有发送数据,有没有接收到数据,此时MQTT客户端会发送一个心跳包,确认一下这个连接是否存在,如果收到服务器的应答,那么说明这个连接还是存在的,可以随时收发数据,而如果不存在了,就尝试重连或者清除会话。
|
||||
|
||||
```c
|
||||
#define MQTT_KEEP_ALIVE_INTERVAL 50 // unit: second
|
||||
```
|
||||
|
||||
- MQTT_VERSION
|
||||
|
||||
选择MQTT协议的版本,默认为4,表示使用MQTT 3.1.1版本,而3则表示使用MQTT 3.1版本。
|
||||
|
||||
```c
|
||||
#define MQTT_VERSION 4 // 4 is mqtt 3.1.1
|
||||
```
|
||||
|
||||
- MQTT_RECONNECT_DEFAULT_DURATION
|
||||
|
||||
设置默认的重连时间间隔,当发生掉线时,会以这个时间间隔尝试重连,默认值为1000。
|
||||
|
||||
```c
|
||||
#define MQTT_RECONNECT_DEFAULT_DURATION 1000
|
||||
```
|
||||
|
||||
- MQTT_THREAD_STACK_SIZE
|
||||
|
||||
MQTT内部维护一个线程,需要设置默认的线程属性信息,MQTT_THREAD_STACK_SIZE表示线程栈的大小,默认值是2048。在linux环境下可以是不需要理会这些参数的,而在RTOS平台则需要配置,如果不使用mbedtls,线程栈2048字节已足够,而使用mbedtls加密后,需要配置4096字节以上
|
||||
|
||||
```c
|
||||
#define MQTT_THREAD_STACK_SIZE 2048 // 线程栈
|
||||
```
|
||||
|
||||
- MQTT_THREAD_PRIO
|
||||
|
||||
设置线程优先级,默认为5。
|
||||
|
||||
```c
|
||||
#define MQTT_THREAD_PRIO 5 // 线程优先级
|
||||
```
|
||||
|
||||
- MQTT_THREAD_TICK
|
||||
|
||||
设置线程优时间片,默认为50。
|
||||
|
||||
```c
|
||||
#define MQTT_THREAD_TICK 50 // 线程时间片
|
||||
```
|
||||
|
||||
- MQTT_NETWORK_TYPE_NO_TLS
|
||||
|
||||
设置是否需要支持TLS加密传输,如果定义了该宏,则表示不支持TLS加密传输,否则默认将支持TLS加密传输。
|
||||
|
||||
```c
|
||||
#define MQTT_NETWORK_TYPE_NO_TLS
|
||||
```
|
||||
|
||||
# salof相关的配置
|
||||
|
||||
[salof](https://github.com/jiejieTop/salof) 全称是:**Synchronous Asynchronous Log Output Framework**(同步异步日志输出框架),它是一个同步异步日志输出框架,可以在空闲时候输出对应的日志信息,并且该库与mqttclient无缝衔接。
|
||||
|
||||
- LOG_LEVEL
|
||||
|
||||
配置对应的日志输出级别,它支持以下4种级别:ERR_LEVEL、WARN_LEVEL、INFO_LEVEL、DEBUG_LEVEL。
|
||||
|
||||
```c
|
||||
#define BASE_LEVEL (0)
|
||||
#define ERR_LEVEL (BASE_LEVEL + 1) /* 日志输出级别:错误级别(高优先级) */
|
||||
#define WARN_LEVEL (ERR_LEVEL + 1) /* 日志输出级别:警告级别(中优先级) */
|
||||
#define INFO_LEVEL (WARN_LEVEL + 1) /* 日志输出级别:信息级别(低优先级) */
|
||||
#define DEBUG_LEVEL (INFO_LEVEL + 1) /* 日志输出级别:调试级别(更低优先级) */
|
||||
|
||||
#define LOG_LEVEL WARN_LEVEL /* 日志输出级别 */
|
||||
```
|
||||
|
||||
- USE_SALOF
|
||||
|
||||
如果该宏大于0,则表示使用salof指定的后端作为日志输出,反之则使用printf作为日志输出接口。
|
||||
|
||||
```c
|
||||
#define USE_SALOF (1U)
|
||||
```
|
||||
|
||||
- SALOF_OS
|
||||
|
||||
设置salof日志库的操作系统,有以下选项:USE_LINUX、USE_TENCENTOS、USE_FREERTOS、USE_RTT。
|
||||
|
||||
```c
|
||||
#define SALOF_OS USE_LINUX
|
||||
```
|
||||
|
||||
- USE_IDLE_HOOK
|
||||
|
||||
如果该宏大于0,则表示在操作系统中的空闲钩子函数中输出日志,反之则通过线程异步处理或者同步处理。
|
||||
|
||||
```c
|
||||
#define USE_IDLE_HOOK (0U)
|
||||
```
|
||||
|
||||
- LOG_COLOR
|
||||
|
||||
如果该宏大于0,则表示输出的日志是带有颜色的(需要终端的支持),反之则没有颜色。
|
||||
|
||||
```c
|
||||
#define LOG_COLOR (1U)
|
||||
```
|
||||
|
||||
- LOG_TS
|
||||
|
||||
如果该宏大于0,则表示输出的日志是带有时间戳的,反之则没有时间戳。
|
||||
|
||||
```c
|
||||
#define LOG_TS (0U)
|
||||
```
|
||||
|
||||
- LOG_TAR
|
||||
|
||||
如果该宏大于0,则表示输出的日志是带有标签的(任务名字),反之则没有标签。
|
||||
|
||||
```c
|
||||
#define LOG_TAR (0U)
|
||||
```
|
||||
|
||||
- SALOF_BUFF_SIZE
|
||||
|
||||
设置日志库salof输出库的buf缓冲区大小,即最大一次性能输出多少个字节。
|
||||
|
||||
```c
|
||||
#define SALOF_BUFF_SIZE (512U)
|
||||
```
|
||||
|
||||
- SALOF_FIFO_SIZE
|
||||
|
||||
设置日志库salof的fifo缓冲区大小,即最能缓存多少个字节的日志数据。
|
||||
|
||||
```c
|
||||
#define SALOF_FIFO_SIZE (1024*4U)
|
||||
```
|
||||
|
||||
- SALOF_TASK_STACK_SIZE
|
||||
|
||||
如果使用了空闲线程输出日志,则配置salof线程栈的大小。
|
||||
|
||||
```c
|
||||
#define SALOF_TASK_STACK_SIZE (2048U)
|
||||
```
|
||||
|
||||
- SALOF_TASK_TICK
|
||||
|
||||
如果使用了空闲线程输出日志,则配置salof线程栈的时间片大小。
|
||||
|
||||
```c
|
||||
#define SALOF_TASK_TICK (50U)
|
||||
```
|
||||
|
||||
# 使用mqttclient裁剪配置工具
|
||||
|
||||
我们可以通过配置很方便地裁剪我们需要的功能,设置上述MQTT客户端的默认参数,完全不需要手动修改代码,直接使用工具裁剪配置即可。
|
||||
|
||||
打开在线mqttclient裁剪配置工具:[https://jiejietop.gitee.io/mqtt/mqtt-config.html](https://jiejietop.gitee.io/mqtt/mqtt-config.html)
|
||||
|
||||
它支持裁剪配置相关的头文件,配置客户端默认的一些参数:
|
||||
|
||||

|
||||
|
||||
还支持配置salof同步异步日志相关的信息,以保证日志能正常运作,配置完成后生成对应的代码,然后覆盖掉原本的MQTT客户端配置即可,如果不知道如何配置,那就导入默认的配置即可。
|
||||
|
||||

|
||||
|
||||
如果是初次接触,看不懂上面配置的信息,没关系,我很贴心地准备了配置的宏定义及其描述信息,保证你能看得懂的。
|
||||
|
||||

|
||||
|
||||
|
||||
**上一篇**:[mqttclient代码生成工具](./mqtt-tool.md)
|
||||
|
||||
**下一篇**:[mqttclient设计与实现方式](./mqtt-design.md)
|
567
components/connectivity/mqttclient/docs/mqtt-design.md
Normal file
567
components/connectivity/mqttclient/docs/mqtt-design.md
Normal file
@@ -0,0 +1,567 @@
|
||||
|
||||
# mqttclient设计与实现方式
|
||||
|
||||
# 设计思想
|
||||
- 整体采用分层式设计,代码实现采用异步设计方式,降低耦合。
|
||||
|
||||
- 消息的处理使用回调的方式处理:用户指定**订阅的主题**与指定**消息的处理函数**。
|
||||
|
||||
- 不依赖外部任何文件。
|
||||
|
||||
# API接口
|
||||
|
||||
**mqttclient**拥有非常简洁的**API**接口,参数都是非常简单的。
|
||||
|
||||
| API | 说明 | 示例 |
|
||||
| -- | -- | -- |
|
||||
| mqtt_lease() | 申请一个mqtt客户端 | mqtt_client_t *client = mqtt_lease(); |
|
||||
| mqtt_release() | 释放已申请的mqtt客户端 | mqtt_release(client); |
|
||||
| mqtt_connect() | 与服务器建立连接 | mqtt_connect(client); |
|
||||
| mqtt_disconnect() | 与服务器断开连接 | mqtt_disconnect(client); |
|
||||
| mqtt_subscribe() | 订阅主题,参数:主题名字、服务质量、指定当收到主题数据时的处理函数。| mqtt_subscribe(client, "topic", QOS0, sub_topic_handle); |
|
||||
| mqtt_unsubscribe() | 取消订阅指定主题,参数:主题名字 | mqtt_unsubscribe(client, | "topic"); |
|
||||
| mqtt_publish() | 向指定主题发布数据,参数:主题名字,mqtt_message_t类型的数据内容 | mqtt_publish(client, "topic", &msg); |
|
||||
| mqtt_list_subscribe_topic() | 列出客户端已订阅的主题 | mqtt_list_subscribe_topic(client); |
|
||||
| mqtt_set_host() | 设置要连接的MQTT服务器地址,参数:域名 / 点分十进制的IP地址 | mqtt_set_host(client, "www.jiejie01.top"); |
|
||||
| mqtt_set_port() | 设置要连接的MQTT服务器端口号 | mqtt_set_port(client, "1883"); |
|
||||
| mqtt_set_ca() | 设置要连接的MQTT服务器ca证书 | mqtt_set_ca(client, "ca ..."); |
|
||||
| mqtt_set_user_name() | 设置客户端的用户名 | mqtt_set_user_name(client, "any"); |
|
||||
| mqtt_set_password() | 设置客户端的密码 | mqtt_set_password(client, "any"); |
|
||||
| mqtt_set_client_id() | 设置客户端的ID | mqtt_set_client_id(client, "any"); |
|
||||
| mqtt_set_clean_session() | 设置在断开连接后清除会话 | mqtt_set_clean_session(client, 1); |
|
||||
| mqtt_set_keep_alive_interval() | 设置心跳间隔时间(秒) | mqtt_set_keep_alive_interval(client, 50); |
|
||||
| mqtt_set_cmd_timeout() | 设置命令超时时间(毫秒),主要用于socket读写超时 | mqtt_set_cmd_timeout(client, 5000); |
|
||||
| mqtt_set_reconnect_try_duration() | 设置重连的时间间隔(毫秒) | mqtt_set_reconnect_try_duration(client, 1024); |
|
||||
| mqtt_set_read_buf_size() | 设置读数据缓冲区的大小 | mqtt_set_read_buf_size(client, 1024); |
|
||||
| mqtt_set_write_buf_size() | 设置写数据缓冲区的大小 | mqtt_set_write_buf_size(client, 1024); |
|
||||
| mqtt_set_will_flag() | 设置遗嘱标记 | mqtt_set_will_flag(client, 1); |
|
||||
| mqtt_set_will_options() | 设置遗嘱的配置信息,指定遗嘱主题,服务质量,遗嘱保留标记,遗嘱内容 | mqtt_set_will_options(client, "will_topic", QOS0, 0, "will_message"); |
|
||||
| mqtt_set_version() | 设置MQTT协议的版本,默认值是4,MQTT版本为3.1.1 | mqtt_set_version(client, 4); |
|
||||
| mqtt_set_reconnect_handler() | 设置重连时的回调函数 | mqtt_set_reconnect_handler(client, reconnect_handler); |
|
||||
| mqtt_set_interceptor_handler() | 设置拦截器处理函数,将所有底层数据上报给用户 | mqtt_set_interceptor_handler(client, interceptor_handler); |
|
||||
|
||||
# MQTT客户端的核心结构
|
||||
|
||||
**mqtt_client_t 结构**
|
||||
|
||||
```c
|
||||
typedef struct mqtt_client {
|
||||
char *mqtt_client_id;
|
||||
char *mqtt_user_name;
|
||||
char *mqtt_password;
|
||||
char *mqtt_read_buf;
|
||||
char *mqtt_write_buf;
|
||||
char *mqtt_host;
|
||||
char *mqtt_port;
|
||||
char *mqtt_ca;
|
||||
void *mqtt_reconnect_data;
|
||||
uint16_t mqtt_keep_alive_interval;
|
||||
uint16_t mqtt_packet_id;
|
||||
uint32_t mqtt_will_flag : 1;
|
||||
uint32_t mqtt_clean_session : 1;
|
||||
uint32_t mqtt_ping_outstanding : 2;
|
||||
uint32_t mqtt_version : 4;
|
||||
uint32_t mqtt_ack_handler_number : 24;
|
||||
uint32_t mqtt_cmd_timeout;
|
||||
uint32_t mqtt_read_buf_size;
|
||||
uint32_t mqtt_write_buf_size;
|
||||
uint32_t mqtt_reconnect_try_duration;
|
||||
size_t mqtt_client_id_len;
|
||||
size_t mqtt_user_name_len;
|
||||
size_t mqtt_password_len;
|
||||
mqtt_will_options_t *mqtt_will_options;
|
||||
client_state_t mqtt_client_state;
|
||||
platform_mutex_t mqtt_write_lock;
|
||||
platform_mutex_t mqtt_global_lock;
|
||||
mqtt_list_t mqtt_msg_handler_list;
|
||||
mqtt_list_t mqtt_ack_handler_list;
|
||||
network_t *mqtt_network;
|
||||
platform_thread_t *mqtt_thread;
|
||||
platform_timer_t mqtt_reconnect_timer;
|
||||
platform_timer_t mqtt_last_sent;
|
||||
platform_timer_t mqtt_last_received;
|
||||
reconnect_handler_t mqtt_reconnect_handler;
|
||||
interceptor_handler_t mqtt_interceptor_handler;
|
||||
} mqtt_client_t;
|
||||
```
|
||||
|
||||
该结构主要维护以下内容:
|
||||
|
||||
1. MQTT客户端连接服务器必要的参数,如**客户端ID mqtt_client_id、用户名mqtt_user_name、密码mqtt_password**以及**客户端ID长度mqtt_client_id_len、用户名长度mqtt_user_name_len、密码长度mqtt_password_len**等。
|
||||
|
||||
2. 读写数据缓冲区**mqtt_read_buf、mqtt_write_buf**及其大小的配置**mqtt_read_buf_size、mqtt_write_buf_size**。
|
||||
|
||||
3. 服务器相关的配置信息,如**服务器地址mqtt_host、服务器端口号mqtt_port、服务器CA证书mqtt_ca**。
|
||||
|
||||
4. 一些MQTT客户端的配置信息:如**心跳时间间隔mqtt_keep_alive_interval、MQTT报文标识符mqtt_packet_id、遗嘱标记位mqtt_will_flag、清除会话标记mqtt_clean_session、MQTT协议版本mqtt_version、等待应答列表的最大记录个数mqtt_ack_handler_number**等。
|
||||
|
||||
5. 一些其他的配置,如**遗嘱消息相关的配置mqtt_will_options、客户端的状态mqtt_client_state、写缓冲区的互斥锁mqtt_write_lock、全局的互斥锁mqtt_global_lock**等。
|
||||
|
||||
6. 命令超时时间**mqtt_cmd_timeout**(主要是读写阻塞时间、等待响应的时间、重连等待时间等)。
|
||||
|
||||
7. 维护消息处理列表**mqtt_msg_handler_list**,这是**mqtt**协议必须实现的内容,所有来自服务器的**publish**报文都会被处理(前提是订阅了对应的消息,或者设置了拦截器)。
|
||||
|
||||
8. 维护**ack**链表**mqtt_ack_handler_list**,这是异步实现的核心,所有等待响应的报文都会被挂载到这个链表上。
|
||||
|
||||
9. 维护一个网络组件层**mqtt_network**,它可以自动选择数据通道。
|
||||
|
||||
10. 维护一个内部线程**mqtt_thread**,所有来自服务器的mqtt包都会在内部线程这里被处理!
|
||||
|
||||
11. 两个定时器,分别是掉线重连定时器与保活定时器**mqtt_reconnect_timer、mqtt_last_sent、mqtt_last_received**
|
||||
|
||||
12. 设置掉线重连后告知应用层的回调函数**mqtt_reconnect_handler**与参数**mqtt_reconnect_data**。
|
||||
|
||||
13. 设置底层的拦截器的回调函数**mqtt_interceptor_handler**,将所有底层数据上报给应用层。
|
||||
|
||||
|
||||
# mqttclient实现
|
||||
|
||||
以下是整个框架的实现方式,方便大家更容易理解mqttclient的代码与设计思想,让大家能够修改源码与使用,还可以提交pr或者issues,开源的世界期待各位大神的参与,感谢!
|
||||
|
||||
除此之外以下代码的**记录机制**与**超时处理机制**是非常好的编程思想,大家有兴趣一定要看源代码!
|
||||
|
||||
## 申请一个mqtt客户端
|
||||
|
||||
```c
|
||||
mqtt_client_t *mqtt_lease(void);
|
||||
```
|
||||
|
||||
1. 这个函数的内部通过动态申请内存的方式申请了一个MQTT客户端结构**mqtt_client_t**。
|
||||
|
||||
2. 调用**_mqtt_init()**函数将其内部的进行了默认的初始化,如申请网络组件的内存空间、初始化相关的互斥锁、链表等。
|
||||
|
||||
|
||||
## 释放已申请的mqtt客户端
|
||||
|
||||
```c
|
||||
mqtt_release()
|
||||
```
|
||||
|
||||
回收MQTT客户端结构**mqtt_client_t**的内存空间、网络组件的内存空间、与服务器断开连接。
|
||||
|
||||
## 设置MQTT客户端的信息
|
||||
|
||||
通过宏定义去统一设置MQTT客户端结构**mqtt_client_t**的信息,定义如下:
|
||||
|
||||
```c
|
||||
#define MQTT_CLIENT_SET_DEFINE(name, type, res) \
|
||||
type mqtt_set_##name(mqtt_client_t *c, type t) { \
|
||||
MQTT_ROBUSTNESS_CHECK((c), res); \
|
||||
c->mqtt_##name = t; \
|
||||
return c->mqtt_##name; \
|
||||
}
|
||||
```
|
||||
|
||||
由编译器预处理得到相关的函数:**mqtt_set_xxx()**。
|
||||
|
||||
```c
|
||||
MQTT_CLIENT_SET_DEFINE(client_id, char*, NULL)
|
||||
MQTT_CLIENT_SET_DEFINE(user_name, char*, NULL)
|
||||
MQTT_CLIENT_SET_DEFINE(password, char*, NULL)
|
||||
MQTT_CLIENT_SET_DEFINE(host, char*, NULL)
|
||||
MQTT_CLIENT_SET_DEFINE(port, char*, NULL)
|
||||
MQTT_CLIENT_SET_DEFINE(ca, char*, NULL)
|
||||
MQTT_CLIENT_SET_DEFINE(reconnect_data, void*, NULL)
|
||||
MQTT_CLIENT_SET_DEFINE(keep_alive_interval, uint16_t, 0)
|
||||
MQTT_CLIENT_SET_DEFINE(will_flag, uint32_t, 0)
|
||||
MQTT_CLIENT_SET_DEFINE(clean_session, uint32_t, 0)
|
||||
MQTT_CLIENT_SET_DEFINE(version, uint32_t, 0)
|
||||
MQTT_CLIENT_SET_DEFINE(cmd_timeout, uint32_t, 0)
|
||||
MQTT_CLIENT_SET_DEFINE(read_buf_size, uint32_t, 0)
|
||||
MQTT_CLIENT_SET_DEFINE(write_buf_size, uint32_t, 0)
|
||||
MQTT_CLIENT_SET_DEFINE(reconnect_try_duration, uint32_t, 0)
|
||||
MQTT_CLIENT_SET_DEFINE(reconnect_handler, reconnect_handler_t, NULL)
|
||||
MQTT_CLIENT_SET_DEFINE(interceptor_handler, interceptor_handler_t, NULL)
|
||||
```
|
||||
|
||||
## 连接服务器
|
||||
|
||||
```c
|
||||
int mqtt_connect(mqtt_client_t* c);
|
||||
```
|
||||
|
||||
参数只有 **mqtt_client_t** 类型的指针,连接服务器则是使用非异步的方式设计,因为必须等待连接上服务器才能进行下一步操作。
|
||||
|
||||
过程如下:
|
||||
|
||||
1. 调用底层的连接函数连接上服务器:
|
||||
|
||||
```c
|
||||
network_connect(c->network);
|
||||
```
|
||||
|
||||
2. 序列化**mqtt**的**CONNECT**报文并且发送。
|
||||
|
||||
```c
|
||||
MQTTSerialize_connect(c->write_buf, c->write_buf_size, &connect_data)
|
||||
mqtt_send_packet(c, len, &connect_timer)
|
||||
```
|
||||
|
||||
3. 等待来自服务器的**CONNACK**报文
|
||||
|
||||
```c
|
||||
mqtt_wait_packet(c, CONNACK, &connect_timer)
|
||||
```
|
||||
|
||||
4. 连接成功后创建一个内部线程**mqtt_yield_thread**,并在合适的时候启动它:
|
||||
|
||||
```c
|
||||
/* connect success, and need init mqtt thread */
|
||||
c->mqtt_thread= platform_thread_init("mqtt_yield_thread", mqtt_yield_thread, c, MQTT_THREAD_STACK_SIZE, MQTT_THREAD_PRIO, MQTT_THREAD_TICK);
|
||||
|
||||
if (NULL != c->mqtt_thread) {
|
||||
mqtt_set_client_state(c, CLIENT_STATE_CONNECTED);
|
||||
platform_thread_startup(c->mqtt_thread);
|
||||
platform_thread_start(c->mqtt_thread); /* start run mqtt thread */
|
||||
}
|
||||
```
|
||||
|
||||
5. 而对于重连来说则不会重新创建线程,直接改变客户端状态为连接状态即可:
|
||||
|
||||
```c
|
||||
mqtt_set_client_state(c, CLIENT_STATE_CONNECTED);
|
||||
```
|
||||
|
||||
## 订阅报文
|
||||
|
||||
```c
|
||||
int mqtt_subscribe(mqtt_client_t* c, const char* topic_filter, mqtt_qos_t qos, message_handler_t handler)
|
||||
```
|
||||
|
||||
订阅报文使用异步设计来实现的,参数有字符串类型的**主题**(支持通配符"#" "+"),主题的**服务质量**,以及收到报文的**处理函数`**,如不指定则有默认处理函数。
|
||||
|
||||
过程如下:
|
||||
|
||||
1. 序列化订阅报文并且发送给服务器
|
||||
|
||||
```c
|
||||
MQTTSerialize_subscribe(c->write_buf, c->write_buf_size, 0, mqtt_get_next_packet_id(c), 1, &topic, (int*)&qos)
|
||||
mqtt_send_packet(c, len, &timer)
|
||||
```
|
||||
|
||||
2. 创建对应的消息处理节点,这个消息节点在收到服务器的**SUBACK**订阅应答报文后会挂载到消息处理列表**mqtt_msg_handler_list**上
|
||||
|
||||
```c
|
||||
mqtt_msg_handler_create(topic_filter, qos, handler)
|
||||
```
|
||||
|
||||
3. 在发送了报文给服务器那就要等待服务器的响应了,先记录这个等待**SUBACK**
|
||||
|
||||
```c
|
||||
mqtt_ack_list_record(c, SUBACK, mqtt_get_next_packet_id(c), len, msg_handler)
|
||||
```
|
||||
|
||||
## 取消订阅
|
||||
|
||||
```c
|
||||
int mqtt_unsubscribe(mqtt_client_t* c, const char* topic_filter);
|
||||
```
|
||||
|
||||
与订阅报文的逻辑基本差不多的,指定了取消订阅的主题。
|
||||
|
||||
实现过程如下:
|
||||
|
||||
1. 序列化订阅报文并且发送给服务器
|
||||
|
||||
```c
|
||||
MQTTSerialize_unsubscribe(c->write_buf, c->write_buf_size, 0, packet_id, 1, &topic)
|
||||
mqtt_send_packet(c, len, &timer)
|
||||
```
|
||||
|
||||
2. 创建对应的消息处理节点,这个消息节点在收到服务器的**UNSUBACK**取消订阅应答报文后将消息处理列表**mqtt_msg_handler_list**上的已经订阅的主题消息节点销毁
|
||||
|
||||
```c
|
||||
mqtt_msg_handler_create((const char*)topic_filter, QOS0, NULL)
|
||||
```
|
||||
|
||||
3. 在发送了报文给服务器那就要等待服务器的响应了,先记录这个等待**UNSUBACK**
|
||||
|
||||
```c
|
||||
mqtt_ack_list_record(c, UNSUBACK, packet_id, len, msg_handler)
|
||||
```
|
||||
|
||||
## 发布报文
|
||||
|
||||
```c
|
||||
int mqtt_publish(mqtt_client_t* c, const char* topic_filter, mqtt_message_t* msg)
|
||||
```
|
||||
|
||||
向指定主题发布一个MQTT报文。参数只有**mqtt_client_t** 类型的指针,字符串类型的**主题**(支持通配符),要发布的消息(包括**服务质量**、**消息主体**)。
|
||||
|
||||
使用如下:
|
||||
|
||||
```c
|
||||
mqtt_message_t msg;
|
||||
|
||||
msg.qos = 2;
|
||||
msg.payload = (void *) buf;
|
||||
|
||||
mqtt_publish(&client, "testtopic1", &msg);
|
||||
```
|
||||
|
||||
代码的实现核心思想都差不多,过程如下:
|
||||
|
||||
1. 先序列化发布报文,然后发送到服务器
|
||||
|
||||
```c
|
||||
MQTTSerialize_publish(c->write_buf, c->write_buf_size, 0, msg->qos, msg->retained, msg->id,topic, (unsigned char*)msg->payload, msg->payloadlen);
|
||||
mqtt_send_packet(c, len, &timer)
|
||||
```
|
||||
|
||||
2. 对于QOS0的逻辑,不做任何处理,对于QOS1和QOS2的报文则需要记录下来,在没收到服务器应答的时候进行重发
|
||||
|
||||
```c
|
||||
if (QOS1 == msg->qos) {
|
||||
rc = mqtt_ack_list_record(c, PUBACK, mqtt_get_next_packet_id(c), len, NULL);
|
||||
} else if (QOS2 == msg->qos) {
|
||||
rc = mqtt_ack_list_record(c, PUBREC, mqtt_get_next_packet_id(c), len, NULL);
|
||||
}
|
||||
```
|
||||
|
||||
3. 还有非常重要的一点,重发报文的MQTT报文头部需要设置DUP标志位,这是MQTT协议的标准,因此,在重发的时候作者直接操作了报文的DUP标志位,因为修改DUP标志位的函数我没有从MQTT库中找到,所以我封装了一个函数,这与LwIP中的交叉存取思想是一个道理,它假设我知道MQTT报文的所有操作,所以我可以操作它,这样子可以提高很多效率:
|
||||
|
||||
```c
|
||||
mqtt_set_publish_dup(c,1); /* may resend this data, set the udp flag in advance */
|
||||
```
|
||||
|
||||
## 内部线程
|
||||
|
||||
```c
|
||||
static void mqtt_yield_thread(void *arg)
|
||||
```
|
||||
|
||||
主要是对**mqtt_yield**函数的返回值做处理,比如在**disconnect**的时候销毁这个线程。
|
||||
|
||||
## 核心的处理函数
|
||||
|
||||
1. 数据包的处理**mqtt_packet_handle**
|
||||
|
||||
```c
|
||||
static int mqtt_packet_handle(mqtt_client_t* c, platform_timer_t* timer)
|
||||
```
|
||||
|
||||
对不同的包使用不一样的处理:
|
||||
|
||||
```c
|
||||
switch (packet_type) {
|
||||
case 0: /* timed out reading packet */
|
||||
break;
|
||||
|
||||
case CONNACK:
|
||||
break;
|
||||
|
||||
case PUBACK:
|
||||
case PUBCOMP:
|
||||
rc = mqtt_puback_and_pubcomp_packet_handle(c, timer);
|
||||
break;
|
||||
|
||||
case SUBACK:
|
||||
rc = mqtt_suback_packet_handle(c, timer);
|
||||
break;
|
||||
|
||||
case UNSUBACK:
|
||||
rc = mqtt_unsuback_packet_handle(c, timer);
|
||||
break;
|
||||
|
||||
case PUBLISH:
|
||||
rc = mqtt_publish_packet_handle(c, timer);
|
||||
break;
|
||||
|
||||
case PUBREC:
|
||||
case PUBREL:
|
||||
rc = mqtt_pubrec_and_pubrel_packet_handle(c, timer);
|
||||
break;
|
||||
|
||||
case PINGRESP:
|
||||
c->ping_outstanding = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
goto exit;
|
||||
}
|
||||
```
|
||||
|
||||
并且做保活的处理:
|
||||
|
||||
```c
|
||||
mqtt_keep_alive(c)
|
||||
```
|
||||
|
||||
当发生超时后的处理:
|
||||
|
||||
```c
|
||||
if (platform_timer_is_expired(&c->last_sent) || platform_timer_is_expired(&c->last_received))
|
||||
```
|
||||
|
||||
序列化一个心跳包并且发送给服务器
|
||||
|
||||
```c
|
||||
MQTTSerialize_pingreq(c->write_buf, c->write_buf_size);
|
||||
mqtt_send_packet(c, len, &timer);
|
||||
```
|
||||
|
||||
当再次发生超时后,表示与服务器的连接已断开,需要重连的操作,设置客户端状态为断开连接
|
||||
|
||||
```c
|
||||
mqtt_set_client_state(c, CLIENT_STATE_DISCONNECTED);
|
||||
```
|
||||
|
||||
2. `ack`链表的扫描,当收到服务器的报文时,对ack列表进行扫描操作
|
||||
|
||||
```c
|
||||
mqtt_ack_list_scan(c);
|
||||
```
|
||||
|
||||
当超时后就销毁ack链表节点:
|
||||
|
||||
```c
|
||||
mqtt_ack_handler_destroy(ack_handler);
|
||||
```
|
||||
|
||||
当然下面这几种报文则需要重发操作:(**PUBACK 、PUBREC、 PUBREL 、PUBCOMP**,保证QOS1 QOS2的服务质量)
|
||||
|
||||
```c
|
||||
if ((ack_handler->type == PUBACK) || (ack_handler->type == PUBREC) || (ack_handler->type == PUBREL) || (ack_handler->type == PUBCOMP))
|
||||
mqtt_ack_handler_resend(c, ack_handler);
|
||||
```
|
||||
|
||||
3. 保持活性的时间过去了,可能掉线了,需要重连操作
|
||||
|
||||
```c
|
||||
mqtt_try_reconnect(c);
|
||||
```
|
||||
|
||||
重连成功后尝试重新订阅报文,保证恢复原始状态~
|
||||
|
||||
```c
|
||||
mqtt_try_resubscribe(c)
|
||||
```
|
||||
|
||||
## 发布应答与发布完成报文的处理
|
||||
|
||||
```c
|
||||
static int mqtt_puback_and_pubcomp_packet_handle(mqtt_client_t *c, platform_timer_t *timer)
|
||||
```
|
||||
|
||||
1. 反序列化报文
|
||||
|
||||
```c
|
||||
MQTTDeserialize_ack(&packet_type, &dup, &packet_id, c->read_buf, c->read_buf_size)
|
||||
```
|
||||
|
||||
2. 取消对应的ack记录
|
||||
|
||||
```c
|
||||
mqtt_ack_list_unrecord(c, packet_type, packet_id, NULL);
|
||||
```
|
||||
|
||||
## 订阅应答报文的处理
|
||||
|
||||
```c
|
||||
static int mqtt_suback_packet_handle(mqtt_client_t *c, platform_timer_t *timer)
|
||||
```
|
||||
|
||||
1. 反序列化报文
|
||||
|
||||
```c
|
||||
MQTTDeserialize_suback(&packet_id, 1, &count, (int*)&granted_qos, c->read_buf, c->read_buf_size)
|
||||
```
|
||||
|
||||
2. 取消对应的ack记录
|
||||
|
||||
```c
|
||||
mqtt_ack_list_unrecord(c, packet_type, packet_id, NULL);
|
||||
```
|
||||
|
||||
3. 安装对应的订阅消息处理函数,如果是已存在的则不会安装
|
||||
|
||||
```c
|
||||
mqtt_msg_handlers_install(c, msg_handler);
|
||||
```
|
||||
|
||||
## 取消订阅应答报文的处理
|
||||
|
||||
```c
|
||||
static int mqtt_unsuback_packet_handle(mqtt_client_t *c, platform_timer_t *timer)
|
||||
```
|
||||
|
||||
1. 反序列化报文
|
||||
|
||||
```c
|
||||
MQTTDeserialize_unsuback(&packet_id, c->read_buf, c->read_buf_size)
|
||||
```
|
||||
|
||||
2. 取消对应的ack记录,并且获取到已经订阅的消息处理节点
|
||||
|
||||
```c
|
||||
mqtt_ack_list_unrecord(c, UNSUBACK, packet_id, &msg_handler)
|
||||
```
|
||||
|
||||
3. 销毁对应的订阅消息处理函数
|
||||
|
||||
```c
|
||||
mqtt_msg_handler_destory(msg_handler);
|
||||
```
|
||||
|
||||
## 来自服务器的发布报文的处理
|
||||
|
||||
```c
|
||||
static int mqtt_publish_packet_handle(mqtt_client_t *c, platform_timer_t *timer)
|
||||
```
|
||||
|
||||
1. 反序列化报文
|
||||
|
||||
```c
|
||||
MQTTDeserialize_publish(&msg.dup, &qos, &msg.retained, &msg.id, &topic_name,
|
||||
(unsigned char**)&msg.payload, (int*)&msg.payloadlen, c->read_buf, c->read_buf_size)
|
||||
```
|
||||
|
||||
2. 对于QOS0、QOS1的报文,直接去处理消息
|
||||
|
||||
```c
|
||||
mqtt_deliver_message(c, &topic_name, &msg);
|
||||
```
|
||||
|
||||
3. 对于QOS1的报文,还需要发送一个**PUBACK**应答报文给服务器
|
||||
|
||||
```c
|
||||
MQTTSerialize_ack(c->write_buf, c->write_buf_size, PUBACK, 0, msg.id);
|
||||
```
|
||||
|
||||
4. 而对于QOS2的报文则需要发送**PUBREC**报文给服务器,除此之外还需要记录**PUBREL**到ack链表上,等待服务器的发布释放报文,最后再去处理这个消息
|
||||
|
||||
```c
|
||||
MQTTSerialize_ack(c->write_buf, c->write_buf_size, PUBREC, 0, msg.id);
|
||||
mqtt_ack_list_record(c, PUBREL, msg.id + 1, len, NULL)
|
||||
mqtt_deliver_message(c, &topic_name, &msg);
|
||||
```
|
||||
|
||||
说明:一旦注册到ack列表上的报文,当具有重复的报文是不会重新被注册的,它会通过**mqtt_ack_list_node_is_exist()**函数判断这个节点是否存在,主要是依赖等待响应的消息类型与msgid。
|
||||
|
||||
## 发布收到与发布释放报文的处理
|
||||
|
||||
```c
|
||||
static int mqtt_pubrec_and_pubrel_packet_handle(mqtt_client_t *c, platform_timer_t *timer)
|
||||
```
|
||||
|
||||
1. 反序列化报文
|
||||
|
||||
```c
|
||||
MQTTDeserialize_ack(&packet_type, &dup, &packet_id, c->read_buf, c->read_buf_size)
|
||||
```
|
||||
|
||||
2. 产生一个对应的应答报文
|
||||
|
||||
```c
|
||||
mqtt_publish_ack_packet(c, packet_id, packet_type);
|
||||
```
|
||||
|
||||
3. 取消对应的ack记录
|
||||
|
||||
```c
|
||||
mqtt_ack_list_unrecord(c, UNSUBACK, packet_id, &msg_handler)
|
||||
```
|
||||
|
||||
**上一篇**:[mqttclient配置及裁剪工具](./mqtt-config.md)
|
||||
|
||||
**下一篇**:[mqttclient连接到百度天工物接入](./mqtt-baidu.md)
|
196
components/connectivity/mqttclient/docs/mqtt-introduction.md
Normal file
196
components/connectivity/mqttclient/docs/mqtt-introduction.md
Normal file
@@ -0,0 +1,196 @@
|
||||
# MQTT协议简介
|
||||
|
||||
MQTT协议全称是Message Queuing Telemetry Transport,翻译过来就是消息队列遥测传输协议,它是物联网常用的应用层协议,运行在TCP/IP中的应用层中,依赖TCP协议,因此它具有非常高的可靠性,同时它是基于TCP协议的 <客户端-服务器> 模型发布/订阅主题消息的轻量级协议,也是我们常说的发送与接收数据,下面我们来初步了解一下mqtt相关的名称与功能。
|
||||
|
||||
MQTT最大的优点在于可以以极少的代码和有限的带宽,为远程设备提供实时可靠的消息服务。做为一种低开销、低带宽占用的即时通讯协议,MQTT在物联网、小型设备、移动应用等方面有广泛的应用。
|
||||
|
||||
# MQTT是哪一层的协议?
|
||||
|
||||
众所周知,TCP/IP参考模型可以分为四层:应用层、传输层、网络层、链路层。TCP和UDP位于传输层,应用层常见的协议有HTTP、FTP、SSH等。MQTT协议运行于TCP之上,属于应用层协议,因此只要是支持TCP/IP协议栈的地方,都可以使用MQTT。
|
||||
|
||||
# MQTT通信模型
|
||||
|
||||
MQTT 协议提供一对多的消息发布,可以降低应用程序的耦合性,用户只需要编写极少量的应用代码就能完成一对多的消息发布与订阅,该协议是基于<客户端-服务器>模型,在协议中主要有三种身份:发布者(Publisher)、服务器(Broker)以及订阅者(Subscriber)。其中,MQTT消息的发布者和订阅者都是客户端,服务器只是作为一个中转的存在,将发布者发布的消息进行转发给所有订阅该主题的订阅者;发布者可以发布在其权限之内的所有主题,并且消息发布者可以同时是订阅者,实现了生产者与消费者的脱耦,发布的消息可以同时被多个订阅者订阅。
|
||||
|
||||
MQTT通信模型示意图如下:
|
||||
|
||||

|
||||
|
||||
|
||||
## MQTT客户端的功能:
|
||||
|
||||
1. 发布消息给其它相关的客户端。
|
||||
|
||||
2. 订阅主题请求接收相关的应用消息。
|
||||
|
||||
3. 取消订阅主题请求移除接收应用消息。
|
||||
|
||||
4. 从服务端终止连接。
|
||||
|
||||
## MQTT客户服务器功能:
|
||||
|
||||
MQTT 服务器常被称为 Broker(消息代理),以是一个应用程序或一台设备,它一般为云服务器,比如BTA三巨头的一些物联网平台就是常使用MQTT协议,它是位于消息发布者和订阅者之间,以便用于接收消息并发送到订阅者之中,它的功能有:
|
||||
|
||||
1. 接受来自客户端的网络连接请求。
|
||||
|
||||
2. 接受客户端发布的应用消息。
|
||||
|
||||
3. 处理客户端的订阅和取消订阅请求。
|
||||
|
||||
4. 转发应用消息给符合条件的已订阅客户端(包括发布者自身)。
|
||||
|
||||
|
||||
# 消息主题与服务质量
|
||||
|
||||
什么是主题?MQTT服务器为每个连接的客户端(订阅者)添加一个标签,该标签与服务器中的所有订阅相匹配,服务器会将消息转发给与标签相匹配的每个客户端(订阅者),当然订阅者也是需要有权限才能订阅对应的主题,比如像阿里云中的,订阅者只能订阅同一个产品下的主题,而不能跨产品订阅,这样子的处理就能达到信息的安全性以及多个订阅者能及时收到消息。一个主题可以有多个级别,各个级别之间用斜杠字符分隔,例如/test 和 /test/test1/test2都 是有效的主题。
|
||||
|
||||
发布者与订阅者可以通过主题名字,一般为UTF-8编码(反正用英文字符串就不会错)的形式发布和订阅主题,比如我们可以直接定义一个名字为“test”的主题,绝大多数的MQTT服务器支持动态发布/定阅主题,即当前服务器中没有某个主题,但是客户端直接可以向该主题发布/订阅消息,这样子服务器就会创建对应的主题,当然,服务器中一般也会默认提供多个系统主题,所有连接的客户端均可订阅。
|
||||
每个客户端与服务器建立连接后就是一个会话,客户端和服务器之间会有状态交互,订阅是基于会话之上,每个订阅中,都会包含一个主题过滤器,它是一个表达式,用于标识订阅相关的一个或多个主题,主题过滤器可以使用通配符,因此订阅者需要指定订阅的主题名字与服务质量(QoS),订阅者能订阅多个主题,也就能接收到多个发布者发布的消息。同理,发布者也需要首先与服务器建立会话,并且指定发送的主题名字与服务质量,同时它也能向多个不同的主题发送消息。
|
||||
|
||||
那么什么是服务质量呢?MQTT的服务质量提供3个等级:
|
||||
|
||||
1. QoS0:最多发送一次消息,在消息发送出去后,接收者不会发送回应,发送者也不会重发消息,消息可能送达一次也可能根本没送达,这个服务质量常用在不重要的消息传递中,因为即使消息丢了也没有太大关系。
|
||||
|
||||
2. QoS1:最少发送一次消息(消息最少需要送达一次,也有可送达多次),QoS 1的PUBLISH报文的可变报头中包含一个报文标识符,需要PUBACK报文确认。即需要接收者返回PUBACK应答报文。
|
||||
|
||||
3. QoS2:这是最高等级的服务质量,消息丢失和重复都是不可接受的,只不过使用这个服务质量等级会有额外的开销,这个等级常用于支付中,因为支付是必须有且仅有一次成功,总不能没给钱或者给了多次钱吧。
|
||||
|
||||
# MQTT控制报文
|
||||
|
||||
## 固定报头
|
||||
|
||||
MQTT协议工作在TCP协议之上,因为客户端和服务器都是应用层,那么必然需要一种协议在两者之间进行通信,那么随之而来的就是MQTT控制报文, MQTT控制报文有3个部分组成,分别是固定报头(fixed header)、可变报头(variable header)、有效荷载(数据区域payload)。固定报头,所有的MQTT控制报文都包含,可变报头与有效载荷是部分MQTT控制报文包含。
|
||||
固定报头占据两字节的空间,具体见
|
||||
|
||||

|
||||
|
||||
|
||||
固定报头的第一个字节分为控制报文的类型(4bit),以及控制报文类型的标志位,控制类型共有14种,其中0与15被系统保留出来,其他的类型具体见:
|
||||
|
||||
| 类型 | 值 | 说明 |
|
||||
| -- | -- | -- |
|
||||
| Reserved | 0 | 系统保留 |
|
||||
| CONNECT | 1 | 客户端请求连接服务端 |
|
||||
| CONNACK | 2 | 连接报文确认 |
|
||||
| PUBLISH | 3 | 发布消息 |
|
||||
| PUBACK | 4 | 消息发布收到确认(QoS 1) |
|
||||
| PUBREC | 5 | 发布收到(QoS2) |
|
||||
| PUBREL | 6 | 发布释放(QoS2) |
|
||||
| PUBCOMP | 7 | 消息发布完成(QoS2) |
|
||||
| SUBSCRIBE | 8 | 客户端订阅请求 |
|
||||
| SUBACK | 9 | 订阅请求报文确认 |
|
||||
| UNSUBSCRIBE | 10 | 客户端取消订阅请求 |
|
||||
| UNSUBACK | 11 | 取消订阅报文确认 |
|
||||
| PINGREQ | 12 | 心跳请求 |
|
||||
| PINGRESP | 13 | 心跳响应 |
|
||||
| DISCONNECT | 14 | 客户端断开连接 |
|
||||
| Reserved | 15 | 系统保留 |
|
||||
|
||||
固定报头的bit0-bit3为标志位,依照报文类型有不同的含义,事实上,除了PUBLISH类型报文以外,其他报文的标志位均为系统保留,PUBLISH报文的第一字节bit3是控制报文的重复分发标志(DUP),bit1-bit2是服务质量等级,bit0是PUBLISH报文的保留标志,用于标识PUBLISH是否保留,当客户端发送一个PUBLISH消息到服务器,如果保留标识位置1,那么服务器应该保留这条消息,当一个新的订阅者订阅这个主题的时候,最后保留的主题消息应被发送到新订阅的用户。
|
||||
|
||||
固定报头的第二个字节开始是剩余长度字段,是用于记录剩余报文长度的,表示当前的消息剩余的字节数,包括可变报头和有效载荷区域(如果存在),但剩余长度不包括用于编码剩余长度字段本身的字节数。
|
||||
|
||||
剩余长度字段使用一个变长度编码方案,对小于128的值它使用单字节编码,而对于更大的数值则按下面的方式处理:每个字节的低7位用于编码数据长度,最高位(bit7)用于标识剩余长度字段是否有更多的字节,且按照大端模式进行编码,因此每个字节可以编码128个数值和一个延续位,剩余长度字段最大可拥有4个字节。
|
||||
|
||||
- 当剩余长度使用1个字节存储时,其取值范围为0(0x00)~127(0x7f)。
|
||||
- 当使用2个字节时,其取值范围为128(0x80,0x01)~16383(0Xff,0x7f)。
|
||||
- 当使用3个字节时,其取值范围为16384(0x80,0x80,0x01)~2097151(0xFF,0xFF,0x7F)。
|
||||
- 当使用4个字节时,其取值范围为2097152(0x80,0x80,0x80,0x01)~268435455(0xFF,0xFF,0xFF,0x7F)。
|
||||
|
||||
总的来说,MQTT报文理论上可以发送最大256M的报文,当然,这种情况是非常少的。
|
||||
|
||||
## 可变报头
|
||||
|
||||
可变报头并不是所有的MQTT报文都带有的(比如PINGREQ心跳请求与PINGRESP心跳响应报文就没有可变报头),只有某些报文才拥有可变报头,它在固定报头和有效负载之间,可变报头的内容会根据报文类型的不同而有所不同,但可变报头的报文标识符(Packet Identifier)字段存在于在多个类型的报文里,而有一些报文又没有报文标识符字段,具体见表格,报文标识符结构具体见图。
|
||||
|
||||
| 报文类型 | 是否需要报文标识符字段 |
|
||||
| -- | -- |
|
||||
| CONNECT | 不需要 |
|
||||
| CONNACK | 不需要 |
|
||||
| PUBLISH | 需要(如果QoS > 0) |
|
||||
| PUBACK | 需要 |
|
||||
| PUBREC | 需要 |
|
||||
| PUBREL | 需要 |
|
||||
| PUBCOMP | 需要 |
|
||||
| SUBSCRIBE | 需要 |
|
||||
| SUBACK | 需要 |
|
||||
| UNSUBSCRIBE | 需要 |
|
||||
| UNSUBACK | 需要 |
|
||||
| PINGREQ | 不需要 |
|
||||
| PINGRESP | 不需要 |
|
||||
| DISCONNECT | 不需要 |
|
||||
|
||||

|
||||
|
||||
因为对于不同的报文,可变报头是不一样的,下面就简单讲解几个报文的可变报头。
|
||||
|
||||
## CONNECT报文
|
||||
|
||||
在一个会话中,客户端只能发送一次CONNECT报文,它是客户端用于请求连接服务器的报文,常称之为连接报文,如果客户端发送多次连接报文,那么服务端必须将客户端发送的第二个CONNECT报文当作协议违规处理并断开客户端的连接。
|
||||
|
||||
CONNECT报文的可变报头包含四个字段:协议名(Protocol Name)、协议级别(Protocol Level)、连接标志(Connect Flags)以及保持连接(Keep Alive)字段。
|
||||
|
||||
协议名是MQTT 的UTF-8编码的字符串,其中还包含用于记录协议名长度的两字节字段MSB与LSB。
|
||||
|
||||
在协议名之后的是协议级别,MQTT协议使用8位的无符号值表示协议的修订版本,对于MQTT3.1版的协议,协议级别字段的值是3(0x03),而对于MQTT3.1.1版的协议,协议级别字段的值是4(0x04)。如果服务器发现连接报文中的协议级别字段是不支持的协议级别,服务端必须给发送一个返回码为0x01(不支持的协议级别)的CONNACK响应连接报文,然后终止客户端的连接请求。
|
||||
|
||||
连接标志字段涉及的内容比较多,它在协议级别之后使用一个字节表示,但分成很多个标志位,具体见
|
||||
|
||||

|
||||
|
||||
bit0是MQTT保留的标志位,在连接过程中,服务器会检测连接标志的bit0是否为0,如果不为0则服务器任务这个连接报文是不合法的,会终止连接请求。
|
||||
|
||||
bit1是清除会话标志Clean Session,一般来说,客户端在请求连接服务器时总是将清除会话标志设置为0或1,在建立会话连接后,这个值就固定了,当然这个值的选择取决于具体的应用,如果清除会话标志设置为1,那么客户端不会收到旧的应用消息,而且在每次连接成功后都需要重新订阅相关的主题。清除会话标志设置为0的客户端在重新连接后会收到所有在它连接断开期间(其他发布者)发布的QoS1和QoS2级别的消息。因此,要确保不丢失连接断开期间的消息,需要使用QoS1或 QoS2级别,同时将清除会话标志设置为0。
|
||||
|
||||
bit2是遗嘱标志 Will Flag,如果该位被设置为1,表示如果客户端与服务器建立了会话,遗嘱消息(Will Message)将必须被存储在服务器中,当这个客户端断开连接的时候,遗嘱消息将被发送到订阅这个会话主题的所有订阅者,这个消息是很有用的,我们可以知道这个设备的状况,它是否已经掉线了,以备启动备用方案,当然,想要不发送遗嘱消息也是可以的,只需要让服务器端收到DISCONNECT报文时删除这个遗嘱消息即可。
|
||||
|
||||
bit3-bit4用于指定发布遗嘱消息时使用的服务质量等级,与其他消息的服务质量是一样的,遗嘱QoS的值可以等于0(0x00),1(0x01),2(0x02),当然,使用遗嘱消息的前提是遗嘱标志位为1。
|
||||
|
||||
bit5表示遗嘱保留标志位,当客户端意外断开连接时,如果 Will Retain置一,那么服务器必须将遗嘱消息当作保留消息发布,反之则无需保留。
|
||||
|
||||
bit6是密码标志位Password Flag,如果密码标志被设置为0,有效载荷中不能包含密码字段,反之则必须包含密码字段。
|
||||
|
||||
bit7是用户名标志位User Name Flag,如果用户名标志被设置为0,有效载荷中不能包含用户名字段,反之则必须包含用户名字段。
|
||||
|
||||
保持连接字段是一个以秒为单位的时间间隔,它使用了两个字节来记录允许客户端最大空闲时间间隔,简单来说就是,客户端必须在这段时间中与服务器进行通信,让服务器知道客户端还处于连接状态而不是断开了,当然,如果没有任何其它的控制报文可以发送,客户端也必须要发送一个PINGREQ报文,以告知服务器还是处于连接状态的。
|
||||
|
||||
总的来说,整个CONNECT报文可变报头的内容如下,具体见
|
||||
|
||||

|
||||
|
||||
## CONNACK报文
|
||||
|
||||
我们再来讲解一下CONNACK报文的可变报头部分,其实有了上一个的经验,这部分对大家来说是很简单的,它是由连接确认标志字段(Connect Acknowledge Flags)与连接返回码字段 (Connect Return code)组成,各占用1个字节。
|
||||
|
||||
它的第1个字节是 连接确认标志字段,bit1-bit7是保留位且必须设置为0, bit0是当前会话(Session Present)标志位。
|
||||
|
||||
它的第2个字节是返回码字段,如果服务器收到一个CONNECT报文,但出于某些原因无法处理它,服务器会返回一个包含返回码的CONNACK报文。如果服务器返回了一个返回码字段是非0的CONNACK报文,那么它必须关闭网络连接,返回码描述具体见
|
||||
|
||||
| 返回码值 | 描述 |
|
||||
| -- | -- |
|
||||
| 0x00 | 连接已被服务端接受 |
|
||||
| 0x01 | 连接已拒绝,服务端不支持客户端请求的MQTT协议级别 |
|
||||
| 0x02 | 连接已拒绝,服务器标识符是正确的UTF-8编码,但不允许使用 |
|
||||
| 0x03 | 连接已拒绝,网络连接已建立,但MQTT服务不可用 |
|
||||
| 0x04 | 连接已拒绝,用户名或密码的数据格式无效 |
|
||||
| 0x05 | 连接已拒绝,客户端未被授权连接到此服务器 |
|
||||
| 0x06~0xFF | 保留未使用 |
|
||||
|
||||
提示:如果服务端收到清理会话(CleanSession)标志为1的连接,除了将CONNACK报文中的返回码设置为0之外,还必须将CONNACK报文中的当前会话设置(Session Present)标志为0。
|
||||
|
||||
那么总的来说,CONNACK报文的可变报头部分内容具体见
|
||||
|
||||

|
||||
|
||||
在此,就不再对MQTT报文的可变报头部分过多赘述,大家可以参考MQTT协议手册,里面有很详细的描述。
|
||||
|
||||
## 有效载荷
|
||||
|
||||
有效载荷也是存在与某些报文中,不同的报文有效载荷也是不一样的,比如:
|
||||
|
||||
CONNECT报文的有效载荷(payload)包含一个或多个以长度为前缀的字段,可变报头中的标志决定是否包含这些字段。如果包含的话,必须按这个顺序出现:客户端标识符,遗嘱主题,遗嘱消息,用户名,密码 。
|
||||
SUBSCRIBE报文的有效载荷包含了一个主题过滤器列表,它们标识着客户端想要订阅的主题,每一个过滤器后面跟着一个字节,这个字节被叫做服务质量要求(Requested QoS),它给出了服务端向客户端发送应用消息所允许的最大QoS等级。
|
||||
|
||||
这里只是讲述了一小部分内容,关于具体的有效载荷部分也可以去看MQTT手册,此处就不再赘述。
|
||||
|
||||
**下一篇**:[MQTT通信过程](./mqtt-communication.md)
|
324
components/connectivity/mqttclient/docs/mqtt-onenet.md
Normal file
324
components/connectivity/mqttclient/docs/mqtt-onenet.md
Normal file
@@ -0,0 +1,324 @@
|
||||
# mqttclient连接到OneNET云平台
|
||||
|
||||
有了前面连接百度云的经验,废话不多说,直接使用OneNET,OneNET平台应该是最开放,对开发者最友好的平台了。
|
||||
|
||||
## 使用OneNET
|
||||
首先注册与登陆OneNET,然后进入开发者中心:https://open.iot.10086.cn/develop/global/product/#/public?protocol=3&other=1, 选择公有协议产品,点击“添加产品”,填写产品相关的信息,联网方式选择wifi(其实我们是以太网,但是没有这个选项,那就选择wifi,没啥影响的),接入协议必须选择MQTT,操作系统选择“linux”,运营商这个随意选择,具体见:
|
||||
|
||||

|
||||
|
||||
|
||||
在添加产品完毕,继续添加设备,填写设备的相关信息,鉴权信息就是登陆密码,不过需要注意的是,这个鉴权信息在产品内是唯一的,一般推荐使用产品序列号,可作为设备登录参数之一,不同协议设备可能鉴权信息的参数不一致,不过现在是做实验,选择一个好记的即可,数据保密性要选择公有,除此之外还剩下一些设备相关的信息,就不过多赘述,具体见图:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
经过上面的步骤,我们就创建完成一个产品了,此时可以去测试连接它。
|
||||
|
||||
## 测试连接
|
||||
|
||||
OneNET的数据交互做的很好,它支持动态创建主题(除系统主题外),即不用我们在平台上创建任何的主题,只需要随意订阅某个主题即可,同一个产品下,即使是不同设备之间的主题之间的消息是共享的,简单来说,我们在开发板上可以随意向某个主题发起订阅请求,也可以向这个主题发布消息,而同一产品的其他设备如果订阅了这个主题,那么将收到开发板发布的消息数据,这样子更加方便嵌入式的开发者,只不过这样子的信息安全就没有阿里云物联那么好。
|
||||
|
||||
我们可以在这个网页中去查看服务器的IP地址:https://open.iot.10086.cn/doc/multiprotocol/book/problem/platformaddress.html, 对于服务器的地址,onenet分为了不同的地区平台,不过我们一般正常使用的都是中心平台。
|
||||
|
||||

|
||||
|
||||
现在我们打开MQTT软件,进行连接测试,与前面的实验操作是一样的,配置好相关信息,即可,这些信息都可以在平台上找到,需要注意的是服务器地址是183.230.40.39;端口号是6002,这与我们常见的1883是不一样的;Client ID是设备ID,在设备列表中是可以找到的;用户名是产品ID,在产品概况页面中可以找到;密码就是创建设备时候的鉴权信息,具体见:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
接下来我们可以通过MQTT软件来测试一下能否正常连接,在这一步之前必须已在物联网平台控制台中创建产品和设备,并获取设备相关的信息。
|
||||
|
||||
其实连接是与百度天工差不多的,直接填写相关的内容即可:
|
||||
|
||||

|
||||
|
||||
配置好就可以连接,然后随便订阅一个主题,因为OneNet平台支持动态创建主题(除系统主题外),所以对我们来说是非常方便的,直接订阅就好了,然后再用客户端进行发布消息,如果收到消息,表明通信成功,
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
回到OneNet的设备列表界面,可以看到刚刚创建的设备是处于在线状态的:
|
||||
|
||||

|
||||
|
||||
|
||||
## 手动安装相关的依赖包
|
||||
|
||||
这些依赖包是使用mqttclient库去连接OneNet云时必须要安装的。
|
||||
|
||||
```bash
|
||||
sudo apt-get -y install git
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo apt-get -y install mkae
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo apt-get -y install gcc
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo apt-get -y install g++
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo apt-get -y install cmake
|
||||
```
|
||||
|
||||
## 拉取mqttclient仓库
|
||||
|
||||
接着到github拉取这个仓库,仓库非常小,下载下来是很快的,注意,这里也需要拉取下来。
|
||||
|
||||
```bash
|
||||
git clone https://github.com/jiejieTop/mqttclient.git
|
||||
```
|
||||
|
||||
当然也可以从gitee仓库下载。
|
||||
|
||||
```bash
|
||||
git clone https://gitee.com/jiejieTop/mqttclient.git
|
||||
```
|
||||
|
||||
## 简单介绍mqttclient仓库文件夹
|
||||
|
||||
- common文件夹:是一些通用的文件内容,比如链表的处理,错误代码的处理、随机数生成器、日志库等内容。
|
||||
|
||||
- mqtt文件夹:著名的paho mqtt库。
|
||||
|
||||
- mqttclient文件夹:实现mqttclient的主要文件,并且包含了一个默认的配置文件。
|
||||
|
||||
- network文件夹:网络抽象层,封装了mbedtls加密库、网络数据的通道类型,自动选择tls加密传输或者是tcp直连。
|
||||
|
||||
- platform文件夹:平台抽象层,此处封装了各种平台的内存管理、互斥锁、线程管理、时间管理等内容,如linux平台,freertos平台、rt-thread平台、TencentOS tiny平台等。
|
||||
|
||||
- test文件夹:一些测试的代码,比如我们连接OneNet云的时候,就会使用test文件夹的OneNet云平台的测试代码。
|
||||
|
||||
## 编译
|
||||
|
||||
拉取下来后看到本地有mqttclient文件夹,我们进去mqttclient目录下,运行它提供的编译脚本,它主要是通过cmake去自动构建整个代码工程。
|
||||
|
||||
```bash
|
||||
./build.sh
|
||||
|
||||
# 产生的信息如下:
|
||||
|
||||
-- Configuring done
|
||||
-- Generating done
|
||||
-- Build files have been written to: /home/jiejie/github/mqttclient/build
|
||||
[ 9%] Built target mqtt
|
||||
[ 12%] Built target common
|
||||
[ 17%] Built target platform
|
||||
[ 21%] Built target wrapper
|
||||
[ 85%] Built target mbedtls
|
||||
[ 88%] Built target network
|
||||
[ 90%] Built target mqttclient
|
||||
[ 92%] Built target arch
|
||||
[ 95%] Built target salof
|
||||
[ 97%] Built target emqx
|
||||
[ 98%] Built target onenet
|
||||
[ 99%] Built target baidu
|
||||
[100%] Built target ali
|
||||
```
|
||||
|
||||
## 运行
|
||||
|
||||
当编译完成后,在`./build/bin`目录下会出现多个可执行文件,在`./build/lib`目录下会出现相关的动态库文件,具体如下:
|
||||
|
||||
```bash
|
||||
➜ mqttclient git:(master) ls build/bin
|
||||
ali baidu emqx onenet
|
||||
|
||||
➜ mqttclient git:(master) ls build/lib
|
||||
libarch.a libcommon.a libmbedtls.a libmqtt.a libmqttclient.a libnetwork.a libplatform.a libsalof.a libwrapper.a
|
||||
```
|
||||
|
||||
我们直接运行`./build/bin/onenet`这个可执行文件:
|
||||
|
||||
```bash
|
||||
➜ mqttclient git:(master) ✗ ./build/bin/onenet
|
||||
|
||||
welcome to mqttclient test...
|
||||
|
||||
[I] >> [TS: 1590547112] /home/jiejie/github/mqttclient/mqttclient/mqttclient.c:948 mqtt_connect_with_results()... mqtt connect success...
|
||||
[I] >> [TS: 1590547113] /home/jiejie/github/mqttclient/mqttclient/mqttclient.c:16 default_msg_handler()...
|
||||
topic: temp_hum, qos: 0,
|
||||
message:welcome to mqttclient, this is a publish test, a rand number: 1804289383 ...
|
||||
[I] >> [TS: 1590547116] /home/jiejie/github/mqttclient/mqttclient/mqttclient.c:16 default_msg_handler()...
|
||||
topic: temp_hum, qos: 0,
|
||||
message:welcome to mqttclient, this is a publish test, a rand number: 1837236902 ...
|
||||
```
|
||||
|
||||
此时表示已经连接上OneNet云了,并且实现了数据的收发。
|
||||
|
||||
## 代码
|
||||
|
||||
那么这个测试的例程代码如下,位于`./test/onenet/test.c`:
|
||||
|
||||
```c
|
||||
/*
|
||||
* @Author: jiejie
|
||||
* @Github: https://github.com/jiejieTop
|
||||
* @Date: 2020-04-18 12:37:34
|
||||
* @LastEditTime: 2020-06-08 20:32:33
|
||||
* @Description: the code belongs to jiejie, please keep the author information and source code according to the license.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include "mqttclient.h"
|
||||
|
||||
extern const char *test_ca_get();
|
||||
|
||||
|
||||
static void interceptor_handler(void* client, message_data_t* msg)
|
||||
{
|
||||
(void) client;
|
||||
MQTT_LOG_I("-----------------------------------------------------------------------------------");
|
||||
MQTT_LOG_I("%s:%d %s()...\ntopic: %s\nmessage:%s", __FILE__, __LINE__, __FUNCTION__, msg->topic_name, (char*)msg->message->payload);
|
||||
MQTT_LOG_I("-----------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
void *mqtt_publish_thread(void *arg)
|
||||
{
|
||||
mqtt_client_t *client = (mqtt_client_t *)arg;
|
||||
|
||||
char buf[100] = { 0 };
|
||||
mqtt_message_t msg;
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
sprintf(buf, "welcome to mqttclient, this is a publish test...");
|
||||
|
||||
msg.qos = 0;
|
||||
msg.payload = (void *) buf;
|
||||
while(1) {
|
||||
sprintf(buf, "welcome to mqttclient, this is a publish test, a rand number: %d ...", random_number());
|
||||
mqtt_publish(client, "topic1", &msg);
|
||||
sleep(4);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int res;
|
||||
pthread_t thread1;
|
||||
mqtt_client_t *client = NULL;
|
||||
|
||||
printf("\nwelcome to mqttclient test...\n");
|
||||
|
||||
mqtt_log_init();
|
||||
|
||||
client = mqtt_lease();
|
||||
|
||||
mqtt_set_port(client, "6002");
|
||||
mqtt_set_host(client, "183.230.40.39");
|
||||
mqtt_set_client_id(client, "599908192");
|
||||
mqtt_set_user_name(client, "348547");
|
||||
mqtt_set_password(client, "mqttclienttest1");
|
||||
mqtt_set_clean_session(client, 1);
|
||||
|
||||
mqtt_connect(client);
|
||||
|
||||
mqtt_subscribe(client, "topic1", QOS0, NULL);
|
||||
|
||||
mqtt_set_interceptor_handler(client, interceptor_handler); // set interceptor handler
|
||||
|
||||
res = pthread_create(&thread1, NULL, mqtt_publish_thread, client);
|
||||
if(res != 0) {
|
||||
MQTT_LOG_E("create mqtt publish thread fail");
|
||||
exit(res);
|
||||
}
|
||||
|
||||
while (1) {
|
||||
sleep(100);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用到的API
|
||||
|
||||
- 申请一个MQTT客户端
|
||||
|
||||
```c
|
||||
mqtt_client_t *client = NULL;
|
||||
client = mqtt_lease();
|
||||
```
|
||||
|
||||
- mqtt客户端配置,主要是配置**mqtt_client_t**结构的相关信息,如果没有指定初始化参数,则系统会提供默认的参数。但连接部分的参数则必须指定,比如连接的端口号、云服务器的地址或者域名、用户名、密码,这些信息都是百度云平台得到的。
|
||||
|
||||
```c
|
||||
mqtt_set_port(client, "6002");
|
||||
mqtt_set_host(client, "183.230.40.39");
|
||||
mqtt_set_client_id(client, "599908192");
|
||||
mqtt_set_user_name(client, "348547");
|
||||
mqtt_set_password(client, "mqttclienttest1");
|
||||
mqtt_set_clean_session(client, 1);
|
||||
```
|
||||
|
||||
- 连接服务器并建立mqtt会话。
|
||||
|
||||
```c
|
||||
mqtt_connect(&client);
|
||||
```
|
||||
|
||||
- 订阅主题,字符串类型的**主题**(支持通配符"#" "+"),主题的**服务质量**,以及收到报文的**回调处理函数**,如不指定则有默认处理函数,订阅主题的处理方式是异步处理的,此处设置为NULL则表示使用默认的回调处理函数。
|
||||
|
||||
```c
|
||||
mqtt_subscribe(client, "topic1", QOS0, NULL);
|
||||
```
|
||||
|
||||
- 创建一个发布主题的线程,并且发布主题数据,指定字符串类型的**主题**(支持通配符),要发布的消息(包括**服务质量**、**消息主体**)。
|
||||
|
||||
```c
|
||||
mqtt_message_t msg;
|
||||
msg.payload = (void *) buf;
|
||||
msg.qos = 0;
|
||||
|
||||
mqtt_publish(&client, "topic1", &msg);
|
||||
```
|
||||
|
||||
- 由于onenet的设备会自动订阅系统主题,而用户是不能直接订阅的,比如我们在线调试的时候,他会下发一些系统主题,那么我们需要接收这些主题,就需要设置拦截器的处理函数,去拦截它们并且通过回调函数上报到应用层,设置如下:
|
||||
|
||||
```c
|
||||
mqtt_set_interceptor_handler(&client, interceptor_handler);
|
||||
```
|
||||
|
||||
我们在设备列表页面,点击下方命令,下发一些数据到开发板上:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
在开发板上可以看到这些数据内容,可以看到他的主题是`$creq/`开头的,而且接下来的内容应该是经过平台加密生成的,是随机的,我们也无法主动去订阅,只能通过拦截器去接收这些数据内容。
|
||||
|
||||
```bash
|
||||
[I] >> [TS: 1590548249] -----------------------------------------------------------------------------------
|
||||
[I] >> [TS: 1590548249] /home/jiejie/github/mqttclient/test/onenet/test.c:26 interceptor_handler()...
|
||||
topic: $creq/f6132232-bbcd-5b1e-9b9a-806da959ce80
|
||||
message:hello world !
|
||||
[I] >> [TS: 1590548249] -----------------------------------------------------------------------------------
|
||||
|
||||
[I] >> [TS: 1590548023] -----------------------------------------------------------------------------------
|
||||
[I] >> [TS: 1590548023] /home/jiejie/github/mqttclient/test/onenet/test.c:24 interceptor_handler()...
|
||||
topic: $creq/5d2670bb-a9a4-5bc9-93d5-107246135af8
|
||||
message:hello world !
|
||||
[I] >> [TS: 1590548023] -----------------------------------------------------------------------------------
|
||||
```
|
||||
|
||||
|
||||
**上一篇**:[mqttclient连接到百度天工物接入](./mqtt-baidu.md)
|
||||
|
||||
**下一篇**:[mqttclient连接到阿里云物联网平台](./mqtt-aliyun.md)
|
83
components/connectivity/mqttclient/docs/mqtt-tool.md
Normal file
83
components/connectivity/mqttclient/docs/mqtt-tool.md
Normal file
@@ -0,0 +1,83 @@
|
||||
|
||||
# mqttclient代码生产工具介绍
|
||||
|
||||
mqttclient代码生产工具主要是用于配置MQTT的参数,并且生成相应的代码,因为是可视化的配置,极易使用。
|
||||
|
||||
地址:[https://jiejietop.gitee.io/mqtt/index.html](https://jiejietop.gitee.io/mqtt/index.html)
|
||||
|
||||

|
||||
|
||||
与此同时改工具页面还包含了mqttclient的API接口介绍及示例,通过它们就能知道这个工具应该配置了什么内容。
|
||||
|
||||

|
||||
|
||||
# 连接参数配置
|
||||
|
||||
首先连接参数代表着MQTT客户端应该如何连接到服务器,建立MQTT会话应该是如何配置,这些就是连接参数
|
||||
|
||||
- 与服务器相关的参数有:
|
||||
|
||||
- 服务器地址。
|
||||
|
||||
- 服务器端口号。
|
||||
|
||||
- 服务器CA证书(如果有TLS加密的话)。
|
||||
|
||||
- 建立MQTT连接需要的参数有:
|
||||
|
||||
- MQTT协议版本。
|
||||
|
||||
- 用户名。
|
||||
|
||||
- 密码。
|
||||
|
||||
- 客户端ID。
|
||||
|
||||
- 配置是否清除会话,默认清除会话。
|
||||
|
||||
- 心跳时间间隔,默认是50秒。
|
||||
|
||||
- 是否使用遗嘱。
|
||||
|
||||
- 是否保留遗嘱消息。
|
||||
|
||||
- 设置遗嘱主题。
|
||||
|
||||
- 设置遗嘱消息的服务质量等级。
|
||||
|
||||
- 设置遗嘱消息的内容。
|
||||
|
||||
- 与与客户端资源相关的配置:
|
||||
|
||||
- 设置命令的超时,它主要是用于socket读写超时,默认是5000毫秒。
|
||||
|
||||
- 读缓冲区大小,默认是1024。
|
||||
|
||||
- 写缓冲区大小,默认是1024。
|
||||
|
||||

|
||||
|
||||
# 订阅主题相关的代码配置
|
||||
|
||||
此配置用于配置MQTT客户端订阅的主题信息,指定订阅主题名字,服务质量等级以及当收到来自这个主题消息时候的回调处理函数(可以为NULL),mqttclient代码生成工具支持动态添加多个主题,满足绝大部分的日常需求。
|
||||
|
||||

|
||||
|
||||
# 发布消息相关的代码配置
|
||||
|
||||
mqttclient代码生成工具支持动态添向多个主题发布消息,满足绝大部分的日常需求。只需指定要发布消息的主题名字、服务质量等级、以及发布的消息内容即可。
|
||||
|
||||

|
||||
|
||||
# 生成代码
|
||||
|
||||
在配置完成后,点击下方的生成代码按钮,即可生成对应的配置代码。当然,你也可以点击导入模板,使用模板默认的配置来生成代码。
|
||||
|
||||

|
||||
|
||||
直接将生成的代码复制到你的main.c文件即可编译运行。
|
||||
|
||||
|
||||
**上一篇**:[MQTT通信过程](./mqtt-communication.md)
|
||||
|
||||
**下一篇**:[mqttclient配置及裁剪工具](./mqtt-config.md)
|
Reference in New Issue
Block a user