一、MQTT简介
MQTT(消息队列遥测传输)是ISO 标准(ISO/IEC PRF 20922)下基于发布/订阅范式的消息协议。它工作在TCP/IP协议族上,是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协议;Mosquitto是一个实现了MQTT3.1协议的代理服务器,由MQTT协议创始人之一的Andy Stanford-Clark开发,应用广泛的低功耗传感器,手机、嵌入式计算机、微型控制器等移动设备
二、MQTT特点
MQTT协议是为大量计算能力有限,且工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而设计的协议,它具有以下主要的几项特性:
1、使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合;
2、对负载内容屏蔽的消息传输;
3、使用 TCP/IP 提供网络连接;
4、有三种消息发布服务质量:
“至多一次”,消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。
“至少一次”,确保消息到达,但消息重复可能会发生。
“只有一次”,确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。
5、小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量;
6、使用 Last Will 和 Testament 特性通知有关各方客户端异常中断的机制。
MQTT介绍与协议:
https://mcxiaoke.gitbooks.io/mqtt-cn/content/mqtt/01-Introduction.html
三、Mosquitto安装
1、用wget下载源码包
wget http://mosquitto.org/files/source/mosquitto-1.5.5.tar.gz
2、解压压缩包
tar -xzvf mosquitto-1.5.5.tar.gz
3、进入目录
cd mosquitto-1.5.5/
4、可以在源码目录里面找到主要的配置文件config.mk,其中包含了所有Mosquitto的安装选项,详细的参数说明如下:
# 是否支持tcpd/libwrap功能.
#WITH_WRAP:=yes
# 是否开启SSL/TLS支持
#WITH_TLS:=yes
# 是否开启TLS/PSK支持
#WITH_TLS_PSK:=yes
# Comment out to disable client client threading support.
#WITH_THREADING:=yes
# 是否使用严格的协议版本(老版本兼容会有点问题)
#WITH_STRICT_PROTOCOL:=yes
# 是否开启桥接模式
#WITH_BRIDGE:=yes
# 是否开启持久化功能
#WITH_PERSISTENCE:=yes
# 是否监控运行状态
#WITH_MEMORY_TRACKING:=yes
参考博客:https://www.cnblogs.com/chen1-kerr/p/7258487.html
5、编译
Make
6、安装
sudo make install
7、可能出现问题及解决方法
【1】编译找不到openssl/ssl.h
【解决方法】——安装openssl
sudo apt-get install libssl-dev
【2】编译过程g++命令未找到:
sudo apt-get install g++
【3】编译过程找不到ares.h
sudo apt-get install libc-ares-dev
【4】编译过程找不到uuid/uuid.h
sudo apt-get install uuid-dev
【5】使用过程中找不到libmosquitto.so.1
error while loading shared libraries: libmosquitto.so.1: cannot open shared object file: No such file or directory
【解决方法】——修改libmosquitto.so位置
# 创建链接
sudo ln -s /usr/local/lib/libmosquitto.so.1 /usr/lib/libmosquitto.so.1
# 更新动态链接库
sudo ldconfig
参考:https://blog.csdn.net/xukai871105/article/details/39252653
四、测试与使用
mosquitto目录下的配置文件mosquitto.conf
# =================================================================
# General configuration
# =================================================================
# 客户端心跳的间隔时间
#retry_interval 20
# 系统状态的刷新时间
#sys_interval 10
# 系统资源的回收时间,0表示尽快处理
#store_clean_interval 10
# 服务进程的PID
#pid_file /var/run/mosquitto.pid
# 服务进程的系统用户
#user mosquitto
# 客户端心跳消息的最大并发数
#max_inflight_messages 10
# 客户端心跳消息缓存队列
#max_queued_messages 100
# 用于设置客户端长连接的过期时间,默认永不过期
#persistent_client_expiration
# =================================================================
# Default listener
# =================================================================
# 服务绑定的IP地址
#bind_address
# 服务绑定的端口号
#port 1883
# 允许的最大连接数,-1表示没有限制
#max_connections -1
# cafile:CA证书文件
# capath:CA证书目录
# certfile:PEM证书文件
# keyfile:PEM密钥文件
#cafile
#capath
#certfile
#keyfile
# 必须提供证书以保证数据安全性
#require_certificate false
# 若require_certificate值为true,use_identity_as_username也必须为true
#use_identity_as_username false
# 启用PSK(Pre-shared-key)支持
#psk_hint
# SSL/TSL加密算法,可以使用“openssl ciphers”命令获取
# as the output of that command.
#ciphers
# =================================================================
# Persistence
# =================================================================
# 消息自动保存的间隔时间
#autosave_interval 1800
# 消息自动保存功能的开关
#autosave_on_changes false
# 持久化功能的开关
persistence true
# 持久化DB文件
#persistence_file mosquitto.db
# 持久化DB文件目录
#persistence_location /var/lib/mosquitto/
# =================================================================
# Logging
# =================================================================
# 4种日志模式:stdout、stderr、syslog、topic
# none 则表示不记日志,此配置可以提升些许性能
log_dest none
# 选择日志的级别(可设置多项)
#log_type error
#log_type warning
#log_type notice
#log_type information
# 是否记录客户端连接信息
#connection_messages true
# 是否记录日志时间
#log_timestamp true
# =================================================================
# Security
# =================================================================
# 客户端ID的前缀限制,可用于保证安全性
#clientid_prefixes
# 允许匿名用户
#allow_anonymous true
# 用户/密码文件,默认格式:username:password
#password_file
# PSK格式密码文件,默认格式:identity:key
#psk_file
# pattern write sensor/%u/data
# ACL权限配置,常用语法如下:
# 用户限制:user <username>
# 话题限制:topic [read|write] <topic>
# 正则限制:pattern write sensor/%u/data
#acl_file
# =================================================================
# Bridges
# =================================================================
# 允许服务之间使用“桥接”模式(可用于分布式部署)
#connection <name>
#address <host>[:<port>]
#topic <topic> [[[out | in | both] qos-level] local-prefix remote-prefix]
# 设置桥接的客户端ID
#clientid
# 桥接断开时,是否清除远程服务器中的消息
#cleansession false
# 是否发布桥接的状态信息
#notifications true
# 设置桥接模式下,消息将会发布到的话题地址
# $SYS/broker/connection/<clientid>/state
#notification_topic
# 设置桥接的keepalive数值
#keepalive_interval 60
# 桥接模式,目前有三种:automatic、lazy、once
#start_type automatic
# 桥接模式automatic的超时时间
#restart_timeout 30
# 桥接模式lazy的超时时间
#idle_timeout 60
# 桥接客户端的用户名
#username
# 桥接客户端的密码
#password
# bridge_cafile:桥接客户端的CA证书文件
# bridge_capath:桥接客户端的CA证书目录
# bridge_certfile:桥接客户端的PEM证书文件
# bridge_keyfile:桥接客户端的PEM密钥文件
#bridge_cafile
#bridge_capath
#bridge_certfile
#bridge_keyfile
启动mosquitto
如果要后台运行 命令行后面加”-d”
mosquitto -v -d
mosquitto -c mosquitto.conf -d(在mosquitto安装目录下)
关闭或重启
ps -aux| grep mosquitto
然后kill停止重新启动
启动代理服务
mosquitto -v
【-v】打印更多的调试信息
订阅主题
mosquitto_sub -v -t ”主题“
【-t】指定主题,此处为sensor
【-v】打印更多的调试信息
发布内容
mosquitto_pub -t ”主题“ -m ”内容“
【-t】指定主题
【-m】指定消息内容
我们打开两个服务器窗口,在一个(订阅)窗口输入:
mosquitto_sub -h test.mosquitto.org -t "hello/world" -v
另一个(发布)窗口输入:
mosquitto_pub -h test.mosquitto.org -t "hello/world" -m "Who's there"
参考博客:https://www.cnblogs.com/chen1-kerr/p/7258487.html
mosquitto_pub 命令参数说明
五、mosquitto_pub和mosquitto_sub 命令参数说明
mosquitto_pub 命令参数说明
1. -d 打印debug信息
2. -f 将指定文件的内容作为发送消息的内容
3. -h 指定要连接的域名 默认为localhost
4. -i 指定要给哪个clientId的用户发送消息
5. -I 指定给哪个clientId前缀的用户发送消息
6. -m 消息内容
7. -n 发送一个空(null)消息
8. -p 连接端口号
9. -q 指定QoS的值(0,1,2)
10. -t 指定topic
11. -u 指定broker访问用户
12. -P 指定broker访问密码
13. -V 指定MQTT协议版本
14. --will-payload 指定一个消息,该消息当客户端与broker意外断开连接时发出。该参数需要与--will-topic一起使用
15. --will-qos Will的QoS值。该参数需要与--will-topic一起使用
16. --will-retain 指定Will消息被当做一个retain消息(即消息被广播后,该消息被保留起来)。该参数需要与--will-topic一起使用
- --will-topic 用户发送Will消息的topic
mosquitto_sub 命令参数说明
1. -c 设定‘clean session’为无效状态,这样一直保持订阅状态,即便是已经失去连接,如果再次连接仍旧能够接收的断开期间发送的消息。
2. -d 打印debug信息
3. -h 指定要连接的域名 默认为localhost
4. -i 指定clientId
5. -I 指定clientId前缀
6. -k keepalive 每隔一段时间,发PING消息通知broker,仍处于连接状态。 默认为60秒。
7. -q 指定希望接收到QoS为什么的消息 默认QoS为0
8. -R 不显示陈旧的消息
9. -t 订阅topic
10. -v 打印消息
11. --will-payload 指定一个消息,该消息当客户端与broker意外断开连接时发出。该参数需要与--will-topic一起使用
12. --will-qos Will的QoS值。该参数需要与--will-topic一起使用
13. --will-retain 指定Will消息被当做一个retain消息(即消息被广播后,该消息被保留起来)。该参数需要与--will-topic一起使用
14. --will-topic 用户发送Will消息的topic
参考博客:http://blog.itpub.net/28624388/viewspace-1440102/
六、Linux c编程mosquitto库的使用:
https://mosquitto.org/api/files/mosquitto-h.html
介绍几个下面用到的函数
libmosq_EXPORT int mosquitto_lib_init(void) ;
必须在任何其他mosquitto函数之前调用。
返回值 总是MOSQ_ERR_SUCCESS
libmosq_EXPORT struct mosquitto *mosquitto_new(const char *id,bool clean_session,void *obj)
创建新的mosquitto实例
第一个参数id,用作mosquitto实例id的字符串。如果为空,将生成随机id。如果id为空,则clean_session必须为ture。
第二个参数clean_session,设置为true可指示代理清除断开连接时的所有消息和订阅,设置为false可指示代理保留这些消息和订阅。
第三个参数obj指针,作为参数传递给指定回调。
返回值:失败返回空指针,错误信息在errno,成功返回mosquitto实例
libmosq_EXPORT void mosquitto_destroy(struct mosquitto *mosq);
用来释放mosquitto实例关联的内存,参数*mosq是mosquitto_new返回的实例。
libmosq_EXPORT int mosquitto_lib_cleanup(void);
用来释放库相关的资源
libmosq_EXPORT int mosquitto_connect(struct mosquitto *mosq,const char *host,int port,int keepalive);
连接mqtt服务器
第一个参数*mosq是mosquitto_new返回的实例
第二个参数是mqtt服务器的ip地址。
第三个参数是mqtt服务器的端口
第四个参数是超时时间。
Returns:
成功返回MOSQ_ERR_SUCCESS
libmosq_EXPORT int mosquitto_publish(struct mosquitto *mosq,int *mid,const char *topic,int payloadlen,const void *payload,int qos,bool retain);
消息发布
第一个参数*mosq是mosquitto_new返回的实例.
第二个参数*mid是int类型的指针。如果不为NULL,函数会将其设置为此特定消息的消息ID。然后,可以将其与发布回调一起使用,以确定何时发送消息。
第三个参数是*topic发布的主题。第四个参数payloadlen是载荷长度,第五个参数*payload是载荷,第六个参数qos是服务质量。
第七个参数retain设置为true可保留消息
Return:
成功返回MOSQ_ERR_SUCCESS
七、树莓派ds18b20使用mosquitto库的温度发布
/*********************************************************************************
* Copyright: (C) 2019 Wu Yujun<540726307@qq.com>
* All rights reserved.
*
* Filename: moqusitto_test.c
* Description: This file mosquitto test
*
* Version: 1.0.0(2019年01月28日)
* Author: Wu Yujun <540726307@qq.com>
* ChangeLog: 1, Release initial version on "2019年02月15日 16时05分17秒"
*
********************************************************************************/
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netdb.h>
#include <stdlib.h>
#include <fcntl.h>
#include <libgen.h>
#include <netinet/in.h>
#include <mosquitto.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <dirent.h>
#define BUF_SIZE 1024
#define KEEP_ALIVE 60
int g_stop = 0 ;
int ds18b20_get_temper(float * temp) ;
void print_usage(const char *program_name) ;
void sig_handler(int SIG_NUM)
{
if(SIG_NUM == SIGUSR1)
g_stop = 1 ;
}
int main(int argc, char **argv)
{
char *sn = "WYJ_Raspberry";
char *program_name ;
time_t tim ;
struct tm *timep;
char info_temp[BUF_SIZE] ;
int daemon_run = 0 ;
int port = 0 ;
int opt = -1 ;
char *ip = NULL;
char *topic = NULL ;
char *hostname = NULL ;
struct hostent * hostnp ;
int no_ipport ;
int no_hostport ;
float temper ;
int log_fd = -1 ;
struct stat f_stat ;
int filelen = 0 ;
int loop = 0 ;
int connect_flag = 1 ;
struct mosquitto *mosq = NULL;
bool session = true ;
int mid ;
program_name = basename(argv[0]) ;
const char *short_opts = "t:i:n:p:hd";
const struct option long_opts[] = {
{"topic", required_argument,NULL,'t'},
{"help", no_argument, NULL, 'h'},
{"hostname", required_argument,NULL,'n'},
{"ip", required_argument, NULL, 'i'},
{"daemon", no_argument, NULL, 'd'},
{ "port", required_argument, NULL, 'p'},
{0, 0, 0, 0}
};
while ((opt= getopt_long(argc, argv, short_opts, long_opts,NULL)) != -1)
{
switch (opt)
{
case 't':
topic = optarg ;
break ;
case 'i':
ip = optarg ;
break ;
case 'n':
hostname = optarg ;
break ;
case 'p':
port = atoi(optarg) ;
break ;
case 'd':
daemon_run = 1 ;
break ;
case 'h':
print_usage(program_name) ;
return 0 ;
}
}
if(!topic)
{
print_usage(program_name) ;
return 0 ;
}
no_hostport = (( !port) || (!hostname) ) ; //port or hostname is NULL, no_hostport = 1;
no_ipport = ( (!ip) || (!port) ) ; //port or ip is NULL, no_ipprot = 1 ;
if( no_hostport && no_ipport)
{
print_usage(program_name);
return 0;
}
/* connect server get host by name */
if ( hostname )
{
if( (hostnp = gethostbyname(hostname) ) == NULL )
{
printf("get host by name failure: %s\n", strerror(h_errno)) ;
return -1 ;
}
printf("hostname %s\n", hostnp->h_name);
ip = inet_ntoa( * (struct in_addr *)hostnp->h_addr );
printf("addr:%s\n",ip) ;
}
/* program running in backgruand */
if(daemon_run == 1)
{
printf("program %s running in backgrund\n", program_name);
if( (log_fd = open("client.log", O_CREAT|O_RDWR, 0666)) < 0)
{
printf("open() failed:%s\n", strerror(errno)) ;
return -2 ;
}
dup2(log_fd, STDOUT_FILENO) ;
dup2(log_fd, STDERR_FILENO) ;
daemon(1,1) ;
}
signal(SIGUSR1, sig_handler);
/* program stop till get sinal "SIGUSR1" */
while(!g_stop)
{
if(ds18b20_get_temper(&temper)<0)
{
printf("ds18b20_get_temper() failed\n") ;
goto cleanup ;
}
time(&tim);
timep = localtime(&tim);
memset(info_temp, 0, sizeof(info_temp)) ;
snprintf(info_temp, sizeof(info_temp), "%s/%d-%d-%d %d:%d:%d/%fC",sn
,(1900+ timep->tm_year), (1+ timep->tm_mon), timep->tm_mday, (timep->tm_hour+8), timep->tm_min,timep->tm_sec
,temper);
printf("%s\n", info_temp) ;
if(connect_flag)
{
/* Must be called before any other mosquitto functions */
mosquitto_lib_init() ;
/* ´Create a new mosquitto client instance. */
mosq = mosquitto_new(NULL, session, NULL) ;
if(!mosq)
{
printf("Mosquitto_new() failed: %s\n", strerror(errno)) ;
mosquitto_destroy(mosq) ;
mosquitto_lib_cleanup() ;
connect_flag = 1 ;
}
printf("Create mosquitto sucessfully!\n");
if( mosquitto_connect(mosq, ip, port, KEEP_ALIVE) != MOSQ_ERR_SUCCESS )
{
printf("Mosq_Connect() failed: %s\n", strerror(errno) );
mosquitto_destroy(mosq) ;
mosquitto_lib_cleanup() ;
connect_flag = 1 ;
}
printf("Connect %s:%d Sucessfully!\n", ip, port);
//Call this once to start a new thread to process network traffic
int loop = mosquitto_loop_start(mosq);
if(loop != MOSQ_ERR_SUCCESS)
{
printf("mosquitto loop error\n");
mosquitto_destroy(mosq) ;
mosquitto_lib_cleanup() ;
connect_flag = 1 ;
}
connect_flag = 0 ;
}
if( mosquitto_publish(mosq,&mid,topic,strlen(info_temp)+1,info_temp,0,0) != MOSQ_ERR_SUCCESS )
{
printf("Mosq_Publish() error: %s\n", strerror(errno));
mosquitto_destroy(mosq) ;
mosquitto_lib_cleanup() ;
connect_flag = 1 ;
}
else
printf("Publish information of temperature Ok!\n") ;
sleep(30) ;
if(daemon_run) //if running in backgrund, client.log should be limited
{
fstat(log_fd, &f_stat);
printf("\nlog file size: %ld bytes\n", f_stat.st_size);
if(filelen == 0)
{
if( f_stat.st_size > 1024 ) //record filelen when file size bigger than 1024
{
filelen = lseek(log_fd, 0, SEEK_END) ;
}
}
else
{ if(loop == 0)
{
printf("client.log too big, So clear\n") ;
lseek(log_fd, 0, SEEK_SET) ;
loop = 1 ;
}
if(filelen <= lseek(log_fd, 0, SEEK_CUR)) //if lseek SEEK_CUR longger than filelen, log file roll back
loop = 0 ;
}
}
}
printf("Program exit\n") ;
return 0 ;
cleanup:
close(log_fd) ;
return -3 ;
}
void print_usage(const char *program_name)
{
printf("\n%s -- (2019.2.15)\n", program_name);
printf(" Usage:(1) %s -i <server_ip> -p <server_port> -t <topic> [-h <server_use_help>]\n", program_name);
printf(" (2) %s -n <server_hostname> -p <server_port> -t <topic> [-h <server_use_help>]\n", program_name);
printf(" -p --port the port of the server you want to connect\n") ;
printf(" -n --hostname the hostname of the server you want to connect\n") ;
printf(" -i --ip the ip address of the server you want to connect\n") ;
printf(" -h --help the client file how to use\n");
printf(" -d --daemon the client progame running in backgruand\n");
return ;
}
int ds18b20_get_temper(float * temp)
{
int count = 0 ;
int flag =0 ;
int fd ;
DIR * dirp ;
struct dirent* direntp ;
char Path_name[512]= "/sys/bus/w1/devices";
char temp_buf[256] ;
char * str ;
dirp = opendir(Path_name) ; //open dir /sys/bus/w1/devices
if(dirp == NULL)
{
printf("open dir %s failed: %s\n", Path_name, strerror(errno)) ;
return -1 ;
}
if( chdir(Path_name) < 0) //change dir to /sys/bus/w1/devices
{
printf("change to dirctory %s failed: %s\n", Path_name, strerror(errno)) ;
goto cleanup ;
}
while( (direntp=readdir(dirp)) != NULL) //list the file from /sys/bus/w1/devices
{
if(strstr(direntp->d_name,"28") != NULL) //find file name is 28-041731f7c0ff
{
count = 1;
break ;
}
}
if(count == 0) //Can't find dir_name include "28-"
{
printf("Can't find dir_name include '28-' \n") ;
return -2 ;
}
strcat(Path_name,"/") ;
strncat(Path_name,direntp->d_name,sizeof(Path_name)) ; //add Pathname become /sys/bus/w1/devices/28-041731f7c0ff
if(chdir(Path_name) < 0) // change to /sys/bus/w1/devices/28-041731f7c0ff
{
perror("change path failed!\n") ;
flag = 1 ;
goto cleanup ;
}
fd = open("w1_slave",O_RDONLY) ; // open w1_slave
if(fd < 0)
{
perror("open file failed!\n");
flag =1 ;
goto cleanup ;
}
if( read(fd, temp_buf, sizeof(temp_buf)) < 0 ) //read w1_slave
{
flag =1 ;
goto cleanup ;
}
str = strstr(temp_buf, "t=") ;
*temp = atof(str+2) /1000 ;
cleanup:
closedir(dirp) ;
close(fd) ;
if(flag)
return -3 ;
else
return 0 ;
}
编译
gcc -Wall Mqtt_Sendtemper.c -o Mqtt_Sendtemper -l mosquitto
然后打开mosquitto服务器
mosquitto -v
发布温度
不同机器订阅温度
对mqtt理解还不深刻,之后深入了解再更...