mqtt client release v1.1.0 ...

This commit is contained in:
jiejietop
2020-06-18 19:49:14 +08:00
parent 0202c5b5c9
commit cd2368dbeb
241 changed files with 117626 additions and 1584 deletions

View File

@@ -0,0 +1,380 @@
# 连接到阿里云物联
既然懂得专门连接百度天工物接入那么连接阿里云物联其实也是一样的因为都是基于MQTT协议进行通信的首先打开阿里云物联https://iot.console.aliyun.com/product创建账号并登陆而且阿里云物联是需要通过实名认证才能使用的。
相对于百度天工物接入,阿里云物联的安全性更好,因为即使是客户端连接服务器都需要各种验证,还使用哈希加密算法进行加密。
## 使用阿里云物联
下面开始使用阿里云物联,首先在“产品”信息中创建一个产品,每个产品下允许有多个设备,产品就是设备的集合,通常是一组具有相同功能定义的设备集合。例如:产品指同一个型号的产品,设备就是该型号下的某个设备。操作步骤如下:
### 创建产品
![mqtt-ali001](http://qiniu.jiejie01.top/mqtt-ali001.png)
- 填写产品信息如产品名称所属类品选择自定义类品、节点类型选择直连设备、联网方式选择wifi、数据格式选择透传的方式、认证方式选择设备秘钥最后点击保存这样子一个产品就创建完成。
![mqtt-ali002](http://qiniu.jiejie01.top/mqtt-ali002.png)
### 添加设备
创建完产品后选择添加设备,并填写设备的名字。
![mqtt-ali003](http://qiniu.jiejie01.top/mqtt-ali003.png)
![mqtt-ali004](http://qiniu.jiejie01.top/mqtt-ali004.png)
- 在设备添加完成后,要保存设备的证书,后续会使用到,比如此时的设备证书是:
```json
{
"ProductKey": "a1w7XupONEX",
"DeviceName": "test1",
"DeviceSecret": "2H1FiqEvyovF8bdckg6RjBcO2LAgGhwu"
}
```
![mqtt-ali005](http://qiniu.jiejie01.top/mqtt-ali005.png)
- 当然,我们也能在设备下查看这些信息,以及其他的一些信息。
![mqtt-ali006](http://qiniu.jiejie01.top/mqtt-ali006.png)
![mqtt-ali007](http://qiniu.jiejie01.top/mqtt-ali007.png)
### 自定义主题
最后我们定义一个主题Topic类列表让设备能对这个主题进行订阅或者发布操作回到“产品”选项选择“Topic类列表”定义“Topic类列表”再填写“Topic类列表”的信息即可注意选择设备的操作权限“发布和订阅”这点很重要如果没有权限设备是无法对这个主题进行操作的具体过程如图所示至此一个产品与设备就创建完成了。
![mqtt-ali008](http://qiniu.jiejie01.top/mqtt-ali008.png)
![mqtt-ali009](http://qiniu.jiejie01.top/mqtt-ali009.png)
![mqtt-ali010](http://qiniu.jiejie01.top/mqtt-ali010.png)
- 回到设备页面可以看到设备的主题已经有了刚刚自定义定义的topic权限是可订阅与可发布。
![mqtt-ali011](http://qiniu.jiejie01.top/mqtt-ali011.png)
## 测试连接
在创建完成后可以通过MQTT软件来测试一下能否正常连接在这一步之前必须已在物联网平台控制台中创建产品和设备并获取设备证书信息ProductKey、DeviceName和DeviceSerect
其实连接是与百度天工差不多的,只不过这里的配置连接的信息比百度云天工物接入麻烦很多,因为考虑了安全的问题,需要进行加密验证。
- 首先回到设备页面,查看设备相关的信息,比如区域(这很重要,后续使用到的)、设备的证书等内容。
![mqtt-ali012](http://qiniu.jiejie01.top/mqtt-ali012.png)
![mqtt-ali013](http://qiniu.jiejie01.top/mqtt-ali013.png)
- 通过阿里云的在线签名工具[https://jiejietop.gitee.io/aliyun/index.html](https://jiejietop.gitee.io/aliyun/index.html) 。 生成秘钥,在这里必须输入**productKey、deviceName、deviceSecret、以及clientId**前三个都是设备证书的内容clientId则是可以随意输入但是要记住输入的是什么生成的秘钥要保存起来后续使用。
![mqtt-ali014](http://qiniu.jiejie01.top/mqtt-ali014.png)
- 打开MQTTX软件在软件中填写名称、Client ID选择MQTT协议填写项目的地址信息、端口号、用户名以及密码然后点击连接。
![mqtt-ali015](http://qiniu.jiejie01.top/mqtt-ali015.png)
这个参数看起来很简单,但是它的组成却不简单,下面具体介绍一下参数是怎么来的,具体见:
- 名称:自定义名称
- Client ID格式`${clientId}|securemode=3,signmethod=hmacsha1|`。${clientId}为设备的ID信息就是上一步我们随意填写的值可取任意值长度在64字符以内即可securemode为安全模式TCP直连模式设置为securemode=3TLS直连为securemode=2signmethod为算法类型支持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即可。
在连接成功后,可以添加订阅的主题名字,向指定的主题发送内容:
![mqtt-ali016](http://qiniu.jiejie01.top/mqtt-ali016.png)
## 更新
为了确保我们安装的软件包的版本是最新版本让我们使用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] -----------------------------------------------------------------------------------
```
此时表示已经连接上阿里云物联了,并且实现了数据的收发,你可以到阿里云平台的日志服务中观看,可以看到设备的日志信息是正常的。
![mqtt-ali017](http://qiniu.jiejie01.top/mqtt-ali017.png)
## 代码
那么这个测试的例程代码如下,位于`./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)
**下一篇**:待完善

View 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条消息则总条数的计算方法为1PUB+5SUB=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-baidu000](http://qiniu.jiejie01.top/mqtt-baidu000.png)
![mqtt-baidu001](http://qiniu.jiejie01.top/mqtt-baidu001.png)
此处补充说明一点:通过项目可以将不同项目的设备进行隔离和管理,一个项目下允许有多个用户(设备),用户与身份进行绑定,而每个身份需要绑定一个策略,而策略下可以创建多个不同的主题,因此一个设备可以订阅多个不同的主题,并且同一个项目下的主题是共享的,所有的设备均可进行订阅。
具体见:
![mqtt-baidu0011](http://qiniu.jiejie01.top/mqtt-baidu0011.png)
### 创建策略
首先我们点击刚刚创建的“mqtt-client”项目进入项目里面首先创建一个策略简单来说就是主题输入对应的名称与主题选择发布与订阅权限当创建完成后项目下的设备就可以订阅这个主题具体见
![mqtt-baidu002](http://qiniu.jiejie01.top/mqtt-baidu002.png)
### 创建身份
身份是开发板连接IoT Hub的重要途径这里面包含了秘钥标识着开发板能否通过IoT Hub的验证这也是安全保障的主要操作首先点击“身份列表”再点击“创建身份”然后根据自己的信息进行填写在这里要注意的是需要勾选密码验证再选择之前我们创建的策略进行绑定如果没有则必须创建当身份创建完成时候生成的秘钥是用于开发板连接IoT Hub的必要条件记住不能丢失如果丢失了就找不回来了在创建的时候就把它保存好。
![mqtt-baidu003](http://qiniu.jiejie01.top/mqtt-baidu003.png)
![mqtt-baidu004](http://qiniu.jiejie01.top/mqtt-baidu004.png)
![mqtt-baidu005](http://qiniu.jiejie01.top/mqtt-baidu005.png)
### 创建用户
在创建用户的时候,只需要把用户名设置好,然后再绑定身份即可,这样子一个设备就对应到云端了,并且可以向策略中设置的主题发布消息和订阅主题,具体见:
![mqtt-baidu006](http://qiniu.jiejie01.top/mqtt-baidu006.png)
![mqtt-baidu007](http://qiniu.jiejie01.top/mqtt-baidu007.png)
![mqtt-baidu008](http://qiniu.jiejie01.top/mqtt-baidu008.png)
### 测试连接
当所有的东西都创建完成我们点击身份操作中的“测试连接”在网页上测试一下我们能不能正常进行MQTT通信、发布与定阅主题在页面中输入刚刚保存的秘钥点击“connect”进行连接测试如果前面的步骤全部做完并且秘钥是正确的就可以发现我们的连接是正常的然后我们订阅一下之前创建策略时候的主题名字“topic1”再向这个主题发送一个消息消息的内容由自己定义
![mqtt-baidu009](http://qiniu.jiejie01.top/mqtt-baidu009.png)
![mqtt-baidu010](http://qiniu.jiejie01.top/mqtt-baidu010.png)
![mqtt-baidu011](http://qiniu.jiejie01.top/mqtt-baidu011.png)
![mqtt-baidu012](http://qiniu.jiejie01.top/mqtt-baidu012.png)
最后回到项目下,可以看到项目的地址信息,等内容。
![mqtt-baidu013](http://qiniu.jiejie01.top/mqtt-baidu013.png)
## MQTT软件测试连接
仔细观察的同学可能会发现这个连接的端口是8884因为这是在网页上测试连接的所以端口不一样是正常的那么我们也可以使用MQTT客户端软件进行连接测试这个软件的界面看起来很漂亮并且它是开源的我们直接用它就好了可以从github下载https://github.com/emqx/MQTTX/releases。
此处演示MQTTX软件去连接百度云
首先获取到用户名与项目的地址信息:
![mqtt-baidu014](http://qiniu.jiejie01.top/mqtt-baidu014.png)
![mqtt-baidu013](http://qiniu.jiejie01.top/mqtt-baidu013.png)
在软件中填写名称、Client ID选择MQTT协议填写项目的地址信息、端口号、用户名以及密码然后点击连接。
![mqtt-baidu015](http://qiniu.jiejie01.top/mqtt-baidu015.png)
在连接成功后,可以添加订阅的主题名字,向指定的主题发送内容:
![mqtt-baidu016](http://qiniu.jiejie01.top/mqtt-baidu016.png)
## 手动安装相关的依赖包
这些依赖包是使用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)

View File

@@ -0,0 +1,153 @@
# 图文并茂学习MQTT协议通信过程
# MQTT连接服务器
客户端到服务器的网络连接建立后客户端发送给服务器的第一个报文必须是CONNECT报文
在一个网络连接上客户端只能发送一次CONNECT报文如果出现第二个CONNECT报文按照协议标准服务器会将第二个CONNECT报文当作协议违规处理并断开客户端的连接。
对于正常的连接请求,服务器必须产生应答报文,如果无法建立会话,服务器应该在应答报文中报告对应的错误代码。
![mqtt007](http://qiniu.jiejie01.top/mqtt007.png)
# MQTT订阅主题
客户端向服务器发送SUBSCRIBE报文用于创建一个或多个订阅。
在服务器中会记录这个客户关注的一个或者多个主题当服务器收到这些主题的PUBLISH报文的时候将分发应用消息到与之匹配的客户端中。
SUBSCRIBE报文支持通配符也为每个订阅指定了最大的QoS等级服务器根据这些信息分发应用消息给客户端。
SUBSCRIBE报文拥有固定报头、可变报头、有效载荷。
当服务器收到客户端发送的一个SUBSCRIBE报文时必须向客户端发送一个SUBACK报文响应同时SUBACK报文必须和等待确认的SUBSCRIBE报文有相同的报文标识符。
如果服务器收到一个SUBSCRIBE报文报文的主题过滤器与一个现存订阅的主题过滤器相同那么必须使用新的订阅彻底替换现存的订阅。新订阅的主题过滤器和之前订阅的相同但是它的最大QoS值可以不同。与这个主题过滤器匹配的任何现存的保留消息必须被重发但是发布流程不能中断。
![mqtt008](http://qiniu.jiejie01.top/mqtt008.png)
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等于0DUP等于0的PUBLISH报文。
在服务器接受PUBLISH报文时要将消息分发给订阅该主题消息的订阅者。
![mqtt009](http://qiniu.jiejie01.top/mqtt009.png)
## QoS1的PUBLISH控制报文
服务质量确保消息至少送达一次甚至可能被多次处理。QoS1的PUBLISH报文的可变报头中包含一个报文标识符需要PUBACK报文确认。
发布者在每次发送新的应用消息都必须分配一个未使用的报文标识符在发布消息的同时将消息存储起来等待服务器的应答直到从接收者那收到对应的PUBACK报文。发送的PUBLISH报文必须包含报文标识符且QoS等于1DUP等于0。
一旦发布者收到来自服务器的PUBACK报文后这个报文标识符就可以重复使用。
接收者响应的PUBACK报文必须包含一个报文标识符这个标识符来自接收到的PUBLISH报文。在发送了PUBACK报文之后接收者必须将任何包含相同报文标识符的入站PUBLISH报文当作一个新的消息并忽略它的DUP标志的值。
![mqtt010](http://qiniu.jiejie01.top/mqtt010.png)
## QoS2的PUBLISH控制报文
这是最高等级的服务质量,必须保证有且只有处理一次消息,消息丢失和重复都是不可接受的。使用这个服务质量等级会有额外的开销。
QoS2的消息可变报头中有报文标识符。
QoS2的PUBLISH报文的接收者使用一个两步确认过程来确认收到。
发送者必须给要发送的新应用消息分配一个未使用的报文标识符。发送的PUBLISH报文必须包含报文标识符且报文的QoS等于2,DUP等于0。
在消息发出去后需要将这个消息存储起来而且必须将这个PUBLISH报文看作是未确认的直到从接收者那收到对应的PUBREC报文。
当发布者收到的PUBREC报文后必须发送一个PUBREL报文。PUBREL报文必须包含与原始PUBLISH报文相同的报文标识符。
而且发布者还必须必须将这个PUBREL报文看作是未确认的直到从接收者那收到对应的PUBCOMP报文。一旦发送了对应的PUBREL报文就不能重发这个PUBLISH报文。
所以就如下图所示在发布消息的时候立马存储消息在收到PUBREC报文后必须将存储的消息丢弃掉然后存储报文标识符与此同时还要将PUBREL报文发送出去最后在收到PUBCOMP报文后才丢弃存储的报文标识符。
![mqtt011](http://qiniu.jiejie01.top/mqtt011.png)
当然啦,对应分发消息也是比较复杂的,它一般有两种处理方案,每一种方案都要确保消息有且只有处理一次。
接收者此处指服务器响应的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响应。
![mqtt012](http://qiniu.jiejie01.top/mqtt012.png)
# 断开连接
DISCONNECT报文是客户端发给服务端的最后一个控制报文。表示客户端正常断开连接。
DISCONNECT报文的固定报头保留位必须全为0。
客户端发送DISCONNECT报文之后必须关闭网络连接不能通过那个网络连接再发送任何控制报文。
服务端在收到DISCONNECT报文时必须丢弃任何与当前连接关联的未发布的遗嘱消息。而且当客户端没有关闭网络连接的时候服务器应该主动去关闭网络连接。
![mqtt013](http://qiniu.jiejie01.top/mqtt013.png)
**上一篇**[MQTT协议简介](./mqtt-introduction.md)
**下一篇**[mqttclient代码生成工具](./mqtt-tool.md)

View 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)
它支持裁剪配置相关的头文件,配置客户端默认的一些参数:
![mqtt-config-tool01](http://qiniu.jiejie01.top/mqtt-config-tool01.png)
还支持配置salof同步异步日志相关的信息以保证日志能正常运作配置完成后生成对应的代码然后覆盖掉原本的MQTT客户端配置即可如果不知道如何配置那就导入默认的配置即可。
![mqtt-config-tool02](http://qiniu.jiejie01.top/mqtt-config-tool02.png)
如果是初次接触,看不懂上面配置的信息,没关系,我很贴心地准备了配置的宏定义及其描述信息,保证你能看得懂的。
![mqtt-config-tool03](http://qiniu.jiejie01.top/mqtt-config-tool03.png)
**上一篇**[mqttclient代码生成工具](./mqtt-tool.md)
**下一篇**[mqttclient设计与实现方式](./mqtt-design.md)

View 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协议的版本默认值是4MQTT版本为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)

View 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通信模型示意图如下
![mqtt001](http://qiniu.jiejie01.top/mqtt001.png)
## 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控制报文包含。
固定报头占据两字节的空间,具体见
![mqtt002](http://qiniu.jiejie01.top/mqtt002.png)
固定报头的第一个字节分为控制报文的类型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是控制报文的重复分发标志DUPbit1-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 | 不需要 |
![mqtt003](http://qiniu.jiejie01.top/mqtt003.png)
因为对于不同的报文,可变报头是不一样的,下面就简单讲解几个报文的可变报头。
## 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响应连接报文然后终止客户端的连接请求。
连接标志字段涉及的内容比较多,它在协议级别之后使用一个字节表示,但分成很多个标志位,具体见
![mqtt004](http://qiniu.jiejie01.top/mqtt004.png)
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报文可变报头的内容如下具体见
![mqtt005](http://qiniu.jiejie01.top/mqtt005.png)
## 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报文的可变报头部分内容具体见
![mqtt006](http://qiniu.jiejie01.top/mqtt006.png)
在此就不再对MQTT报文的可变报头部分过多赘述大家可以参考MQTT协议手册里面有很详细的描述。
## 有效载荷
有效载荷也是存在与某些报文中,不同的报文有效载荷也是不一样的,比如:
CONNECT报文的有效载荷payload包含一个或多个以长度为前缀的字段可变报头中的标志决定是否包含这些字段。如果包含的话必须按这个顺序出现客户端标识符遗嘱主题遗嘱消息用户名密码 。
SUBSCRIBE报文的有效载荷包含了一个主题过滤器列表它们标识着客户端想要订阅的主题每一个过滤器后面跟着一个字节这个字节被叫做服务质量要求Requested QoS它给出了服务端向客户端发送应用消息所允许的最大QoS等级。
这里只是讲述了一小部分内容关于具体的有效载荷部分也可以去看MQTT手册此处就不再赘述。
**下一篇**[MQTT通信过程](./mqtt-communication.md)

View File

@@ -0,0 +1,324 @@
# mqttclient连接到OneNET云平台
有了前面连接百度云的经验废话不多说直接使用OneNETOneNET平台应该是最开放对开发者最友好的平台了。
## 使用OneNET
首先注册与登陆OneNET然后进入开发者中心https://open.iot.10086.cn/develop/global/product/#/public?protocol=3&other=1 选择公有协议产品点击“添加产品”填写产品相关的信息联网方式选择wifi其实我们是以太网但是没有这个选项那就选择wifi没啥影响的接入协议必须选择MQTT操作系统选择“linux”运营商这个随意选择具体见
![mqtt-onenet001](http://qiniu.jiejie01.top/mqtt-onenet001.png)
在添加产品完毕,继续添加设备,填写设备的相关信息,鉴权信息就是登陆密码,不过需要注意的是,这个鉴权信息在产品内是唯一的,一般推荐使用产品序列号,可作为设备登录参数之一,不同协议设备可能鉴权信息的参数不一致,不过现在是做实验,选择一个好记的即可,数据保密性要选择公有,除此之外还剩下一些设备相关的信息,就不过多赘述,具体见图:
![mqtt-onenet002](http://qiniu.jiejie01.top/mqtt-onenet002.png)
![mqtt-onenet003](http://qiniu.jiejie01.top/mqtt-onenet003.png)
![mqtt-onenet004](http://qiniu.jiejie01.top/mqtt-onenet004.png)
经过上面的步骤,我们就创建完成一个产品了,此时可以去测试连接它。
## 测试连接
OneNET的数据交互做的很好它支持动态创建主题除系统主题外即不用我们在平台上创建任何的主题只需要随意订阅某个主题即可同一个产品下即使是不同设备之间的主题之间的消息是共享的简单来说我们在开发板上可以随意向某个主题发起订阅请求也可以向这个主题发布消息而同一产品的其他设备如果订阅了这个主题那么将收到开发板发布的消息数据这样子更加方便嵌入式的开发者只不过这样子的信息安全就没有阿里云物联那么好。
我们可以在这个网页中去查看服务器的IP地址https://open.iot.10086.cn/doc/multiprotocol/book/problem/platformaddress.html 对于服务器的地址onenet分为了不同的地区平台不过我们一般正常使用的都是中心平台。
![mqtt-onenet005](http://qiniu.jiejie01.top/mqtt-onenet005.png)
现在我们打开MQTT软件进行连接测试与前面的实验操作是一样的配置好相关信息即可这些信息都可以在平台上找到需要注意的是服务器地址是183.230.40.39端口号是6002这与我们常见的1883是不一样的Client ID是设备ID在设备列表中是可以找到的用户名是产品ID在产品概况页面中可以找到密码就是创建设备时候的鉴权信息具体见
![mqtt-onenet006](http://qiniu.jiejie01.top/mqtt-onenet006.png)
![mqtt-onenet007](http://qiniu.jiejie01.top/mqtt-onenet007.png)
![mqtt-onenet008](http://qiniu.jiejie01.top/mqtt-onenet008.png)
接下来我们可以通过MQTT软件来测试一下能否正常连接在这一步之前必须已在物联网平台控制台中创建产品和设备并获取设备相关的信息。
其实连接是与百度天工差不多的,直接填写相关的内容即可:
![mqtt-onenet009](http://qiniu.jiejie01.top/mqtt-onenet009.png)
配置好就可以连接然后随便订阅一个主题因为OneNet平台支持动态创建主题除系统主题外所以对我们来说是非常方便的直接订阅就好了然后再用客户端进行发布消息如果收到消息表明通信成功
![mqtt-onenet010](http://qiniu.jiejie01.top/mqtt-onenet010.png)
![mqtt-onenet011](http://qiniu.jiejie01.top/mqtt-onenet011.png)
回到OneNet的设备列表界面可以看到刚刚创建的设备是处于在线状态的
![mqtt-onenet012](http://qiniu.jiejie01.top/mqtt-onenet012.png)
## 手动安装相关的依赖包
这些依赖包是使用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);
```
我们在设备列表页面,点击下方命令,下发一些数据到开发板上:
![mqtt-onenet013](http://qiniu.jiejie01.top/mqtt-onenet013.png)
![mqtt-onenet014](http://qiniu.jiejie01.top/mqtt-onenet014.png)
![mqtt-onenet015](http://qiniu.jiejie01.top/mqtt-onenet015.png)
在开发板上可以看到这些数据内容,可以看到他的主题是`$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)

View File

@@ -0,0 +1,83 @@
# mqttclient代码生产工具介绍
mqttclient代码生产工具主要是用于配置MQTT的参数并且生成相应的代码因为是可视化的配置极易使用。
地址:[https://jiejietop.gitee.io/mqtt/index.html](https://jiejietop.gitee.io/mqtt/index.html)
![在线代码生成工具](http://qiniu.jiejie01.top/mqtt-tool.png)
与此同时改工具页面还包含了mqttclient的API接口介绍及示例通过它们就能知道这个工具应该配置了什么内容。
![mqtt-tool-api](http://qiniu.jiejie01.top/mqtt-tool-api.png)
# 连接参数配置
首先连接参数代表着MQTT客户端应该如何连接到服务器建立MQTT会话应该是如何配置这些就是连接参数
- 与服务器相关的参数有:
- 服务器地址。
- 服务器端口号。
- 服务器CA证书如果有TLS加密的话
- 建立MQTT连接需要的参数有
- MQTT协议版本。
- 用户名。
- 密码。
- 客户端ID。
- 配置是否清除会话,默认清除会话。
- 心跳时间间隔默认是50秒。
- 是否使用遗嘱。
- 是否保留遗嘱消息。
- 设置遗嘱主题。
- 设置遗嘱消息的服务质量等级。
- 设置遗嘱消息的内容。
- 与与客户端资源相关的配置:
- 设置命令的超时它主要是用于socket读写超时默认是5000毫秒。
- 读缓冲区大小默认是1024。
- 写缓冲区大小默认是1024。
![mqtt-tool-connect](http://qiniu.jiejie01.top/mqtt-tool-connect.png)
# 订阅主题相关的代码配置
此配置用于配置MQTT客户端订阅的主题信息指定订阅主题名字服务质量等级以及当收到来自这个主题消息时候的回调处理函数可以为NULLmqttclient代码生成工具支持动态添加多个主题满足绝大部分的日常需求。
![mqtt-tool-sub](http://qiniu.jiejie01.top/mqtt-tool-sub.png)
# 发布消息相关的代码配置
mqttclient代码生成工具支持动态添向多个主题发布消息满足绝大部分的日常需求。只需指定要发布消息的主题名字、服务质量等级、以及发布的消息内容即可。
![mqtt-tool-pub](http://qiniu.jiejie01.top/mqtt-tool-pub.png)
# 生成代码
在配置完成后,点击下方的生成代码按钮,即可生成对应的配置代码。当然,你也可以点击导入模板,使用模板默认的配置来生成代码。
![mqtt-tool-code](http://qiniu.jiejie01.top/mqtt-tool-code.png)
直接将生成的代码复制到你的main.c文件即可编译运行。
**上一篇**[MQTT通信过程](./mqtt-communication.md)
**下一篇**[mqttclient配置及裁剪工具](./mqtt-config.md)