轉載:https://blog.csdn.net/chenliang0224/article/details/89188959
上一篇文章我們已經講了 MQTT 服務器的搭建,參看:MQTT再學習 -- 搭建MQTT服務器及測試
接下來我們看一下 MQTT 客戶端。
一、客戶端下載
首先,客戶端也有多種,我們需要面臨選擇了。
參看:基於mqtt的消息推送(三)客戶端實現
現有客戶端sdk分析,基本分爲兩大類:一類移植自C類庫,如Mosquitto,一類是用objc或者swift原生實現。
各種sdk對比如下,我選用的是MQTT-Client,使用swift的同學可以使用CocoaMQTT,這個sdk的作者同時也是服務端實現emqtt的作者。
Name Type Programming Language Code
Paho Original C Open-Source. Eclipse project
IBM Original C Close Source. IBM SDK
Mosquitto Original C Open-Source. Eclipse project
MQTTKit Wrapper (Mosquitto) Objective-C Open-Source. Github
Marquette Wrapper (Mosquitto) Objective-C Open-Source. Github
Moscapsule Wrapper (Mosquitto) Swift Open-Source. Github
Musqueteer Wrapper (Mosquitto) Objective-C
MQTT-Client Native Objective-C Open-Source. Github
MQTTSDK Native Objective-C
CocoaMQTT Native Swift Open-Source. Github
我們上一篇就用到了 Mosquitto 就是它了。
如果你下載的是上面這個鏈接裏的,比如說 org.eclipse.mosquitto-1.4.8.tar.gz
安裝的時候和上一篇文章是一樣的。
不過遇到出現兩個問題:
參看:mosquitto 使用時出現的一些問題及其解決辦法
xsltproc:命令未找到
解決方法:sudo apt-get install xsltproc
make[1]: 正在進入目錄 `/home/tarena/project/MQTT/org.eclipse.mosquitto-1.4.8/man'
xsltproc mosquitto.8.xml
warning: failed to load external entity "/usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl"
compilation error: file manpage.xsl line 3 element import
xsl:import : unable to load /usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl
compilation error: file mosquitto.8.xml line 4 element refentry
xsltParseStylesheetProcess : document is not a stylesheet
make[1]: *** [mosquitto.8] 錯誤 5
make[1]:正在離開目錄 `/home/tarena/project/MQTT/org.eclipse.mosquitto-1.4.8/man'
make: *** [docs] 錯誤 2
解決方法:
sudo apt-get install docbook-xsl
二、自寫客戶端
看完客戶端源碼(其實先寫的這個,源碼還沒看...),那麼接下來我們自己來寫一個客戶端。
首先講一下會用到的一些 C/UINX 的知識點,當然你要是基礎很紮實再好不過。
這裏用到了 消息隊列、線程、fgets、共享庫等一些知識點。
看到這裏,不由得慶幸一下,看來我用了一年時間總結基礎知識是沒有白費的,雖然很多已經忘的差不多了。但是回過頭來,再看就明白了。特麼,我都佩服我自己,一篇文章寫這麼多,我都快看不下去了。
參看:UNIX再學習 -- XSI IPC通信方式
參看:UNIX再學習 -- 線程
參看:C語言再學習 -- 輸入/輸出
參看:UNIX再學習 -- 靜態庫與共享庫
(1)自寫源碼
言歸正傳。先將我寫的源碼貼出。當然是閹割版的,其中的 PM2.5 上一篇文章也提到了採集器不同協議也不一樣。並且我手頭沒有,我木有什麼辦法呀啊。所以只能發送接收一個 hello world ,向世界問聲好了。
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/time.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <pthread.h>
#include "mosquitto.h"
#include "net_zslf.h"
#include <string.h>
#include <pthread.h>
#include <sys/wait.h>
#include <sys/types.h>
//const char *mqtt_broker_address = "192.168.x.xx"; /* mqtt_broker ip address */
int mqtt_broker_port = 1883; /* mqtt_broker port number */
long msgtype = 10; /* pm sensor message type */
int msgsize = 100; /* pm sensor message size */
int main(int argc, char** argv)
{
int gflags;
int msgid;
key_t key;
pthread_t thread1, thread2;
int ret;
/* struct msqid_ds msg_ginfo, msg_sinfo; */
char *msgpath = "home/tarena/project/MQTT/test/a.txt";
//消息隊列的 鍵
key = ftok(msgpath, 'a');
gflags = IPC_CREAT;
//創建消息隊列
msgid = msgget(key, gflags | 00666);
if(msgid == -1)
{
DUG_PRINTF("msg create error\n");
return -1;
}
/* msg_stat(msgid,msg_ginfo); */
//創建消息隊列發送線程
ret = pthread_create(&thread1, NULL, &start_thread_msgsend, (void *)&msgid);
if (ret != 0) {
perror("pthread msgsend create error\n");
return -1;
}
//創建消息隊列接收線程
ret = pthread_create(&thread2, NULL, &start_thread_msgrcv, (void *)&msgid);
if (ret != 0){
perror("pthread msgrcv create error\n");
return -1;
}
//線程等待
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
return 0;
}
//消息隊列接收線程
void *start_thread_msgrcv(void *arg)
{
int rflags = 0;
int ret;
int msgid = *(int *)(arg);
MSG_data_buf msg_rbuf; //消息隊列類型
struct mosquitto *mosq; //保存一個MQTT客戶端連接的所有信息
//下面的代碼是從xml文件中讀取
FILE *fp;
char szFileBuff[1024] = {0};
char serverADDR[16] = {0},devID[15] = {0},devName[15] = {0};
char Longitude[15] = {0},Latitude[15] = {0},frequency[3] = {0};
char *lFirst, *lEnd;
char devInfo[70];
FILE *fp_re;
char buffer_re[4];
//打開xml文件
fp = fopen("/home/tarena/project/MQTT/test/deviceCfg.xml","r");
if (fp==NULL)
{
DUG_PRINTF("read XML file error!\n");
}
//你只要知道while裏面是獲取xml信息的,至於這種操作有點6
while(fgets(szFileBuff, 1023, fp))
{
if ((lFirst = strstr(szFileBuff, "<serverADDR>")) != NULL)
{
lEnd = strstr(lFirst + 1, "</serverADDR>");
memcpy(serverADDR, lFirst + 12, lEnd - lFirst - 12);
}
if ((lFirst = strstr(szFileBuff, "<devID>")) != NULL)
{
lEnd = strstr(lFirst + 1, "</devID>");
memcpy(devID, lFirst + 7, lEnd - lFirst - 7);
}
if ((lFirst = strstr(szFileBuff, "<devName>")) != NULL)
{
lEnd = strstr(lFirst + 1, "</devName>");
memcpy(devName, lFirst + 9, lEnd - lFirst - 9);
}
if ((lFirst = strstr(szFileBuff, "<Longitude>")) != NULL)
{
lEnd = strstr(lFirst + 1, "</Longitude>");
memcpy(Longitude, lFirst + 11, lEnd - lFirst - 11);
}
if ((lFirst = strstr(szFileBuff, "<Latitude>")) != NULL)
{
lEnd = strstr(lFirst + 1, "</Latitude>");
memcpy(Latitude, lFirst + 10, lEnd - lFirst - 10);
}
//下面這個語句是用於分頻率傳送數據的
if ((lFirst = strstr(szFileBuff, "<frequency>")) != NULL)
{
lEnd = strstr(lFirst + 1, "</frequency>");
memcpy(frequency, lFirst + 11, lEnd - lFirst - 11);
}
if ((lFirst = strstr(szFileBuff, "</display>")) != NULL)
{
sprintf(devInfo, "&%s&%s&%s&%s&\n",devID,devName,Longitude,Latitude);
}
}
fclose(fp);
//這裏是關鍵了,MQTT協議 要上演了 這部分爲 pub 發佈內容
//MQTT 庫初始化
mosquitto_lib_init();
//新建
mosq = mosquitto_new(devID, true, NULL);
//連接回調設置
mosquitto_connect_callback_set(mosq, my_connect_callback);
//斷開回調設置
mosquitto_disconnect_callback_set(mosq, my_disconnect_callback);
//發佈回調設置
mosquitto_publish_callback_set(mosq, my_publish_callback);
//MQTT連接
if(mosquitto_connect(mosq, serverADDR, mqtt_broker_port, 600) != MOSQ_ERR_SUCCESS)
{
DUG_PRINTF("mosquitto connection error\n");
//銷燬
mosquitto_destroy(mosq);
//清空
mosquitto_lib_cleanup();
}
//這裏開始 消息隊列接收消息
while (1)
{
//接收消息
ret=msgrcv(msgid, &msg_rbuf, msgsize, msgtype, rflags);
sleep(1);
if (ret == -1)
{
DUG_PRINTF("read msg error\n");
}
DUG_PRINTF("%s\n", msg_rbuf.mtext);
//將接收到的信息發佈
mosquitto_publish(mosq, NULL, "pmsensor", ret, (void *)msg_rbuf.mtext, 0, 0);
sleep(5);
}
/* should never run below */
//銷燬
mosquitto_destroy(mosq);
//清空
mosquitto_lib_cleanup();
return NULL;
}
void *start_thread_msgsend(void *arg)
{
/* 這裏寫 PM.25 採集 */
int retval;
MSG_data_buf msg_sbuf; //消息隊列類型
int msgid = *(int *)(arg);
int sflags=IPC_NOWAIT;
msg_sbuf.mtype = msgtype;
strcpy(msg_sbuf.mtext,"hello world");
//消息隊列發送
retval = msgsnd(msgid, &msg_sbuf,msgsize, sflags);
if(retval == -1)
{
DUG_PRINTF("message send error\n");
}
}
(2)源碼分析
簡單來看一下上面的代碼
首先創建了一個消息隊列
//消息隊列的 鍵
key = ftok(msgpath, 'a');
gflags = IPC_CREAT;
//創建消息隊列
msgid = msgget(key, gflags | 00666);
if(msgid == -1)
{
DUG_PRINTF("msg create error\n");
return -1;
}
然後創建了兩個線程
//創建消息隊列發送線程
ret = pthread_create(&thread1, NULL, &start_thread_msgsend, (void *)&msgid);
if (ret != 0) {
perror("pthread msgsend create error\n");
return -1;
}
//創建消息隊列接收線程
ret = pthread_create(&thread2, NULL, &start_thread_msgrcv, (void *)&msgid);
if (ret != 0){
perror("pthread msgrcv create error\n");
return -1;
}
先看一下發送消息隊列線程
strcpy(msg_sbuf.mtext,"hello world");
//消息隊列發送
retval = msgsnd(msgid, &msg_sbuf,msgsize, sflags);
無非將 hello world 替換成採集到的PM2.5數據。通過消息隊列函數 msgsnd 發送。
再看一下接收消息隊列線程
打開xml文件,從 xml 文件中獲取信息
fp = fopen("/home/tarena/project/MQTT/test/deviceCfg.xml","r");
if (fp==NULL)
{
DUG_PRINTF("read XML file error!\n");
}
然後 while循環裏的獲取信息,居然還有這種操作...
然後就是關鍵了,MQTT 協議要上演了
//MQTT 庫初始化
mosquitto_lib_init();
//新建
mosq = mosquitto_new(devID, true, NULL);
//連接回調設置
mosquitto_connect_callback_set(mosq, my_connect_callback);
//斷開回調設置
mosquitto_disconnect_callback_set(mosq, my_disconnect_callback);
//發佈回調設置
mosquitto_publish_callback_set(mosq, my_publish_callback);
接收消息隊列、發佈內容
//接收消息
ret=msgrcv(msgid, &msg_rbuf, msgsize, msgtype, rflags);
//將接收到的信息發佈
mosquitto_publish(mosq, NULL, "pmsensor", ret, (void *)msg_rbuf.mtext, 0, 0);
最後銷燬清空 MQTT
//銷燬
mosquitto_destroy(mosq);
//清空
mosquitto_lib_cleanup();
(3)項目下載
將 deviceCfg.xml 裏的 IP 地址換成你自己的,服務器IP還是不能暴露滴。
下載:MQTT 客戶端
(4)演示