mqtt_paho

https://mqtt.org/software/
https://github.com/eclipse/paho.mqtt.c

基础

mosquitto client是异步库,paho.mqtt.c.git支持同步和异步两种API。paho应用调用MQTTClient_setCallbacks(),client是异步模式,否则同步模式。
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/ 同步API
https://www.eclipse.org/paho/files/mqttdoc/MQTTAsync/html/index.html 异步API

同步&异步

同步模式下,client应用运行在一个线程中。推送消息使用MQTTClient_publish()或MQTTClient_publishMessage()。为了确保QoS1或QoS2成功投递,调用MQTTClient_waitForCompletion()。接收消息,使用MQTTClient_receive()或MQTTClient_yield()。应用程序必须频繁调用MQTTClient_receive()或MQTTClient_yield(),以准许确认处理和MQTT “pings”。需要注意的是,客户端与服务器端通过ping来keepalive的时候也会导致MQTTClient_receive函数返回MQTTCLIENT_SUCCESS,所以就需要对传出的参数进行判断。

同步方式中也支持设置callback回调函数来进行异步接收消息。通过MQTTClient_setCallbacks()设置,此时意味着同步方式采用多线程实现。Asking for callbacks means that you have to have a background thread, right?

异步模式下,client运行在多个线程中。后台处理握手(handshaking)和维持网络连接。设置回调函数:MQTTClient_setCallbacks()(MQTTClient_messageArrived(), MQTTClient_connectionLost() and MQTTClient_deliveryComplete()).

选择

MQTTClient, single-threaded: when you want to use only one thread for your entire application.
MQTTClient, multi-threaded: when you want ease of use, aren’t bothered about some calls blocking, but still want messages to be sent and received in the background.
MQTTAsync: when you want no calls to block at all. (Messages will be sent and received on two background threads).
The MQTTAsync API happens to perform better than the MQTTClient API, but that wasn’t the rationale behind it.

client流程

  • Create a client object
  • Set the options to connect to an MQTT server
  • Set up callback functions if multi-threaded (asynchronous mode) operation is being used (see Asynchronous vs synchronous client applications).
  • Subscribe to any topics the client needs to receive
  • Repeat until finished:
    • Publish any messages the client needs to
    • Handle any incoming messages
  • Disconnect the client
  • Free any memory being used by the client

编译运行

git clone https://github.com/eclipse/paho.mqtt.c
cd paho.mqtt.c
make
sudo make install // 不用执行
make html   // api

gcc mqtta.c -lpaho-mqtt3c -L /home/wang/repoc/paho.mqtt.c/build/output -I paho/ -o puba

同步API

typedef void* MQTTClient;
typedef int MQTTClient_deliveryToken;
typedef int MQTTClient_token;

int MQTTClient_create(MQTTClient* handle, const char* serverURI, const char* clientId,
		int persistence_type, void* persistence_context);
int MQTTClient_createWithOptions(MQTTClient* handle, const char* serverURI, const char* clientId,
	int persistence_type, void* persistence_context, MQTTClient_createOptions* options);
void MQTTClient_destroy(MQTTClient* handle);

// 异步时必须提供这三个回调函数
int MQTTClient_setCallbacks(MQTTClient handle, void* context, MQTTClient_connectionLost* cl,
    MQTTClient_messageArrived* ma, MQTTClient_deliveryComplete* dc)
    

/* It is called by the client library when a new message that matches a client
 * subscription has been received from the server. This function is executed on
 * a separate thread to the one on which the client application is running.
 * @return This function must return 0 or 1 indicating whether or not
 * the message has been safely received by the client application. <br>
 * Returning 1 indicates that the message has been successfully handled.
 * To free the message storage, ::MQTTClient_freeMessage must be called.
 * To free the topic name storage, ::MQTTClient_free must be called.<br>
 * Returning 0 indicates that there was a problem. In this
 * case, the client library will reinvoke MQTTClient_messageArrived() to
 * attempt to deliver the message to the application again.
 * Do not free the message and topic storage when returning 0, otherwise
 * the redelivery will fail.
 */
typedef int MQTTClient_messageArrived(void* context, char* topicName, int topicLen, MQTTClient_message* message);

/* It is called by the client library if the client
 * loses its connection to the server. The client application must take
 * appropriate action, such as trying to reconnect or reporting the problem.
 * This function is executed on a separate thread to the one on which the
 * client application is running.
 * Currently, <i>cause</i> is always set to NULL.
 */
typedef void MQTTClient_connectionLost(void* context, char* cause);

/* It indicates that the necessary
 * handshaking and acknowledgements for the requested quality of service (see
 * MQTTClient_message.qos) have been completed. This function is executed on a
 * separate thread to the one on which the client application is running.
 * <b>Note:</b>MQTTClient_deliveryComplete() is not called when messages are
 * published at QoS0.
 */
typedef void MQTTClient_deliveryComplete(void* context, MQTTClient_deliveryToken dt);
typedef struct
{
	/** The eyecatcher for this structure.  must be MQTC. */
	char struct_id[4];

	int struct_version;

	int keepAliveInterval;

	int cleansession;

	int reliable;

	MQTTClient_willOptions* will;

	const char* username;

	const char* password;

	int connectTimeout;

	int retryInterval;

	MQTTClient_SSLOptions* ssl;

	int serverURIcount;

	char* const* serverURIs;

	int MQTTVersion;

	struct
	{
		const char* serverURI;     /**< the serverURI connected to */
		int MQTTVersion;     /**< the MQTT version used to connect with */
		int sessionPresent;  /**< if the MQTT version is 3.1.1, the value of sessionPresent returned in the connack */
	} returned;
	/**
   * Optional binary password.  Only checked and used if the password option is NULL
   */
	struct
	{
		int len;           /**< binary password length */
		const void* data;  /**< binary password data */
	} binarypwd;

	int maxInflightMessages;
	/*
	 * MQTT V5 clean start flag.  Only clears state at the beginning of the session.
	 */
	int cleanstart;

	const MQTTClient_nameValue* httpHeaders;

	const char* httpProxy;

	const char* httpsProxy;
} MQTTClient_connectOptions;

int MQTTClient_connect(MQTTClient handle, MQTTClient_connectOptions* options);
int MQTTClient_disconnect(MQTTClient handle, int timeout); // timeout: milliseconds

#define MQTTClient_connectOptions_initializer { {'M', 'Q', 'T', 'C'}, 8, 60, 1, 1, NULL, NULL, NULL, 30, 0, NULL,\
0, NULL, MQTTVERSION_DEFAULT, {NULL, 0, 0}, {0, NULL}, -1, 0, NULL, NULL, NULL}

#define MQTTClient_connectOptions_initializer5 { {'M', 'Q', 'T', 'C'}, 8, 60, 0, 1, NULL, NULL, NULL, 30, 0, NULL,\
0, NULL, MQTTVERSION_5, {NULL, 0, 0}, {0, NULL}, -1, 1, NULL, NULL, NULL}

#define MQTTClient_connectOptions_initializer_ws { {'M', 'Q', 'T', 'C'}, 8, 45, 1, 1, NULL, NULL, NULL, 30, 0, NULL,\
0, NULL, MQTTVERSION_DEFAULT, {NULL, 0, 0}, {0, NULL}, -1, 0, NULL, NULL, NULL}

#define MQTTClient_connectOptions_initializer5_ws { {'M', 'Q', 'T', 'C'}, 8, 45, 0, 1, NULL, NULL, NULL, 30, 0, NULL,\
0, NULL, MQTTVERSION_5, {NULL, 0, 0}, {0, NULL}, -1, 1, NULL, NULL, NULL}
// topicName 不是message字段
typedef struct
{
	/** The eyecatcher for this structure.  must be MQTM. */
	char struct_id[4];
	/** The version number of this structure.  Must be 0 or 1
	 *  0 indicates no message properties */
	int struct_version;
	/** The length of the MQTT message payload in bytes. */
	int payloadlen;
	/** A pointer to the payload of the MQTT message. */
	void* payload;

	int qos;

	int retained;
	/**
      * The dup flag indicates whether or not this message is a duplicate.
      * It is only meaningful when receiving QoS1 messages. When true, the
      * client application should take appropriate action to deal with the
      * duplicate message.
      */
	int dup;
	/** The message identifier is normally reserved for internal use by the
      * MQTT client and server.
      */
	int msgid;
	/**
	 * The MQTT V5 properties associated with the message.
	 */
	MQTTProperties properties;
} MQTTClient_message;

#define MQTTClient_message_initializer { {'M', 'Q', 'T', 'M'}, 1, 0, NULL, 0, 0, 0, 0, MQTTProperties_initializer }

/* @param dt A pointer to an ::MQTTClient_deliveryToken. This is populated
  * with a token representing the message when the function returns
  * successfully. If your application does not use delivery tokens, set this
  * argument to NULL.
  */
int MQTTClient_publishMessage(MQTTClient handle, const char* topicName, MQTTClient_message* message,
	    MQTTClient_deliveryToken* deliveryToken)
LIBMQTT_API int MQTTClient_publish(MQTTClient handle, const char* topicName, int payloadlen, const void* payload, int qos, int retained,
		MQTTClient_deliveryToken* dt);

LIBMQTT_API int MQTTClient_receive(MQTTClient handle, char** topicName, int* topicLen, MQTTClient_message** message,
		unsigned long timeout);
LIBMQTT_API void MQTTClient_freeMessage(MQTTClient_message** msg);

MQTTClient实际指向:

typedef struct
{
	char* serverURI;
	const char* currentServerURI; /* when using HA options, set the currently used serverURI */
#if defined(OPENSSL)
	int ssl;
#endif
	int websocket;
	Clients* c;
	MQTTClient_connectionLost* cl;
	MQTTClient_messageArrived* ma;
	MQTTClient_deliveryComplete* dc;
	void* context;

	MQTTClient_disconnected* disconnected;
	void* disconnected_context; /* the context to be associated with the disconnected callback*/

	MQTTClient_published* published;
	void* published_context; /* the context to be associated with the disconnected callback*/

#if 0
	MQTTClient_authHandle* auth_handle;
	void* auth_handle_context; /* the context to be associated with the authHandle callback*/
#endif

	sem_type connect_sem;
	int rc; /* getsockopt return code in connect */
	sem_type connack_sem;
	sem_type suback_sem;
	sem_type unsuback_sem;
	MQTTPacket* pack;

	unsigned long commandTimeout;
} MQTTClients;

异步API

typedef void* MQTTAsync;
int MQTTAsync_create(MQTTAsync* handle, const char* serverURI, const char* clientId,
		int persistence_type, void* persistence_context)
int MQTTAsync_setCallbacks(MQTTAsync handle, void* context,
	MQTTAsync_connectionLost* cl,
	MQTTAsync_messageArrived* ma,
	MQTTAsync_deliveryComplete* dc)

void MQTTAsync_destroy(MQTTAsync* handle)

typedef void MQTTAsync_connectionLost(void* context, char* cause);
typedef int MQTTAsync_messageArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* message);
typedef void MQTTAsync_deliveryComplete(void* context, MQTTAsync_token token);
MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer;

conn_opts.keepAliveInterval = 20;
conn_opts.cleansession = 1;
conn_opts.onSuccess = onConnect;
conn_opts.onFailure = onConnectFailure;
conn_opts.context = client;
int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options)

MQTTAsync_disconnectOptions disc_opts = MQTTAsync_disconnectOptions_initializer;
disc_opts.onSuccess = onDisconnect;
int MQTTAsync_disconnect(MQTTAsync handle, const MQTTAsync_disconnectOptions* options)
MQTTAsync_message pubmsg = MQTTAsync_message_initializer;
int MQTTAsync_sendMessage(MQTTAsync handle, const char* destinationName, 
    const MQTTAsync_message* message, MQTTAsync_responseOptions* response)
int MQTTAsync_subscribe(MQTTAsync handle, const char* topic, int qos, MQTTAsync_responseOptions* response)
LIBMQTT_API int MQTTAsync_subscribeMany(MQTTAsync handle, int count, char* const* topic, const int* qos, MQTTAsync_responseOptions* response);
LIBMQTT_API int MQTTAsync_unsubscribe(MQTTAsync handle, const char* topic, MQTTAsync_responseOptions* response);
LIBMQTT_API int MQTTAsync_unsubscribeMany(MQTTAsync handle, int count, char* const* topic, MQTTAsync_responseOptions* response);

MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer;
opts.onSuccess = onSubscribe;
opts.onFailure = onSubscribeFailure;
opts.context = client;

跟踪调试

A number of environment variables control runtime tracing of the C library.

Tracing is switched on using MQTT_C_CLIENT_TRACE (a value of ON traces to stdout, any other value should specify a file to trace to).

The verbosity of the output is controlled using the MQTT_C_CLIENT_TRACE_LEVEL environment variable - valid values are ERROR, PROTOCOL, MINIMUM, MEDIUM and MAXIMUM (from least to most verbose).

The variable MQTT_C_CLIENT_TRACE_MAX_LINES limits the number of lines of trace that are output.

export MQTT_C_CLIENT_TRACE=ON
export MQTT_C_CLIENT_TRACE_LEVEL=PROTOCOL

示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include "MQTTAsync.h"
#include "cjson/cJSON.h"

#define ADDRESS     "tcp://localhost:1883"
#define CLIENTID    "ExampleClientSub"
#define TOPIC       "/root/async"
#define PAYLOAD     "Hello World!"
#define QOS         1
#define TIMEOUT     10000L

#define USERNAME    "wang"
#define PASSWORD    "wangpasswd"
#define WAN_CLIENT_ID  1 

typedef struct{
    int clientId;    // indicate which client
    MQTTAsync handle;
} ClientContext;

volatile MQTTAsync_token deliveredtoken;

bool flag_mqtt_end = false;

static void onConnectSuccess(void *context, MQTTAsync_successData* response);
static void onConnectFailure(void *context, MQTTAsync_failureData* response);

void onSend(void *context, MQTTAsync_successData* response)
{
    printf("Successful send\n");
}

void reconnect(ClientContext *context)
{
    int rc = 0;

    MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer;
    conn_opts.keepAliveInterval = 20;
    conn_opts.cleansession = 1;
    conn_opts.username = USERNAME;
    conn_opts.password = PASSWORD;
    conn_opts.onSuccess = onConnectSuccess;
    conn_opts.onFailure = onConnectFailure;
    conn_opts.context = context;

    if ((rc = MQTTAsync_connect(context->handle, &conn_opts)) != MQTTASYNC_SUCCESS){
        printf("Failed to start connect, return code %d\n", rc);
    }
}

void connlost(void *context, char *cause)
{
    printf("Connection lost cause: %s\n", cause);
    reconnect(context);
}

// mosquitto_pub -t /root/async -m '{"deviceid":12345}'
// mosquitto_sub -t /root/async/response/id
int msgarrvd(void *context, char *topicName, int topicLen, MQTTAsync_message *message)
{
    int i;
    int rc;
    char* payloadptr;
    cJSON *root, *tmp;
    char topic[64] = {0};
    MQTTAsync_message pubmsg = MQTTAsync_message_initializer;
    MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer;

    MQTTAsync client = ((ClientContext*)context)->handle;
    if(strncmp(message->payload, "mqtt_end", message->payloadlen) == 0){
        flag_mqtt_end = true;
    }
#if 0
    printf("Message arrived: topic[%s], message[", topicName);
    payloadptr = message->payload;
    for(i=0; i<message->payloadlen; i++){
        putchar(*payloadptr++);
    }
    printf("]\n");
    
#else
    printf("Message arrived: topic[%s], ", topicName);

    root = cJSON_Parse((char*)message->payload);
    if(root == NULL){
        MQTTAsync_freeMessage(&message);
        MQTTAsync_free(topicName);
        return 1;
    }
    payloadptr = cJSON_Print(root);
    printf("message[%s]\n", payloadptr);
    cJSON_free(payloadptr);

    tmp = cJSON_GetObjectItem(root, "deviceid");
    if(tmp == NULL){
        printf("Get message.deviceid error\n");
        goto end;
    }
    sprintf(topic, "%s/response/%d", topicName, tmp->valueint);
    cJSON_AddStringToObject(root, "result", "ok");

    opts.onSuccess = onSend;
    opts.context = context;

    payloadptr = cJSON_Print(root);

    pubmsg.payload = payloadptr;
    pubmsg.payloadlen = strlen(payloadptr);
    pubmsg.qos = QOS;
    pubmsg.retained = 0;
    
    if((rc = MQTTAsync_sendMessage(client, topic, &pubmsg, &opts))!= MQTTASYNC_SUCCESS){
        printf("Failed to start sendMessage, return code %d\n", rc);
    }

    cJSON_free(payloadptr);
    cJSON_Delete(root);
#endif

end:
    MQTTAsync_freeMessage(&message);
    MQTTAsync_free(topicName);
    return 1;
}

void onDisconnect(void* context, MQTTAsync_successData* response)
{
    printf("Successful disconnection\n");
}

void onSubscribeSuccess(void* context, MQTTAsync_successData* response)
{
    printf("Subscribe succeeded\n");
}

void onSubscribeFailure(void* context, MQTTAsync_failureData* response)
{
    printf("Subscribe failed, rc %d\n", response ? response->code : 0);
}

void onConnectFailure(void* context, MQTTAsync_failureData* response)
{
    ClientContext *clicontext = (ClientContext*) context;
    printf("Connect failed, rc %d\n", response?response->code:0);
    reconnect(context);
}

void onConnectSuccess(void* context, MQTTAsync_successData* response)
{
    int rc;
    ClientContext *clicontext = (ClientContext*)context;
    MQTTAsync client = clicontext->handle;
    MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer;
    MQTTAsync_message pubmsg = MQTTAsync_message_initializer;

    printf("Successful connection\n");
    printf("Subscribing to topic %s for client %s using QoS%d. "
        "Press Q<Enter> to quit\n", TOPIC, CLIENTID, QOS);
    opts.onSuccess = onSubscribeSuccess;
    opts.onFailure = onSubscribeFailure;
    opts.context = clicontext;
    if ((rc = MQTTAsync_subscribe(client, TOPIC, QOS, &opts)) != MQTTASYNC_SUCCESS){
        printf("Failed to start subscribe, return code %d\n", rc);
    }
}

void *mqttclient(void *argc)
{  
    MQTTAsync client;
    ClientContext context;
	MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer;
    MQTTAsync_disconnectOptions disc_opts = MQTTAsync_disconnectOptions_initializer;
	int rc;

	MQTTAsync_create(&client, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL);

	context.clientId = WAN_CLIENT_ID;
    context.handle = client;
    
	MQTTAsync_setCallbacks(client, (void*)&context, connlost, msgarrvd, NULL);  

	conn_opts.keepAliveInterval = 20;
	conn_opts.cleansession = 1;
	conn_opts.username = USERNAME;
	conn_opts.password = PASSWORD;
	conn_opts.onSuccess = onConnectSuccess;
	conn_opts.onFailure = onConnectFailure;
	conn_opts.context = (void*)&context;
    MQTTAsync_connect(client, &conn_opts);

    while(!flag_mqtt_end){
        //rc = MQTTAsync_connect(client, &conn_opts);
        //pause();
        sleep(2);
	}

    disc_opts.onSuccess = onDisconnect;
    disc_opts.context = &context;
    MQTTAsync_disconnect(client, &disc_opts);
    MQTTAsync_destroy(&client);   
}

int main(int argc, char *argv[])
{
    mqttclient(NULL);
    return 0;
}
gcc mqtta.c cjson/*.c -I cjson/ -lpaho-mqtt3a -L /home/wang/repoc/paho.mqtt.c/build/output -I paho/ -lm -o suba

mosquitto_pub -t /root/async -m '{"deviceid":12345}'
mosquitto_sub -t /root/async/response/12345 -d

valgrind --tool=memcheck --leak-check=full --show-reachable=yes --trace-children=yes   --show-mismatched-frees=yes ./suba

参考:

  1. Linux:MQTT通信协议之五 -- 编译paho.mqtt.c及编写简单的C例程(同步函数)
posted @ 2022-10-06 23:53  yuxi_o  阅读(463)  评论(0编辑  收藏  举报