物联网Mqtt协议使用(1)

MQTT是轻量级基于代理的发布/订阅的消息传输协议,设计思想是开放、简单、轻量、易于实现。这些特点使它适用于受限环境。特别是资源受限的嵌入式平台,该协议的特点有:

  1. 使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合。
  2. 对负载内容屏蔽的消息传输。
  3. 使用 TCP/IP 提供网络连接。
  4. 小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量。
  5. 使用 Last Will 和 Testament 特性通知有关各方客户端异常中断的机制。

MQTT提供了三种消息发布服务质量:

“至多一次”,消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无>所谓,因为不久后还会有第二次发送。

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

“只有一次”,确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。

协议里还有2个主要的角色:

·         client,客户端,又包括发布消息客户端和订阅消息客户端

·         broker,服务器端

即如下图所示,订阅者先向服务器订阅消息主题,当发布者发布消息给服务器时,订阅者就会从服务器订阅得到消息值。

  • MQTT服务器搭建

EMQTT是分布式开源物联网MQTT消息服务器 ,具有标准协议,开放源码,高并发低延时,分布集群桥接,多数据库存储,私有云部署,专业团队支持。各种操作系统平台下EMQTT源码下载连接:http://www.emqtt.com/downloads

本次在使用阿里云ECS服务器搭建broker,服务器系统为centos7,下载安装使用流程如下所示:

从 http://emqtt.com/downloads/下载centos7下的源码
unzip centos7
cd cemqttd/
 ./bin/emqttd console //启动MQTT 脱离终端不能工作
 ./bin/emqttd start  //启动MQTT,开启守护进程模式,脱离终端还在工作

服务器工作后,会监听四个端口,1883,8883,8083,18083,对应的服务内容如下:

1883    ======  MQTT协议端口 ,以后使用MQTT客服端就需要连接TCP 1883端口
8883    ======  MQTT(SSL)端口,这是MQTT加密安全端口
8083    ======  MQTT(WebSocket), HTTP API端口
18083   ======  Dashboard管理控制台端口  

现在在浏览器中打开终端,输入 服务器ip:18083就会进入dash终端,默认用户名admin 密码  public

使用mosquitoo体验一下MQTT协议的使用,使用emqtt作为服务器,mosquitto作为客户端使用(mosquitoo安装使用可以查看相关文档,这里不做 介绍)

  • 客户端首先要订阅主体,主题为"mqtt"
mosquitto_sub -t mqtt –h  [服务器IP]
  • 再打开一个终端作为发布端,指定主题"mqtt"推送消息,推送内容为"hello world"
mosquitto_pub -h localhost –t mqtt -m "hello world"

在订阅了"mqtt"的订阅端会接收到消息可以在emqtt中查看到相关主题,以及该主题对应的消息内容

  • 嵌入式平台上的MQTT客户端移植

PAHO是一个开源的MQTT客户端的库,支持Java, C/C++, GoLang, Python C语言包下载,

嵌入式平台使用的包下载:

git clone https://github.com/eclipse/paho.mqtt.embedded-c.git

主要包含了三个主要目录,MQTTPacket 文件夹是一些MQTT包的初始化,MQTTClient文件夹是C++使用的API,MQTTClient-c文件夹是C语言的相关API。在本次移植目标是nanopi-m1开发板

编译安装流程 ,指定CC指定交叉编译器,编译后在源码目录下的 build/output/下生成共享库和相关测试程序、

在开发板上挂接网络文件系统,将此目录下的共享库拷贝到/lib目录下,其中的sample程序中有两个可执行文件,pub0sub1 qos0pub。拷贝库之后发现可以执行。使用find 命令搜索整个源码找到源文件,发现在MQTTPacket/samples/pub0sub1.c MQTTPacket/samples/qos0pub.c。经过多次测试,发现后面直接修改这两个源文件,然后实现我们的应用直接简明多了,一开始准备编译,安装,然后自己写程序交叉编译就连接libpaho这个库,可是编译老是出现问题。pub0sub1.c里面是先订阅一个主题 ,然后推送这个主题,订阅部分就会收到这个主题的内容

make CC=/samba_share/nanopi_4.14/4.9.3/bin/arm-linux-gcc     #交叉编译,CC指定交叉编译器
mount -t nfs  192.168.1.145:/samba_share/nanopi_4.14/ /mnt   #挂载NFS

root@wu:/mnt/paho.mqtt.embedded-c/build/output# ls           #build目录下生成相关库和可执行文文件
libpaho-embed-mqtt3c.so      samples
libpaho-embed-mqtt3c.so.1    test
libpaho-embed-mqtt3c.so.1.0
root@wu:/mnt/paho.mqtt.embedded-c/build/output#cp *.so.*  -rfd   /lib #将库拷贝到目标文件系统的lib目录

   修改pub0sub1.c中的服务器地址,改成之前搭建的MQTT服务器域名,重新使用上面的命令编译,执行时出现,sockerr 怀疑是网关和dns没设置。设置nanopi-m1的网关和DNS服务器

echo nameserver 61.128.128.68 > /etc/resolv.conf
route add default gw 192.168.1.1

先为了方便测试,在nanopi上推送消息。python paho是一个符合MQTT v3.1协议的客户端,paho-python可连接MQTT代理服务器、发布消息、订阅消息和获得推送消息。

在ubuntu主机上搭建python paho来进行订阅测试,ubuntu上搭建python paho过程如下所示:

 git clone https://github.com/eclipse/paho.mqtt.python.git   //下载python  paho源码包
 python setup.py install   //安装 python  paho到ubuntu系统目录        

python使用编程简单,非常方便我们的整个系统的测试,整个过程是连接到我们之前搭建的MQTT服务器后,就订阅主题为"test",订阅后就不断等待发布端发布"test"主题,使用在开发板上sample下的qos0pub来进行推送消息

#coding=utf-8
import paho.mqtt.client as mqtt
import time
HOST = "47.106.117.13"
PORT = 1883

def client_loop():
    client_id = time.strftime('%Y%m%d%H%M%S',time.localtime(time.time()))
    client = mqtt.Client(client_id)    # ClientId不能重复,所以使用当前时间
    #client.username_pw_set("admin", "123456")  # 必须设置,否则会返回「Connected with result code 4」
    client.on_connect = on_connect
    client.on_message = on_message
    client.connect(HOST, PORT, 60) 
    client.loop_forever()

def on_connect(client, userdata, flags, rc):
    print("Connected with result code "+str(rc))
    client.subscribe("test")

def on_message(client, userdata, msg):
    print(msg.topic+" "+msg.payload.decode("utf-8"))

if __name__ == '__main__':
    client_loop()

分别运行订阅客户端和发布客户端的程序,当使用nanopi开发板每发布一次消息时,ubuntu主机端就会接受到这条消息。

发布:


root@wu:/mnt/paho.mqtt.embedded-c/build/output/samples# ./qos0pub
Sending to hostname 47.106.117.13 port 1883
Successfully published

订阅:

wu@ubuntu:~/MQTTclient/mqtt-python$ python mqtt_recv.py 
Connected with result code 0
test mqtt_payload
test mqtt_payload

先使用nanopi上订阅消息,主题为"chat",ubuntu主机发布主题为"chat"的消息

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "MQTTPacket.h"
#include "transport.h"

/* This is in order to get an asynchronous signal to stop the sample,
as the code loops waiting for msgs on the subscribed topic.
Your actual code will depend on your hw and approach*/
#include <signal.h>

int toStop = 0;

void cfinish(int sig)
{
	signal(SIGINT, NULL);
	toStop = 1;
}

void stop_init(void)
{
	signal(SIGINT, cfinish);
	signal(SIGTERM, cfinish);
}
/* */

int main(int argc, char *argv[])
{
	MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
	int rc = 0;
	int mysock = 0;
	unsigned char buf[200];
	int buflen = sizeof(buf);
	int msgid = 1;
	MQTTString topicString = MQTTString_initializer;
	int req_qos = 0;
	char* payload = "mypayload";
	int payloadlen = strlen(payload);
	int len = 0;
	char *host = "47.106.117.13";
	int port = 1883;

	stop_init();
	mysock = transport_open(host, port);
	if(mysock < 0)
	{	
		//return mysock;
		printf("mysock err!\n");
		return mysock;
	}
	printf("Sending to hostname %s port %d\n", host, port);

	data.clientID.cstring = "me";
	data.keepAliveInterval = 20;
	data.cleansession = 1;

	len = MQTTSerialize_connect(buf, buflen, &data);
	rc = transport_sendPacketBuffer(mysock, buf, len);

	/* wait for connack */
	if (MQTTPacket_read(buf, buflen, transport_getdata) == CONNACK)
	{
		unsigned char sessionPresent, connack_rc;

		if (MQTTDeserialize_connack(&sessionPresent, &connack_rc, buf, buflen) != 1 || connack_rc != 0)
		{
			printf("Unable to connect, return code %d\n", connack_rc);
			goto exit;
		}
	}
	else
		goto exit;

	/* subscribe */
	topicString.cstring = "chat";
	len = MQTTSerialize_subscribe(buf, buflen, 0, msgid, 1, &topicString, &req_qos);

	rc = transport_sendPacketBuffer(mysock, buf, len);
	if (MQTTPacket_read(buf, buflen, transport_getdata) == SUBACK) 	/* wait for suback */
	{
		unsigned short submsgid;
		int subcount;
		int granted_qos;

		rc = MQTTDeserialize_suback(&submsgid, 1, &subcount, &granted_qos, buf, buflen);
		if (granted_qos != 0)
		{
			printf("granted qos != 0, %d\n", granted_qos);
			goto exit;
		}
	}
	else
		goto exit;

	/* loop getting msgs on subscribed topic */
	topicString.cstring = "chat";
	while (!toStop)
	{
		/* transport_getdata() has a built-in 1 second timeout,
		your mileage will vary */
		if (MQTTPacket_read(buf, buflen, transport_getdata) == PUBLISH)
		{
			unsigned char dup;
			int qos;
			unsigned char retained;
			unsigned short msgid;
			int payloadlen_in;
			unsigned char* payload_in;
			int rc;
			MQTTString receivedTopic;

			rc = MQTTDeserialize_publish(&dup, &qos, &retained, &msgid, &receivedTopic,
					&payload_in, &payloadlen_in, buf, buflen);
			printf("message arrived %.*s\n", payloadlen_in, payload_in);
		}
	}

	printf("disconnecting\n");
	len = MQTTSerialize_disconnect(buf, buflen);
	rc = transport_sendPacketBuffer(mysock, buf, len);

exit:
	transport_close(mysock);
	return 0;
}

pub.py:

#coding=utf-8
import paho.mqtt.client as mqtt
import paho.mqtt.publish as publish
import time

HOST = "47.106.117.13"
PORT = 1883
def on_connect(client, userdata, flags, rc):
    print("Connected with result code "+str(rc))
    #client.subscribe("test")

def on_message(client, userdata, msg):
    print(msg.topic+" "+msg.payload.decode("utf-8"))

if __name__ == '__main__':
    client_id = time.strftime('%Y%m%d%H%M%S',time.localtime(time.time()))
    # client = mqtt.Client(client_id)    # ClientId不能重复,所以使用当前时间
    # client.username_pw_set("admin", "123456")  # 必须设置,否则会返回「Connected with result code 4」
    # client.on_connect = on_connect
    # client.on_message = on_message
    # client.connect(HOST, PORT, 60)
    # client.publish("test", "你好 MQTT", qos=0, retain=False)  # 发布消息

    publish.single("chat", "你好 MQTT", qos = 1,hostname=HOST,port=PORT, client_id=client_id)

每次在ubuntu主机上发布chat主题的消息时,nanopi就会接收这个消息

root@wu:/mnt/paho.mqtt.embedded-c/build/output/samples# ./pub0sub1
Sending to hostname 47.106.117.13 port 1883
message arrived 你好 MQTT

下一讲,将深入paho相关发布订阅相关的API。。。

猜你喜欢

转载自blog.csdn.net/qq_18737805/article/details/86408632