Based on a cross-platform client MQTT above the socket API, support qos2

mqttclient

Based on a socket API cross-platform client MQTT

Source address https://github.com/jiejieTop/mqttclient

Overall framework

Overall framework

Note: currently only implements linux platform, TencentOS tiny and are transplanted RT-Thread

Testing using linux platform

Install cmake:

sudo apt-get install cmake

Configuration

In the mqttclient/test/test.cModify the following files:

    init_params.connect_params.network_params.network_ssl_params.ca_crt = test_ca_get();    /* CA证书 */
    init_params.connect_params.network_params.addr = "xxxxxxx";                             /* 服务器域名 */
    init_params.connect_params.network_params.port = "8883";
    init_params.connect_params.user_name = "xxxxxxx";                                       /* 用户名 */
    init_params.connect_params.password = "xxxxxxx";                                        /* 密码 */
    init_params.connect_params.client_id = "xxxxxxx";                                       /* 客户端id */

Open salof

salof name is: Synchronous Asynchronous Log Output Framework(output frame synchronous asynchronous log)

It is an asynchronous log output library, corresponding to the output log information in an idle time, and the bank and mqttclient seamlessly, if not then LOG_IS_SALOFdefined as 0 to.

#define LOG_IS_SALOF    0

In the mqttclient/common/log/config.hopen configuration output level corresponding log file:

#define BASE_LEVEL      (0)
#define ASSERT_LEVEL    (BASE_LEVEL + 1)            /* 日志输出级别:断言级别(非常高优先级) */
#define ERR_LEVEL       (ASSERT_LEVEL + 1)          /* 日志输出级别:错误级别(高优先级) */
#define WARN_LEVEL      (ERR_LEVEL + 1)             /* 日志输出级别:警告级别(中优先级) */
#define INFO_LEVEL      (WARN_LEVEL + 1)            /* 日志输出级别:信息级别(低优先级) */
#define DEBUG_LEVEL     (INFO_LEVEL + 1)            /* 日志输出级别:调试级别(更低优先级) */

#define         SALOF_OS                    USE_LINUX       /* 选择对应的平台:Linux/FreeRTOS/TencentOS */
#define         LOG_LEVEL                   WARN_LEVEL      /* 日志输出级别 */

mqttclient configuration

The configuration file is: mqttclient/mqtt_config.hwhere can corresponding configuration information according to their needs.
The selection whether mbedtlsencryption layer:

#define     MQTT_NETWORK_TYPE_TLS               MQTT_YES

Compile & Run

./build.sh

Run build.shthe script will ./build/bin/generate an executable file directory mqtt-clientcan be run directly.

Design ideas

  • The overall use of hierarchical design, code implementation mode using asynchronous design, reduce coupling.
  • Handled using the callback message processing: the user specifies [订阅的主题]the specified[消息的处理函数]
  • External dependence does not

API

mqttclientIt has a very simple apiinterface to

int mqtt_keep_alive(mqtt_client_t* c);
int mqtt_init(mqtt_client_t* c, client_init_params_t* init);
int mqtt_release(mqtt_client_t* c);
int mqtt_connect(mqtt_client_t* c);
int mqtt_disconnect(mqtt_client_t* c);
int mqtt_subscribe(mqtt_client_t* c, const char* topic_filter, mqtt_qos_t qos, message_handler_t msg_handler);
int mqtt_unsubscribe(mqtt_client_t* c, const char* topic_filter);
int mqtt_publish(mqtt_client_t* c, const char* topic_filter, mqtt_message_t* msg);
int mqtt_yield(mqtt_client_t* c, int timeout_ms);

core

mqtt_client_t structure

typedef struct mqtt_client {
    unsigned short              packet_id;
    unsigned char               *read_buf;
    unsigned char               *write_buf;
    unsigned char               ping_outstanding;
    unsigned char               ack_handler_number;
    unsigned int                cmd_timeout;
    unsigned int                read_buf_size;
    unsigned int                write_buf_size;
    unsigned int                reconnect_try_duration;
    void                        *reconnect_date;
    reconnect_handler_t         reconnect_handler;
    client_state_t              client_state;
    platform_mutex_t            write_lock;
    platform_mutex_t            global_lock;
    list_t                      msg_handler_list;
    list_t                      ack_handler_list;
    network_t                   *network;
    platform_thread_t           *thread;
    platform_timer_t            reconnect_timer;
    platform_timer_t            ping_timer;
    connect_params_t            *connect_params;
} mqtt_client_t;

The main structure maintenance following:

  1. Read and write data bufferread_buf、write_buf
  2. Command timeout period cmd_timeout(mainly write blocking time, wait for a response, reconnection wait time)
  3. 维护ack链表ack_handler_list,这是异步实现的核心,所有等待响应的报文都会被挂载到这个链表上
  4. 维护消息处理列表msg_handler_list,这是mqtt协议必须实现的内容,所有来自服务器的publish报文都会被处理(前提是订阅了对应的消息)
  5. 维护一个网卡接口network
  6. 维护一个内部线程thread,所有来自服务器的mqtt包都会在这里被处理!
  7. 两个定时器,分别是掉线重连定时器与保活定时器reconnect_timer、ping_timer
  8. 一些连接的参数connect_params

初始化

int mqtt_init(mqtt_client_t* c, client_init_params_t* init)

主要是配置mqtt_client_t结构的相关信息,如果没有指定初始化参数,则系统会提供默认的参数。
但连接部分的参数则必须指定:

    init_params.connect_params.network_params.addr = "[你的mqtt服务器IP地址或者是域名]";
    init_params.connect_params.network_params.port = 1883;	//端口号
    init_params.connect_params.user_name = "jiejietop";
    init_params.connect_params.password = "123456";
    init_params.connect_params.client_id = "clientid";

连接服务器

int mqtt_connect(mqtt_client_t* c);

连接服务器则是使用非异步的方式设计,因为必须等待连接上服务器才能进行下一步操作。
过程如下

  1. 调用底层的连接函数连接上服务器:
c->network->connect(c->network);
  1. 序列化mqttCONNECT报文并且发送
MQTTSerialize_connect(c->write_buf, c->write_buf_size, &connect_data)
mqtt_send_packet(c, len, &connect_timer)
  1. 等待来自服务器的CONNACK报文
mqtt_wait_packet(c, CONNACK, &connect_timer)
  1. 连接成功后创建一个内部线程mqtt_yield_thread
platform_thread_init("mqtt_yield_thread", mqtt_yield_thread, c, MQTT_THREAD_STACK_SIZE, MQTT_THREAD_PRIO, MQTT_THREAD_TICK)

订阅报文

int mqtt_subscribe(mqtt_client_t* c, const char* topic_filter, mqtt_qos_t qos, message_handler_t handler)

订阅报文使用异步设计来实现的:
过程如下:

  1. 序列化订阅报文并且发送给服务器
MQTTSerialize_subscribe(c->write_buf, c->write_buf_size, 0, mqtt_get_next_packet_id(c), 1, &topic, (int*)&qos)
mqtt_send_packet(c, len, &timer)
  1. 创建对应的消息处理节点,这个消息节点在收到服务器的SUBACK订阅应答报文后会挂载到消息处理列表msg_handler_list
mqtt_msg_handler_create(topic_filter, qos, handler)
  1. 在发送了报文给服务器那就要等待服务器的响应了,记录这个等待SUBACK
mqtt_ack_list_record(c, SUBACK, mqtt_get_next_packet_id(c), len, msg_handler)

取消订阅

与订阅报文的逻辑基本差不多的~

发布报文

int mqtt_publish(mqtt_client_t* c, const char* topic_filter, mqtt_message_t* msg)

核心思想都差不多,过程如下:

  1. 先序列化发布报文,然后发送到服务器
MQTTSerialize_publish(c->write_buf, c->write_buf_size, 0, msg->qos, msg->retained, msg->id,
              topic, (unsigned char*)msg->payload, msg->payloadlen);
mqtt_send_packet(c, len, &timer)
  1. 对于QOS0的逻辑,不做任何处理,对于QOS1和QOS2的报文则需要记录下来,在没收到服务器应答的时候进行重发
    if (QOS1 == msg->qos) {
        rc = mqtt_ack_list_record(c, PUBACK, mqtt_get_next_packet_id(c), len, NULL);
    } else if (QOS2 == msg->qos) {
        rc = mqtt_ack_list_record(c, PUBREC, mqtt_get_next_packet_id(c), len, NULL);
    }

内部线程

static void mqtt_yield_thread(void *arg)

主要是对mqtt_yield函数的返回值做处理,比如在disconnect的时候销毁这个线程。

The core handlermqtt_yield

  1. Packet processingmqtt_packet_handle
static int mqtt_packet_handle(mqtt_client_t* c, platform_timer_t* timer)

Different treatment for different packages use:

    switch (packet_type) {
        case 0: /* timed out reading packet */
            break;

        case CONNACK:
            break;

        case PUBACK:
        case PUBCOMP:
            rc = mqtt_puback_and_pubcomp_packet_handle(c, timer);
            break;

        case SUBACK:
            rc = mqtt_suback_packet_handle(c, timer);
            break;
            
        case UNSUBACK:
            rc = mqtt_unsuback_packet_handle(c, timer);
            break;

        case PUBLISH:
            rc = mqtt_publish_packet_handle(c, timer);
            break;

        case PUBREC:
        case PUBREL:
            rc = mqtt_pubrec_and_pubrel_packet_handle(c, timer);
            break;

        case PINGRESP:
            c->ping_outstanding = 0;
            break;

        default:
            goto exit;
    }

And do keep alive the process:

mqtt_keep_alive(c)
  1. ackScan the list, when the server receives a packet, the scanning operation of the ack list
mqtt_ack_list_scan(c);

When the time-out after the destruction ack list node:

mqtt_ack_handler_destroy(ack_handler);

Of course, following these types of messages you will need to re-send operation :( PUBACK 、PUBREC、 PUBREL 、PUBCOMPensure quality of service QOS1 QOS2)

if ((ack_handler->type ==  PUBACK) || (ack_handler->type ==  PUBREC) || (ack_handler->type ==  PUBREL) || (ack_handler->type ==  PUBCOMP))
	mqtt_ack_handler_resend(c, ack_handler);
  1. Activity holding time has passed, it may be dropped, and the need to reconnect operation
mqtt_try_reconnect(c);

Reconnection after a successful attempt to re-subscribe message, ensure to restore the original state ~

mqtt_try_resubscribe(c)

发布应答And 发布完成packet processing

static int mqtt_puback_and_pubcomp_packet_handle(mqtt_client_t *c, platform_timer_t *timer)
  1. Deserialized message
MQTTDeserialize_ack(&packet_type, &dup, &packet_id, c->read_buf, c->read_buf_size)
  1. Cancel corresponding ack record
mqtt_ack_list_unrecord(c, packet_type, packet_id, NULL);

订阅应答Packet processing

static int mqtt_suback_packet_handle(mqtt_client_t *c, platform_timer_t *timer)
  1. Deserialized message
MQTTDeserialize_suback(&packet_id, 1, &count, (int*)&granted_qos, c->read_buf, c->read_buf_size)
  1. Cancel corresponding ack record
mqtt_ack_list_unrecord(c, packet_type, packet_id, NULL);
  1. Installation corresponding subscription message processing function, if it is not already present in the installation
mqtt_msg_handlers_install(c, msg_handler);

取消订阅应答Packet processing

static int mqtt_unsuback_packet_handle(mqtt_client_t *c, platform_timer_t *timer)
  1. Deserialized message
MQTTDeserialize_unsuback(&packet_id, c->read_buf, c->read_buf_size)
  1. Cancel corresponding ack record
mqtt_ack_list_unrecord(c, UNSUBACK, packet_id, &msg_handler)
  1. Destruction corresponding subscription message processing function
mqtt_msg_handler_destory(msg_handler);

From the server 发布message processing

static int mqtt_publish_packet_handle(mqtt_client_t *c, platform_timer_t *timer)
  1. Deserialized message
MQTTDeserialize_publish(&msg.dup, &qos, &msg.retained, &msg.id, &topic_name,
        (unsigned char**)&msg.payload, (int*)&msg.payloadlen, c->read_buf, c->read_buf_size)
  1. For a message QOS0, QOS1 directly to process messages
mqtt_deliver_message(c, &topic_name, &msg);
  1. For a message QOS1 also need to send a PUBACKreply message to the server
MQTTSerialize_ack(c->write_buf, c->write_buf_size, PUBACK, 0, msg.id);
  1. As for the message you need to send QOS2 the PUBRECmessage to the server, in addition to records required PUBRELto ack on the list, waiting for it to release the message server, and finally go to process the message
MQTTSerialize_ack(c->write_buf, c->write_buf_size, PUBREC, 0, msg.id);
mqtt_ack_list_record(c, PUBREL, msg.id + 1, len, NULL)
mqtt_deliver_message(c, &topic_name, &msg);

Description: Once registered the ack packet to the list, when the repetition packet having not re-registered, it will pass mqtt_ack_list_node_is_existfunction determines whether the node exists, mainly dependent on the message type and waits for a response msgid.

发布收到And 发布释放packet processing

static int mqtt_pubrec_and_pubrel_packet_handle(mqtt_client_t *c, platform_timer_t *timer)
  1. Deserialized message
MQTTDeserialize_ack(&packet_type, &dup, &packet_id, c->read_buf, c->read_buf_size)
  1. It generates a corresponding response message
mqtt_publish_ack_packet(c, packet_id, packet_type);
  1. Cancel corresponding ack record
mqtt_ack_list_unrecord(c, UNSUBACK, packet_id, &msg_handler)

Source address https://github.com/jiejieTop/mqttclient

Published 115 original articles · won praise 283 · views 190 000 +

Guess you like

Origin blog.csdn.net/jiejiemcu/article/details/103845392