WIFI学习三(MQTT相关函数,基于MT7682)

        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);

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值