分片重组指令 (0x32)
对应源码:
components/airlink/src/exec_rpc/luat_airlink_cmd_exec_fragment.c启用宏: always (无条件启用)
cmd 0x32 用于 cmd payload 超过 1580 字节时的自动分片与重组. 常见应用场景是大体积 RPC 请求或大文件传输.
线格式
[ original_cmd : 2 bytes ] // 原始 cmd id (例如 0x30 表示 RPC)
[ reassembly_id : 2 bytes ] // 唯一重组会话 id
[ total_len : 2 bytes ] // 原始 cmd->data 的总长度
[ frag_index : 1 byte ] // 0-based 分片序号
[ frag_total : 1 byte ] // 总分片数
[ chunk : N bytes ] // 本片数据
最小头部 FRAG_HDR_LEN = 8 字节.
关键常量
来源:
components/airlink/src/exec_rpc/luat_airlink_cmd_exec_fragment.c
| 常量 | 值 | 含义 |
|---|---|---|
FRAG_HDR_LEN |
8 | 分片头部长度 |
FRAG_POOL_SIZE |
4 | 最大并发重组会话数 |
FRAG_TIMEOUT_MS |
2000 | 会话超时 (ms) |
FRAG_MAP_SIZE |
32 | 去重 bitmap 大小 (256 位, 支持最多 255 分片) |
AIRLINK_FRAG_MAX_DATA_LEN |
1580 | 单片最大数据 (1600 链路 − 12 链路头 − 4 cmd 头 − 8 frag 头 − ...) |
重组会话结构
typedef struct {
uint16_t reassembly_id;
uint16_t original_cmd;
uint16_t total_len;
uint8_t frag_total;
uint8_t received_count;
uint16_t received_bytes;
uint16_t chunk_max; // 从第一个非末位分片学习
uint8_t received_map[FRAG_MAP_SIZE]; // 256 位去重 bitmap
uint8_t* buffer; // 分配的: luat_airlink_cmd_t + total_len
uint64_t last_tick;
uint8_t active;
} frag_ctx_t;
static frag_ctx_t g_frag_pool[FRAG_POOL_SIZE];
static uint8_t g_frag_pool_hwm = 0;
重组算法
Offset 计算
按 frag_index (而非到达顺序) 计算:
| 分片类型 | 计算公式 |
|---|---|
| 非末位 (frag_index < frag_total - 1) | offset = frag_index * chunk_max |
| 末位 (frag_index == frag_total - 1) | offset = total_len - chunk_len |
协议不变式: 所有非末位分片必须等长 (chunk_max). 服务端从第一个非末位分片学习 chunk_max, 后续非末位分片若长度不一致则视为协议错误, 整个会话丢弃.
去重
通过 received_map[FRAG_MAP_SIZE] 256 位 bitmap, 每个分片到位即 set 对应位:
static int map_test(const uint8_t* map, uint8_t idx) {
return (map[idx / 8] >> (idx % 8)) & 1;
}
static void map_set(uint8_t* map, uint8_t idx) {
map[idx / 8] |= (uint8_t)(1 << (idx % 8));
}
重复到达的分片被静默忽略 (LLOGD + return 0).
完成判定
if (ctx->received_count == ctx->frag_total && ctx->received_bytes == total_len) {
// 重组完成, 投递到 airlink_task
luat_airlink_on_data_recv(buf, full_len);
}
满足两条:
- bitmap 全部 set (即 received_count == frag_total)
- 累计字节数等于 total_len (即 received_bytes == total_len)
完成后, 构造 luat_airlink_cmd_t 并通过 luat_airlink_on_data_recv() 投递回指令分发层, 与普通未分片 cmd 走同一路径.
超时清理
每次新分片到达时, 遍历活跃会话, tnow - last_tick > FRAG_TIMEOUT_MS (2s) 的会话视为过期:
if (tnow - g_frag_pool[i].last_tick <= FRAG_TIMEOUT_MS) continue;
luat_heap_opt_free(AIRLINK_MEM_TYPE, g_frag_pool[i].buffer);
memset(&g_frag_pool[i], 0, sizeof(frag_ctx_t));
客户端入口
来源:
components/airlink/src/luat_airlink.c:253-298
客户端 (例如 luat_airlink_send2slave) 检测到 payload > AIRLINK_FRAG_MAX_DATA_LEN 时自动分片:
original payload (L bytes)
↓
按 AIRLINK_FRAG_MAX_DATA_LEN 分成 N 片
↓
for i = 0..N-1:
frag_cmd = cmd_new(0x32, 8 + chunk_len)
frag_cmd->data = [original_cmd:2] [reassembly_id:2] [total_len:2] [i:1] [N:1] [chunk]
send2slave(frag_cmd)
限制
| 项目 | 值 |
|---|---|
| 单分片数据最大 | 1580 字节 |
| 单会话总分片数 | ≤ 255 (frag_total <= 255) |
| 重组后总数据上限 | ≤ 0xFFF0 (65520 字节) |
| 并发会话数 | ≤ 4 (超出时新会话直接丢弃, 返回 -2) |
一致性检查
会话内, 后续到达的分片必须满足:
original_cmd与会话初始一致frag_total与会话初始一致total_len与会话初始一致
不一致则整个会话丢弃, 释放 buffer, 重置会话槽位.