一、代码功能解析
-
核心功能
实现基于状态机的环形缓冲区帧解析器,支持协议格式:帧头(N字节) + 长度(1字节) + 数据载荷
。该设计符合常见通信协议解析模式。 -
状态机流程
switch (framehead_code->chk_step) {
case 0: // 帧头校验(含滑动对齐)
case 1: // 长度字段提取(含字节序转换)
case 2: // 有效载荷读取(边界检查)
采用三阶段状态机,与WebSocket帧解析的FIN-OPCODE-MASK-PAYLOAD
结构类似。
二、关键技术点
- 帧头校验机制
// 滑动窗口实现(类似TCP协议)
memmove(&ptr[0], &ptr[1], framehead_code->loaded_length);
通过memmove
实现字节滑动对齐,最多重试framehead_mem
次,该策略可防止无效数据阻塞缓冲区。
- 长度字段处理
// 大端/小端转换
if (framehead_code->egg_type == framelen_bigegg)
mx_lebeswap((uint8_t *)&frame_len, ...);
支持1/2/4字节长度字段,类似WebSocket的Payload Length
扩展机制。
- 缓冲区安全
if (frame_len > size) frame_len = size; // 防溢出截断
强制限制输出缓冲区长度,避免内存越界,该防护措施在嵌入式开发中尤为重要3
/**
* @file frame_parser.c
* @brief 基于环形缓冲区的协议帧解析器(协议格式:帧头+长度+数据)
* @version 2.1
* @date 2025-03-19
*
* @par 协议帧结构
* +----------------+---------------+----------------+
* | 帧头(N字节) | 长度(1字节) | 数据载荷(N字节) |
* +----------------+---------------+----------------+
*
* @warning 调用前需确保ptr缓冲区大小≥最大可能帧长度
*/
/* 状态机步骤枚举 */
typedef enum {
PARSE_STEP_HEADER, ///< 帧头解析阶段(含滑动对齐机制)
PARSE_STEP_LENGTH, ///< 长度字段提取阶段
PARSE_STEP_PAYLOAD ///< 有效载荷获取阶段
} ParseStep;
/**
* @brief 从环形缓冲区提取完整数据帧
* @param[in] mx 环形缓冲区对象(采用循环缓冲区设计[1](@ref))
* @param[in] frame_config 帧配置参数(含校验函数/长度偏移等)
* @param[out] output_buf 输出缓冲区地址(需预分配足够空间)
* @param[in] max_size 输出缓冲区最大容量
* @return 成功解析的帧字节数(0表示数据不足或校验失败)
*
* @note 状态机流程:
* 1. HEADER阶段:通过memmove实现滑动窗口检测帧头[1](@ref),最多重试5次
* 2. LENGTH阶段:解析长度字段(支持大端/小端格式)
* 3. PAYLOAD阶段:批量读取数据载荷(采用内存预分配策略[1](@ref))
*
* @warning 非线程安全,多线程环境需外部同步
*/
uint32_t mx_chk_ringbuff_frame(void *mx,
FrameConfig *frame_config,
uint8_t *output_buf,
uint32_t max_size)
{
/* 初始化局部变量缓存配置参数(减少指针解引用) */
const uint8_t header_len = frame_config->header_size;
uint8_t retry_count = 0;
//=============================================
// 阶段1:帧头检测与对齐(含滑动窗口机制)
//=============================================
while (frame_config->parse_step == PARSE_STEP_HEADER)
{
/* 计算环形缓冲区可用数据量(避免重复调用API) */
uint32_t available_data = mx_ringbuffer_available(mx);
/* 检查最小数据量要求:已加载长度 + 剩余长度 ≥ 帧头长度 */
if ((frame_config->loaded_bytes + available_data) < header_len) {
frame_config->retry_count = 0;
return 0; // 数据不足直接返回[1](@ref)
}
/* 填充帧头缓冲区(采用分批加载策略减少内存拷贝) */
mx_ringbuffer_read(mx,
output_buf + frame_config->loaded_bytes,
header_len - frame_config->loaded_bytes);
/* 执行自定义帧头校验(支持多模式匹配) */
if (frame_config->header_validator(output_buf,
frame_config->header_pattern,
header_len))
{
frame_config->parse_step = PARSE_STEP_LENGTH;
break; // 校验成功进入下一阶段
}
/* 帧头失配处理:滑动窗口右移1字节(内存高效管理[1](@ref)) */
memmove(output_buf, output_buf + 1, --frame_config->loaded_bytes);
if (++retry_count > MAX_HEADER_RETRIES) {
log_warn("Header validation failed after %d attempts", retry_count);
return 0; // 超过最大重试次数终止
}
}
//=============================================
// 阶段2:解析长度字段(含字节序转换)
//=============================================
if (frame_config->parse_step == PARSE_STEP_LENGTH)
{
/* 计算需要读取的字节数(含长度字段和保留位) */
const uint8_t len_field_size = 1 + frame_config->reserved_bytes;
const uint32_t expect_len = header_len + len_field_size;
/* 批量读取长度字段(减少多次小数据量读取) */
mx_ringbuffer_read(mx,
output_buf + header_len,
expect_len - frame_config->loaded_bytes);
/* 解析长度值(支持大端格式转换) */
uint32_t payload_len = 0;
memcpy(&payload_len,
output_buf + header_len - frame_config->len_offset,
len_field_size);
if (frame_config->is_big_endian) {
payload_len = ntohl(payload_len); // 网络字节序转换
}
/* 检查输出缓冲区容量限制(防止内存溢出[1](@ref)) */
if ((header_len + payload_len) > max_size) {
log_error("Frame size %u exceeds buffer capacity %u",
header_len + payload_len, max_size);
return 0;
}
frame_config->parse_step = PARSE_STEP_PAYLOAD;
}
//=============================================
// 阶段3:读取有效载荷(批量处理优化)
//=============================================
if (frame_config->parse_step == PARSE_STEP_PAYLOAD)
{
/* 计算剩余需要读取的字节数 */
const uint32_t total_frame_len = header_len + payload_len;
const uint32_t remaining = total_frame_len - frame_config->loaded_bytes;
/* 批量读取数据载荷(最大化读取效率[1](@ref)) */
mx_ringbuffer_read(mx,
output_buf + frame_config->loaded_bytes,
remaining);
/* 状态机重置为初始状态 */
frame_config->parse_step = PARSE_STEP_HEADER;
frame_config->loaded_bytes = 0;
return total_frame_len; // 返回成功解析的帧长度
}
return 0;
}