mosquitto简单应用

1. 简述

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

官网:

http://www.mosquitto.org/  

https://github.com/eclipse/mosquitto

http://www.mosquitto.org/man/  

http://www.mosquitto.org/api/

https://mosquitto.org/man/mosquitto-8.html IBM的一个server(broker)示例用法说明

客户端可以通过订阅$SYS及以下层级主题,获取broker status。

Clients can find information about the broker by subscribing to topics in the $SYS hierarchy. 

主题通配符支持:+和#。

+ is the wildcard used to match a single level of hierarchy.  a/b/c/d  +/b/c/d a/b/+/+ +/+/+/+ 不匹配a/b/c、+/+/+

# is used to match all subsequent levels of hierarchy. a/b/c/d # a/# a/b/# a/b/c# +/b/c/#

注:The $SYS hierarchy does not match a subscription of "#". If you want to observe the entire $SYS hierarchy, subscribe to $SYS/#.

注:通配符必须单独使用。a/b+/c不是一个可用的通配符主题(仅是普通主题),#仅可用于主题的最后一层。

QoS

客户端可以publish和subscribe任意QoS的消息。这意味着客户端会选择最大的消息QoS。

For example, if a message is published at QoS 2 and a client is subscribed with QoS 0, the message will be delivered to that client with QoS 0. If a second client is also subscribed to the same topic, but with QoS 2, then it will receive the same message but with QoS 2. For a second example, if a client is subscribed with QoS 2 and a message is published on QoS 0, the client will receive it on QoS 0.

Higher levels of QoS are more reliable, but involve higher latency and have higher bandwidth requirements.

0: The broker/client will deliver the message once, with no confirmation.
1: The broker/client will deliver the message at least once, with confirmation required.
2: The broker/client will deliver the message exactly once by using a four step handshake.

paho.mqtt.c.git

mosquitto client是异步库,paho.mqtt.c.git支持同步和异步两种API。

In fact there are two C APIs. "Synchronous" and "asynchronous" for which the API calls start with MQTTClient and MQTTAsync respectively. The synchronous API is intended to be simpler and more helpful. To this end, some of the calls will block until the operation has completed, which makes programming easier. In contrast, only one call blocks in the asynchronous API - waitForCompletion. Notifications of results are made by callbacks which makes the API suitable for use in environments where the application is not the main thread of control.

https://eclipse.github.io/paho.mqtt.c/MQTTClient/html/

2. 函数

mosquitto结构体:

struct mosquitto;
struct mosquitto_message{
    int mid;
    char *topic;
    void *payload;
    int payloadlen;
    int qos;
    bool retain;
};

 mosquitto支持推送和订阅消息模式:

/* 
 * Function: mosquitto_publish
 *
 * Publish a message on a given topic.
 * 
 * Parameters:
 *  mosq -       a valid mosquitto instance.
 *  mid -        pointer to an int. If not NULL, the function will set this
 *               to the message id of this particular message. This can be then
 *               used with the publish callback to determine when the message
 *               has been sent.
 *               Note that although the MQTT protocol doesn't use message ids
 *               for messages with QoS=0, libmosquitto assigns them message ids
 *               so they can be tracked with this parameter.
 *  topic -      null terminated string of the topic to publish to.
 *  payloadlen - the size of the payload (bytes). Valid values are between 0 and
 *               268,435,455.
 *  payload -    pointer to the data to send. If payloadlen > 0 this must be a
 *               valid memory location.
 *  qos -        integer value 0, 1 or 2 indicating the Quality of Service to be
 *               used for the message.
 *  retain -     set to true to make the message retained.
 *
 * Returns:
 *  MOSQ_ERR_SUCCESS -      on success.
 *  MOSQ_ERR_INVAL -        if the input parameters were invalid.
 *  MOSQ_ERR_NOMEM -        if an out of memory condition occurred.
 *  MOSQ_ERR_NO_CONN -      if the client isn't connected to a broker.
 *  MOSQ_ERR_PROTOCOL -     if there is a protocol error communicating with the
 *                          broker.
 *  MOSQ_ERR_PAYLOAD_SIZE - if payloadlen is too large.
 *
 * See Also: 
 *  <mosquitto_max_inflight_messages_set>
 */
// 将数据包放到发送队列中就返回,异步方式。
libmosq_EXPORT int mosquitto_publish(struct mosquitto *mosq, int *mid, const char *topic, int payloadlen, const void *payload, int qos, bool retain);
/*
 * Function: mosquitto_subscribe
 *
 * Subscribe to a topic.
 *
 * Parameters:
 *  mosq - a valid mosquitto instance.
 *  mid -  a pointer to an int. If not NULL, the function will set this to
 *         the message id of this particular message. This can be then used
 *         with the subscribe callback to determine when the message has been
 *         sent.
 *  sub -  the subscription pattern.
 *  qos -  the requested Quality of Service for this subscription.
 *
 * Returns:
 *  MOSQ_ERR_SUCCESS - on success.
 *  MOSQ_ERR_INVAL -   if the input parameters were invalid.
 *  MOSQ_ERR_NOMEM -   if an out of memory condition occurred.
 *  MOSQ_ERR_NO_CONN - if the client isn't connected to a broker.
 */
libmosq_EXPORT int mosquitto_subscribe(struct mosquitto *mosq, int *mid, const char *sub, int qos);

/*
 * Function: mosquitto_unsubscribe
 *
 * Unsubscribe from a topic.
 *
 * Parameters:
 *  mosq - a valid mosquitto instance.
 *  mid -  a pointer to an int. If not NULL, the function will set this to
 *         the message id of this particular message. This can be then used
 *         with the unsubscribe callback to determine when the message has been
 *         sent.
 *  sub -  the unsubscription pattern.
 *
 * Returns:
 *  MOSQ_ERR_SUCCESS - on success.
 *  MOSQ_ERR_INVAL -   if the input parameters were invalid.
 *  MOSQ_ERR_NOMEM -   if an out of memory condition occurred.
 *  MOSQ_ERR_NO_CONN - if the client isn't connected to a broker.
 */
libmosq_EXPORT int mosquitto_unsubscribe(struct mosquitto *mosq, int *mid, const char *sub);

network loop 

The internal network loop must be called at a regular interval.

The two recommended approaches are to use either <mosquitto_loop_forever> or  <mosquitto_loop_start>.

<mosquitto_loop_forever> is a blocking call and is suitable for the situation

where you only want to handle incoming messages in callbacks.

<mosquitto_loop_start> is a non-blocking call, it creates a  separate thread to run the loop for you.

Use this function when you have other tasks you need to run at the same time as the MQTT client, e.g. reading data from a sensor.

三种方法:

mosquitto_loop_forever(阻塞)适用于仅callback处理接收信息的场合(内部调用mosquitto_loop);

mosquitto_loop_start(非阻塞)用于开启一个线程loop_forever内部调用mosquitto_loop),调用完后继续执行后续流程;

手动重复调用mosquitto_loop,循环执行。

/*
 * Function: mosquitto_loop_forever
 *
 * This function call loop() for you in an infinite blocking loop. It is useful
 * for the case where you only want to run the MQTT client loop in your
 * program.
 *
 * It handles reconnecting in case server connection is lost. If you call
 * mosquitto_disconnect() in a callback it will return.
 *
 * Parameters:
 *  mosq - a valid mosquitto instance.
 *  timeout -     Maximum number of milliseconds to wait for network activity
 *                in the select() call before timing out. Set to 0 for instant
 *                return.  Set negative to use the default of 1000ms.
 *  max_packets - this parameter is currently unused and should be set to 1 for
 *                future compatibility.
 *
 * Returns:
 *  MOSQ_ERR_SUCCESS -   on success.
 *  MOSQ_ERR_INVAL -     if the input parameters were invalid.
 *  MOSQ_ERR_NOMEM -     if an out of memory condition occurred.
 *  MOSQ_ERR_NO_CONN -   if the client isn't connected to a broker.
 *  MOSQ_ERR_CONN_LOST - if the connection to the broker was lost.
 *  MOSQ_ERR_PROTOCOL -  if there is a protocol error communicating with the
 *                       broker.
 *  MOSQ_ERR_ERRNO -     if a system call returned an error. The variable errno
 *                       contains the error code, even on Windows.
 *                       Use strerror_r() where available or FormatMessage() on
 *                       Windows.
 */
libmosq_EXPORT int mosquitto_loop_forever(struct mosquitto *mosq, int timeout, int max_packets);
/*
 * Function: mosquitto_loop_start
 *
 * This is part of the threaded client interface. Call this once to start a new
 * thread to process network traffic. This provides an alternative to
 * repeatedly calling <mosquitto_loop> yourself.
 *
 * Parameters:
 *  mosq - a valid mosquitto instance.
 *
 * Returns:
 *  MOSQ_ERR_SUCCESS -       on success.
 *  MOSQ_ERR_INVAL -         if the input parameters were invalid.
 *  MOSQ_ERR_NOT_SUPPORTED - if thread support is not available.
 */
libmosq_EXPORT int mosquitto_loop_start(struct mosquitto *mosq);
/*
 * Function: mosquitto_loop
 *
 * The main network loop for the client. This must be called frequently
 * to keep communications between the client and broker working. This is
 * carried out by <mosquitto_loop_forever> and <mosquitto_loop_start>, which
 * are the recommended ways of handling the network loop. You may also use this
 * function if you wish. It must not be called inside a callback.
 *
 * If incoming data is present it will then be processed. Outgoing commands,
 * from e.g.  <mosquitto_publish>, are normally sent immediately that their
 * function is called, but this is not always possible. <mosquitto_loop> will
 * also attempt to send any remaining outgoing messages, which also includes
 * commands that are part of the flow for messages with QoS>0.
 *
 * This calls select() to monitor the client network socket. If you want to
 * integrate mosquitto client operation with your own select() call, use
 * <mosquitto_socket>, <mosquitto_loop_read>, <mosquitto_loop_write> and
 * <mosquitto_loop_misc>.
 *
 * Threads:
 *
 * Parameters:
 *  mosq -        a valid mosquitto instance.
 *  timeout -     Maximum number of milliseconds to wait for network activity
 *                in the select() call before timing out. Set to 0 for instant
 *                return.  Set negative to use the default of 1000ms.
 *  max_packets - this parameter is currently unused and should be set to 1 for
 *                future compatibility.
 *
 * Returns:
 *  MOSQ_ERR_SUCCESS -   on success.
 *  MOSQ_ERR_INVAL -     if the input parameters were invalid.
 *  MOSQ_ERR_NOMEM -     if an out of memory condition occurred.
 *  MOSQ_ERR_NO_CONN -   if the client isn't connected to a broker.
 *  MOSQ_ERR_CONN_LOST - if the connection to the broker was lost.
 *  MOSQ_ERR_PROTOCOL -  if there is a protocol error communicating with the
 *                       broker.
 *  MOSQ_ERR_ERRNO -     if a system call returned an error. The variable errno
 *                       contains the error code, even on Windows.
 *                       Use strerror_r() where available or FormatMessage() on
 *                       Windows.
 * See Also:
 *  <mosquitto_loop_forever>, <mosquitto_loop_start>, <mosquitto_loop_stop>
 */
libmosq_EXPORT int mosquitto_loop(struct mosquitto *mosq, int timeout, int max_packets);

一般使用流程如下:

mosquitto_lib_init();
mosq=mosquitto_new();

mosquitto_connect_callback_set();  ----mosquitto_subscribe();
mosquitto_disconnect_callback_set();
mosquitto_message_callback_set();  ----接收解析消息 并推送mosquitto_publish()

mosquitto_username_pw_set(mosq, "user", "pw");
mosquitto_connect();
mosquitto_loop(mosq, timeout, 1);  // 需要不断循环判断, 也可根据需要永远运行:mosquitto_loop_forever(mosq, timeout, 1)

mosquitto_publish();

mosquitto_destroy(mosq);
mosquitto_lib_cleanup();

3. C应用

一般使用流程:

0. 依赖库安装

apt-get install openssl libssl-dev  uuid-dev

1. 编译安装
make 
make install
交叉编译:
CC=arm-linux-gnueabihf-gcc CXX=arm-linux-gnueabihf-g++ make WITH_SRV=no  WITH_UUID=no WITH_TLS=no WITH_DOCS=no WITH_WEBSOCKETS=no
2. 创建mosquitto用户 mosquitto默认以mosquitto用户启动 groupadd mosquitto useradd -g mosquitto mosquitto 3. 配置文件修改 根据需求修改配置文件/etc/mosquitto/mosquitto.conf
一般不修改直接可用,本机所有IP都可达,外部访问本机IP可达。
4. 启动 mosquitto -c /etc/mosquitto/mosquitto.conf -d 5. 测试 mosquitto_sub -t wang/ming mosquitto_pub -m "hello"
mosquitto_pub -h 172.16.1.20 -p 1883 -t data_topic -m "hello_wang"
mosquitto_pub -h 172.16.1.20 -p 1883 -t data_topic -m "hello_wang"  -u admin -P 48e848df75c08d4c0ba75bee
服务器需要配置密码,客户端不用修改可直接采用密码上报

密码配置

$ mosquitto_passwd -h
mosquitto_passwd is a tool for managing password files for mosquitto.

Usage: mosquitto_passwd [-c | -D] passwordfile username
       mosquitto_passwd [-c] -b passwordfile username password
       mosquitto_passwd -U passwordfile
 -b : run in batch mode to allow passing passwords on the command line.
 -c : create a new password file. This will overwrite existing files.
 -D : delete the username rather than adding/updating its password.
 -U : update a plain text password file to use hashed passwords.

创建admin用户名和密码命令如下:  
mosquitto_passwd -c /etc/mosquitto/pwfile.example wang
提示连续两次输入密码、创建成功。
命令解释: -c 创建一个用户,/etc/mosquitto/pwfile.example 是将用户创建到pwfile.example文件中,wang是用户名。

一个可参考配置文件如下:

 # Port to use for the default listener.
#port 1883
port 1883

 # Websockets support is currently disabled by default at compile time.
 # Certificate based TLS may be used with websockets, except that
 # only the cafile, certfile, keyfile and ciphers options are supported.
#protocol mqtt
listener 9001
protocol websockets
listener 8883
protocol websockets

 #cafile
 #capath
 
certfile /etc/cert/server.crt
keyfile /etc/cert/server.key

 # For example, setting "secure-" here would mean a client "secure-
 # client" could connect but another with clientid "mqtt" couldn't.
#clientid_prefixes
clientid_prefixes ABC
 
 # Boolean value that determines whether clients that connect 
 # without providing a username are allowed to connect. If set to 
 # false then a password file should be created (see the 
 # password_file option) to control authenticated client access. 
 # Defaults to true.
#allow_anonymous true
allow_anonymous false
 
 # See the TLS client require_certificate and use_identity_as_username options
 # for alternative authentication options.
#password_file
password_file /etc/mosquitto/pwfile

日志配置

log_dest file /tmp/mosquitto.log
log_type all
connection_messages true
log_timestamp true

TLS单向认证

port 8883
cafile /tmp/cert/ca.crt  // must
certfile /tmp/cert/server.crt
keyfile /tmp/cert/server.key

log_dest file /tmp/mosquitto.log
log_type all
connection_messages true
log_timestamp true

clientid_prefixes peerid

allow_anonymous false
password_file /etc/mosquitto/pwfile

----------------------------------------------------------------
mosquitto_sub -t wang/publish -u wang -P 'wangq' -i peerid_qing -h server --cafile /tmp/cert/ca.crt -d
mosquitto_pub  -i peerid_house -u wang -P 'wangq' --cafile /tmp/cert/ca.crt -h server -t wang/publish -m "house" -d
----------------------------------------------------------------

char peerid[] = "peerid_wang";
//char host[] = "192.168.3.122";
//int port = 1883;
char host[] = "server";
int port = 8883;
char cafile[] = "/tmp/cert/ca.crt";

mosquitto_tls_set(mosq, cafile, NULL, NULL, NULL, NULL);

双向认证

listener 8883
protocol mqtt
cafile /tmp/cert/ca.crt
certfile /tmp/cert/server.crt
keyfile /tmp/cert/server.key
require_certificate true

log_dest file /tmp/mosquitto.log
log_type all
connection_messages true
log_timestamp true

clientid_prefixes peerid
allow_anonymous false
password_file /etc/mosquitto/pwfile
---------------------------------------------------------
mosquitto_sub -t wang/publish -u wang -P 'wangqh' -i peerid_qing -h server --cafile /tmp/cert/ca.crt  --cert /tmp/cert/client.crt --key /tmp/cert/client.key  -d
mosquitto_pub  -i peerid_house -u wang -P 'wangqh' --cafile /tmp/cert/ca.crt -h server -p 8883 --cert /tmp/cert/client.crt --key /tmp/cert/client.key -t wang/publish -m "house"  -d
---------------------------------------------------------
char peerid[] = "peerid_wang";
//char host[] = "192.168.3.122";
//int port = 1883;
char host[] = "server";
int port = 8883;
char cafile[] = "/tmp/cert/ca.crt";
char client_cert[] = "/tmp/cert/client.crt";
char client_key[] = "/tmp/cert/client.key";

mosquitto_tls_set(mosq, cafile, NULL, client_cert, client_key, NULL);

同时支持1883和8883端口,其中1883端口不需要证书,8883端口需要证书。

port 1883

listener 8883
protocol mqtt
cafile /home/wang/configdir/cert/ca.crt
certfile /home/wang/configdir/cert/server.crt
keyfile /home/wang/configdir/cert/server.key
require_certificate true

log_dest file /tmp/mosquitto.log
log_type all
connection_messages true
log_timestamp true

mosquitto是一个server broker,可直接运行测试客户端。

manpage上一个示例:

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

void my_message_callback(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *message)
{
    if(message->payloadlen){
        printf("%s %s\n", message->topic, message->payload);
    }else{
        printf("%s (null)\n", message->topic);
    }
    fflush(stdout);
}

void my_connect_callback(struct mosquitto *mosq, void *userdata, int result)
{
    int i;
    if(!result){
        /* Subscribe to broker information topics on successful connect. */
        mosquitto_subscribe(mosq, NULL, "$SYS/#", 2);
    }else{
        fprintf(stderr, "Connect failed\n");
    }
}

void my_subscribe_callback(struct mosquitto *mosq, void *userdata, int mid, int qos_count, const int *granted_qos)
{
    int i;

    printf("Subscribed (mid: %d): %d", mid, granted_qos[0]);
    for(i=1; i<qos_count; i++){
        printf(", %d", granted_qos[i]);
    }
    printf("\n");
}

void my_log_callback(struct mosquitto *mosq, void *userdata, int level, const char *str)
{
    /* Pring all log messages regardless of level. */
    printf("%s\n", str);
}

int main(int argc, char *argv[])
{
    int i;
    char *host = "localhost";
    int port = 1883;
    int keepalive = 60;
    bool clean_session = true;
    struct mosquitto *mosq = NULL;

    mosquitto_lib_init();
    mosq = mosquitto_new(NULL, clean_session, NULL);
    if(!mosq){
        fprintf(stderr, "Error: Out of memory.\n");
        return 1;
    }
    mosquitto_log_callback_set(mosq, my_log_callback);
    mosquitto_connect_callback_set(mosq, my_connect_callback);
    mosquitto_message_callback_set(mosq, my_message_callback);
    mosquitto_subscribe_callback_set(mosq, my_subscribe_callback);

    if(mosquitto_connect(mosq, host, port, keepalive)){
        fprintf(stderr, "Unable to connect.\n");
        return 1;
    }

    mosquitto_loop_forever(mosq, -1, 1);

    mosquitto_destroy(mosq);
    mosquitto_lib_cleanup();
    return 0;
}
View Code
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <mosquitto.h>
#include <time.h>
#include <signal.h>

char peerid[] = "peerid_wang";
//char host[] = "192.168.3.122";
//int port = 1883;
char host[] = "server";
int port = 8883;
char cafile[] = "/tmp/cert/ca.crt";
char client_cert[] = "/tmp/cert/client.crt";
char client_key[] = "/tmp/cert/client.key";
int keepalive = 10;
int send_qos = 1;
bool clean_session = true; // need to subscribe per connect if clean session flag is true
struct mosquitto *mosq = NULL;
pthread_t pmosid = 0;
static int mid_send = -1;
volatile char flag_stop = 0;
volatile char flag_connect = 0;

void my_message_callback(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *message)
{
    if(message->payloadlen){
        printf("====>recv:[%s](%d)%s\n", message->topic, message->mid, (char *)message->payload);
    }else{
        printf("====>recv:[%s](%d)(null)\n", message->topic, message->mid);
    }
}

// Be called when the message has been sent to the broker successfully
void my_publish_callback(struct mosquitto *mosq, void *userdata, int mid)
{
    printf("[%ld]publish callback mid %d\n", time(NULL), mid);
}

void my_disconnect_callback(struct mosquitto *mosq, void *userdata, int rc)
{
    printf("[%ld]disconnect callback rc %d\n", time(NULL), rc);
    flag_connect = 0;
}

void my_connect_callback(struct mosquitto *mosq, void *userdata, int rc)
{
    printf("[%ld]connect callback rc %d\n", time(NULL), rc);
    if(!rc){
        // 订阅发布的topic,这样消息发送后,会从broker再发送过来
        // 适用于消息机制,订阅机制,可准确知道是否发送成功
        mosquitto_subscribe(mosq, NULL, "wang/publish", 1);
        flag_connect = 1;
    }
}

void mos_init()
{
    mosquitto_lib_init();
    mosq = mosquitto_new(peerid, clean_session, NULL);
    if(!mosq){
        fprintf(stderr, "Error: Out of memory.\n");
        exit(-1);
    }

    mosquitto_message_callback_set(mosq, my_message_callback);
    mosquitto_publish_callback_set(mosq, my_publish_callback);
    mosquitto_connect_callback_set(mosq, my_connect_callback);
    mosquitto_disconnect_callback_set(mosq, my_disconnect_callback);
    mosquitto_will_set(mosq,"wang/will", sizeof("livewill"), "livewill", 2, false);
    // Used to tell the library that your application is using threads, 
    // but not using mosquitto_loop_start. 
    //mosquitto_threaded_set(mosq, 1);
    mosquitto_username_pw_set(mosq, "wang", "wangqh");
    mosquitto_tls_set(mosq, cafile, NULL, client_cert, client_key, NULL);
}

void cfinish(int sig)
{
    signal(SIGINT, NULL);
    flag_stop = 1;
}

int main(int argc, char *argv[])
{
    int ret = 0;
    int toserver = -1;
    int timeout = 0;
    char buf[16];
    
    signal(SIGINT, cfinish);
    mos_init();
    while(toserver){
        toserver = mosquitto_connect(mosq, host, port, keepalive);
        if(toserver){
            timeout++;
            fprintf(stderr, "Unable to connect server [%d] times.\n", timeout);
            if(timeout > 3){
                fprintf(stderr, "Unable to connect server, exit.\n" );
                exit(-1);
            }
            sleep(10);
        }
    }

//    mosquitto_subscribe(mosq, NULL, "wang/publish", 1);
    ret = mosquitto_loop_start(mosq);
    if(ret != MOSQ_ERR_SUCCESS){
        printf("loop start error: %s\n", mosquitto_strerror(ret));
        goto clean;
    }

    while(!flag_stop){
        ret = 0;
        sprintf(buf, "%ld", time(NULL));
        strcat(buf, "love");
        if(flag_connect){
            ret = mosquitto_publish(mosq, &mid_send, "wang/publish", strlen(buf), buf, send_qos, false);
            printf("[%ld]push [%s] mid %d:%s(%d)\n", time(NULL), buf, mid_send, mosquitto_strerror(ret), ret);
        }

        if(!flag_connect || ret){
            printf("Message publish failed, need to store!\n");
        }
        
        sleep(5);
    }

    mosquitto_disconnect(mosq);
    mosquitto_loop_stop(mosq, true);
    printf("mosquitto finished\n");
clean:
    mosquitto_destroy(mosq);
    mosquitto_lib_cleanup();
    return 0;
}

 自测程序

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <mosquitto.h>

char peerid[] = "wangq";
char host[] = "127.0.0.1";
int port = 1883;
int keepalive = 60;
bool clean_session = true;
struct mosquitto *mosq = NULL;
pthread_t pmosid = 0;


void my_message_callback(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *message)
{
    if(message->payloadlen){
        printf("====>recv:%s %s\n", message->topic, message->payload);
    }else{
        printf("%s (null)\n", message->topic);
    }

    mosquitto_publish(mosq, NULL, "wang/result", sizeof("loveresult"), "loveresult", 2, false);
    sleep(2);

    fflush(stdout);
}

void my_connect_callback(struct mosquitto *mosq, void *userdata, int result)
{
    if(!result){
        /* Subscribe to broker information topics on successful connect. */
        //mosquitto_subscribe(mosq, NULL, "$SYS/broker/uptime", 2);
        mosquitto_subscribe(mosq, NULL, "wang/hua", 1);
    }else{
        fprintf(stderr, "Connect failed\n");
    }
}

void my_subscribe_callback(struct mosquitto *mosq, void *userdata, int mid, int qos_count, const int *granted_qos)
{
    int i;

    printf("Subscribed (mid: %d): %d", mid, granted_qos[0]);
    for(i=1; i<qos_count; i++){
        printf(", %d", granted_qos[i]);
    }
    printf("\n");
}

void my_log_callback(struct mosquitto *mosq, void *userdata, int level, const char *str)
{
    /* Pring all log messages regardless of level. */
    printf("====>log:%s\n", str);
}

void mos_init()
{
    mosquitto_lib_init();
    mosq = mosquitto_new(peerid, clean_session, NULL);
    if(!mosq){
        fprintf(stderr, "Error: Out of memory.\n");
        exit(-1);
    }
    mosquitto_log_callback_set(mosq, my_log_callback);
    mosquitto_connect_callback_set(mosq, my_connect_callback);
    mosquitto_message_callback_set(mosq, my_message_callback);
    mosquitto_subscribe_callback_set(mosq, my_subscribe_callback);
    mosquitto_will_set(mosq,"xiao/ming", sizeof("livewill"), "livewill", 2, false);
    mosquitto_threaded_set(mosq, 1);
}

void * pthread_mos(void *arg)
{
    int toserver = -1;
    int timeout = 0;
    
    while(toserver){
        toserver = mosquitto_connect(mosq, host, port, keepalive);
        if(toserver){
            timeout++;
            fprintf(stderr, "Unable to connect server [%d] times.\n", timeout);
            if(timeout > 3){
                fprintf(stderr, "Unable to connect server, exit.\n" );
                pthread_exit(NULL);
            }
            sleep(10);
        }
    }

    mosquitto_loop_forever(mosq, -1, 1);

    mosquitto_disconnect(mosq);
    mosquitto_destroy(mosq);
    mosquitto_lib_cleanup();

    pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
    int ret = 0;

    mos_init();

    ret = pthread_create(&pmosid, NULL, pthread_mos, NULL);
    if(ret != 0){
        printf("create pthread mos error.\n");
        exit(-1);
    }
    pthread_detach(pmosid);

    while(1){
        mosquitto_publish(mosq, NULL, "wang/ming", sizeof("love"), "love", 2, false);
        sleep(2);
    }

/*    
    mosquitto_loop_forever(mosq, -1, 1);

    while(1){
        mosquitto_publish(mosq, NULL, "wang/qin/hua", sizeof("love"), "love", 2, false);
        sleep(2);
    }
*/
    return 0;
}
void mos_init()
{
    mosquitto_lib_init();
    mosq = mosquitto_new(peerid, clean_session, NULL);
    if(!mosq){
        fprintf(stderr, "Error: Out of memory.\n");
        exit(-1);
    }
    mosquitto_log_callback_set(mosq, my_log_callback);
    mosquitto_connect_callback_set(mosq, my_connect_callback);
    mosquitto_message_callback_set(mosq, my_message_callback);
    mosquitto_subscribe_callback_set(mosq, my_subscribe_callback);
    mosquitto_will_set(mosq,"xiao/ming", sizeof("livewill"), "livewill", 2, false);
// Used to tell the library that your application is using threads, but not using mosquitto_loop_start.
//    mosquitto_threaded_set(mosq, 1);
        mosquitto_username_pw_set(mosq, "wang", "wangqh");
}

int main(int argc, char *argv[])
{
    int ret = 0;
        int toserver = -1;
    int timeout = 0;

    mos_init();
#if 1
mosquitto_connect(mosq, host, port, keepalive); //根据实际情况使用,无连接也可继续运行(等待之后连接)
#else
    while(toserver){
        toserver = mosquitto_connect(mosq, host, port, keepalive);
        if(toserver){
            timeout++;
            fprintf(stderr, "Unable to connect server [%d] times.\n", timeout);
            if(timeout > 3){
                fprintf(stderr, "Unable to connect server, exit.\n" );
                pthread_exit(NULL);
            }
            sleep(10);
        }
    }
#endif ret
= mosquitto_loop_start(mosq); if(ret != MOSQ_ERR_SUCCESS){ printf("loop start error: %s\n", mosquitto_strerror(ret)); goto clean; } while(1){ mosquitto_publish(mosq, NULL, "wang/ming", sizeof("love"), "love", 2, false); printf("publish over------------------\n"); sleep(60); } printf("mosquitto finished\n"); mosquitto_loop_stop(mosq, true); clean: mosquitto_destroy(mosq); mosquitto_lib_cleanup(); return 0; }

运行结果:

#./a.out
====>log:Client wangq sending CONNECT
====>log:Client wangq received CONNACK (0)
====>log:Client wangq sending SUBSCRIBE (Mid: 2, Topic: wang/hua, QoS: 1)
====>log:Client wangq sending PUBLISH (d1, q2, r0, m1, 'wang/ming', ... (5 bytes))
====>log:Client wangq received SUBACK
Subscribed (mid: 2): 1
====>log:Client wangq received PUBREC (Mid: 1)
====>log:Client wangq sending PUBREL (Mid: 1)
====>log:Client wangq received PUBCOMP (Mid: 1)
====>log:Client wangq sending PUBLISH (d0, q2, r0, m3, 'wang/ming', ... (5 bytes))
====>log:Client wangq received PUBREC (Mid: 3)
====>log:Client wangq sending PUBREL (Mid: 3)
====>log:Client wangq received PUBCOMP (Mid: 3)

订阅:

#mosquitto_sub -t wang/ming
love
love

will订阅:

#mosquitto_sub -t xiao/ming 
livewill

4. CPP应用

mosquitto支持C和CPP两种库,编译完成后两个库同时生成。CPP接口只是对C的简单封装,需要同时包含C头文件和库。

C库:mosquitto.h libmosquitto.so
CPP库:mosquittopp.h libmosquittopp.so

cpp示例可参考:examples/temperature_conversion,如下一个网络示例:

#include <string>
#include <stdio.h>
#include <iostream> 
#include <stdlib.h>
#include <cstring>
#include "mosquitto.h"
#include "mosquittopp.h" 
#pragma comment(lib, "mosquittopp.lib")
class mqtt_test:public mosqpp::mosquittopp 
{ 
public: 
    mqtt_test(const char *id):mosquittopp(id){} 
    void on_connect(int rc) {std::cout<<"on_connect"<<std::endl;} 
    void on_disconnect() {std::cout<<"on_disconnect"<<std::endl;} 
    void on_publish(int mid) {std::cout<<"on_publish"<<std::endl;} 
    void on_subscribe(int mid, int qos_count, const int *granted_qos);//订阅回调函数
    void on_message(const struct mosquitto_message *message);//订阅主题接收到消息
}; 
std::string g_subTopic="subTopic";
void mqtt_test::on_subscribe(int mid, int qos_count, const int *granted_qos)
{
    std::cout<<"订阅 mid: %d "<<mid<<std::endl;
}
void mqtt_test::on_message(const struct mosquitto_message *message) 
{
    bool res=false;
    mosqpp::topic_matches_sub(g_subTopic.c_str(),message->topic,&res);
    if(res)
    {
        std::string strRcv=(char *)message->payload;
        std::cout<<"来自<"<<message->topic<<">的消息:"<<strRcv<<std::endl;
    }
}
int main(int argc, char* argv[]) 
{ 
    mosqpp::lib_init(); 
    mqtt_test test("client6"); 

    int rc; 
    char buf[1024] = "This is test"; 
    rc = test.connect("localhost",1883,600);//本地IP 
    char err[1024];
    if(rc == MOSQ_ERR_ERRNO)
        std::cout<<"连接错误:"<< mosqpp::strerror(rc)<<std::endl;//连接出错
    else if (MOSQ_ERR_SUCCESS == rc) 
    { 
        //发布测试
        rc = test.loop_start(); 
       while(fgets(buf, 1024, stdin) != NULL)
       {
            std::string topic1="test";
            rc = test.publish(NULL, topic1.c_str(), strlen(buf), (const void *)buf); 
       }
    }         
    mosqpp::lib_cleanup(); 
    return 0; 
}

5. Golang应用

golang中采用库:github.com/eclipse/paho.mqtt.golang 

阿里的MQTT服务需要tls认证,可参考:Paho-MQTT Go接入示例,有完整的参考示例:

    // set the login broker url
    var raw_broker bytes.Buffer
    raw_broker.WriteString("tls://")
    raw_broker.WriteString(productKey)
    raw_broker.WriteString(".iot-as-mqtt.cn-shanghai.aliyuncs.com:1883")
    opts := MQTT.NewClientOptions().AddBroker(raw_broker.String());

    // calculate the login auth info, and set it into the connection options
    auth := calculate_sign(clientId, productKey, deviceName, deviceSecret, timeStamp)
    opts.SetClientID(auth.mqttClientId)
    opts.SetUsername(auth.username)
    opts.SetPassword(auth.password)
    opts.SetKeepAlive(60 * 2 * time.Second)
    opts.SetDefaultPublishHandler(f)

    // set the tls configuration
    tlsconfig := NewTLSConfig()
    opts.SetTLSConfig(tlsconfig)

    // create and start a client using the above ClientOptions
    c := MQTT.NewClient(opts)
    if token := c.Connect(); token.Wait() && token.Error() != nil {
        panic(token.Error())
    }
    fmt.Print("Connect aliyun IoT Cloud Sucess\n");

    // subscribe to subTopic("/a1Zd7n5yTt8/deng/user/get") and request messages to be delivered
    if token := c.Subscribe(subTopic, 0, nil); token.Wait() && token.Error() != nil {
        fmt.Println(token.Error())
        os.Exit(1)
    }
    fmt.Print("Subscribe topic " + subTopic + " success\n");

    // publish 5 messages to pubTopic("/a1Zd7n5yTt8/deng/user/update")
    for i := 0; i < 5; i++ {
        fmt.Println("publish msg:", i)
        text := fmt.Sprintf("ABC #%d", i)
        token := c.Publish(pubTopic, 0, false, text)
        fmt.Println("publish msg: ", text)
        token.Wait()
        time.Sleep(2 * time.Second)
    }

    // unsubscribe from subTopic("/a1Zd7n5yTt8/deng/user/get")
    if token := c.Unsubscribe(subTopic);token.Wait() && token.Error() != nil {
        fmt.Println(token.Error())
        os.Exit(1)
    }

    c.Disconnect(250)

// define a function for the default message handler
var f MQTT.MessageHandler = func(client MQTT.Client, msg MQTT.Message) {
    fmt.Printf("TOPIC: %s\n", msg.Topic())
    fmt.Printf("MSG: %s\n", msg.Payload())
}

简单的通过用户名和密码使用:

     addr := fmt.Sprintf("tcp://%s:%d", m.MqttIp, m.MqttPort)   
     opts := MQTT.NewClientOptions().AddBroker(addr)
        ////设置用户名
        opts.SetUsername(m.UserName)
        opts.SetPassword(m.PassWord)
        opts.SetKeepAlive(180 * time.Second)
        opts.SetDefaultPublishHandler(f)
        opts.SetPingTimeout(180 * time.Second)
        opts.SetAutoReconnect(true)

        //生成客户端对象
        o := MQTT.NewClient(opts)
        if token := o.Connect(); token.Wait() && token.Error() != nil {
                log.Println(token.Error())
        }

        //连接mqtt后,发布消息体
        if token := o.Publish("DEVICE_INFO_REPORT", 2, false, data); token.Wait() && token.Error() != nil {
                log.Println(token.Error())
        }
        log.Println("TOPIC: DEVICE_INFO_REPORT")
        log.Printf("MSG: %s\n", string(data))
        o.Disconnect(250)

6. mosquitto c client使用问题 

1. clean session设置为false时,建立一个长连接,离线再上线后可以接收到离线的qos0,1,2的所有消息。

clean session设置为true时,离线再上线后需要重新订阅主题,原来订阅的主题丢失。所以最好在connect callback中subscribe。

2. publish消息时设置retain为true,则新上线的client可接收到最后一条retain消息。

3. will消息是发送给订阅方的,而不是自己。

4. mosquitto_disconnect()会断开连接,且不会再进行自动重连,所以不要在callback或循环中调用,应在程序退出时调用。

mosquitto库会自动重连,默认时间间隔为1s。可通过mosquitto_reconnect_delay_set()修改默认重连时间。

5. mosquitto_connect()中的keepalive是broker检测client掉线的时间间隔。

the number of seconds after which the client should send a PING message to the broker(PINGREG)

if no other messages have been exchanged in that time.

If the Keep Alive value is non-zero and the Server does not receive a Control Packet from the Client 
within one and a half times the Keep Alive time period, 
it MUST disconnect the Network Connection to the Client as if the network had failed.

若keepalive设为60则broker 90s后才知道client是否掉线,而client掉线后每隔1s重连。

若客户端断线后很快重连上(如1s就重连上了),则可能出现两个相同客户端的情况(90s内broker不知道原来客户端掉线)。

要合理设置keepalive,将心跳改为10s,自动重连时间设定20s,保证充足的时间服务器侧能检测到客户端掉线,避免同时存在多个客户端的情况。

 

参考:

1. MQTT协议规范 阿里云物理网

2. Mosquitto安装及使用

3. MQTT协议及EMQ应用

4. GO之MQTT使用中文文档

5. mosquitto MQTT使用中遇到的问题

6. Mosquitto SSL Configuration -MQTT TLS Security

posted @ 2018-07-29 18:14  yuxi_o  阅读(5132)  评论(0编辑  收藏  举报