4. CoAP选项系统与内容协商
CoAP把“请求什么资源、以什么格式返回、缓存多久”这些语义全部编码进选项(Option)字段。与HTTP把方法、路径、头字段混在文本行里不同,CoAP选项采用二进制“编号+长度+值”的TLV结构,并规定编号越小越靠前、同类选项可重复出现。理解选项系统,是设备端资源模型能否被正确寻址、正确表示、正确缓存的关键。
选项的编码规则与排序
每个选项由三部分构成:Option Delta(相对上一个选项编号的增量)、Option Length(值长度)、Option Value。Delta和Length各占4位,当值小于13时直接编码;13~268用“13+扩展字节”表示;269~65804用“14+两字节扩展”表示。这种设计让常见选项(如Uri-Path编号11)只占1字节头部,非常适合NB-IoT、LoRa等受限链路。
选项必须按编号升序排列。若同一编号出现多次,则按出现顺序拼接语义。例如Uri-Path编号11重复出现,就表示路径被“/”分段:Uri-Path="sensors"、Uri-Path="temp"等价于HTTP的/sensors/temp。设备端解析时不能假设选项连续,必须按Delta累加还原真实编号。
核心选项的语义与设备端处理
Uri-Path(11):定位资源,可重复。设备端通常维护一棵资源树,解析时逐段匹配。例如收到Uri-Path="dev"、Uri-Path="sensor"、Uri-Path="temp",就应命中/dev/sensor/temp这个资源对象。
Uri-Query(15):传递查询参数,可重复,格式为key=value。它不参与资源定位,而是影响资源表示。例如Uri-Query="unit=c"要求温度以摄氏度返回,Uri-Query="avg=10"要求返回10秒均值。设备端应在资源处理函数中读取这些参数,而不是把它们当作路径的一部分。
Content-Format(12):出现在请求中表示请求体格式,出现在响应中表示响应体格式。常用取值:0表示text/plain;charset=utf-8,50表示application/json,60表示application/cbor。设备端发送响应时必须显式设置该选项,否则客户端无法正确解码。
Accept(17):客户端声明“我希望收到什么格式”。设备端应据此选择最合适的表示。若无法满足,应返回4.06 Not Acceptable,而不是强行返回不支持的格式。
Max-Age(14):响应中表示该表示可缓存的最大秒数,默认60。设备端对变化缓慢的资源(如固件版本)可设较大值,对实时传感器数据应设较小值甚至0,避免客户端使用过期数据。
此外还有ETag(4)用于版本校验、If-None-Match(5)用于条件请求、Block1/Block2(27/23)用于分块传输,它们共同支撑起完整的资源表示协商链路。
设备端实现要点
在嵌入式设备上实现选项处理,建议把“解析”和“业务”分离。解析层负责把原始字节流还原成option_list结构,业务层只读取已命名的选项值。下面是一段伪代码示例,展示如何从选项列表中提取路径与查询参数:
// 伪代码:选项解析与资源匹配
option_t *opt = parse_options(packet);
char path[64] = "/";
char query[64] = "";
for (int i = 0; i < opt_count; i++) {
switch (opt[i].number) {
case 11: // Uri-Path
strcat(path, opt[i].value);
strcat(path, "/");
break;
case 15: // Uri-Query
strcat(query, opt[i].value);
break;
case 17: // Accept
accept_format = atoi(opt[i].value);
break;
}
}
resource_t *res = find_resource(path);
if (!res) return coap_error(4.04);
if (accept_format != 0 && accept_format != res->format) {
return coap_error(4.06); // Not Acceptable
}
// 处理查询参数
if (strstr(query, "unit=c")) {
convert_to_celsius(res->value);
}
coap_response(2.05, res->format, res->payload, max_age=30);
这段代码体现了三个关键判断:路径不存在返回4.04,格式不匹配返回4.06,查询参数影响表示内容。设备端必须显式设置Content-Format和Max-Age,否则客户端可能按默认值处理,导致解析错误或缓存过期。
常见问题与排查
实际调试中,选项相关故障往往表现为“资源找不到”或“数据乱码”。可按以下顺序排查:
- 路径匹配失败:检查Uri-Path是否被正确分段。有些客户端把
/dev/sensor/temp编码为单个Uri-Path值,此时设备端需要按“/”再拆分一次。 - 格式协商失败:确认Accept值是否在设备支持列表内。若设备只支持CBOR(60),而客户端请求JSON(50),应返回4.06而非空响应。
- 缓存不一致:检查Max-Age是否设置合理。对温度这类快变数据,Max-Age建议不超过10秒;对固件版本可设3600秒。
- 选项顺序错误:CoAP要求选项按编号升序排列。若设备端手工构造报文时把Content-Format放在Uri-Path之前,部分客户端会直接丢弃报文。
/dev/sensor/temp支持50和60,默认Max-Age=10;/fw支持0,默认Max-Age=3600。这样协商逻辑可以统一由框架处理,业务代码只需关注数据本身。
掌握选项系统后,设备端就不再只是“被动应答”,而是能根据客户端声明的Accept、Uri-Query和缓存策略,主动选择最合适的表示形式。这正是CoAP内容协商的核心价值:在受限网络中,用最小的开销完成格式、语言、缓存策略的协商,让资源表示与客户端期望精确对齐。