跳转至

分片重组指令 (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);
}

满足两条:

  1. bitmap 全部 set (即 received_count == frag_total)
  2. 累计字节数等于 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, 重置会话槽位.

问一下 AI