什么是MQTT协议
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,有可能成为物联网的重要组成部分。该协议支持所有平台,几乎可以把所有联网物品和外部连接起来,被用来当做传感器和制动器(比如通过Twitter让房屋联网)的通信协议。MQTT是一个轻量级的传输协议,所以适用于物联网方面。
客户端和服务端
客户端 Client
使用MQTT的程序或设备。客户端总是通过网络连接到服务端。它可以
- 发布应用消息给其它相关的客户端。
- 订阅以请求接受相关的应用消息。
- 取消订阅以移除接受应用消息的请求。
- 从服务端断开连接。
服务端 Server(Broker)
一个程序或设备,作为发送消息的客户端和请求订阅的客户端之间的中介。服务端
- 接受来自客户端的网络连接。
- 接受客户端发布的应用消息。
- 处理客户端的订阅和取消订阅请求。
- 转发应用消息给符合条件的已订阅客户端。
简单来说就是客户端具有发布消息以及订阅消息的功能,而所谓的服务器端,这里我们称为Broker,是消息的中转站,他来处理消息以及客户端发布和订阅的请求。所以说物联网,可以这样理解吧。
publish&subscribe
3.1 CONNECT – 连接服务端
3.2 CONNACK – 确认连接请求
3.3 PUBLISH – 发布消息
3.4 PUBACK –发布确认
3.5 PUBREC – 发布收到(QoS ,第一步)
3.6 PUBREL – 发布释放(QoS 2,第二步)
3.7 PUBCOMP – 发布完成(QoS 2,第三步)
3.8 SUBSCRIBE - 订阅主题
3.9 SUBACK – 订阅确认
3.10 UNSUBSCRIBE –取消订阅
3.11 UNSUBACK – 取消订阅确认
3.12 PINGREQ – 心跳请求
3.13 PINGRESP – 心跳响应
3.14 DISCONNECT –断开连接
以上是所有的通信流程,服务太多我都懒得去看。。。
主要还是发布和订阅 ,还有连接connecet
首先是连接,所有的网络通信都是基于socket()通信,这里他只是封装成相应的库,我们调用库中的函数就OK了,所有的客户端到服务端的网络连接建立后,客户端发送给服务端的第一个报文必须是CONNECT报文,在一个网络连接上,客户端只能发送一次CONNECT报文。服务端必须将客户端发送的第二个CONNECT报文当作协议违规处理并断开客户端的连接。
int mosquitto_connect( struct mosquitto *mosq,
const char *host,
int port,
int keepalive,
);
连接一个 broker 。第一个参数是 client 实例。第二个参数是 broker 的 IP 或者 hostname 。第三参数是连接的端口,通常是 1883 。第四个参数是保持连接的时间间隔,单位是秒,关于这个参数的详细含义可以查看 MQTT 协议的 CONNECT 报文格式。连接成功会返回 MOSQ_ERR_SUCCESS 。假如连接成功,就往后执行到mosquitto_connect_callback_set,这个函数会调用回调函数处理接下来要做的事,比如发布和订阅。
(重要)
int mosquitto_publish( struct mosquitto *mosq,
int *mid,
const char *topic,
int payloadlen,
const void *payload,
int qos,
bool retain
);
发布一个消息。第一个参数是 client 实例。第二个参数要指向一个整数,不能为 NULL ,它会被当做这个消息的 ID 。topic相当于两个客户端的暗号,那边发布消息订阅只有暗号一致才能收到消息。payloadlen 表示消息的长度,*playload 表示消息的内容。 qos 表示服务质量,retain 表示是否保留信息,详细含义可以查看 MQTT 协议的 3.1 节,对 PUBLISH 控制报文的详细描述。
int mosquitto_subscribe( struct mosquitto *mosq,
int *mid,
const char *sub,
int qos
);
向 broker 发送 SUBSCRIBE 报文请求订阅一个话题。第一个参数是 client 实例。第二个参数如果不为 NULL ,函数会把它作为该话题消息的 ID ,它可以用于订阅回调函数。第三个参数是话题名称。第四个参数qos是向 broker 请求的服务质量:
- 0 表示最多分发一次,消息的分发依赖于底层网络的能力。接收者不会发送响应,发送者也不会重试。消息可能送达一次也可能根本没送达。
- 1 表示确保消息至少送达一次,需要发布者和订阅者双方在报文中确认,可能重复。
- 2 表示仅分发一次,这是最高等级的服务质量,消息丢失和重复都是不可接受的。使用这个服务质量等级会有额外的开销。
调用成功会返回 MOSQ_ERR_SUCCESS。
还有很多相关函数参考:
http://shaocheng.li/post/blog/2015-08-11
想了解具体协议的:
https://legacy.gitbook.com/book/mcxiaoke/mqtt-cn/discussions/11
MQTT mosquitto
一般比较流行的就是paho和mosquitto,这都是一些已经搭建好了的MQTT服务器的库
这是最新的mosquitto下载地址:
https://mosquitto.org/download/
装好之后我直接从从windows传进虚拟机然后解压就直接用了,没有权限所以就没有安装,想要完整的装mosquitto的可以看看别的博客。这里就不详述了。
测试:
这里在src下开启我们的broker
在client目录下运行./mosquitto_pub -t “MQTT” -m “hello mqtt”
再开一个本机窗口 ,在client目录下运行./mosquitto_sub -t “MQTT”
他就能收到同一主题发布的消息。
代码实现温度解析并发布
1 /*
2 * to do :
3 * publish message of temperature
4 *
5 */
6 #include <stdio.h>
7 #include <unistd.h>
8 #include <string.h>
9 #include <getopt.h>
10 #include <mosquitto.h>
11 #include "ds18b20_temper.h"
12 #include "get_time.h"
13
14 #define ID "LIHAOJIE"
15
16 static char *topic;
17 static char buf[64];
18
19 void printf_usage()
20 {
21 printf("usage:\n");
22 printf("-t(--Topic):publish with a sepicfy topic.\n");
23 }
24
25
26 void my_connect_callback(struct mosquitto *mosq, void *userdata, int result)
27 {
28 int mode;
29 double temper;
30 char datime[32];
31 //char buf[64];
32 struct mosquitto_message *message;
33
34 temper = ds18b20_get_temper();
35 get_sys_time(datime);
36 memset(buf, 0, sizeof(buf));
37 snprintf(buf, sizeof(buf), "%s|%s|%.3fC", ID, datime, temper);
38 printf("%s\n",buf);
39 //message->payload buf;
40 if(!result)
41 {
42 mosquitto_publish(mosq, NULL, topic, strlen(buf)+1, buf, 2, true);
43 if(mode != MOSQ_ERR_SUCCESS)
44 {
45 mosquitto_lib_cleanup();
46 }
47 }
48 else
49 {
50 printf("cone_call else\n");
51 fprintf(stderr,"Connect failed\n");
52 }
53 }
54
55
56
57 int main(int argc, char* argv[])
58 {
59 int opt = -1;
60 char *temper;
61 struct mosquitto_message *msg;
62 struct mosquitto *mosq = NULL;
63
64 struct option opts[]={
65 {"Topic",required_argument,NULL,'t'},
66 {"Help" ,no_argument,NULL,'h'},
67 };
68
69 while((opt = getopt_long(argc, argv, "t:h", opts, NULL))!=-1)
70 {
71 switch(opt)
72 {
73 case 't':
74 topic = optarg;
75 break;
76 case 'h':
77 printf_usage();
78 break;
79 defult:
80 printf_usage();
81 }
82 }
83
84 if(!topic)
85 printf_usage();
86
87 /* init of lib */
88 mosquitto_lib_init();
89
90
91 /* create a client example */
92 mosq = mosquitto_new("1111", true, "HAHA");
93 if(!mosq)
94 {
95 fprintf(stderr, "Error:out of memory.\n");
96 return 1;
97 }
98
99 /* connect to broker */
100 mosquitto_connect_callback_set(mosq, my_connect_callback);
101
102 if(mosquitto_connect(mosq, "127.0.0.1", 1883, 30))
103 {
104 fprintf(stderr, "Unable to connect.\n");
105 return 1;
106 }
107
108 mosquitto_loop_forever(mosq, -1, 1);
109 mosquitto_destroy(mosq);
110 mosquitto_lib_cleanup();
111
112
113 }
非常简陋的一个程序=-=,子程序就不贴了。用mosquitto自带的订阅客户端收得到。
库的概念
尽管对静态库动态库多多少少了解点,但是根本很少用过,所以学习mqtt的时候也顺带的学习了库的编译链接。
静态库:
是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为”.a”。
动态库:
与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为”.so”,gcc/g++在编译时默认使用动态库。无论静态库,还是动态库,都是由.o文件创建的。
连接库的时候因为,如果没有说明,系统会默认在/usr/bin 路径下查找想要的库,显然你可以把mosquitto的库丢进去,但是因为某些因素我没有权限,所以还有一种方法就是连接库的时候带上库的路径。
gcc的参数 -L 就是带上我们库的路径(路径最好为绝对路径)。然后就是 -l 接上我们的库名称去掉前面的lib 和后面的.so 比如。库名称为libXXXX.so。那我们就直接 -lXXXX 就行了。然后还要接上 -I (大写的i)mosquitto.h的头文件的路径,不然会报错。
但是这里有一个问题,虽说这里一般的动态库的后缀名是.so。但也只是一般,不巧的是这里mosquitto的库后缀名是.so.1。所以我们再创建一个库的连接linux执行 ln -sv mosquitto.so.1 mosquitto.so,这样就不会有问题了。
CC=gcc
PATHS=/home/iot/lihaojie/mqtt/mosquitto-1.5.8/lib
KU=mosquitto
all:
@${CC} *.c -L${PATHS} -I${PATHS} -o pub -l${KU}
这里我写了一个makefile
有一个小问题就是用了PATH作为变量名,
然后就报了这个错,这个错是因为和环境变量名冲突了,所以在后面加S就可以正常编译了。