5-Openwrt MQTT client使用

mosquitto已經(jīng)集成了命令行mosquitto_sub和mosquitto_pub,這個(gè)一般就是調(diào)試的時(shí)候使用刹淌,后面還是要使用mosquitto提供的庫(kù)函數(shù)實(shí)現(xiàn)C語言代碼層的客戶端帽哑。

1. 添加client

在mosquitto里面有個(gè)client目錄谜酒,里面就是使用libmosquitto實(shí)現(xiàn)的客戶端程序,封裝成mosquitto_sub和mosquitto_pub命令行妻枕。

所以新建一個(gè)跟client同一級(jí)僻族,自己的client,添加對(duì)應(yīng)的文件

tree myclient/
myclient/
├── main.c
├── Makefile
└── myclient.h

0 directories, 3 files

Makefile的內(nèi)容

include ../config.mk

.PHONY: all install uninstall reallyclean clean

all : myclient

myclient : main.o
    ${CROSS_COMPILE}${CC} $^ -o $@ ${CLIENT_LDFLAGS} -lpthread

main.o : main.c ../lib/libmosquitto.so.${SOVERSION}
    ${CROSS_COMPILE}${CC} -c $< -o $@ ${CLIENT_CFLAGS}

../lib/libmosquitto.so.${SOVERSION} :
    $(MAKE) -C ../lib

../lib/libmosquitto.a :
    $(MAKE) -C ../lib libmosquitto.a

install : all
    $(INSTALL) -d "${DESTDIR}$(prefix)/bin"
    $(INSTALL) ${STRIP_OPTS} myclient "${DESTDIR}${prefix}/bin/myclient"

uninstall :
    -rm -f "${DESTDIR}${prefix}/bin/myclient"

reallyclean : clean

clean : 
    -rm -f *.o myclient

main.c的內(nèi)容

#include <stdio.h>
#include <signal.h>
#include <pthread.h>
#include <mosquitto.h>
#include "myclient.h"

int debugLevel = MSG_INFO;

int main (int argc, char **argv)
{
    dbg_printf(MSG_DEBUG, "start...\r\n");
    return 0;
}

myclient.h的內(nèi)容

#ifndef MYCLIENT_H
#define MYCLIENT_H

#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>

extern int debugLevel;

enum{
    MSG_ERROR,
    MSG_WARNING,
    MSG_INFO,
    MSG_DEBUG,
    MSG_MSGDUMP,
    MSG_EXCESSIVE,
};

#define dbg_printf(level, ...)                      \
do                                                  \
{                                                   \
    if (debugLevel >= level)                         \
    {                                               \
        syslog(LOG_WARNING, __VA_ARGS__);           \
    }                                               \
}                                                   \
while (0)
//printf(__VA_ARGS__);

#endif

外層的mosquitto/src/Makefile里面添加myclient文件的編譯

DIRS=lib client src myclient

編譯測(cè)試一切正常屡谐,接下去添加mqtt的內(nèi)容

2. 添加mqtt訂閱test1述么,發(fā)布test2

mqtt client里面最主要的就是幾個(gè)回調(diào)函數(shù),先調(diào)用lib_init,正常后愕掏,就這只各個(gè)callback度秘,然后在callback里面做邏輯。

int main (int argc, char **argv)
{
    int rc;

    mosquitto_lib_init();  //初始化庫(kù)

    mosq = mosquitto_new("client_name", CLEAN_SESSION, NULL);  //創(chuàng)建一個(gè)客戶端實(shí)例
    if(!mosq){
        mosquitto_lib_cleanup();
        return 1;
    }
    mosquitto_log_callback_set(mosq, myclient_log_callback);  //打印與broker的交互log
    mosquitto_connect_callback_set(mosq, myclient_connect_callback);  //連接成功的回調(diào)函數(shù)
    mosquitto_disconnect_callback_set(mosq, myclient_disconnect_callback);  //離線的回調(diào)函數(shù)
    mosquitto_message_callback_set(mosq, myclient_message_callback);  //收到消息的回調(diào)函數(shù)
    mosquitto_subscribe_callback_set(mosq, myclient_subscribe_callback);  //訂閱成功的回調(diào)函數(shù)
    mosquitto_reconnect_delay_set(mosq, RECONNECT_DELAY, RECONNECT_DELAY_MAX, RECONNECT_EXPONENTIAL_BACKOFF);  //設(shè)置離線后重連時(shí)間

    dbg_printf(MSG_INFO, "start connect\n");
    while(mosquitto_connect(mosq, BROKER_NAME, BROKER_PORT, BROKER_TIMEOUT) != MOSQ_ERR_SUCCESS) {
        dbg_printf(MSG_ERROR, "AC Client connect faild\r\n");
        sleep(5);
    }  //開始連接

    rc = mosquitto_loop_forever(mosq, -1, 1);  //循環(huán)饵撑,事件觸發(fā)到各自的回調(diào)函數(shù)里面
    
out:
    dbg_printf(MSG_ERROR, "loop error %d\r\n", rc);

    mosquitto_destroy(mosq);
    mosquitto_lib_cleanup();

    mosq = NULL;
    
    if(rc){
        fprintf(stderr, "Subscribe Error: %s\n", mosquitto_strerror(rc));
    }

    return 0;
}

各回調(diào)函數(shù)的內(nèi)容

static void publish_test2_msg(void)
{
    int mid = 0;
    char payload[32] = "\"test2\":\"1111\"";

    mosquitto_publish(mosq, &mid, TOPIC_TEST2, strlen(payload), payload, TOPIC_QOS, false);

}

static void myclient_message_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *message)
{
    if (message->payloadlen) {

        dbg_printf(MSG_DEBUG, "收到主題:%s\r\n", message->topic);
        dbg_printf(MSG_DEBUG, "收到消息:%s, 消息長(zhǎng)度:%d\r\n", message->payload, message->payloadlen);
    }
    
    return;
}

static void myclient_subscribe_callback(struct mosquitto *mosq, void *obj, int mid, int qos_count, const int *granted_qos)
{
    dbg_printf(MSG_DEBUG, "Subscribed (mid: %d): %d\r\n", mid, granted_qos[0]);
}

static void myclient_log_callback(struct mosquitto *mosq, void *obj, int level, const char *str)
{
    dbg_printf(MSG_DEBUG, "%s\r\n", str);
}

static void myclient_connect_callback(struct mosquitto *mosq, void *obj, int result)
{
    if(!result) {
        dbg_printf(MSG_INFO, "connect ok\n");
        mosquitto_subscribe(mosq, NULL, TOPIC_TEST1, TOPIC_QOS);
        publish_test2_msg();
    }
}

static void myclient_disconnect_callback(struct mosquitto *mosq, void *obj, int result)
{
    dbg_printf(MSG_WARNING, "connect lost\r\n");
}

邏輯應(yīng)該也比較直觀剑梳,當(dāng)connect成功后,在回調(diào)函數(shù)里面訂閱test1主題的內(nèi)容滑潘,然后發(fā)布test2主題的內(nèi)容垢乙。

收到內(nèi)容就在myclient_message_callback回調(diào)里面打印處理。

3. 賬號(hào)密碼登錄

正常情況我們都會(huì)讓客戶端的連接做一些賬號(hào)密碼的設(shè)置语卤,避免別人攻擊追逮。

3.1 默認(rèn)方式添加賬號(hào)密碼

將allow_anonymous改成不允許匿名登陸,并制定pwfile粹舵。

vim /etc/mosquittoConf/mosquitto.conf

allow_anonymous false
password_file /etc/mosquittoConf/pwfile

在ubuntu上面使用mosquitto_passwd生成密碼

mosquitto_passwd -c /home/linye/mqtt/pwfile root
Password:
Reenter password:

就會(huì)在pwfile文件下生成賬號(hào)和加密的密碼root/admin

cat ~/mqtt/pwfile
root:$6$Mf+7EtctNlbTlwvV$IYYMsLiGos6OLwxCzYRKOOEilm5haU/K88ChsYDsru/oj2S8dn9XL4B4XX/CkdY9TIp5GcU5g7WziNd5lxVD/w==

這是后登陸的時(shí)候就需要-u root -P admin進(jìn)行登陸

osquitto_sub  -c -h 127.0.0.1 -p 1883 -k 30 -t /local/test1
cacert.pem -u root -P admin
start connect
connect ok
3.2 自定義方式添加賬號(hào)密碼

mosquitto提供了mosquitto_passwd命令來生成賬號(hào)密碼等钮孵,不過這個(gè)方式不喜歡,因?yàn)闆]辦法定制化自己想要的賬號(hào)密碼加密方式眼滤,所以做了一些小改動(dòng)巴席。

在myclient里面加我們想要的加密方式,然后在mosquitto broker的源碼里面添加對(duì)應(yīng)的解密方式即可诅需。

如下漾唉,賬號(hào)為client_name睬关,然后通過rsa和base64生成密碼,myclient的試下調(diào)用mosquitto_username_pw_set函數(shù)毡证。

static void myclient_set_username_pw(struct mosquitto *mosq)
{
    unsigned char *username_rsa = NULL;
    unsigned char *username_base64 = NULL;

    username_rsa = rsa_encrypt(client_name);
    if(username_rsa){
        username_base64 = base64_encode(username_rsa, RSA_LENGTH);
    }

    if(username_base64){
        mosquitto_username_pw_set(mosq, client_name, username_base64);
    }
    
    if(username_rsa){
        free(username_rsa);
        username_rsa = NULL;
    }

    if(username_base64){
        free(username_base64);
        username_base64 = NULL;
    }
}

然后在mosquitto broker里面添加解密电爹,位于mosquitto/src/security.c文件的mosquitto_unpwd_check函數(shù)里面。

int mosquitto_myclient_auth_unpwd_check(struct mosquitto *context,const char *username, const char *password)
{    
    if(context->address == NULL)
        return MOSQ_ERR_AUTH;
    
    int rc = MOSQ_ERR_AUTH;
    unsigned char *user_rsa =NULL;
    unsigned char *user =NULL;

    user_rsa = base64_decode(password);
    if(user_rsa){
        user = rsa_decrypt(user_rsa);
    }
    
    if(user){
        if(!strcmp(user, username)){
            rc = MOSQ_ERR_SUCCESS;
        }
        else{
            rc = MOSQ_ERR_AUTH;
        }
    }
    
    if(user_rsa){
        free(user_rsa);
        user_rsa = NULL;
    }
    
    if(user){
        free(user);
        user = NULL;
    }

    return rc;
    
}

int mosquitto_unpwd_check(struct mosquitto_db *db, struct mosquitto *context, const char *username, const char *password)
{
    int rc;
    int i;
    struct mosquitto__security_options *opts;

    rc = mosquitto_unpwd_check_default(db, context, username, password);
    if(rc != MOSQ_ERR_PLUGIN_DEFER){
        return rc;
    }
    ...

    rc = mosquitto_myclient_auth_unpwd_check(context, username, password);

    ...
    return rc;
}

4. SSL認(rèn)證

另一個(gè)加密方式就是SSL認(rèn)證料睛,給客戶端提供相應(yīng)的證書丐箩,和配置協(xié)議(mqtt or websockets)一樣,在配置文件監(jiān)聽的端口下面可以添加ssl的配置選項(xiàng)恤煞,每個(gè)port都可以單獨(dú)配置ssl的證書內(nèi)同容屎勘。

如下:從端口7885連接進(jìn)來的設(shè)備需要下面的證書要求

port 7885

#CA證書文件
cafile /etc/mosquittoConf/cacert.pem
#PEM證書文件
certfile /etc/mosquittoConf/deviceCert.pem
#PEM密鑰文件
keyfile /etc/mosquittoConf/deviceKey.pem

設(shè)備的認(rèn)證有單向認(rèn)證和雙向認(rèn)證兩種:

  • 不管單向還是雙向,broker端都需要配置cafile居扒、certfile概漱、keyfile
  • 單向的時(shí)候,client只需要提供cafile喜喂,雙向的時(shí)候client需要提供cafile瓤摧、certfile、keyfile
  • 雙向的時(shí)候玉吁,broker需要將require_certificateuse_identity_as_username設(shè)置成true

單向認(rèn)證照弥,只需要提供ca證書

mosquitto_sub -h 192.168.18.1 -p 6885 -t "local/iot/ziroom/broadcast" --cafile ~/mqtt/zgateway/cacert.pem --insecure

mosquitto_sub -h 192.168.18.1 -p 1883 -t "local/iot/ziroom/broadcast"

雙向認(rèn)證,需要ca,pem,key三個(gè)

mosquitto_sub -h 10.30.11.47 -p 8883 -t "local/test1" --cafile ./ca/ca.crt --cert ./client/client.pem --key ./client/client.key &

mosquitto_pub -h 10.30.11.47 -p 8883 -t "mqtt/server/topic" -m "hello,world!" --cafile ./ca/ca.crt --cert ./server/server.pem --key ./server/server.key

按步驟一步一步執(zhí)行进副,生成證書(里面也可以指定各參數(shù)这揣,有效時(shí)間):

// ======================================================
//                  SSL 
// ======================================================
openssl req -new -x509 -days 36500 -extensions v3_ca -keyout ca.key -out ca.crt
# PEM pass phrase: 123456
# Country Name: CH
# State Or Province Name: Shanghai
# Locality Name: Yangpu
# Organization Name: Fanyi
# Organizational Unit Name: Embed
# Common Name: 192.168.100.33
# Email Address: 916634969@qq.com
 
// 給mosquitto_server端
openssl genrsa -out server.key 2048
openssl req -out server.csr -key server.key -new
# Country Name: CH
# State Or Province Name: Shanghai
# Locality Name: Yangpu
# Organization Name: Fanyi
# Organizational Unit Name: Embed
# Common Name: 192.168.100.34
# Email Address: 916634969@qq.com
# A challenge password: 123456
# An optional company name: Fanyi
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 36500
# Enter pass phrase for ca.key: 123456
 
// 給mosquitto_client_sub端
openssl genrsa -out client_sub.key 2048
openssl req -out client_sub.csr -key client_sub.key -new
# Country Name: CH
# State Or Province Name: Shanghai
# Locality Name: Yangpu
# Organization Name: Fanyi
# Organizational Unit Name: Embed
# Common Name: 192.168.100.40
# Email Address: 916634969@qq.com
# A challenge password: 123456
# An optional company name: Fanyi
openssl x509 -req -in client_sub.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client_sub.crt -days 36500
# Enter pass phrase for ca.key: 123456
 
// 給mosquitto_client_pub端
openssl genrsa -out client_pub.key 2048
openssl req -out client_pub.csr -key client_pub.key -new
# Country Name: CH
# State Or Province Name: Shanghai
# Locality Name: Yangpu
# Organization Name: Fanyi
# Organizational Unit Name: Embed
# Common Name: 192.168.100.41
# Email Address: 916634969@qq.com
# A challenge password: 123456
# An optional company name: Fanyi
openssl x509 -req -in client_pub.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client_pub.crt -days 36500
# Enter pass phrase for ca.key: 123456
 
// 驗(yàn)證生成的服務(wù)端和客戶端的證書是否可用
openssl verify -CAfile /home/ares/mqtt/server_ssl/ca.crt /home/ares/mqtt/server_ssl/server.crt
openssl verify -CAfile /home/ares/mqtt/client_sub_ssl/ca.crt /home/ares/mqtt/client_sub_ssl/client_sub.crt
openssl verify -CAfile /home/ares/mqtt/client_pub_ssl/ca.crt /home/ares/mqtt/client_pub_ssl/client_pub.crt
————————————————
原文鏈接:https://blog.csdn.net/Auris/article/details/92570180

按上面的步驟可以生成如下文件

ubuntu:~/mqtt/ssl$ ls
ca.crt  client_pub.crt  client_sub.crt  server.crt
ca.key  client_pub.csr  client_sub.csr  server.csr
ca.srl  client_pub.key  client_sub.key  server.key

在服務(wù)器端需要放三個(gè)文件

  • ca.crt即cafile
  • server.crt即certfile
  • server.key即keyfile

如果是單向認(rèn)證,客戶端只需要一個(gè)文件

  • ca.crt即cafile

如果是雙向認(rèn)證影斑,客戶端只需要三個(gè)文件

  • ca.crt即cafile
  • client.crt即certfile
  • client.key即keyfile

查看證書的有效時(shí)間

ubuntu:~$ openssl x509 -in cacert.pem -noout -dates
notBefore=Apr 23 12:40:56 2019 GMT
notAfter=Apr 19 12:40:56 2032 GMT
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末给赞,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子矫户,更是在濱河造成了極大的恐慌片迅,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吏垮,死亡現(xiàn)場(chǎng)離奇詭異障涯,居然都是意外死亡罐旗,警方通過查閱死者的電腦和手機(jī)膳汪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來九秀,“玉大人遗嗽,你說我怎么就攤上這事」难眩” “怎么了痹换?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵征字,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我娇豫,道長(zhǎng)匙姜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任冯痢,我火速辦了婚禮氮昧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘浦楣。我一直安慰自己袖肥,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布振劳。 她就那樣靜靜地躺著椎组,像睡著了一般。 火紅的嫁衣襯著肌膚如雪历恐。 梳的紋絲不亂的頭發(fā)上寸癌,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音弱贼,去河邊找鬼灵份。 笑死,一個(gè)胖子當(dāng)著我的面吹牛哮洽,可吹牛的內(nèi)容都是我干的填渠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鸟辅,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼氛什!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起匪凉,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤枪眉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后再层,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贸铜,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年聂受,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蒿秦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蛋济,死狀恐怖棍鳖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情碗旅,我是刑警寧澤渡处,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布镜悉,位于F島的核電站,受9級(jí)特大地震影響医瘫,放射性物質(zhì)發(fā)生泄漏侣肄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一醇份、第九天 我趴在偏房一處隱蔽的房頂上張望茫孔。 院中可真熱鬧,春花似錦被芳、人聲如沸缰贝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽剩晴。三九已至,卻和暖如春侵状,著一層夾襖步出監(jiān)牢的瞬間赞弥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工趣兄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绽左,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓艇潭,卻偏偏與公主長(zhǎng)得像拼窥,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蹋凝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355