MQTT概念的介绍请看该文章:MQTT学习总结_qq_34981的博客-CSDN博客
NewNetwork()
该函数初始化MQTT相关的参数和回调。
函数原型:
void NewNetwork(Network *n);
参数:
N:
网络结构体。看Network的定义。
/**
* @brief The structure of MQTT network connection used in the MQTT library. The user has to allocate memory for this structure.
*/
struct Network {
int my_socket; /**< 连接socket的句柄. */
int (*mqttread)(Network *, unsigned char *, int, int); /**< MQTT读数据函数指针*/
int (*mqttwrite)(Network *, unsigned char *, int, int); /**< MQTT发送数据函数指针*/
void (*disconnect)(Network *); /**< MQTT断开连接函数指针*/
void (*on_disconnect_callback)(Network *n); /**< MQTT断开连接回调函数*/
/*以下都是TLS(安全传输)需要配置的参数*/
mbedtls_ssl_context ssl; /**< mbed TLS control context. */
mbedtls_net_context fd; /**< mbed TLS network context. */
mbedtls_ssl_config conf; /**< mbed TLS configuration context. */
mbedtls_x509_crt cacertl; /**< mbed TLS CA certification. */
mbedtls_x509_crt clicert; /**< mbed TLS Client certification. */
mbedtls_pk_context pkey; /**< mbed TLS Client key. */
};
看该函数内部操作
void NewNetwork(Network *n)
{
memset(n, 0, sizeof(Network)); //设置默认值
n->my_socket = -1; //初始化值
n->mqttread = mqtt_read; //读
n->mqttwrite = mqtt_write; //写
n->disconnect = mqtt_disconnect; //断开
}
以上参数均在MQTT的库文件中,不做过多深入研究。
返回值:
无
函数实例:
Network n;
NewNetwork(&n);
ConnectNetwork()
连接MQTT服务器(不使用TLS)
函数原型:
int ConnectNetwork(Network *n, char *addr, char *port);
参数:
N:
指向网络参数结构体的指针。该参数被NewNetwork函数初始化过。
Addr:
服务器主机名或IP地址
Port:
服务器端口名
返回值:
0 连接创建成功
-1 调用lwip socket()失败
-2 调用lwip connect()失败
其他值 调用lwip getaddrinfo()失败
实例:
#define MQTT_SERVER "test.mosquitto.org"
#define MQTT_PORT "1883"
rc = ConnectNetwork(&n, MQTT_SERVER, MQTT_PORT);
TLSConnectNetwork()
该函数通过TLS(安全传输)连接MQTT,整个通信基于加密。
函数原型:
int TLSConnectNetwork(Network *n, const char *addr, const char *port,
const char *ca_crt, size_t ca_crt_len,
const char *client_crt, size_t client_crt_len,
const char *client_key, size_t client_key_len,
const char *client_pwd, size_t client_pwd_len);
参数:
N:
指向网络参数结构体的指针。该参数被NewNetwork函数初始化过。
Addr:
服务器主机名或IP地址
Port:
服务器端口名
ca_crt:
服务器的证书权威(Certificate Authority,CA)认证码。
ca_crt_len:
认证码长度
client_crt:
客户端认证码
client_crt_len:
客户端认证码长度
client_key:
客户端密钥
client_key_len:
客户端密钥长度
client_pwd:
客户端密码
client_pwd_len:
客户端密码长度
返回值:
0 连接创建成功
-1 调用lwip socket()失败
-2 调用lwip connect()失败
其他值 调用lwip getaddrinfo()失败
实例:
#define MQTT_SERVER "test.mosquitto.org"
#define MQTT_PORT "1883"
static const char mqtt_ca_cert[] = \
"-----BEGIN CERTIFICATE-----\r\n" \
"HMUfpIBvFSDJ3gSZp4A==\r\n" \
"-----END CERTIFICATE-----";
static const size_t mqtt_ca_crt_len = sizeof( mqtt_ca_cert );
TLSConnectNetwork(n, MQTT_SERVER, MQTT_PORT, mqtt_ca_cert, mqtt_ca_crt_len,
NULL, 0,
NULL, 0,
NULL, 0); //基于TLS连接TCP网络
MQTTClientInit()
该函数创建一个MQTT客户端实例,后续都通过这个实例进行操作。
函数原型:
void MQTTClientInit(MQTTClient *client, Network *network, unsigned int command_timeout_ms,unsigned char *sendbuf, size_t sendbuf_size, unsigned char *readbuf, size_t readbuf_size);
参数:
Client:
创建的客户端,看MQTTClient结构体:
typedef struct MQTTClient {
unsigned int next_packetid, //下个包ID
command_timeout_ms; //指令超时时间
size_t buf_size, //缓冲区大小
readbuf_size; //读缓冲区大小
unsigned char *buf, //缓冲区指针
*readbuf; //读缓冲区指针
unsigned int keepAliveInterval; //保活间隔
char ping_outstanding; //ping包
int isconnected; //已连接标志
int cleansession; //清除会话
struct MessageHandlers {
const char *topicFilter; //主题文件
void (*fp)(MessageData *); //消息数据
} messageHandlers[MAX_MESSAGE_HANDLERS]; /* Message handlers are indexed by subscription topic */
void (*defaultMessageHandler)(MessageData *); //默认消息回调
Network *ipstack; //IP栈
Timer last_sent, last_received; //最近一次的发送和接收时间戳
#if defined(MQTT_TASK)
mqtt_task_state state;
Mutex mutex;
Thread thread;
#endif
} MQTTClient;
Network:
由NewNetwork初始化的网络参数结构体
command_timeout_ms:
指令超时时间
Sendbuf:
发送缓冲区
sendbuf_size:
发送缓冲区大小
Readbuf:
读缓冲区
readbuf_size:
读缓冲区大小
返回值:
无
实例:
#define MQTT_SERVER "test.mosquitto.org"
#define MQTT_PORT "1883"
Network n;
MQTTClient c;
uint8_t msg_sendbuf[100] = {0};
uint8_t msg_readbuf[100] = {0};
NewNetwork(&n);
rc = ConnectNetwork(&n, MQTT_SERVER, MQTT_PORT);
MQTTClientInit(&c, &n, 12000, msg_sendbuf, sizeof(msg_sendbuf), msg_readbuf, sizeof(msg_readbuf));
MQTTConnect()
MQTT链接,发送一个MQTT连接包到服务器,并等待链接ACK
函数原型:
int MQTTConnect(MQTTClient *client, MQTTPacket_connectData *options);
参数:
Client:
MQTTClientInit函数创建的客户端
Options:
客户端链接参数,看MQTTPacket_connectData结构体:
typedef struct {
/** The eyecatcher for this structure. must be MQTC. */
char struct_id[4]; //协议名称,固定字符“MQTC”
/** The version number of this structure. Must be 0 */
int struct_version; //结构版本,固定为0
/** Version of MQTT to be used. 3 = 3.1 4 = 3.1.1*/
unsigned char MQTTVersion; //MQTT版本
MQTTString clientID; //客户端ID
unsigned short keepAliveInterval; //连接保活间隔
unsigned char cleansession; //绘画清除标识
unsigned char willFlag; //遗愿标志
MQTTPacket_willOptions will;
MQTTString username; //用户名
MQTTString password; //密码
} MQTTPacket_connectData;
返回值:
0 成功
其他 失败
实例:
MQTTClient c;
MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
data.willFlag = 0; //遗愿标志
data.MQTTVersion = 3; //MQTT版本
data.clientID.cstring = MQTT_CLIENT_ID; //客户端ID
data.username.cstring = NULL; //用户名
data.password.cstring = NULL; //用户密码
data.keepAliveInterval = 200; //保活间隔
data.cleansession = 1; //会话清除标志
rc = MQTTConnect(&c, &data);
MQTTSubscribe()
MQTT订阅响应的主题,发送MQTT订阅包,并等待应答
函数原型:
int MQTTSubscribe(MQTTClient *client, const char *topicFilter, enum QoS, messageHandler);
参数:
Client:
MQTTClientInit函数创建的客户端
topicFilter:
订阅的主题
QoS:
通讯包发送状态
QoS0:至多发送一次(可能会丢包)
QoS1:最少一次(保证包到达,可能会出现重包)
QoS2:只有一次(保证包会到达目的地,且不会出现重包)
messageHandler:
消息接收回调
返回值:
0 成功
其他值 失败
实例:
#define MQTT_TOPIC "7687test"
MQTTClient c;
static void messageArrived(MessageData *md)
{
MQTTMessage *message = md->message;
LOG_I(mqtt, "Message arrived: qos %d, retained %d, dup %d, packetid %d\n",
message->qos, message->retained, message->dup, message->id);
LOG_I(mqtt, "Payload %d.%s\n", (size_t)(message->payloadlen), (char *)(message->payload));
}
MQTTSubscribe(&c, topic, QOS1, messageArrived);
MQTTPublish()
发送一个MQTT发布包并且等待应答包去完成QoS
函数原型:
int MQTTPublish(MQTTClient *client, const char * topic , MQTTMessage *message);
参数:
Client:
MQTTClientInit函数创建的客户端
Topic:
推送的目的主题
Message:
推送的消息,MQTTMessage 结构体:
typedef struct MQTTMessage {
enum QoS qos; //QoS值
unsigned char retained; //为1时,briker应该保存该条消息,当之后有任何新的订阅主题设备上线时,都会先收到这条消息。
unsigned char dup; //消息重复表示,为1时,表示该消息时一条重发消息
unsigned short id; //数据包标识
void *payload; //数据
size_t payloadlen; //数据长度
} MQTTMessage;
返回值:
0 成功
其他值 失败
实例:
#define MQTT_TOPIC "7687test"
MQTTMessage message;
MQTTClient c;
message.qos = QOS0;
message.retained = false;
message.dup = false;
message.payload = (void *)buf;
message.payloadlen = strlen(buf) + 1;
rc = MQTTPublish(&c, MQTT_TOPIC, &message);
MQTTYield()
MQTT主动获取数据,获取到数据,会触发MQTTSubscribe订阅函数的接收回调
函数原型:
int MQTTYield(MQTTClient *client, int time);
参数:
Client:
MQTTClientInit函数创建的客户端
Time:
获取等待的时间
返回值:
0 成功
其他值 失败
实例:
MQTTClient c;
MQTTYield(&c, 1000);
注:查看该函数的源码
int MQTTYield(MQTTClient *c, int timeout_ms)
{
int rc = SUCCESS;
Timer timer;
TimerInit(&timer);
TimerCountdownMS(&timer, timeout_ms);
do {
if (cycle(c, &timer) < 0) {
rc = FAILURE;
break;
}
} while (!TimerIsExpired(&timer));
return rc;
}
先看TimerInit()函数
void TimerInit(Timer *timer)
{
timer->end_time = 0;
}
该函数只是将timer的end_time设置为0.
再看TimerCountdownMS()函数
void TimerCountdownMS(Timer *timer, unsigned int timeout)
{
timer->end_time = mqtt_current_time_ms() + timeout;
}
该函数意思是设置timer的end_time为当前时间+超时时间。可以理解为设置超时时间。
TimerIsExpired()函数
char TimerIsExpired(Timer *timer)
{
unsigned int cur_time = 0;
cur_time = mqtt_current_time_ms();
if (timer->end_time < cur_time || timer->end_time == cur_time) {
MQTT_DBG("MQTT expired enter");
return 1;
} else {
MQTT_DBG("MQTT not expired");
return 0;
}
}
该函数可以看到是获取当前的时间,然后跟end_time进行比对,查看是否超时。超时返回1,未超时返回0。这时候再看MQTTYield()函数,就知道设置一个超时时间,然后在超时时间内不停的调用cycle()函数。那cycle()函数又是什么呢?继续看源码。
int cycle(MQTTClient *c, Timer *timer)
{
int len = 0, rc = SUCCESS;
MQTT_DBG("cycle enter");
int packet_type = readPacket(c, timer); /* read the socket, see what work is due */
switch (packet_type) {
case READ_TIMEOUT:
break;
default: {
/* no more data to read, unrecoverable. Or read packet fails due to unexpected network error */
rc = packet_type;
goto exit;
}
case 0: /* timed out reading packet */
break;
case CONNACK:
case PUBACK:
case SUBACK:
break;
case PUBLISH: {
MQTTString topicName;
MQTTMessage msg;
int intQoS;
msg.payloadlen = 0; /* this is a size_t, but deserialize publish sets this as int */
if (MQTTDeserialize_publish(&msg.dup, &intQoS, &msg.retained, &msg.id, &topicName,
(unsigned char **)&msg.payload, (int *)&msg.payloadlen, c->readbuf, c->readbuf_size) != 1) {
goto exit;
}
msg.qos = (enum QoS)intQoS;
deliverMessage(c, &topicName, &msg);
if (msg.qos != QOS0) {
if (msg.qos == QOS1) {
len = MQTTSerialize_ack(c->buf, c->buf_size, PUBACK, 0, msg.id);
} else if (msg.qos == QOS2) {
len = MQTTSerialize_ack(c->buf, c->buf_size, PUBREC, 0, msg.id);
}
if (len <= 0) {
rc = FAILURE;
} else {
rc = sendPacket(c, len, timer);
}
if (rc == FAILURE) {
goto exit; // there was a problem
}
}
break;
}
case PUBREC:
case PUBREL: {
unsigned short mypacketid;
unsigned char dup, type;
if (MQTTDeserialize_ack(&type, &dup, &mypacketid, c->readbuf, c->readbuf_size) != 1) {
rc = FAILURE;
} else if ((len = MQTTSerialize_ack(c->buf, c->buf_size,
(packet_type == PUBREC) ? PUBREL : PUBCOMP, 0, mypacketid)) <= 0) {
rc = FAILURE;
} else if ((rc = sendPacket(c, len, timer)) != SUCCESS) { // send the PUBREL packet
rc = FAILURE; // there was a problem
}
if (rc == FAILURE) {
goto exit; // there was a problem
}
break;
}
case PUBCOMP:
break;
case PINGRESP:
c->ping_outstanding = 0;
break;
}
if (keepalive(c) != SUCCESS) {
//check only keepalive FAILURE status so that previous FAILURE status can be considered as FAULT
rc = FAILURE;
}
exit:
MQTT_DBG("cycle packet_type=%d rc=%d", packet_type, rc);
if (rc == SUCCESS) {
rc = packet_type;
} else if (rc == READ_TIMEOUT) {
// do nothing
} else if (c->isconnected) {
MQTT_DBG("cycle MQTTCloseSession");
MQTTCloseSession(c);
}
return rc;
}
这里边的分支比较多,先不用关注别的,看keepalive()函数。
int keepalive(MQTTClient *c)
{
int rc = SUCCESS;
MQTT_DBG("keepalive enter");
if (c->keepAliveInterval == 0) {
goto exit;
}
if (TimerIsExpired(&c->last_sent) || TimerIsExpired(&c->last_received)) {
if (c->ping_outstanding) {
rc = FAILURE; /* PINGRESP not received in keepalive interval */
} else {
Timer timer;
TimerInit(&timer);
TimerCountdownMS(&timer, 1000);
int len = MQTTSerialize_pingreq(c->buf, c->buf_size);
MQTT_DBG("keepalive send pingreq");
if (len > 0 && (rc = sendPacket(c, len, &timer)) == SUCCESS) { // send the ping packet
c->ping_outstanding = 1;
}
}
}
MQTT_DBG("keepalive rc=%d", rc);
exit:
return rc;
}
看源码,keepalive()函数中,先判断keepAliveInterval是否为0。这个keepAliveInterval参数,就是在配置MQTT连接时候设置的。忘记的话,再看一下MQTTConnect函数。
TimerIsExpired(&c->last_sent)和TimerIsExpired(&c->last_received)函数用来检测发送和接收的超时时间。Last_sent和last_received参数分别在sendPacket()和readPacket()函数中被设置。
TimerCountdown(&c->last_sent, c->keepAliveInterval);
TimerCountdown(&c->last_received, c->keepAliveInterval);
这两个参数的超时时间都被设置为keepAliveInterval的值。也就是MQTT的保活周期。
超时后,进入到接下来的判断,判断ping_outstanding参数。该参数在发送Ping包之后会被置1。
这里设置新的定时器,超时周期为1秒。然后调用MQTTSerialize_pingreq()函数,该函数为组装一个MQTT ping包。组装好的包存在c->buf参数中。然后调用sendPacket函数将Ping包发送出去。发送成功后,将ping_outstanding参数置1。该参数在收到ping的ack包后,会被清掉。
总结一下。MQTTYield函数会在设置的超时时间内不停轮训的检测MQTT读消息。在检测的过程中还会检测保活周期,如果保活周期到了,就向MQTT服务器发送一个ping包来进行保活。如果在这期间有MQTT数据交互,则重新开始计数。这也就是MQTT的keepalive的保活逻辑。
该函数的超时函数是一个循环的过程,程序不停的执行,直到超时时间到。所以如果调用该函数进行数据接收到时候,要注意执行该函数会占用CPU的使用权,导致低优先级的任务无法执行。
MQTTUnsubscribe()
MQTT取消订阅。发送一个MQTT取消订阅的包然后等待应答
函数原型:
int MQTTUnsubscribe(MQTTClient *client, const char *topicFilter);
参数:
Client:
MQTTClientInit函数创建的客户端
topicFilter:
取消订阅的主题
返回值:
0 成功
其他值 失败
实例:
if ((rc = MQTTUnsubscribe(&c, topic)) != 0)
{
//取消订阅失败
}
MQTTDisconnect()
断开连接MQTT,发送一个MQTT断开包并断开连接
函数原型:
int MQTTDisconnect(MQTTClient *client);
参数:
Client:
要断开的MQTT客户端。MQTTClientInit函数创建的客户端
返回值:
0 成功
其他值 失败
实例:
if ((rc = MQTTDisconnect(&c)) != 0) {
//断开失败
}
mqtt_disconnect()
断开网络连接
函数原型:
void mqtt_disconnect(Network *n)
参数:
N:
TCP网络实例,最早通过NewNetwork()初始化。
在NewNetwork函数初始化的时候,会给参数n初始化disconnect回调函数。一般情况下,该回调函数就是mqtt_disconnect。
void NewNetwork(Network *n)
{
memset(n, 0, sizeof(Network));
n->my_socket = -1;
n->mqttread = mqtt_read;
n->mqttwrite = mqtt_write;
n->disconnect = mqtt_disconnect;
}
mqtt_disconnect函数内容如下:
void mqtt_disconnect(Network *n)
{
close(n->my_socket);
}
通过调用close函数,来关闭socket连接。
返回值:
无
实例:
Network n;
NewNetwork(&n);
n.disconnect(&n);