一、简述
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。
MQTT是一个基于客户端-服务器的消息发布/订阅传输协议。MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。在很多情况下,包括受限的环境中,如:机器与机器(M2M)通信和物联网(IoT)。其在,通过卫星链路通信传感器、偶尔拨号的医疗设备、智能家居、及一些小型化设备中已广泛使用。
二、移植
要移植mqtt,首先需要先移植openssl,因为在编译mqtt时会用到openssl的lib库
2.1 openssl移植到ARM Linux
OpenSSL 是一个安全套接字层密码库,囊括主要的密码算法、常用的**和证书封装管理功能及SSL协议,并提供丰富的应用程序供测试或其它目的使用。
备注:开发环境:Ubuntu 18.04 交叉编译工具:aarch64-linux-gnu-gcc version 7.3.0。开发环境不一定是我这配置,只要是linux操作系统,和ARM对应的交叉工具链就可以。
首先从OpenSSL官网下载 源码,建议下载最新源码低一个版本的
官网链接:https://www.openssl.org/source/
- 执行下面命名解压缩:
tar zxvf openssl-1.0.2l.tar.gz
2.进入刚解压的目录cd openssl-1.0.2l/,执行下面指令,做相应的配置:
./config no-asm shared --prefix=$(pwd)/__install
备注:
- no-asm: 是在交叉编译过程中不使用汇编代码代码加速编译过程,原因是它的汇编代码是对arm格式不支持的。
- shared :生成动态连接库。
- --prefix :指定make install后生成目录的路径,不修改此项则默认为OPENSSLDIR目录(/usr/local/ssl)。
- 建议不指定pwd具体路径
- 建议将CFLAG中的-m64删掉,同时找到SHARED_LDFLAGS=-m64这一句,将-m64删除
3.修改Makefile:
以上为修改后的内容
make
make install
成功后查看如图所示目录下是否有相同文件夹
include下文件在编译程序的时候需要指定include的路径。而lib下在程序运行时会用到,需要将lib下文件拷贝到开发板中。
2.2 交叉编译mqtt
1.下载源码 源码地址:http://www.eclipse.org/paho/downloads.php
下载源码后,将压缩包拷贝到Ubuntu中,解压 tar xvf 文件名
进入解压后的目录,找到Makefile文件,打开
找到在101行 添加CC ?= 处,然后修改为如下图所示
make
不出意外则可以编译成功,使用使用可以直接修改src/sample中的例子
三、arm端使用
首先将编译好的生成的output文件整个copy到arm中,然后将output中的所有库文件copy到arm板的lib目录下,然后可以运行sample中的可执行程序,查看效果。过程较为简单,就不贴图了。
3.1Qt端调用mqtt
因Qt5.10之前都没有官方支持的mqtt库。所以只能下载mqtt的源码自行编译,但多次测试只能在Windows和linux下用Qt编译mqtt源码,生成动态库。但用交叉编译链移植无法编译,如果有大神编译成功,请不吝赐教,感激不尽。
所以之前交叉编译的是基于c语言的mqtt源码包;
1.发布
首先修改sample中的
1.修改宏定义,如图所示
2.在main函数中重新定义一个PAYLOAD来接收从命令行输入的发布内容,建议变量名不要修改,就用原有的命名,这样就不需要修改其他函数了。
至此发布就可以自己手动输入要发布的内容了。
2.订阅
订阅的话也是同样的要先修改宏定义,修改方式和发布是一样的。
然后就可以正常订阅服务器发出的消息了。
为了方便Qt端访问将订阅的消息存成到一个临时文件中,在Qt端调用时就可以直接打开文件读取
3.Qt调用
在arm端使用Qt程序来编写mqtt的客户端的话,首先需要了解Qt的QProcess进程类
QProcess
可用于完成启动外部程序,并与之交互通信。启动时注意需要带上执行文件的路径
一、启动外部程序的两种方式
1)一体式:void QProcess::start(const QString & program,const QStringList &arguments,OpenMode mode = ReadWrite)
外部程序启动后,将随主程序的退出而退出。
2)分离式:void QProcess::startDetached(const QString & program,const QStringList & arguments,const QString&workingDirectory=QString(),qint64 *pid =0)
外部程序启动后,当主程序退出时并不退出,而是继续运行。
二、启动之前需要做的工作:
启动一个外部程序,需要传递外部程序的路径和执行参数,参数用QStringList来带入。
QString str_exe;
str_exe = "./pubsync"; //相对于Qt执行程序的相对路径,也可以改成绝对路径
QStringList args;
args << "dasdaefgedfg" << "asfwefewfrew"; //传递的参数,可以改成实时变化的
process->start(str_exe,args);
if(!process->waitForStarted()) //等待程序启动,会阻塞,建议使用多线程调用
{
ui->Edit_tip->append("process call failed");
}
ui->Edit_tip->append("process call sucessed!");
在启动前建议调用QProcess的信号和槽机制来对程序进行检测
connect(process,SIGNAL(readyReadStandardError()),this,SLOT(startReadErrOutput()));
connect(process,SIGNAL(finished(int,QProcess::ExitStatus)),this,SLOT(finishProcess(int,QProcess::ExitStatus)));
void MainWindow::finishProcess(int, QProcess::ExitStatus exitStatus)
{
if(exitStatus == QProcess::NormalExit)
{
ui->Edit_tip->append("process exit normal");
}
else
{
ui->Edit_tip->append("process exit crash");
}
ui->Edit_tip->append("process finished");
}
同时需要重新一下closeEvent(QCloseEvent *event)这个虚函数,来回收进程
process->kill();
if(!process->waitForFinished(100))
return;
event->accept();
订阅同样也是需要创建一个QProcess *recv_process;
QString str_exe;
str_exe = "./subasync";
recv_process->start(str_exe);
if(!recv_process->waitForStarted())
{
ui->Edit_tip->append("recv_process call failed");
}
else
{
ui->Edit_tip->append("recv_process call sucessed!");
}
启动程序后就可以读取文件了
QFile file("./temp");
if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
return;
QByteArray ba = file.readAll();
ui->Edit_recvBrowser->append(QString::fromLocal8Bit(ba));
file.close(); //建议每次读取后将文件清空
读取完后同样也需要对进程进行同上的处理。至此Qt调用完成。
对于订阅时通过read直接读取外部进程中接收的内容,经常测试无法访问,推测是因为此进程一直处于阻塞状态,导致无法读取,所以采用读取文件的方式直接读取。