SDK的文件上傳功能使用HTTP2流式傳輸協議,將文件上傳至阿里云物聯網平臺服務器。

  • 支持多種上傳模式,如以創建文件的方式上傳,或以覆蓋文件的方式上傳。
  • 支持指定上傳長度,并在下次上傳時續傳,用戶可在上傳時根據網絡帶寬配置上傳分配大小(part_len),以提高帶寬利用效率。

本文以src/http2/http2_example_uploadfile.c為例講解如何使用文件上傳功能。

1. 與云端建立連接

調用IOT_HTTP2_UploadFile_Connect建立HTTP2連接,指定設備的證書信息和服務器地址或端口號。

    http2_upload_conn_info_t conn_info;
    void *handle;

    memset(&conn_info, 0, sizeof(http2_upload_conn_info_t));
    conn_info.product_key = HTTP2_PRODUCT_KEY;
    conn_info.device_name = HTTP2_DEVICE_NAME;
    conn_info.device_secret = HTTP2_DEVICE_SECRET;
    conn_info.url = HTTP2_ONLINE_SERVER_URL;
    conn_info.port = HTTP2_ONLINE_SERVER_PORT;

    handle = IOT_HTTP2_UploadFile_Connect(&conn_info, NULL);

    if(handle == NULL) {
        return -1;
    }
            

目前各個區域對應的域名和端口如下,其中*符號應使用設備的ProductKey替換,如ProductKeya1Ign******I時對應的URL、PORT如下:

#define HTTP2_ONLINE_SERVER_URL             "a1I******vI.iot-as-http2.cn-shanghai.aliyuncs.com"
#define HTTP2_ONLINE_SERVER_PORT            443
            
*.iot-as-http2.cn-shanghai.aliyuncs.com:443          // 上海正式
*.iot-as-http2.us-west-1.aliyuncs.com:443            // 美西正式
*.iot-as-http2.us-east-1.aliyuncs.com:443            // 美東正式
*.iot-as-http2.eu-central-1.aliyuncs.com:443         // 德國正式
*.iot-as-http2.ap-southeast-1.aliyuncs.com:443       // 新加坡正式
*.iot-as-http2.ap-northeast-1.aliyuncs.com:443       // 日本正式
            
說明 如果查看網絡狀態,可以注冊相應的回調函數,目前支持網絡斷開連接,和網絡重連成功兩個回調函數。

2. 文件上傳

使用IOT_HTTP2_UploadFile_Request請求文件上傳,示例程序以UPLOAD_FILE_OPT_BIT_OVERWRITE的方式上傳,每次上傳都會覆蓋云端文件。此接口為異步接口,可以插入多個上傳請求到內部隊列中。

    http2_upload_params_t fs_params;
    http2_upload_result_cb_t result_cb;

    memset(&result_cb, 0, sizeof(http2_upload_result_cb_t));
    result_cb.upload_completed_cb = upload_file_result;
    result_cb.upload_id_received_cb = upload_id_received_handle;

    memset(&fs_params, 0, sizeof(fs_params));
    fs_params.file_path = argv[1];      /* 文件名稱以命令行參數傳入 */
    fs_params.opt_bit_map = UPLOAD_FILE_OPT_BIT_OVERWRITE;

    ret = IOT_HTTP2_UploadFile_Request(handle, &fs_params, &result_cb, NULL);
    if(ret < 0) {
        return -1;
    }
            

示例程序中注冊了2個回調函數,分別用于接收上傳結果,和接收云端返回的上傳標示符(upload_id),在SDK調用了upload_file_result后,文件上傳操作結束,可進行下一步操作。

    void upload_file_result(const char *file_path, int result, void *user_data)
    {
        upload_end++;
        EXAMPLE_TRACE("=========== file_path = %s, result = %d, finish num = %d ===========", file_path, result, upload_end);
    }

    void upload_id_received_handle(const char *file_path, const char *upload_id, void *user_data)
    {
        EXAMPLE_TRACE("=========== file_path = %s, upload_id = %s ===========", file_path, upload_id);

        if (upload_id != NULL) {
            memcpy(g_upload_id, upload_id, strlen(upload_id));
        }
    }
            
在上傳過程中可以在Log中看到HTTP2的報文交互。
  1. 設備端請求云端打開文件上傳的通道。
    [inf] on_frame_send_callback(143): [INFO] C ---------> S (HEADERS) stream_id [1]
    [inf] on_frame_send_callback(145): > :method: POST
    [inf] on_frame_send_callback(145): > :path: /stream/open/c/iot/sys/thing/file/upload
    [inf] on_frame_send_callback(145): > :scheme: https
    [inf] on_frame_send_callback(145): > x-auth-name: devicename
    [inf] on_frame_send_callback(145): > x-auth-param-client-id: *******D7vI.H2_FS01
    [inf] on_frame_send_callback(145): > x-auth-param-signmethod: hmacsha1
    [inf] on_frame_send_callback(145): > x-auth-param-product-key: a1IgnO*****
    [inf] on_frame_send_callback(145): > x-auth-param-device-name: H2_FS01
    [inf] on_frame_send_callback(145): > x-auth-param-sign: 8d6b80749ed63823d**********
    [inf] on_frame_send_callback(145): > x-sdk-version: 301
    [inf] on_frame_send_callback(145): > x-sdk-version-name: 3.0.1
    [inf] on_frame_send_callback(145): > x-sdk-platform: c
    [inf] on_frame_send_callback(145): > content-length: 0
    [inf] on_frame_send_callback(145): > x-file-name: upload1M
    [inf] on_frame_send_callback(145): > x-file-overwrite: 1
    [inf] on_begin_headers_callback(393): [INFO] C <--------- S (HEADERS) stream_id [1]
    [inf] on_header_callback(363): < :status: 200
    [inf] on_header_callback(363): < x-request-id: 11039195007*******
    [inf] on_header_callback(363): < x-next-append-position: 0
    [inf] on_header_callback(363): < x-data-stream-id: DS1103919500********
    [inf] on_header_callback(363): < x-file-upload-id: ULDS11039195********
    [inf] on_header_callback(363): < x-response-status: 200
                
  2. 通道打開成功,接收到云端返回的文件上傳標示符,并調用用戶回調函數。
    upload_id_received_handle|037 :: =========== file_path = upload1M, upload_id = ULDS11039195008******** ===========
                
  3. 通道打開成功后,設備端通過HTTP2請求上傳文件。
    [inf] on_frame_send_callback(143): [INFO] C ---------> S (HEADERS) stream_id [3]
    [inf] on_frame_send_callback(145): > :method: POST
    [inf] on_frame_send_callback(145): > :path: /stream/send/c/iot/sys/thing/file/upload
    [inf] on_frame_send_callback(145): > :scheme: https
    [inf] on_frame_send_callback(145): > content-length: 1048576
    [inf] on_frame_send_callback(145): > x-data-stream-id: DS1103919500********
    [inf] on_frame_send_callback(145): > x-sdk-version: 301
    [inf] on_frame_send_callback(145): > x-sdk-version-name: 3.0.1
    [inf] on_frame_send_callback(145): > x-sdk-platform: c
    [inf] on_frame_send_callback(145): > x-file-upload-id: ULDS1103919500********
    [dbg] http2_stream_node_search(168): stream node not exist, stream_id = 3
    [inf] send_callback(63): send_callback data len 10249, session->remote_window_size=16777215!
    [inf] send_callback(72): send_callback data ends len = 10249!
    [dbg] http2_stream_node_search(168): stream node not exist, stream_id = 3
    [inf] iotx_http2_client_send(563): nghttp2_session_send 0
    [dbg] _http2_fs_part_send_sync(250): send len = 10240
    [inf] send_callback(63): send_callback data len 10249, session->remote_window_size=16766975!
    [inf] send_callback(72): send_callback data ends len = 10249!
    [inf] iotx_http2_client_send(563): nghttp2_session_send 0
    [dbg] _http2_fs_part_send_sync(250): send len = 20480
    [inf] send_callback(63): send_callback data len 10249, session->remote_window_size=16756735!
    [inf] send_callback(72): send_callback data ends len = 10249!
    [inf] iotx_http2_client_send(563): nghttp2_session_send 0
    [dbg] _http2_fs_part_send_sync(250): send len = 30720
    [inf] send_callback(63): send_callback data len 10249, session->remote_window_size=16746495!
    [inf] send_callback(72): send_callback data ends len = 10249!
    [inf] iotx_http2_client_send(563): nghttp2_session_send 0
    [dbg] _http2_fs_part_send_sync(250): send len = 40960
                
  4. 文件上傳結束,等待云端上傳結構應答,應答中的x-next-append-position是已上傳文件的大小。
    [inf] on_frame_recv_callback(196): on_frame_recv_callback, type = 8
    [inf] on_frame_recv_callback(197): on_frame_recv_callback, stream_id = 3
    [inf] on_frame_recv_callback(205): stream user data is not exist
    [inf] on_begin_headers_callback(393): [INFO] C <--------- S (HEADERS) stream_id [3]
    [inf] on_header_callback(363): < :status: 200
    [inf] on_header_callback(363): < x-request-id: 11039195011********
    [inf] on_header_callback(363): < x-next-append-position: 1048576
    [inf] on_header_callback(363): < x-data-stream-id: DS110391950088********
    [inf] on_header_callback(363): < x-response-status: 200
    [inf] on_frame_recv_callback(196): [dbg] _http2_fs_part_send_sync(250): on_frame_recv_callback, type = 1
    [inf] on_frame_recv_callback(197): on_frame_recv_callback, stream_id = 3
    [inf] on_frame_recv_callback(205): send len = 1048576
    [inf] _http2_fs_node_handle(350): file offset = 1048576 now
                
  5. SDK關閉文件上傳通道。
    [inf] on_frame_send_callback(143): [INFO] C ---------> S (HEADERS) stream_id [5]
    [inf] on_frame_send_callback(145): > :method: POST
    [inf] on_frame_send_callback(145): > :path: /stream/close/c/iot/sys/thing/file/upload
    [inf] on_frame_send_callback(145): > :scheme: https
    [inf] on_frame_send_callback(145): > x-data-stream-id: DS1103919500********
    [inf] on_frame_send_callback(145): > x-sdk-version: 301
    [inf] on_frame_send_callback(145): > x-sdk-version-name: 3.0.1
    [inf] on_frame_send_callback(145): > x-sdk-platform: c
    [dbg] http2_stream_node_search(168): stream node not exist, stream_id = 5
    [inf] iotx_http2_client_send(563): nghttp2_session_send 0
    [inf] on_begin_headers_callback(393): [INFO] C <--------- S (HEADERS) stream_id [5]
    [inf] on_header_callback(363): < :status: 200
    [inf] on_header_callback(363): < x-request-id: 11039195021********
    [inf] on_header_callback(363): < x-data-stream-id: DS1103919500*********
    [inf] on_header_callback(363): < x-file-crc64ecma: 694777069228*********
    [inf] on_header_callback(363): < x-response-status: 200
    [inf] on_header_callback(363): < x-file-store-id: 101184
                

3. 斷開連接

所有文件上傳結束后使用IOT_HTTP2_UploadFile_Disconnect斷開云端連接。

ret = IOT_HTTP2_UploadFile_Disconnect(handle);
            

功能API接口

src/http2/http2_upload_api.h列出了HTTP2文件上傳的所有API和相關數據類型定義。

src/http2/http2_wrapper.h列出了HTTP2所需的底層接口

HTTP2建立連接

接口原型

void *IOT_HTTP2_UploadFile_Connect(http2_upload_conn_info_t *conn_info, http2_status_cb_t *cb);
            

接口說明

創建HTTP2連接,并注冊相關狀態回調函數,此接口為同步接口,當建連成功后會返回HTTP2連接句柄,否則返回NULL。

參數說明

參數 數據類型 方向 說明
conn_info http2_upload_conn_info_t * 輸入 設備連接信息。
cb http2_status_cb_t * 輸入 設備狀態回調函數結構體指針。

返回值說明

說明
非NULL 建連成功。
NULL 建連失敗。

參數附加說明

typedef struct {
    char  *product_key;
    char  *device_name;
    char  *device_secret;
    char  *url;
    int   port;
} http2_upload_conn_info_t;
            
  • product_key:產品標示符。
  • device_name:設備名稱。
  • device_secret:識別密鑰。
  • url:云端服務器地址。
  • port:云端服務器端口。
typedef struct {
    http2_disconnect_cb_t   on_disconnect_cb;
    http2_reconnect_cb_t    on_reconnect_cb;
} http2_status_cb_t;
            
  • on_disconnect_cb:HTTP2斷連回調函數。
  • on_reconnect_cb:HTTP2重連回調函數。

文件上傳請求

接口原型

int IOT_HTTP2_UploadFile_Request(void *http2_handle, http2_upload_params_t *params, http2_upload_result_cb_t *cb, void *user_data);
            

接口說明

按照指定參數配置上傳文件,并注冊相關結果回調函數,此接口為異步接口,上傳結果由回調函數返回。

參數說明

參數 數據類型 方向 說明
http2_handle void * 輸入 調用IOT_HTTP2_UploadFile_Connect建連成功后返回的句柄。
params http2_upload_params_t * 輸入 上傳參數結構體指針。
cb http2_upload_result_cb_t 輸入 上傳結構回調函數結構體指針。
user_data void * 輸入 用戶數據。

返回值說明

說明
0 函數調用成功。
< 0 函數調用失敗。
typedef struct {
    const char *file_path;      /* file path, filename must be ASCII string and strlen < 2014 */
    uint32_t part_len;          /* maximum content len of one http2 request, must be in range of 100KB ~ 100MB */
    const char *upload_id;      /* a specific id used to indicate one upload session, only required when UPLOAD_FILE_OPT_BIT_RESUME option set */
    uint32_t upload_len;        /* used to indicate the upload length, only required when UPLOAD_FILE_OPT_BIT_SPECIFIC_LEN option set */
    uint32_t opt_bit_map;       /* option bit map, support UPLOAD_FILE_OPT_BIT_OVERWRITE, UPLOAD_FILE_OPT_BIT_RESUME and UPLOAD_FILE_OPT_BIT_SPECIFIC_LEN */
} http2_upload_params_t;
            
  • file_path:文件路徑。
    說明 文件名必須為ASCII編碼,且不能使用數字開頭。
  • part_len:文件上傳分片大小,即HTTP2請求content_len的最大長度, 必須在100KB ~ 100MB范圍內, 否則會使用http2_config.h中的默認長度。
  • upload_id:上傳標示符,首次上傳時返回。在需要使用斷點續傳方式上傳時需添加opt_bit_map參數UPLOAD_FILE_OPT_BIT_RESUME, 并指定對應上傳標示符。
  • upload_len:指定本次請求的長度,僅在opt_bit_map參數中有配置UPLOAD_FILE_OPT_BIT_SPECIFIC_LEN時才能起作用。
  • opt_bit_map:位定義的選項表,可以使用按位或的方式配置此選項。

    opt_bit_map = UPLOAD_FILE_OPT_BIT_OVERWRITE | UPLOAD_FILE_OPT_BIT_SPECIFIC_LEN表示使用覆蓋方式上傳指定的文件長度。

#define UPLOAD_FILE_OPT_BIT_OVERWRITE       (0x00000001)
#define UPLOAD_FILE_OPT_BIT_RESUME          (0x00000002)
#define UPLOAD_FILE_OPT_BIT_SPECIFIC_LEN    (0x00000004)
            
  • UPLOAD_FILE_OPT_BIT_OVERWRITE:使用覆蓋的方式上傳文件。如果云端文件已存在,而未使用覆蓋方式,則文件上傳會失敗。未使用此選項,將使用創建文件的方式上傳文件。
  • UPLOAD_FILE_OPT_BIT_RESUME:使用斷點續傳的方式上傳文件。使用此選項需填寫上傳標示符參數upload_id
  • UPLOAD_FILE_OPT_BIT_SPECIFIC_LEN:使用指定長度的方式上傳。使用此選項需要填寫上傳長度參數(upload_len),否則將上傳整個文件。
typedef struct {
    http2_upload_id_received_cb_t   upload_id_received_cb;
    http2_upload_completed_cb_t     upload_completed_cb;
} http2_upload_result_cb_t;
            
  • upload_id_received_cb:接收到云端服務器返回的上傳標示符時,將調用此回調函數。
  • upload_completed_cb:文件上傳結束時,將調用此回調函數,result參數指示了上傳結果。

HTTP2斷開連接

接口原型

int IOT_HTTP2_UploadFile_Disconnect(void *handle);
            

接口說明

斷開參數handle指定的HTTP2連接。

參數說明

參數 數據類型 方向 說明
http2_handle void * 輸入 調用IOT_HTTP2_UploadFile_Connect建連成功后返回的句柄。

返回值說明

說明
0 函數調用成功。
< 0 函數調用失敗。

需要對接的HAL接口

文件src/http2/http2_wrapper.h中包含了對接HTTP2文件上傳需要適配的部分HAL接口。

函數名 說明
HAL_SSL_Destroy 銷毀一個TLS連接,用于MQTT功能、HTTPS功能。
HAL_SSL_Establish 建立一個TLS連接,用于MQTT功能、HTTPS功能。
HAL_SSL_Read 從一個TLS連接中讀數據,用于MQTT功能、HTTPS功能。
HAL_SSL_Write 向一個TLS連接中寫數據,用于MQTT功能、HTTPS功能。
HAL_MutexCreate 創建一個互斥量對象。
HAL_MutexDestroy 銷毀一個互斥量對象。
HAL_MutexLock 鎖住一個互斥量。
HAL_MutexUnlock 解鎖一個互斥量。
HAL_SemaphoreCreate 創建信號量。
HAL_SemaphoreDestroy 銷毀信號量。
HAL_SemaphorePost post信號量。
HAL_SemaphoreWait 等待信號量。
HAL_ThreadCreate 創建線程。
HAL_ThreadDelete 銷毀線程。
HAL_ThreadDetach 分離線程。
HAL_Fopen 打開文件。
HAL_Fread 讀取文件數據。
HAL_Fwrite 向文件寫入數據。
HAL_Fseek 設置文件指針stream的位置。
HAL_Fclose 關閉文件。
HAL_Ftell 得到文件位置指針當前位置相對于文件首的偏移字節數。
HAL_Printf 打印函數。
HAL_SleepMs 睡眠函數。
HAL_Malloc 內存分配。
HAL_Free 內存釋放。