前言

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


一、MQTT简介

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

MQTT特性

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协议原理

MQTT协议实现方式
实现MQTT协议需要客户端和服务器端通讯完成,在通讯过程中,MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。

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

(1)Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload)

(2)payload,可以理解为消息的内容,是指订阅者具体要使用的内容。

网络传输与应用消息

MQTT会构建底层网络传输:它将建立客户端到服务器的连接,提供两者之间的一个有序的、无损的、基于字节流的双向传输。

当应用数据通过MQTT网络发送时,MQTT会把与之相关的服务质量(QoS)和主题名(Topic)相关连。

MQTT客户端

一个使用MQTT协议的应用程序或者设备,它总是建立到服务器的网络连接。客户端可以:

(1)发布其他客户端可能会订阅的信息;
(2)订阅其它客户端发布的消息;
(3)退订或删除应用程序的消息;
(4)断开与服务器连接。

MQTT服务器

MQTT服务器以称为"消息代理"(Broker),可以是一个应用程序或一台设备。它是位于消息发布者和订阅者之间,它可以:

(1)接受来自客户的网络连接;
(2)接受客户发布的应用信息;
(3)处理来自客户端的订阅和退订请求;
(4)向订阅的客户转发应用程序消息。

MQTT协议中的订阅、主题、会话

一、订阅(Subscription)

订阅包含主题筛选器(Topic Filter)和最大服务质量(QoS)。订阅会与一个会话(Session)关联。一个会话可以包含多个订阅。每一个会话中的每个订阅都有一个不同的主题筛选器。

二、会话(Session)

每个客户端与服务器建立连接后就是一个会话,客户端和服务器之间有状态交互。会话存在于一个网络之间,也可能在客户端和服务器之间跨越多个连续的网络连接。

三、主题名(Topic Name)

连接到一个应用程序消息的标签,该标签与服务器的订阅相匹配。服务器会将消息发送给订阅所匹配标签的每个客户端。

四、主题筛选器(Topic Filter)

一个对主题名通配符筛选器,在订阅表达式中使用,表示订阅所匹配到的多个主题。

五、负载(Payload)

消息订阅者所具体接收的内容。

MQTT协议中的方法

MQTT协议中定义了一些方法(也被称为动作),来于表示对确定资源所进行操作。这个资源可以代表预先存在的数据或动态生成数据,这取决于服务器的实现。通常来说,资源指服务器上的文件或输出。主要方法有:

(1)Connect。等待与服务器建立连接。
(2)Disconnect。等待MQTT客户端完成所做的工作,并与服务器断开TCP/IP会话。
(3)Subscribe。等待完成订阅。
(4)UnSubscribe。等待服务器取消客户端的一个或多个topics订阅。
(5)Publish。MQTT客户端发送消息请求,发送完成后返回应用程序线程。

MQTT协议数据包结构

在MQTT协议中,一个MQTT数据包由:固定头(Fixed header)、可变头(Variable header)、消息体(payload)三部分构成。MQTT数据包结构如下:

(1)固定头(Fixed header)。存在于所有MQTT数据包中,表示数据包类型及数据包的分组类标识。
(2)可变头(Variable header)。存在于部分MQTT数据包中,数据包类型决定了可变头是否存在及其具体内容。
(3)消息体(Payload)。存在于部分MQTT数据包中,表示客户端收到的具体内容。

MQTT固定头

固定报头,所有的MQTT控制报文都包含,可变报头与有效载荷是部分MQTT控制报文包含。 固定报头占据两字节的空间,具体见
在这里插入图片描述
固定报头的第一个字节分为控制报文的类型(4bit),以及控制报文类型的标志位,控制类型共有14种,其中0与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数据包中,其结构如下:

MQTT数据包类型

位置:Byte 1中bits 7-4。

相于一个4位的无符号值,类型、取值及描述如下:

标识位

位置:Byte 1中bits 3-0。

在不使用标识位的消息类型中,标识位被作为保留位。如果收到无效的标志时,接收端必须关闭网络连接:

(1)DUP:发布消息的副本。用来在保证消息的可靠传输,如果设置为1,则在下面的变长中增加MessageId,并且需要回复确认,以保证消息传输完成,但不能用于检测消息重复发送。

(2)QoS:发布消息的服务质量,即:保证消息传递的次数

        Ø00:最多一次,即:<=1

        Ø01:至少一次,即:>=1
        
        Ø10:一次,即:=1
        
        Ø11:预留

(3)RETAIN: 发布保留标识,表示服务器要保留这次推送的信息,如果有新的订阅者出现,就把这消息推送给它,如果设有那么推送至当前订阅者后释放。

剩余长度(Remaining Length)

地址:Byte 2。

固定头的第二字节用来保存变长头部和消息体的总大小的,但不是直接保存的。这一字节是可以扩展,其保存机制,前7位用于保存长度,后一部用做标识。当最后一位为1时,表示长度不足,需要使用二个字节继续保存。例如:计算出后面的大小为0

MQTT可变头

MQTT数据包中包含一个可变头,它驻位于固定的头和负载之间。可变头的内容因数据包类型而不同,较常的应用是作为包的标识:
在这里插入图片描述
只有某些报文才拥有可变报头,它在固定报头和有效负载之间,可变报头的内容会根据报文类型的不同而有所不同,但可变报头的报文标识符(Packet Identifier)字段存在于在多个类型的报文里,而有一些报文又没有报文标识符字段,具体见表格,报文标识符结构具体见图:
在这里插入图片描述
Payload消息体

Payload消息体位MQTT数据包的第三部分,包含CONNECT、SUBSCRIBE、SUBACK、UNSUBSCRIBE四种类型的消息:

(1)CONNECT,消息体内容主要是:客户端的ClientID、订阅的Topic、Message以及用户名和密码。
(2)SUBSCRIBE,消息体内容是一系列的要订阅的主题以及QoS。
(3)SUBACK,消息体内容是服务器对于SUBSCRIBE所申请的主题及QoS进行确认和回复。
(4)UNSUBSCRIBE,消息体内容是要订阅的主题。

二、安装MQTT

1.mosquitto简介

Mosquitto是用C语言实现MQTT协议的Broker。是一款实现了消息推送协议 MQTT v3.1 的开源消息代理软件,提供轻量级的,支持可发布/可订阅的的消息推送模式,使设备对设备之间的短消息通信变得简单,比如现在应用广泛的低功耗传感器,手机、嵌入式计算机、微型控制器等移动设备。一个典型的应用案例就是 Andy Stanford-ClarkMosquitto(MQTT协议创始人之一)在家中实现的远程监控和自动化。
Mosquitto官网
Mosquitto源码

2.安装mosquitto库

apt-get命令安装:

# 安装mosquitto
sudo apt-get install mosquitto
# 安装客户端
sudo apt-get install mosquitto-clients
# 安装设备端
sudo apt-get install mosquitto-dev
#查询mosquitto是否正确运行
mosquitto -p 1883   //mosquitto默认时候用1883端口

本机终端测试MQTT

打开一个终端,订阅主题
mosquitto_sub -t mqtt

【-h】指定要连接的MQTT服务器 
【-t】订阅主题,此处为mqtt 
【-v】打印更多的调试信息

再打开一个终端,发布主题
mosquitto_pub -h localhost -t mqtt -m "haha"-h】指定要连接的MQTT服务器 
【-t】向指定主题推送消息 
【-m】指定消息内容

MQTT运行成功
MQTT发送成功
其他处理

查看运行状态 systemctl status mosquitto

开启/关闭systemctl start/stop mosquitto

查看进程ps -aux | grep mosquitto

查看1883端口占用netstat -apn | grep 1883

查看lsof -i:1883

关闭进程kill -9 pid

2.常用MQTT库函数

1. MQTT 初始化
函数原型:

int mosquitto_lib_init(void)
功能:
使用mosquitto库函数前,要先初始化,使用之后就要清除。清除函数;int mosquitto_lib_cleanup()
返回值:MOSQ_ERR_SUCCESS

2. MQTT 清除
函数原型:

int mosquitto_lib_cleanup(void)
功能:
使用MQTT之后,清除工作
返回值MOSQ_ERR_SUCCESS

3. 新建客户端
函数原型:

struct mosquitto *mosquitto_new( const char * id, bool clean_session, void * obj )
参数
    id:如果为NULL,将生成一个随机客户端ID。如果id为NULL,clean_session必须为true。
    clean_session:设置为true以指示代理在断开连接时清除所有消息和订阅,设置为false以指示其保留它们,客户端将永远不会在断开连接时丢弃自己的传出消息就是断开后是否保留订阅信息true/false
	obj:用户指针,将作为参数传递给指定的任何回调
返回
成功时返回结构mosquitto的指针,失败时返回NULL,询问errno以确定失败的原因:ENOMEM内存不足。EINVAL输入参数无效。      

4. 释放客户端
函数原型:

void mosquitto_destroy( struct mosquitto * mosq)
功能
释放客户端
参数:mosq: struct mosquitto指针

5. 确认连接回调函数
函数原型:

void mosquitto_connect_callback_set(struct mosquitto * mosq, void (*on_connect)(struct mosquitto *mosq, void *obj, int rc) )

功能:连接确认回调函数,当代理发送CONNACK消息以响应连接时,将调用此方法。
参数:
struct mosquitto * mosq:客户端通配符
void (*on_connect):回调函数
struct mosquitto *mosq:客户端数据
void *obj:创建客户端的回调参数(mosquitto_new中提供的用户数据)
int rc: 
0-成功
1-连接被拒绝(协议版本不可接受)
2-连接被拒绝(标识符被拒绝)
3-连接被拒绝(经纪人不可用)
4-255-保留供将来使用

6. 断开连接回调函数
函数原型:

void mosquitto_disconnect_callback_set( struct mosquitto *mosq,void (*on_disconnect)( struct mosquitto *mosq,void *obj, int rc) )

功能:断开连接回调函数,当代理收到DISCONNECT命令并断开与客户端的连接,将调用此方法。
参数:
struct mosquitto *mosq:客户端
void (*on_disconnect):回调函数
 struct mosquitto *mosq:客户端数据
void *obj:创建客户端时的回调函数
int rc:表示断开原因(0表示客户端已经调用mosquitto_disconnect,任何其他值,表示断开连接时意外的)

7. 连接MQTT代理/服务器
函数原型:

int mosquitto_connect( struct mosquitto * mosq, const char * host, int port, int keepalive )
 功能: 连接到MQTT代理/服务器(主题订阅要在连接服务器之后进行)
 参数:
struct mosquitto * mosq:客户端
const char * host:服务器ip
int port:服务器端口号
int keepalive:保持连接的时间间隔, 单位秒 
返回:
MOSQ_ERR_SUCCESS 成功。
MOSQ_ERR_INVAL 如果输入参数无效。
MOSQ_ERR_ERRNO 如果系统调用返回错误。变量errno包含错误代码

8. 断开MQTT代理/服务器
函数原型:

int mosquitto_disconnect( struct mosquitto * mosq )
功能:断开与代理/服务器的连接。
返回:
MOSQ_ERR_SUCCESS 成功。
MOSQ_ERR_INVAL 如果输入参数无效。
MOSQ_ERR_NO_CONN 如果客户端未连接到代理。 

9. 发布主题
函数原型:

int mosquitto_publish( struct mosquitto * mosq, int * mid, const char * topic, int payloadlen, const void * payload, int qos, bool retain )

功能:主题发布的函数
参数:
struct mosquitto * mosq:客户端
int * mid:指向int的指针。如果不为NULL,则函数会将其设置为该特定消息的消息ID
const char * topic:要发布的主题,'\0'结尾的字符串
int payloadlen:主题消息的内容长度
const void * payload:主题消息的内容,指向要发送的数据的指针,如果payloadlen >0,则它必须时有效的存储位置
int qos:整数值012指示要用于消息的服务质量
bool retain:设置为true以保留消息
返回:
MOSQ_ERR_SUCCESS 成功。
MOSQ_ERR_INVAL 如果输入参数无效。
MOSQ_ERR_NOMEM 如果发生内存不足的情况。
MOSQ_ERR_NO_CONN 如果客户端未连接到代理。
MOSQ_ERR_PROTOCOL 与代理进行通信时是否存在协议错误。
MOSQ_ERR_PAYLOAD_SIZE 如果payloadlen太大。
MOSQ_ERR_MALFORMED_UTF8 如果主题无效,则为UTF-8
MOSQ_ERR_QOS_NOT_SUPPORTED 如果QoS大于代理支持的QoS。
MOSQ_ERR_OVERSIZE_PACKET 如果结果包大于代理支持的包。

10. 订阅主题
函数原型:

int mosquitto_subscribe( struct mosquitto * mosq, int * mid, const char * sub, int qos )
参数:
struct mosquitto * mosq:客户端
int * mid:主题的消息ID。如果不为NULL,则函数会将其设置为该特定消息的消息ID
char * sub:主题名称,订阅模式
int qos:此订阅请求的服务质量
返回值:
MOSQ_ERR_SUCCESS 成功。
MOSQ_ERR_INVAL 如果输入参数无效。
MOSQ_ERR_NOMEM 如果发生内存不足的情况。
MOSQ_ERR_NO_CONN 如果客户端未连接到代理。
MOSQ_ERR_MALFORMED_UTF8 如果主题无效,则为UTF-8
MOSQ_ERR_OVERSIZE_PACKET 如果结果包大于代理支持的包

11. 消息回调函数
函数原型:

void mosquitto_message_callback_set( struct mosquitto * mosq, void (*on_message)(struct mosquitto *, void *, const struct mosquitto_message *) )

功能:消息回调函数,收到订阅的消息后调用。
参数:
struct mosquitto * mosq:客户端
(*on_message):void callback(struct mosquitto * mosq,void * obj,const struct mosquitto_message * message)
回调的参数
struct mosquitto * mosq:客户端
void * obj: mosquitto_new中提供的用户数据

const struct mosquitto_message * message
{
int mid;//消息序号ID
char *topic; //主题
void *payload; //主题内容 ,MQTT 中有效载荷
int payloadlen; //消息的长度,单位是字节
int qos; //服务质量
bool retain; //是否保留消息
};

12. MQTT循环调用/断开重新连接
函数原型:

int mosquitto_loop_forever( struct mosquitto * mosq, int timeout, int max_packets )
功能:此函数在无限阻塞循环中为你调用loop(),对于只想在程序中运行MQTT客户端循环的情况,这很有用,如果服务器连接丢失,它将处理重新连接,如果在回调中调用mosqitto_disconnect()它将返回。
参数:
struct mosquitto * mosq: 客户端
int timeout:超时之前,在select()调用中等待网络活动的最大毫秒数,设置为0以立即返回,设置为负可使用默认值为1000ms。
int max_packets:该参数当前未使用,应设为为1,以备来兼容
返回值:
MOSQ_ERR_SUCCESS 成功。
MOSQ_ERR_INVAL 如果输入参数无效。
MOSQ_ERR_NOMEM 如果发生内存不足的情况。
MOSQ_ERR_NO_CONN 如果客户端未连接到代理。
MOSQ_ERR_CONN_LOST 如果与代理的连接丢失。
MOSQ_ERR_PROTOCOL 与代理进行通信时是否存在协议错误。
MOSQ_ERR_ERRNO 如果系统调用返回错误。变量errno包含错误代码

13. :网络事件阻塞回收结束处理
函数原型:

int mosquitto_loop_stop( struct mosquitto * mosq, bool force )

功能:网络事件阻塞回收结束处理函数,这是线程客户端接口的一部分。调用一次可停止先前使用mosquitto_loop_start创建的网络线程。该调用将一直阻塞,直到网络线程结束。为了使网络线程结束,您必须事先调用mosquitto_disconnect或将force参数设置为true。
参数:
struct mosquitto * mosq: 客户端
bool force:设置为true强制取消线程。如果为false,则必须已经调用mosquitto_disconnect。
返回:
MOSQ_ERR_SUCCESS 成功。
MOSQ_ERR_INVAL 如果输入参数无效。
MOSQ_ERR_NOT_SUPPORTED 如果没有线程支持

14. 网络事件循环处理
函数原型:

int mosquitto_loop_start( struct mosquitto * mosq )
功能:网络事件循环处理函数,通过创建新的线程不断调用mosquitto_loop() 函数处理网络事件,不阻塞
参数:
struct mosquitto * mosq:客户端
返回:
MOSQ_ERR_SUCCESS 成功。
MOSQ_ERR_INVAL 如果输入参数无效。
MOSQ_ERR_NOT_SUPPORTED 如果没有线程支持。

15. 配置用户名/密码
函数原型:

int mosquitto_username_pw_set(struct mosquitto *mosq, const char *username,  const char *passworp)
 参数
 struct mosquitto *mosq :客户端
 const char *username : 以字符串形式发送的用户名,或以NULL形式关闭认证。
 const char *passworp:以字符串形式发送的密码。 当用户名有效时,设置为NULL,以便只发送一个用户名。
 返回值
	成功时返回MOSQ_ERR_SUCCESS。
	如果输入参数无效,返回MOSQ_ERR_INVAL。
	如果发生内存不足的情况,返回MOSQ_ERR_NOMEM。

16.循环接受订阅发代理的内容
函数原型:

int mosquitto_loop(struct mosquitto *mosq,int timeout,int max_packets)

参数
    struct mosquitto *mosq :客户端
    int timeout:超时之前,在select()调用中等待网络活动的最大毫秒数,设置为0以立即返回,设置为负可使用默认值为1000ms。
	int max_packets:该参数当前未使用,应设为为1,以备来兼容
返回值:
MOSQ_ERR_SUCCESS 成功。
MOSQ_ERR_INVAL 如果输入参数无效。
MOSQ_ERR_NOMEM 如果发生内存不足的情况。
MOSQ_ERR_NO_CONN 如果客户端未连接到代理。
MOSQ_ERR_CONN_LOST 如果与代理的连接丢失。
MOSQ_ERR_PROTOCOL 与代理进行通信时是否存在协议错误。
MOSQ_ERR_ERRNO 如果系统调用返回错误。变量errno包含错误代码 

了解mosquitto库更多API请点击这里


三.代码实现

MQTT发布端

/*********************************************************************************
 *      Copyright:  (C) 2022 Ye Xingwei<2929273315@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  subscribe.c
 *    Description:  This file MQTT_pub
 *                 
 *        Version:  1.0.0(2022年01月04日)
 *         Author:  Ye Xingwei <2929273315@qq.com>
 *      ChangeLog:  1, Release initial version on "2022年01月04日 15时08分27秒"
 *                 
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <mosquitto.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <signal.h>
#include <ctype.h>

#include "cJSON.h"


#define HOST           "localhost"
#define PORT            1883
#define KEEP_ALIVE      60
#define MSG_MAX_SIZE    512


static int  g_stop = 0;


void mqtt_connect_callback(struct mosquitto *mosq, void *obj, int rc);
void mqtt_disconnect_callback(struct mosquitto *mosq, void *obj, int rc);
int get_time(char *datetime, int bytes);
int get_temperature(float *temp);
int get_ipaddr(char *interface,char *ipaddr,int ipaddr_size);
void sig_handle(int signum);



int main (int argc, char **argv)
{
    int                 rv;
    struct mosquitto    *mosq = NULL;



    /*安装信号*/
    signal(SIGUSR1,sig_handle);
    
    
    
    
    /* MQTT 初始化 */
    rv = mosquitto_lib_init();
    if(rv != MOSQ_ERR_SUCCESS)
    {
        printf("mosquitto lib int failure:%s\n", strerror(errno));
        goto cleanup;
    }
    
    /* 创建新的客户端 */
    mosq = mosquitto_new(NULL,true,NULL);
    if(!mosq)
    {
        printf("create client failure:%s\n",strerror(errno));
        goto cleanup;
    }
    
    /* 回调函数 */
    mosquitto_connect_callback_set(mosq, mqtt_connect_callback);
    
    
    
    while(!g_stop)
    {
        /*  连接MQTT服务器,ip,端口,时间 */ 
        if(mosquitto_connect(mosq,HOST,PORT,KEEP_ALIVE) != MOSQ_ERR_SUCCESS)
        {
            printf("mosquitto_connect() failed: %s\n",strerror(errno));
            goto cleanup;
        }
        printf("connect successfully\n");

        /* 无阻塞 断线连接 */
        mosquitto_loop_forever(mosq,-1,1);


        sleep(10);

    }

cleanup: 
    mosquitto_destroy(mosq);
    mosquitto_lib_cleanup();
    return 0;
} 

/*确认连接回函数*/
void mqtt_connect_callback(struct mosquitto *mosq, void *obj, int rc)
{
    char                    ipaddr[16];
    char                    *interface="eth0";
    char                    datetime[64];
    cJSON                   *root;
    cJSON                   *item;
    char                    *msg;
    struct mqtt_user_data   *mqtt;


    printf("Connection successful cJSON call packaging\n");

    float temper = 0.000000;

    if(get_temperature(&temper) < 0)
    {
        printf("get_temperature failed.\n");
        return;
    }

    if(get_time(datetime,sizeof(datetime))<0)
    {
        printf("get_time failure\n");
        return ;
    }

    memset(ipaddr,0,sizeof(ipaddr));
    if(get_ipaddr(interface,ipaddr,sizeof(ipaddr))<0)
    {
        printf("ERROR:get ip address failure\n");
        return ;
    }


    root = cJSON_CreateObject();
    item = cJSON_CreateObject();



    /* cJSON打包 */
    cJSON_AddItemToObject(root,"id",cJSON_CreateString(ipaddr));
    cJSON_AddItemToObject(root,"time",cJSON_CreateString(datetime));
    cJSON_AddItemToObject(root,"Temperature",cJSON_CreateNumber(temper));
   
    msg = cJSON_Print(root);
    //printf("%s\n",msg);


    if(!rc)
    {
        if(mosquitto_publish(mosq,NULL,"temp",strlen(msg),msg,0,NULL) != MOSQ_ERR_SUCCESS)
        {
            printf("mosquitto_publish failed: %s\n",strerror(errno));
            return;
        }
    }

    mosquitto_disconnect(mosq);
}





/* 获取时间 */
int get_time(char *datetime, int bytes)
{
    time_t              now;
    struct tm          *t;

    time(&now);
    t = localtime(&now);

    snprintf(datetime, bytes, "%04d-%02d-%02d %02d:%02d:%02d", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, (t->tm_hour)+8, t->tm_min, t->tm_sec);

    return 0;
}
/* 安装信号 */
void sig_handle(int signum)
{
    if(SIGUSR1 == signum)
    {
        g_stop = 1;
    }
}

/* 获取温度 */
int get_temperature(float *temp)
{
    int     fd = -1;
    char    buf[128];
    char    *ptr=NULL;
    DIR     *dirp = NULL;
    struct dirent *direntp = NULL;
    char    w1_path[64]="/sys/bus/w1/devices/";
    char    chip_sn[32];
    int     found = 0;


    dirp=opendir(w1_path);
    if(!dirp)
    {
        printf("open foldir %s failure:%s\n",w1_path,strerror(errno));
        return -1;
    }

    while(NULL!=(direntp=readdir(dirp)))
    {
        if(strstr(direntp->d_name,"28-"))
        {
            strncpy(chip_sn, direntp->d_name,sizeof(chip_sn));
            found = -1;
        }
    }
    closedir(dirp);
    if(!found)
    {
        printf("can not find ds18b20 chipset\n");
        return  -2;
    }


    strncat(w1_path,chip_sn,sizeof(w1_path)-strlen(w1_path));
    strncat(w1_path,"/w1_slave",sizeof(w1_path)-strlen(w1_path));

    if((fd = open(w1_path,O_RDONLY))<0)
    {
        printf("File opened successfully:%s\n",strerror(errno));
        return -3;
    }

    memset(buf, 0, sizeof(buf));
    if(read(fd, buf, sizeof(buf))<0)
    {
        printf("read data from fd=%d failure:%s\n",fd,strerror(errno));
        return -4;
    }

    ptr = strstr(buf,"t=");
    if(!ptr)
    {
        printf("t=string\n");
        return -5;

    }

    ptr+= 2;
    *temp = atof(ptr)/1000;
    close(fd);
    return 0;

}




/* 获取IP地址 */
int get_ipaddr(char *interface,char *ipaddr,int ipaddr_size)
{
    char            buf[1024];
    char            *ptr;
    char            *ip_start;
    char            *ip_end;
    FILE            *fp;
    int             len;
    int             rv;

    if(!interface || !ipaddr || ipaddr_size <16)
    {
        printf("Invalid input argument\n");
        return -2;
    }

    memset(buf, 0 , sizeof(buf));

    snprintf(buf,sizeof(buf),"ifconfig %s",interface);
    fp = popen(buf,"r");
    if(NULL==fp)
    {
        printf("popen() to extern command\"%s\"failure:%s\n",buf,strerror(errno));
        return -2;
    }
    rv = -3;
    while(fgets(buf,sizeof(buf),fp))
    {
        if(strstr(buf,"netmask"))
        {
            ptr = strstr(buf,"inet");
            if(!ptr)
            {
                break;
            }
            ptr +=strlen("inet");

            while(isblank(*ptr))
                ptr++;
            ip_start = ptr;
            while(!isblank(*ptr))
                ptr++;
            ip_end = ptr;
            memset(ipaddr,0,sizeof(ipaddr));

            len = ip_end-ip_start;
            len = len>ipaddr_size ? ipaddr_size:len;

            memcpy(ipaddr,ip_start,len);
            rv = 0;

            break;
        }
    }
    pclose(fp);
    return rv;
}

MQTT订阅端

/*********************************************************************************
 *      Copyright:  (C) 2022 Ye Xingwei<2929273315@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  subscribe.c
 *    Description:  This file MQTT_sub
 *                 
 *        Version:  1.0.0(2022年01月05日)
 *         Author:  Ye Xingwei <2929273315@qq.com>
 *      ChangeLog:  1, Release initial version on "2022年01月05日 10时27分36秒"
 *                 
 ********************************************************************************/

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

#include "cJSON.h"


#define HOST "localhost"
#define PORT  1883
#define KEEP_ALIVE 60
#define MSG_MAX_SIZE  512

static int running = 1;


/* 确认连接回调函数 */
void mqtt_connect_callback(struct mosquitto *mosq, void *obj, int rc)
{
    printf("Confirm the connection to the client\n");
    if(rc)
    {
        printf("on_connect error!\n");
        exit(1);
    }
    else
    {
        if(mosquitto_subscribe(mosq, NULL, "temp", 2))
        {
            printf("Set the topic error!\n");
            exit(1);
        }
    }
}


/*获取到订阅的内容*/
void mqtt_message_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg)
{

        printf("Obtaining content successfully\n");
        printf("\n");
        printf("Succeeded in obtaining the time and temperature:%s\n", (char *)msg->payload);
}



int main (int argc, char **argv)
{
    int                 ret;
    struct mosquitto    *mosq;

    /* MQTT 初始化 */
    ret = mosquitto_lib_init();
    if(ret)
    {
        printf("Init lib error!\n");
        goto cleanup;
        return -1;
    }

    /* 创建新的客户端 */
    mosq = mosquitto_new(NULL,true, NULL);
    if(mosq == NULL)
    {
        printf("Create a new client failure\n");
        goto cleanup;
        return -1;
    }
    /* 回调函数 */
    mosquitto_connect_callback_set(mosq, mqtt_connect_callback);
    mosquitto_message_callback_set(mosq, mqtt_message_callback);

    /* 连接代理 */
    ret = mosquitto_connect(mosq, HOST, PORT, KEEP_ALIVE);
    if(ret)
    {

        printf("Connect server error!\n");
        goto cleanup;
        return -1;
    }
    printf("connection client is OK\n");

    while(running)
    {
        mosquitto_loop(mosq, -1, 1);
    }
    

/* 释放 清空 */
cleanup:
    mosquitto_destroy(mosq);
    mosquitto_lib_cleanup();
    return 0;
} 

MQTT运行
MQTT发布端MQTT订阅端

MQTT详细博客

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐