先看下cm_http.h头文件的定义,这里只保留了http回调相关的数据结构体
/**HTTP回调事件*/
typedef enum{
CM_HTTP_CALLBACK_EVENT_REQ_START_SUCC_IND=1, /*!< 请求启动成功事件,连接建立并发送请求头已开始 */
CM_HTTP_CALLBACK_EVENT_RSP_HEADER_IND, /*!< 接收到响应头事件,进入响应阶段 */
CM_HTTP_CALLBACK_EVENT_RSP_CONTENT_IND, /*!< 接收到响应体数据块事件 (可能多次回调)*/
CM_HTTP_CALLBACK_EVENT_RSP_END_IND, /*!< 响应接收结束(响应阶段完成) */
CM_HTTP_CALLBACK_EVENT_ERROR_IND, /*!< 请求过程或响应过程出现错误(如连接失败/超时/解析失败等) */
}cm_httpclient_callback_event_e;
/**HTTP异常状态码
* 运行过程中的异常事件码,通过回调在请求执行中途上报(函数已返回成功后)。
* 它描述执行过程中出现的问题(DNS 失败、连接/握手超时、SSL 失败、接收超时、解析失败等)。
*/
typedef enum{
CM_HTTP_EVENT_CODE_DNS_FAIL=1, /*!< DNS解析失败 */
CM_HTTP_EVENT_CODE_CONNECT_FAIL, /*!< 连接服务器失败 */
CM_HTTP_EVENT_CODE_CONNECT_TIMEOUT, /*!< 连接超时 */
CM_HTTP_EVENT_CODE_SSL_CONNECT_FAIL, /*!< SSL握手失败 */
CM_HTTP_EVENT_CODE_CONNECT_BREAK, /*!< 连接异常断开 */
CM_HTTP_EVENT_CODE_WAITRSP_TIMEOUT, /*!< 等待响应超时 */
CM_HTTP_EVENT_CODE_DATA_PARSE_FAIL, /*!< 数据解析失败 */
CM_HTTP_EVENT_CODE_CACHR_NOT_ENOUGH, /*!< 缓存空间不足 */
CM_HTTP_EVENT_CODE_DATA_DROP, /*!< 数据丢包 */
CM_HTTP_EVENT_CODE_WRITE_FILE_FAIL, /*!< 写文件失败 */
CM_HTTP_EVENT_CODE_UNKNOWN=255, /*!< 未知错误 */
}cm_httpclient_error_event_e;
/**HTTP结果码
* 接口返回码,表示你刚调用的函数是否立即成功或失败(参数错误、没有空闲客户端、连接/发送失败等)。
* 这是函数调用的同步结果
*/
typedef enum{
CM_HTTP_RET_CODE_OK = 0, /*!< 成功 */
CM_HTTP_RET_CODE_OPERATION_NOT_ALLOWED = 3, /*!< 操作不被允许 3 */
CM_HTTP_RET_CODE_MALLOC_FAIL = 23, /*!< 内存分配失败 23 */
CM_HTTP_RET_CODE_PARAM_ERROR = 50, /*!< 参数错误 50 */
CM_HTTP_RET_CODE_UNKNOWN_ERROR = 650, /*!< 未知错误 650 */
CM_HTTP_RET_CODE_NO_MORE_FREE_CLIENT = 651, /*!< 没有空闲客户端 651 */
CM_HTTP_RET_CODE_CLIENT_NOT_CREATE = 652, /*!< 客户端未创建 652 */
CM_HTTP_RET_CODE_CLIENT_IS_BUSY = 653, /*!< 客户端忙 653 */
CM_HTTP_RET_CODE_URL_PARSE_FAIL = 654, /*!< URL解析失败 654 */
CM_HTTP_RET_CODE_SSL_NOT_ENABLE = 655, /*!< SSL未使能 655 */
CM_HTTP_RET_CODE_CONNECT_FAIL = 656, /*!< 连接失败 656 */
CM_HTTP_RET_CODE_SEND_DATA_FAIL = 657, /*!< 数据发送失败 657 */
CM_HTTP_RET_CODE_OPEN_FILE_FAIL = 658, /*!< 打开文件失败 658 */
} cm_httpclient_ret_code_e;
/**HTTP实例句柄*/
typedef void *cm_httpclient_handle_t;
/**HTTP回调*/
/*
* @param [in] client_handle 客户端句柄
* @param [in] event 回调消息事件
* @param [in] param 事件相关对应响应数据
*
* CM_HTTP_CALLBACK_EVENT_REQ_START_SUCC_IND事件类型 传NULL
* CM_HTTP_CALLBACK_EVENT_RSP_HEADER_IND事件类型 传cm_httpclient_callback_rsp_header_param_t
* CM_HTTP_CALLBACK_EVENT_RSP_CONTENT_IND事件类型 传cm_httpclient_callback_rsp_content_param_t
* CM_HTTP_CALLBACK_EVENT_RSP_END_IND事件类型 传NULL
* CM_HTTP_CALLBACK_EVENT_ERROR_IND事件类型 传cm_httpclient_error_event_e
*/
typedef void (*cm_httpclient_event_callback_func)(cm_httpclient_handle_t client_handle, cm_httpclient_callback_event_e event, void *param);
/**HTTP相关回调函数*/
typedef struct {
uint32_t total_len; /*!< 数据总长度,不包含消息头(chunked模式时为0) */
uint32_t sum_len; /*!< 已接收长度 */
uint32_t current_len; /*!< 当前包长度,本次回调中这块数据的长度 */
const uint8_t *response_content; /*!< 指向本次收到的数据块*/
}cm_httpclient_callback_rsp_content_param_t;
/**
* @brief uri编码(不对字母数字以及-_.!~*'();/?:@&=+$,#进行编码)
*
* @param [in] src 原始数据
* @param [in] len 原始数据长度
*
* @return 编码数据/NULL 失败
*
* @details uri编码,不对字母数字以及-_.!~*'();/?:@&=+$,#进行编码,返回值为内部分配空间使用完后,需自行释放。不支持中文域名解析。
*/
uint8_t *cm_httpclient_uri_encode(const uint8_t *src, const uint32_t len);
/**
* @brief uri编码(不对字母数字以及-_.!~*'()进行编码)
*
* @param [in] src 原始数据
* @param [in] len 原始数据长度
*
* @return 编码数据/NULL 失败
*
* @details uri编码,不对字母数字以及-_.!~*'()进行编码,返回值为内部分配空间使用完后,需自行释放。不支持中文域名解析。
*/
uint8_t *cm_httpclient_uri_encode_component(const uint8_t *src, const uint32_t len);
/**
* @brief 创建客户端实例
*
* @param [in] url 服务器地址(服务器地址url需要填写完整,例如(服务器url仅为格式示例)"https://39.106.55.200:80")
* @param [in] callback 客户端相关回调函数(使用cm_httpclient_sync_request()同步接口可忽略该参数,传NULL即可)
* @param [out] handle 实例句柄
*
* @return 0 成功/其他 失败(见cm_httpclient_ret_code_e)
*
* @details 创建客户端实例
*/
cm_httpclient_ret_code_e cm_httpclient_create(const uint8_t *url, cm_httpclient_event_callback_func callback, cm_httpclient_handle_t *handle);
/**
* @brief 删除客户端实例
*
* @param [in] handle 客户端实例句柄
*
* @return 0 成功/其他 失败(见cm_httpclient_ret_code_e)
*
* @details 本接口会将close socket(HTTP)操作发送至eloop模块中让其执行close socket操作。本接口返回成功代表操作已发送至eloop中,不代表已完成实例删除操作。用户连续两次调用本接口时建议中间保证100ms的延时。
*/
cm_httpclient_ret_code_e cm_httpclient_delete(cm_httpclient_handle_t handle);
/**
* @brief 检测是否在执行请求
*
* @param [in] client 客户端实例句柄
*
* @return false 未执行请求/ true 正在执行请求
*
* @details 检测是否在执行请求
*/
bool cm_httpclient_is_busy(cm_httpclient_handle_t handle);
/**
* @brief 客户端参数设置
*
* @param [in] handle 客户端实例句柄
* @param [in] cfg 客户端配置
*
* @return 0 成功/其他 失败(见cm_httpclient_ret_code_e)
*
* @details 客户端参数设置,创建实例后设置,请求过程中不可设置;服务器应答重定向信息的情况下,采用cm_httpclient_set_cfg()配置的参数连接重定向的服务器
*/
cm_httpclient_ret_code_e cm_httpclient_set_cfg(cm_httpclient_handle_t handle, cm_httpclient_cfg_t cfg);
/**
* @brief 终止http连接
*
* @param [in] handle 客户端实例句柄
*
* @return
*
* @details 终止http连接
*/
void cm_httpclient_terminate(cm_httpclient_handle_t handle);
/**
* @brief 设置通用报头
*
* @param [in] handle 客户端实例句柄
* @param [in] header 报头内容
* @param [in] header_len 长度
*
* @return 0 成功/其他 失败(见cm_httpclient_ret_code_e)
*
* @details 设置通用报头,实例期间有效,不设置时,发送请求时将自动添加默认报头。
* 直接引用header指针,不做拷贝,需保证请求过程中不做修改和释放,如外部释放,也
* 需调用cm_httpclient_custom_header_free接口释放内部设置。
*/
cm_httpclient_ret_code_e cm_httpclient_custom_header_set(cm_httpclient_handle_t handle, uint8_t *header, uint16_t header_len);
/**
* @brief 释放通用报头
*
* @param [in] handle 客户端实例句柄
*
* @return 0 成功/其他 失败(见cm_httpclient_ret_code_e)
*
* @details 重置客户端内部指针为NULL、长度为0。
*/
cm_httpclient_ret_code_e cm_httpclient_custom_header_free(cm_httpclient_handle_t handle);
/**
* @brief 设置特定报头
*
* @param [in] handle 客户端实例句柄
* @param [in] header 报头内容
* @param [in] header_len 长度
*
* @return 0 成功/其他 失败(见cm_httpclient_ret_code_e)
*
* @details 设置特定报头,用于设置报头中,在不同请求时存在变化的字段,
* 如果通用报头中同样存在该字段,将在发送请求时自动替换为当前设置值。
* 直接引用header指针,不做拷贝,需保证请求过程中不做修改和释放,如外部释放,也
* 需调用cm_httpclient_specific_header_free接口释放内部设置。
*/
cm_httpclient_ret_code_e cm_httpclient_specific_header_set(cm_httpclient_handle_t handle, uint8_t *header, uint16_t header_len);
/**
* @brief 释放特定报头
*
* @param [in] handle 客户端实例句柄
*
* @return 0 成功/其他 失败(见cm_httpclient_ret_code_e)
*
* @details 重置客户端内部指针为NULL、长度为0。
*/
cm_httpclient_ret_code_e cm_httpclient_specific_header_free(cm_httpclient_handle_t handle);
/**
* @brief 开始发送请求
*异步接口
* @param [in] handle 客户端实例句柄
* @param [in] method 请求类型
* @param [in] path 请求路径
* @param [in] chunked 是否为chunked模式
* @param [in] content_length content数据总长度(chunked模式时无效)
*
* @return 0 成功/其他 失败(见cm_httpclient_ret_code_e)
*
* @details 开始发送请求,主要为建立连接,并发送报头,将在request_start_func回调中指示开始完成
* ,后续可发送content数据。
*/
cm_httpclient_ret_code_e cm_httpclient_request_start(cm_httpclient_handle_t handle, cm_httpclient_request_type_e method, const uint8_t *path,
bool chunked, uint32_t content_length);
/**
* @brief 发送消息体
* 异步接口
* @param [in] handle 客户端实例句柄
* @param [in] content 消息体内容
* @param [in] content_len 消息体长度
*
* @return 0 成功/其他 失败(见cm_httpclient_ret_code_e)
*
* @details 发送消息体,执行request_start_func回调后可使用。
* socket发送缓存空间64240,单次发送超过64240长度数据时可能会造成缓存区溢出从而导致发送失败。
*/
cm_httpclient_ret_code_e cm_httpclient_request_send(cm_httpclient_handle_t handle, const uint8_t *content, uint32_t content_len);
/**
* @brief 请求发送结束
* 异步接口
* @param [in] handle 客户端实例句柄
*
* @return 0 成功/其他 失败(见cm_httpclient_ret_code_e)
*
* @details 请求发送结束,chunked模式下将发送结束包,普通模式下无实际效果,可不执行。
*/
cm_httpclient_ret_code_e cm_httpclient_request_end(cm_httpclient_handle_t handle);
/**
* @brief 获取响应结果码
*
* @param [in] handle 客户端实例句柄
*
* @return -1 失败/其他 结果码
*
* @details 获取响应结果码。
*/
int32_t cm_httpclient_get_response_code(cm_httpclient_handle_t handle);
/**
* @brief 解析报头
*
* @param [in] header 需解析的报头
* @param [in] key 需要找到的字段key
* @param [out] value 需要找到的字段数据
*
* @return 解析到的数据长度
*
* @details 解析报头,用于解析接收到的报头,按键值对进行解析,输入相应key,返回对应value,
* value输出的为header中的指针位置,未单独分配空间。
*/
uint32_t cm_httpclient_parse_header(const uint8_t *header, const uint8_t *key, uint8_t **value);
/**
* @brief 发送请求(同步接口)
*
* @param [in] handle 客户端实例句柄
* @param [in] param 输入参数(见cm_httpclient_sync_param_t)
* @param [out] response 响应结果(见cm_httpclient_sync_response_t)
*
* @return 见cm_httpclient_ret_code_e
*
* @details 发送http请求同步接口,只可用于非chunk模式发送,需先创建实例并完成相关参数设置,
*响应结果中的数据未内部动态分配空间,使用完后可通过cm_httpclient_sync_free_data接口释放,
*下次请求时也会自动释放。
* socket发送缓存空间64240,单次发送超过64240长度数据时可能会造成缓存区溢出从而导致发送失败。
*/
cm_httpclient_ret_code_e cm_httpclient_sync_request(cm_httpclient_handle_t handle, cm_httpclient_sync_param_t param,
cm_httpclient_sync_response_t *response);
/**
* @brief 释放响应数据(同步接口)
*
* @param [in] handle 客户端实例句柄
*
* @return
*
* @details 释放响应数据。
*/
void cm_httpclient_sync_free_data(cm_httpclient_handle_t handle);
回调函数的创建和定义如下
const char *server_url = "http://xxx.com:8080"; //仅作为示例,url不可使用
const char *url_path = "/download/system_patch.bin"; //仅作为示例,url path不可使用
if(s_ota_http_handle == NULL)
{
//创建客户端实例,s_ota_http_handle为输出参数,用于保存客户端句柄
cm_httpclient_create((const uint8_t*)server_url, __cm_ota_download_cb, &s_ota_http_handle);//初始化并设置回调函数
}
if(s_ota_http_handle == NULL)//初始化失败
{
cm_demo_printf("http handhle init fail
");
return;
}
//开始发送请求
cm_httpclient_request_start(s_ota_http_handle, HTTPCLIENT_REQUEST_GET, (const uint8_t*)url_path, false, 0);
/**
* rief http下载回调函数
*
* param [in] client_handle http句柄
* param [in] event http回调类型
* param [in] param http数据
*
*
eturn None
*
* details 此示例下,http content内容即为差分包内容
*/
static void __cm_ota_download_cb(cm_httpclient_handle_t client_handle, cm_httpclient_callback_event_e event, void *param)
{
(void)client_handle;
int ret = 0;
// HTTP 异步下载回调,只有在事件为 CM_HTTP_CALLBACK_EVENT_RSP_CONTENT_IND 时处理数据
if(param == NULL || event != CM_HTTP_CALLBACK_EVENT_RSP_CONTENT_IND)
{
return;
}
/*写入升级包*/
cm_httpclient_callback_rsp_content_param_t *ota_pkg = (cm_httpclient_callback_rsp_content_param_t *)param;
//已接收长度等于本地回调数据块的大小时,表示这块数据接收完成,更新升级包总大小
if(ota_pkg->current_len == ota_pkg->sum_len)
{
cm_log_printf(0, "set total
");
cm_ota_set_otasize(ota_pkg->total_len);
}
//把本次数据块写入升级包文件
//这里的文件系统是模组内部可读写的闪存文件系统(如 SPI/NAND/NOR Flash 上的虚拟文件系统),用于暂存 OTA 升级包(差分/整包)。
//下载的数据先写到这个文件系统文件中,待下载完整后再由 OTA 逻辑从该文件读取并烧录升级区域
ret = cm_ota_firmware_write((const char*)ota_pkg->response_content, ota_pkg->current_len);
if(ret)
{
return;
}
//显示下载进度
cm_log_printf(0, "Writed: %d/%d - %d%%
",
/*ota_pkg->sum_len*/cm_ota_get_written_size(),
ota_pkg->total_len, ota_pkg->sum_len * 100 / ota_pkg->total_len);
if(ota_pkg->sum_len >= ota_pkg->total_len)//下载完成
{
if(ota_pkg->sum_len > cm_ota_get_written_size())
{
cm_demo_printf("download ota pkg err!
");
}
else
{
cm_demo_printf("download ota pkg complete!
");
}
}
}
回调问题的回答
ota_pkg->current_len、ota_pkg->sum_len、ota_pkg->current_len三者什么关系,在一次回调中还是在多次回调中更新?
total_len:整个响应体的总长度;若服务器是 chunked 传输,可能为 0。total_len 在一次 HTTP 响应生命周期内是恒定的:如果服务器返回了 Content-Length,它在第一块数据到来前就由底层 HTTP 客户端填好,后续每次回调值不变;如果是 chunked 传输,通常为 0(或保持 0)。
设置时机:建立连接并收到响应头后,HTTP 客户端解析响应头(含 Content-Length),在首个内容块触发回调前就将 total_len 写入。之后的每次 CM_HTTP_CALLBACK_EVENT_RSP_CONTENT_IND 回调都会带同一个 total_len。
current_len:本次回调这块数据的长度(单块大小)。
sum_len:到当前回调为止累计收到的长度(含本次块)
HTTP 响应体可能很大或分块传输,底层不会一次性把数据全部交给你,而是按到达/缓冲窗口大小分段推给应用层。每到一段数据就触发一次 CM_HTTP_CALLBACK_EVENT_RSP_CONTENT_IND 回调,并更新:
current_len:这一段的大小。
sum_len:累计已收到的总字节数(包含当前段)。
total_len:整体大小(非 chunked 时已知;chunked 可能为 0)。
这样做的好处:
节省内存:不用一次性分配整包缓冲。
边收边写:能流式写入文件/闪存,减少 RAM 占用。
进度可视:用 sum_len/total_len 实时显示进度。
适配 chunked/不定长响应:chunked 时总长未知,也能持续接收直到结束。
关系与更新方式:
每次收到一块数据都会触发一次 CM_HTTP_CALLBACK_EVENT_RSP_CONTENT_IND 回调。
在每次回调里,current_len 是这一块的大小,sum_len 会随着每次回调累加增长,直到等于或超过 total_len(非 chunked 时)。
当 current_len == sum_len 时,通常意味着这是第一块数据(之前累计为 0);示例代码借此在首块时调用 cm_ota_set_otasize 设置总大小。
进度计算用 sum_len / total_len,因此它是在多次回调中逐步更新的。
cm_httpclient_create函数中的回调函数,在什么情况下会被触发?
cm_httpclient_create 里的回调只在你用异步接口时才会触发(你把回调非 NULL 传进去,并调用 cm_httpclient_request_start / cm_httpclient_request_send / cm_httpclient_request_end 这类异步请求流程)。触发时机由事件枚举决定:
不会“调用就立即回调”,回调是按异步网络事件触发:
cm_httpclient_request_start 发起请求后,若成功,会先收到 REQ_START_SUCC,随后网络收响应头/响应体时再收 RSP_HEADER、多次 RSP_CONTENT,结束时 RSP_END,出错则 ERROR。
cm_httpclient_request_send 只是追加发送 body 数据,本身不必然触发新的回调;只有在发送/后续收响应时发生事件或错误才会回调。
cm_httpclient_request_end 表示请求体结束(含 chunked 结束块),本身不保证立刻有回调;后续进入响应阶段才会有 RSP_HEADER/RSP_CONTENT/RSP_END 或 ERROR。
所以回调由“通信进展或错误”驱动,不是每次调用 API 都同步触发。
CM_HTTP_CALLBACK_EVENT_REQ_START_SUCC_IND:连接建立、请求头开始发送完成。
CM_HTTP_CALLBACK_EVENT_RSP_HEADER_IND:收到响应头。
CM_HTTP_CALLBACK_EVENT_RSP_CONTENT_IND:收到一段响应体数据(可能多次回调,分块推送)。
CM_HTTP_CALLBACK_EVENT_RSP_END_IND:响应体收完。
CM_HTTP_CALLBACK_EVENT_ERROR_IND:请求/响应过程出错(DNS、连接/握手失败、超时、解析失败等)。
如果回调参数传 NULL,或者使用同步接口 cm_httpclient_sync_request,就不会有这些回调。
回调函数参数param由HTTP客户端库在触发回调时填好并传入。它不是用户传的:库根据不同事件传不同的内容指针,例如:
CM_HTTP_CALLBACK_EVENT_RSP_HEADER_IND事件类型:传 cm_httpclient_callback_rsp_header_param_t*
CM_HTTP_CALLBACK_EVENT_RSP_CONTENT_IND事件类型:传 cm_httpclient_callback_rsp_content_param_t*
CM_HTTP_CALLBACK_EVENT_ERROR_IND事件类型:传 cm_httpclient_error_event_e*(枚举值地址)
CM_HTTP_CALLBACK_EVENT_REQ_START_SUCC_IND / CM_HTTP_CALLBACK_EVENT_RSP_END_IND事件类型:通常传 NULL
你的回调只需要按事件类型把 param 转成对应的结构体/枚举使用。
cm_ota_firmware_write函数,最终将数据写到哪里?
只有在事件为 CM_HTTP_CALLBACK_EVENT_RSP_CONTENT_IND 时处理数据。每次收到一块响应体数据(ota_pkg),先在第一次块达到累计长度时设置总大小 cm_ota_set_otasize,然后把本次数据块写入升级包文件(cm_ota_firmware_write),打印进度,累计长度达到总长度后输出完成或错误提示。
“写入升级包到文件系统”:这里的文件系统是模组内部可读写的闪存文件系统(如 SPI/NAND/NOR Flash 上的虚拟文件系统)。数据先写到这个文件系统文件中,待下载完整后再由 OTA 逻辑从该文件读取并烧录升级区域。
cm_ota_firmware_write 会把传入的固件数据块写入模块内部的 OTA 存储区域,底层实现会选择模块预留的文件系统/Flash 区(用于暂存 OTA 升级包(差分/整包)),不是写到用户自定义路径。你在应用侧只需按顺序调用它流式写入整包,后续 cm_ota_upgrade() 会从这个内部缓存区读取并烧录。





















暂无评论内容