在鸿蒙系统上使用MQTT编程 精华

连志安
发布于 2020-11-18 15:27
浏览
14收藏

一、简述
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。

MQTT是一个基于客户端-服务器的消息发布/订阅传输协议。MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。在很多情况下,包括受限的环境中,如:机器与机器(M2M)通信和物联网(IoT)。其在,通过卫星链路通信传感器、偶尔拨号的医疗设备、智能家居、及一些小型化设备中已广泛使用。

 
二、设计规范
由于物联网的环境是非常特别的,所以MQTT遵循以下设计原则:

(1)精简,不添加可有可无的功能;
(2)发布/订阅(Pub/Sub)模式,方便消息在传感器之间传递;
(3)允许用户动态创建主题,零运维成本;
(4)把传输量降到最低以提高传输效率;
(5)把低带宽、高延迟、不稳定的网络等因素考虑在内;
(6)支持连续的会话控制;
(7)理解客户端计算能力可能很低;
(8)提供服务质量管理;
(9)假设数据不可知,不强求传输数据的类型与格式,保持灵活性。
 
三、主要特性
MQTT协议工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而设计的协议,它具有以下主要的几项特性:

(1)使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合。

这一点很类似于XMPP,但是MQTT的信息冗余远小于XMPP,,因为XMPP使用XML格式文本来传递数据。
(2)对负载内容屏蔽的消息传输。
(3)使用TCP/IP提供网络连接。

主流的MQTT是基于TCP连接进行数据推送的,但是同样有基于UDP的版本,叫做MQTT-SN。这两种版本由于基于不同的连接方式,优缺点自然也就各有不同了。
(4)有三种消息发布服务质量:

"至多一次",消息发布完全依赖底层TCP/IP网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。这一种方式主要普通APP的推送,倘若你的智能设备在消息推送时未联网,推送过去没收到,再次联网也就收不到了。

"至少一次",确保消息到达,但消息重复可能会发生。

"只有一次",确保消息到达一次。在一些要求比较严格的计费系统中,可以使用此级别。在计费系统中,消息重复或丢失会导致不正确的结果。这种最高质量的消息发布服务还可以用于即时通讯类的APP的推送,确保用户收到且只会收到一次。
(5)小型传输,开销很小(固定长度的头部是2字节),协议交换最小化,以降低网络流量。

这就是为什么在介绍里说它非常适合"在物联网领域,传感器与服务器的通信,信息的收集",要知道嵌入式设备的运算能力和带宽都相对薄弱,使用这种协议来传递消息再适合不过了。
(6)使用Last Will和Testament特性通知有关各方客户端异常中断的机制。

Last Will:即遗言机制,用于通知同一主题下的其他设备发送遗言的设备已经断开了连接。

Testament:遗嘱机制,功能类似于Last Will。
 
四、MQTT协议原理
4.1 MQTT协议实现方式
实现MQTT协议需要客户端和服务器端通讯完成,在通讯过程中,MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。

MQTT传输的消息分为:主题(Topic)和负载(payload)两部分:

(1)Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload);
(2)payload,可以理解为消息的内容,是指订阅者具体要使用的内容。

 

五、在鸿蒙上使用MQTT协议

我们使用的是paho mqtt软件包,这里介绍一下怎么使用mqtt协议编程。关于鸿蒙系统的mqtt移植好的软件包,相关github链接如下:

https://gitee.com/qidiyun/harmony_mqtt 

这里提供一个简单的编程示例:

这里我们使用MQTTClient编程模型,他支持多任务多线程,非常适合用在鸿蒙系统上。

 

1. 网络初始化

这里定义一个 Network 结构体,然后指定我们的MQTT服务器的IP和端口号。

Network n;
   //初始化结构体
 NetworkInit(&n);
 //连接到指定的MQTT服务器IP、端口号
 NetworkConnect(&n, “XXX.XXX.XXX.XXX”, XXXX);

 

2. 设置MQTT缓存和启动MQTT线程

我们这里使用的是MQTT线程功能。

 MQTTClientInit(&c, &n, 1000, buf, 100, readbuf, 100);
 MQTTStartTask(&c);

 

3. 设置MQTT相关参数

接下来我们设置MQTT的相关参数,包括版本号、客户端ID、账户密码等

MQTTPacket_connectData data = MQTTPacket_connectData_initializer; 
 data.willFlag = 0;
 //MQTT版本为 v3
 data.MQTTVersion = 3;
 //设置客户端ID
 data.clientID.cstring = opts.clientid;
 //设置客户端账户
 data.username.cstring = opts.username;
 //设置客户端密码
 data.password.cstring = opts.password;
 data.keepAliveInterval = 10;
 data.cleansession = 1;

 //连接到MQTT服务器
 rc = MQTTConnect(&c, &data);

 

4. 订阅主题和接收消息

订阅主题可以使用如下函数

MQTTSubscribe(&c, topic, opts.qos, messageArrived);

它的函数原型如下:

DLLExport int MQTTSubscribe(MQTTClient* client, const char* topicFilter, enum QoS, messageHandler);

其中:

MQTTClient* c :我们前面定义的MQTTClient结构体

const char* topicFilter:订阅的主题

messageHandler messageHandler :接收到主题信息后的回调处理函数。

例如上面我们的回调函数是  messageArrived ,它的原型如下:

void messageArrived(MessageData* md)
{
 MQTTMessage* message = md->message;
 //打印接收到的消息的长度、和消息内容
 printf("%.*s", (int)message->payloadlen, (char*)message->payload);
}

 

5. 发送消息

发送消息也比较简单,我们只需要设置好我们的主题和消息内容即可

memset(&pubmsg, '\0', sizeof(pubmsg));
 //消息内容为 hello harmonyOS !
   pubmsg.payload = (void*)"hello harmonyOS !";
 //消息长度
   pubmsg.payloadlen = strlen((char*)pubmsg.payload);
   pubmsg.qos = QOS0;
   pubmsg.retained = 0;
   pubmsg.dup = 0;

 //推送消息,主题为 pubtest
 MQTTPublish(&c, "pubtest", &pubmsg);

 

完整源码如下:

#include <stdio.h>

#include <unistd.h>

#include "ohos_init.h"
#include "cmsis_os2.h"

#include <unistd.h>
#include "hi_wifi_api.h"
//#include "wifi_sta.h"
#include "lwip/ip_addr.h"
#include "lwip/netifapi.h"

#include "lwip/sockets.h"

#include "MQTTClient.h"

/**
 * MQTT URI farmat:
 * domain mode
 * tcp://iot.eclipse.org:1883
 *
 * ipv4 mode
 * tcp://192.168.10.1:1883
 * ssl://192.168.10.1:1884
 *
 * ipv6 mode
 * tcp://[fe80::20c:29ff:fe9a:a07e]:1883
 * ssl://[fe80::20c:29ff:fe9a:a07e]:1884
 */
#define MQTT_URI                "tcp://106.13.62.194:1883"

struct opts_struct
{
	char* clientid;
	int nodelimiter;
	char* delimiter;
	enum QoS qos;
	char* username;
	char* password;
	char* host;
	int port;
	int showtopics;
} opts =
{
	(char*)"stdout-subscriber", 0, (char*)"\n", QOS2, NULL, NULL, (char*)"106.13.62.194", 1883, 1
};


void messageArrived(MessageData* md)
{
	MQTTMessage* message = md->message;

	if (opts.showtopics)
		printf("%.*s\t", md->topicName->lenstring.len, md->topicName->lenstring.data);
	if (opts.nodelimiter)
		printf("%.*s", (int)message->payloadlen, (char*)message->payload);
	else
		printf("%.*s%s", (int)message->payloadlen, (char*)message->payload, opts.delimiter);
	//fflush(stdout);
}


unsigned char buf[100];
unsigned char readbuf[100];

int mqtt_test(void)
{
	int rc = 0;
	
	MQTTMessage pubmsg;

	
	char* topic = "test";

	if (strchr(topic, '#') || strchr(topic, '+'))
		opts.showtopics = 1;
	if (opts.showtopics)
		printf("topic is %s\n", topic);

	Network n;
	MQTTClient c;

	NetworkInit(&n);
	NetworkConnect(&n, opts.host, opts.port);
	
	MQTTClientInit(&c, &n, 1000, buf, 100, readbuf, 100);
	MQTTStartTask(&c);

	MQTTPacket_connectData data = MQTTPacket_connectData_initializer;       
	data.willFlag = 0;
	data.MQTTVersion = 3;
	data.clientID.cstring = opts.clientid;
	data.username.cstring = opts.username;
	data.password.cstring = opts.password;

	data.keepAliveInterval = 10;
	data.cleansession = 1;
	printf("Connecting to %s %d\n", opts.host, opts.port);
	
	rc = MQTTConnect(&c, &data);
	printf("Connected %d\n", rc);
    
	

    printf("Subscribing to %s\n", topic);
	rc = MQTTSubscribe(&c, topic, opts.qos, messageArrived);
	printf("Subscribed %d\n", rc);

	memset(&pubmsg, '\0', sizeof(pubmsg));
  	pubmsg.payload = (void*)"hello harmonyOS !";
  	pubmsg.payloadlen = strlen((char*)pubmsg.payload);
  	pubmsg.qos = QOS0;
  	pubmsg.retained = 0;
  	pubmsg.dup = 0;

	

	while (1)
	{
		MQTTPublish(&c, "pubtest", &pubmsg);
		sleep(1);	
	}
	
	printf("Stopping\n");

	MQTTDisconnect(&c);
	NetworkDisconnect(&n);

	return 0;
}

分类
标签
已于2020-12-15 23:38:06修改
9
收藏 14
回复
举报
3条回复
按时间正序
/
按时间倒序
gordonlonglong
gordonlonglong

好帖,必须顶起来!

回复
2020-11-18 15:44:17
jacksky
jacksky

老师的MTQQ系列讲的很细致,赶紧加入和老师学习一波

回复
2020-11-18 15:52:03
张荣超_九丘教育
张荣超_九丘教育

好文👍👍👍

回复
2021-2-22 08:37:03
回复
    相关推荐