全部產品
Search
文件中心

:使用樣本

更新時間:Jun 30, 2024

./demos/mqtt_x509_auth_demo.c為例,介紹使用X.509認證,將MQTT

背景資訊

步驟一:初始化

  1. 添加標頭檔。

    #include "aiot_state_api.h"
    #include "aiot_sysdep_api.h"
    #include "aiot_mqtt_api.h"
  2. 配置底層依賴和日誌輸出。

        aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
        aiot_state_set_logcb(demo_state_logcb);
  3. 調用aiot_mqtt_init,建立MQTT用戶端執行個體,並初始化預設參數。

        mqtt_handle = aiot_mqtt_init();
        if (mqtt_handle == NULL) {
            printf("aiot_mqtt_init failed\n");
            return -1;
        }

步驟二:配置功能

調用aiot_mqtt_setopt,配置以下功能。

  1. 配置串連參數
  2. 配置裝置身份資訊的回調
  3. 配置狀態監控和訊息回調

更多功能的配置項,請參見aiot_mqtt_option_t

  1. 配置串連參數。
    • const char client_cert[] = {
          "-----BEGIN CERTIFICATE-----\r\n"
          "MIIDiDCCAnCgAwIBAgIIAJ3GD7c2860wDQYJKoZIhvcNAQELBQAwUzEoMCYGA1UE\r\n"
          … 
          …
          "v4aDacYavCH03JXKQ6zWpAwnwLcYrbW7XdhtDrqFCj+v6VJ6NDZaTGEW3/I=\r\n"
          "-----END CERTIFICATE-----\r\n"
      };
      
      const char client_private_key[] = {
      
          "-----BEGIN RSA PRIVATE KEY-----\r\n"
          "MIIEowIBAAKCAQEApyRaelm4b4sKOlqBywOIR4RIJrYEfNtYIAofMIkkwnClrqgh\r\n"
          …    
          …
          "mPw5JEAkNBy6wOWepJ9Tv1wY8yFEzV2dVsx3P93p5P3UdZb4M7i0\r\n"
          "-----END RSA PRIVATE KEY-----\r\n"
      };
          ...
      int main(int argc, char *argv[])
      {
          int32_t     res = STATE_SUCCESS;
          void       *mqtt_handle = NULL;
          char       *host = "x509.itls.cn-shanghai.aliyuncs.com";
      
          uint16_t    port = 1883; 
          aiot_sysdep_network_cred_t cred; 
      
          char *product_key       = "";
          char *device_name       = "";
          char *device_secret     = "";
      
          ...
          /* 安全憑據結構體, 如果要用TLS, 這個結構體中配置CA認證等參數 */
          aiot_sysdep_network_cred_t cred;
      
          /* 建立SDK的安全憑據, 用於建立TLS串連 */
          memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
          cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA;  /* 使用RSA認證校正MQTT服務端 */
          cred.max_tls_fragment = 16384; /* 最大的分區長度為16 KB, 其它可選值還有4 KB、2 KB、1 KB和0.5 KB */
          cred.sni_enabled = 1;                               /* TLS建連時, 支援Server Name Indicator */
          cred.x509_server_cert = ali_ca_crt;                 /* 用來驗證MQTT服務端的RSA根憑證 */
          cred.x509_server_cert_len = strlen(ali_ca_crt);     /* 用來驗證MQTT服務端的RSA根憑證長度 */
      
          /* TODO: 請注意以下四行代碼, 使用X509雙向認證時, 對安全憑據的設定就只要增加這一部分 */
          cred.x509_client_cert = client_cert;
          cred.x509_client_cert_len = strlen(client_cert);
          cred.x509_client_privkey = client_private_key;
          cred.x509_client_privkey_len = strlen(client_private_key);
      
          /* 配置網路連接的安全憑據 */
          aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
      
          ...
      }
    • 參數樣本說明
      client_cert[]
          "-----BEGIN CERTIFICATE-----\r\n"
          "MIIDiDCCAnCgAwIBAgIIAJ3GD7c2860wDQYJKoZIhvcNAQELBQAwUzEoMCYGA1UE\r\n"
          … 
          …
          "v4aDacYavCH03JXKQ6zWpAwnwLcYrbW7XdhtDrqFCj+v6VJ6NDZaTGEW3/I=\r\n"
          "-----END CERTIFICATE-----\r\n"
      裝置的X.509認證資訊。

      在物聯網平台的裝置詳情頁,單擊X.509認證對應的下載按鈕,下載認證資訊。解壓認證檔案後,以樣本值的格式,將該參數的值替換為.cer檔案中的資訊。

      認證內容由多行字串組成。樣本中的省略符號(…)表示省去的字串,每一行字串的開頭加",結尾加\r\n"

      client_private_key[]
          "-----BEGIN RSA PRIVATE KEY-----\r\n"
          "MIIEowIBAAKCAQEApyRaelm4b4sKOlqBywOIR4RIJrYEfNtYIAofMIkkwnClrqgh\r\n"
          … 
          …
          "mPw5JEAkNBy6wOWepJ9Tv1wY8yFEzV2dVsx3P93p5P3UdZb4M7i0\r\n"
          "-----END RSA PRIVATE KEY-----\r\n"
      裝置的X.509認證密鑰。

      在物聯網平台的裝置詳情頁,單擊X.509認證對應的下載按鈕,下載認證資訊。解壓認證檔案後,以樣本值的格式,將該參數的值替換為.key檔案中的資訊。

      認證密鑰內容由多行字串組成。樣本中的省略符號(…)表示省去的字串,每一行字串的開頭加",結尾加\r\n"

      hostx509.itls.cn-shanghai.aliyuncs.com其格式為x509.itls.${YourRegionId}.aliyuncs.com

      其中,${YourRegionId}為裝置接入的地區ID。更多資訊,請參見地區和可用性區域

      port1883連接埠號碼。
      product_key""X.509認證無需配置這三個參數,請確保其值為空白。
      device_name""
      device_secret""
  2. 配置裝置身份資訊的回調。
    裝置使用X.509認證,與物聯網平台建立串連後,物聯網平台將含有裝置ProductKeyDeviceName的訊息發送至裝置。因此,您需提前配置用於接收該訊息的回呼函數。根據定義的回呼函數,將這兩個參數儲存至指定位置,以便後續使用。

    您需自訂函數demo_get_device_info,以下範例程式碼對該訊息做資料解析和列印處理。

    • 
      static void demo_get_device_info(const char *topic, uint16_t topic_len, const char *payload, uint32_t payload_len)
      {
          const char *target_topic = "/ext/auth/identity/response";
          char *p_product_key = NULL;
          uint32_t product_key_len = 0;
          char *p_device_name = NULL;
          uint32_t device_name_len = 0;
          int32_t res = STATE_SUCCESS;
      
          if (topic_len != strlen(target_topic) || memcmp(topic, target_topic, topic_len) != 0) {
              return;
          }
      
          /* TODO: 為了便於示範, 此處使用了SDK內部介面core_json_value(),該介面僅供示範使用。
      
                   實際使用時, 需要換成裝置上可用的JSON解析函數庫的介面,以處理Payload, 例如cJSON等
          */
          res = core_json_value(payload, payload_len, "productKey", strlen("productKey"), &p_product_key, &product_key_len);
          if (res < 0) {
              return;
          }
          res = core_json_value(payload, payload_len, "deviceName", strlen("deviceName"), &p_device_name, &device_name_len);
          if (res < 0) {
              return;
          }
      
          if (g_product_key == NULL) {
              g_product_key = malloc(product_key_len + 1);
              if (NULL == g_product_key) {
                  return;
              }
      
              memset(g_product_key, 0, product_key_len + 1);
              memcpy(g_product_key, p_product_key, product_key_len);
          }
          if (g_device_name == NULL) {
              g_device_name = malloc(device_name_len + 1);
              if (NULL == g_product_key) {
                  return;
              }
      
              memset(g_device_name, 0, device_name_len + 1);
              memcpy(g_device_name, p_device_name, device_name_len);
          }
      
          printf("device productKey: %s\r\n", g_product_key);
          printf("device deviceName: %s\r\n", g_device_name);
      }
    • 相關說明:

      物聯網平台通過Topic /ext/auth/identity/response下發裝置的ProductKeyDeviceNamePayload格式為:

      {
          "productKey":"***",
          "deviceName":"***"
      }
  3. 配置狀態監控和訊息回調。

    1. 配置狀態監控回呼函數。

      • 範例程式碼:

         int main(int argc, char *argv[])
        {
            ...
            ...
        
            /* 配置MQTT預設訊息接收回呼函數。 */
            aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_HANDLER, (void *)demo_mqtt_default_recv_handler);
            /* 配置MQTT事件回呼函數。 */
            aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
            ...
            ...
        }
                                      
      • 相關參數:

        配置項

        樣本值

        說明

        AIOT_MQTTOPT_RECV_HANDLER

        demo_mqtt_default_recv_handler

        當接收訊息時,根據該回呼函數定義的處理邏輯,執行對應的處理。

        AIOT_MQTTOPT_EVENT_HANDLER

        demo_mqtt_event_handler

        當裝置串連狀態發生變化時,根據該回呼函數定義的處理邏輯,執行對應的處理。

    2. 定義狀態監控的回呼函數。

      重要
      • 避免定義過於耗時的事件處理邏輯,以免阻塞收包線程。

      • 串連狀態的變化包括網路異常、自動重連已成功、已中斷連線等。

      • 如果要根據串連狀態的變化做應對處理,可在TODO處,按照需要修改代碼。

      /* MQTT事件回呼函數, 當網路連接、重連或斷開時,觸發該函數, 事件定義見core/aiot_mqtt_api.h。 */
      void demo_mqtt_event_handler(void *handle, const aiot_mqtt_event_t *event, void *userdata)
      {
          switch (event->type) {
              /* 調用了aiot_mqtt_connect()介面, 與MQTT伺服器建立串連。 */
              case AIOT_MQTTEVT_CONNECT: {
                  printf("AIOT_MQTTEVT_CONNECT\n");
                  /* TODO: 處理SDK建立串連成功, 不可在此調用耗時較長的阻塞函數。 */
              }
              break;
      
              /* SDK因網路狀況被動中斷連線後, 成功自動發起重連。 */
              case AIOT_MQTTEVT_RECONNECT: {
                  printf("AIOT_MQTTEVT_RECONNECT\n");
                  /* TODO: 處理SDK重連成功, 不可在此調用耗時較長的阻塞函數。 */
              }
              break;
      
              /* SDK因網路狀況被動斷開了串連, network底層讀寫失敗, heartbeat沒有按預期得到服務端心跳應答。 */
              case AIOT_MQTTEVT_DISCONNECT: {
                  char *cause = (event->data.disconnect == AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT) ? ("network disconnect") :
                                ("heartbeat disconnect");
                  printf("AIOT_MQTTEVT_DISCONNECT: %s\n", cause);
                  /* TODO: 處理SDK被動中斷連線, 不可在此調用耗時較長的阻塞函數。 */
              }
              break;
      
              default: {
      
              }
          }
      }
                                      
    3. 定義訊息接收的回呼函數。

      重要
      • 避免定義過於耗時的事件處理邏輯,以免阻塞收包線程。

      • 如果您要根據接收的訊息做應對處理,可在TODO處,按照需要修改代碼。

      
      /* MQTT預設訊息處理回調, 當SDK從伺服器收到MQTT訊息時, 且您未設定對應回調的處理時,以下介面被調用。 */
      void demo_mqtt_default_recv_handler(void *handle, const aiot_mqtt_recv_t *packet, void *userdata)
      {
          switch (packet->type) {
              case AIOT_MQTTRECV_HEARTBEAT_RESPONSE: {
                  printf("heartbeat response\n");
                  /* TODO: 處理伺服器對心跳的回應, 一般不處理。 */
              }
              break;
      
              case AIOT_MQTTRECV_SUB_ACK: {
                  printf("suback, res: -0x%04X, packet id: %d, max qos: %d\n",
                         -packet->data.sub_ack.res, packet->data.sub_ack.packet_id, packet->data.sub_ack.max_qos);
                  /* TODO: 處理伺服器對訂閱請求的回應, 一般不處理。 */
              }
              break;
      
              case AIOT_MQTTRECV_PUB: {
                  printf("pub, qos: %d, topic: %.*s\n", packet->data.pub.qos, packet->data.pub.topic_len, packet->data.pub.topic);
                  printf("pub, payload: %.*s\n", packet->data.pub.payload_len, packet->data.pub.payload);
                  /* TODO: 處理伺服器下發的業務報文。 */
              }
              break;
      
              case AIOT_MQTTRECV_PUB_ACK: {
                  printf("puback, packet id: %d\n", packet->data.pub_ack.packet_id);
                  /* TODO: 處理伺服器對QoS=1上報訊息的回應, 一般不處理。 */
              }
              break;
      
              default: {
      
              }
          }
      }

步驟三:請求串連

調用aiot_mqtt_connect向物聯網平台,發起串連認證請求。

/* 與伺服器建立MQTT串連。 */
    res = aiot_mqtt_connect(mqtt_handle);
    if (res < STATE_SUCCESS) {
        /* 嘗試建立串連失敗, 銷毀MQTT執行個體, 回收資源。 */
        aiot_mqtt_deinit(&mqtt_handle);
        printf("aiot_mqtt_connect failed: -0x%04X\n", -res);
        return -1;
    }

步驟四:開啟保活線程

調用aiot_mqtt_process,向伺服器發送心跳報文,使裝置保持長串連狀態,並重發QoS=1的未應答報文。

  1. 開啟保活線程。

        res = pthread_create(&g_mqtt_process_thread, NULL, demo_mqtt_process_thread, mqtt_handle);
        if (res < 0) {
            printf("pthread_create demo_mqtt_process_thread failed: %d\n", res);
            return -1;
        }
  2. 設定保活線程處理函數。

    void *demo_mqtt_process_thread(void *args)
    {
        int32_t res = STATE_SUCCESS;
    
        while (g_mqtt_process_thread_running) {
            res = aiot_mqtt_process(args);
            if (res == STATE_USER_INPUT_EXEC_DISABLED) {
                break;
            }
            sleep(1);
        }
        return NULL;
    }

步驟五:開啟接收線程

調用aiot_mqtt_recv,收取伺服器下發的MQTT訊息,根據訊息回呼函數,執行對應處理。在斷線時自動重連,根據事件回呼函數,執行對應處理。

  1. 開啟接收線程。

    
        res = pthread_create(&g_mqtt_recv_thread, NULL, demo_mqtt_recv_thread, mqtt_handle);
        if (res < 0) {
            printf("pthread_create demo_mqtt_recv_thread failed: %d\n", res);
            return -1;
        }
                                        
  2. 設定接收線程處理函數。

    void *demo_mqtt_recv_thread(void *args)
    {
        int32_t res = STATE_SUCCESS;
    
        while (g_mqtt_recv_thread_running) {
            res = aiot_mqtt_recv(args);
            if (res < STATE_SUCCESS) {
                if (res == STATE_USER_INPUT_EXEC_DISABLED) {
                    break;
                }
                sleep(1);
            }
        }
        return NULL;
    }

步驟六:訂閱Topic

調用aiot_mqtt_sub,訂閱指定Topic。

  • 範例程式碼:

        {
            char *sub_topic = "/a18wP******/LightSwitch/user/get";
    
            res = aiot_mqtt_sub(mqtt_handle, sub_topic, NULL, 1, NULL);
            if (res < 0) {
                printf("aiot_mqtt_sub failed, res: -0x%04X\n", -res);
                return -1;
            }
        }
    說明

    完成配置後,請刪除相關代碼兩邊的注釋符號。

  • 相關參數:

    參數

    樣本

    說明

    sub_topic

    /a18wP******/LightSwitch/user/get

    擁有訂閱許可權的Topic。

    • a18wP******為裝置的ProductKey。

    • LightSwitch為裝置的DeviceName。

    本樣本為預設的自訂Topic。

    裝置通過該Topic,可接收物聯網平台的訊息。

    關於Topic的更多資訊,請參見什麼是Topic

步驟七:發送訊息

調用aiot_mqtt_pub,向指定Topic發送訊息。

  • 範例程式碼:

         {
            char *pub_topic = "/a18wP******/LightSwitch/user/update";
            char *pub_payload = "{\"id\":\"1\",\"version\":\"1.0\",\"params\":{\"LightSwitch\":0}}";
    
            res = aiot_mqtt_pub(mqtt_handle, pub_topic, (uint8_t *)pub_payload, (uint32_t)strlen(pub_payload), 0);
            if (res < 0) {
                printf("aiot_mqtt_sub failed, res: -0x%04X\n", -res);
                return -1;
            }
        }
    說明

    完成配置後,請刪除相關代碼兩邊的注釋符號。

  • 相關參數:

    參數

    樣本

    說明

    pub_topic

    /a18wP******/LightSwitch/user/update

    擁有發布許可權的Topic。

    • a18wP******為裝置的ProductKey。

    • LightSwitch為裝置的DeviceName。

    裝置通過該Topic向物聯網平台發送訊息。

    關於Topic的更多資訊,請參見什麼是Topic

    pub_payload

    {\"id\":\"1\",\"version\":\"1.0\",\"params\":{\"LightSwitch\":0}}

    上報至物聯網平台的訊息內容。

    由於樣本訊息的Topic類型為自訂,因此資料格式可自訂。

    關於資料格式的更多資訊,請參見資料格式

裝置與物聯網平台建立MQTT通訊後,請確保通訊量不超過閾值。

步驟八:中斷連線

說明

MQTT接入常應用於長串連的裝置,程式通常不會運行至此。

常式的主線程任務為配置參數並成功建立串連。串連建立後,主線程可進入休眠。

調用aiot_mqtt_disconnect,向物聯網平台發送中斷連線的報文,然後斷開網路連接。

    res = aiot_mqtt_disconnect(mqtt_handle);
    if (res < STATE_SUCCESS) {
        aiot_mqtt_deinit(&mqtt_handle);
        printf("aiot_mqtt_disconnect failed: -0x%04X\n", -res);
        return -1;
    }

步驟九:退出程式

調用aiot_mqtt_deinit,銷毀MQTT用戶端執行個體,釋放資源。

    res = aiot_mqtt_deinit(&mqtt_handle);
    if (res < STATE_SUCCESS) {
        printf("aiot_mqtt_deinit failed: -0x%04X\n", -res);
        return -1;
    }

後續步驟