mqttclient
Based on a socket API cross-platform client MQTT
Source address https://github.com/jiejieTop/mqttclient
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.c
Modify 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_SALOF
defined as 0 to.
#define LOG_IS_SALOF 0
In the mqttclient/common/log/config.h
open 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.h
where can corresponding configuration information according to their needs.
The selection whether mbedtls
encryption layer:
#define MQTT_NETWORK_TYPE_TLS MQTT_YES
Compile & Run
./build.sh
Run build.sh
the script will ./build/bin/
generate an executable file directory mqtt-client
can 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
mqttclient
It has a very simple api
interface 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:
- Read and write data buffer
read_buf、write_buf
- Command timeout period
cmd_timeout
(mainly write blocking time, wait for a response, reconnection wait time) - 维护
ack
链表ack_handler_list
,这是异步实现的核心,所有等待响应的报文都会被挂载到这个链表上 - 维护消息处理列表
msg_handler_list
,这是mqtt
协议必须实现的内容,所有来自服务器的publish
报文都会被处理(前提是订阅了对应的消息) - 维护一个网卡接口
network
- 维护一个内部线程
thread
,所有来自服务器的mqtt包都会在这里被处理! - 两个定时器,分别是掉线重连定时器与保活定时器
reconnect_timer、ping_timer
- 一些连接的参数
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);
连接服务器则是使用非异步的方式设计,因为必须等待连接上服务器才能进行下一步操作。
过程如下
- 调用底层的连接函数连接上服务器:
c->network->connect(c->network);
- 序列化
mqtt
的CONNECT
报文并且发送
MQTTSerialize_connect(c->write_buf, c->write_buf_size, &connect_data)
mqtt_send_packet(c, len, &connect_timer)
- 等待来自服务器的
CONNACK
报文
mqtt_wait_packet(c, CONNACK, &connect_timer)
- 连接成功后创建一个内部线程
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)
订阅报文使用异步设计来实现的:
过程如下:
- 序列化订阅报文并且发送给服务器
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)
- 创建对应的消息处理节点,这个消息节点在收到服务器的
SUBACK
订阅应答报文后会挂载到消息处理列表msg_handler_list
上
mqtt_msg_handler_create(topic_filter, qos, handler)
- 在发送了报文给服务器那就要等待服务器的响应了,记录这个等待
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)
核心思想都差不多,过程如下:
- 先序列化发布报文,然后发送到服务器
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)
- 对于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
- Packet processing
mqtt_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)
ack
Scan 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 、PUBCOMP
ensure 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);
- 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)
- Deserialized message
MQTTDeserialize_ack(&packet_type, &dup, &packet_id, c->read_buf, c->read_buf_size)
- 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)
- Deserialized message
MQTTDeserialize_suback(&packet_id, 1, &count, (int*)&granted_qos, c->read_buf, c->read_buf_size)
- Cancel corresponding ack record
mqtt_ack_list_unrecord(c, packet_type, packet_id, NULL);
- 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)
- Deserialized message
MQTTDeserialize_unsuback(&packet_id, c->read_buf, c->read_buf_size)
- Cancel corresponding ack record
mqtt_ack_list_unrecord(c, UNSUBACK, packet_id, &msg_handler)
- 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)
- 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)
- For a message QOS0, QOS1 directly to process messages
mqtt_deliver_message(c, &topic_name, &msg);
- For a message QOS1 also need to send a
PUBACK
reply message to the server
MQTTSerialize_ack(c->write_buf, c->write_buf_size, PUBACK, 0, msg.id);
- As for the message you need to send QOS2 the
PUBREC
message to the server, in addition to records requiredPUBREL
to 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_exist
function 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)
- Deserialized message
MQTTDeserialize_ack(&packet_type, &dup, &packet_id, c->read_buf, c->read_buf_size)
- It generates a corresponding response message
mqtt_publish_ack_packet(c, packet_id, packet_type);
- Cancel corresponding ack record
mqtt_ack_list_unrecord(c, UNSUBACK, packet_id, &msg_handler)