
RIOT unicoap 消息 API 实战指南CoAP PDU 的解析与序列化【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT导读unicoap是 RIOT 操作系统中的新一代 CoAP 实现其核心价值在于把 CoAP 消息的解析、选项操作与序列化封装成一组安全、易用且适合资源受限节点的 C API。本文以官方文档《Using Message APIs》为主体围绕示例应用examples/networking/coap/unicoap_message完整演示如何把一段字节流PDU解析为unicoap_message_t消息、如何读写各类 CoAP 选项以及如何把构造好的消息重新序列化为可发送的 PDU。读完本文你将掌握unicoap消息层 API 的完整调用链能够独立在 RIOT 应用中收发、检查与构造 CoAP 报文。一、认识 unicoap 消息 API在unicoap中消息与**传输格式PDU**是两个相互独立的概念unicoap_message_t是与传输无关的中央容器类型承载 CoAP 代码Code、载荷Payload与选项集合Options见 message 文档CoAP 报文在不同传输UDP、DTLS、Slipmux下头部格式不同因此解析/序列化按传输提供专用函数例如 RFC 7252 格式对应unicoap_pdu_parse_rfc7252_result与unicoap_pdu_build_rfc7252。从源码结构看解析与序列化各分两步完成见 pdu 文档先由传输驱动解析/写入头部到选项之前的部分再由unicoap公共实现解析/写入选项区。这样既复用了选项处理逻辑又保留了传输差异。本文展示的完整可编译示例位于 examples/networking/coap/unicoap_message在主机上即可运行BOARDnative make flash term程序会依次打印解析示例消息与创建示例消息两个阶段的输出与下文代码一一对应。二、Bytes to Message从 PDU 反序列化2.1 解析一段 PDU假设pdu是缓冲区中收到的 CoAP PDU以 RFC 7252 报文为例const uint8_t pdu[] { /* ... */ };手工解析需要依次完成分配消息结构、分配选项结构、分配属性结构、把选项挂接到消息等一整套样板工作。unicoap为此提供了聚合辅助结构unicoap_parser_result_t一次分配即可unicoap_parser_result_t parsed { 0 };然后直接调用针对 RFC 7252 传输的解析器。由于 CoAP 支持多种传输PDU 头部各不相同若报文来自 UDP 或 DTLS使用 RFC 7252 格式即可if ((res unicoap_pdu_parse_rfc7252_result(pdu, sizeof(pdu), parsed)) 0) { puts(Error: parsing failed); return; } unicoap_message_t* message parsed.message;从实现上看unicoap_pdu_parse_rfc7252_result只是一个内联包装它把parsed-options挂到parsed-message.options上再转调底层unicoap_pdu_parse_rfc7252。这样调用方就无需关心选项缓冲区的分配与装配。该函数失败时返回负 errno例如选项非法返回-EBADOPT选项缓冲区过小返回-ENOBUFS。由于头部随传输变化像 RFC 7252 消息类型Confirmable/Non-confirmable 等与 Message ID 这类传输相关字段统一通过unicoap_message_properties_t的rfc7252成员访问printf(CoAP message has token%i bytes\n, parsed.properties.token_length); printf(CoAP over UDP/DTLS has id%i type%s\n, parsed.properties.rfc7252.id, unicoap_string_from_rfc7252_type(parsed.properties.rfc7252.type));unicoap_string_from_rfc7252_type会把数值类型转换为可打印的常量 C 字符串便于日志输出。2.2 检查消息类别unicoap提供三个谓词函数区分消息类别unicoap_message_code_is_request—— 请求Requestunicoap_message_code_is_response—— 响应Responseunicoap_message_code_is_signal—— 信令消息Signal如 RFC 7252 的 CSM/Ping/Pong/Release/Abort。消息代码Code本身是一个字节但unicoap_message_t提供三种类型化视图分别对应三类消息message-method——unicoap_method_tGET/POST/PUT/DELETE 等请求方法message-status——unicoap_status_t2.05 Content、4.04 Not Found 等响应状态码message-signal—— 信令码。获取可读字符串也有对应版本unicoap_string_from_method、unicoap_string_from_status、unicoap_string_from_signal。如果不想先判断消息类别可以统一调用unicoap_string_from_code它会根据 Code 自动生成描述字符串const char* method_name unicoap_string_from_method(message-method);载荷本身通过message-payload与message-payload_size字节数访问。2.3 读取选项调试阶段可以直接把所有选项打印到标准输出unicoap_options_dump_all(message-options);对不可重复选项如Content-Format每个报文至多出现一次unicoap提供以unicoap_options_get为前缀的只读访问器unicoap_content_format_t format 0; if (unicoap_options_get_content_format(message-options, format) 0) { puts(Error: could not read Content-Format!); } assert(format UNICOAP_FORMAT_JSON);对可重复选项如Uri-Query可出现多次unicoap提供多组便捷访问器。先看取第一个const char* query NULL; ssize_t res unicoap_options_get_first_uri_query(message-options, query); if (res 0) { if (res -ENOENT) { puts(Message has no Uri-Query option); } printf(Error: could read first Uri-Query option); }注意first类 getter 返回的是PDU 缓冲区内部视图返回的字符串不带\0终止符因此打印时必须用%.*s配合返回长度printf(First URI query: %.*s\n, (int)res, query);若查询串遵循namevalue格式还可以按名字取第一个查询项res unicoap_options_get_first_uri_query_by_name_string(message-options, color, query); if (res 0) { /* The getter also fails in cases where no option was found */ if (res -ENOENT) { puts(Message has no color query); } printf(Error: could read first color query); }对于Uri-Path、Location-Path、Uri-Query、Location-Query这类可重复选项unicoap还提供还原连续表示的复制型访问器多个Uri-Path选项会被拼接回/original/path形式。这类访问器会执行拷贝调用方需提供目标缓冲区。下面把多个查询拼成一个?a1b2c3形式的查询字符串char query_string[50] { 0 }; res unicoap_options_copy_uri_queries(message-options, query_string, sizeof(query_string)); if (res 0) { puts(Error: could not generate URI query string); }如果不想拷贝、不想额外分配可以改用选项迭代器遍历所有查询选项。迭代器是遍历选项的主要工具先分配unicoap_options_iterator_t再用unicoap_options_iterator_init初始化unicoap_options_iterator_t iterator; unicoap_options_iterator_init(iterator, message-options); while ((res unicoap_options_get_next_uri_query(iterator, query)) 0) { printf(- URI query: %.*s\n, (int)res, query); }迭代器同样可以遍历所有选项而不管其类型unicoap_options_iterator_init(iterator, message-options); unicoap_option_number_t number; const uint8_t* value NULL; while ((res unicoap_options_get_next(iterator, number, value)) 0) { const char* name unicoap_string_from_option_number(number); printf(- option %s nr%i contains % PRIuSIZE bytes\n, name, number, res); }2.4 一个真实的解析输入示例应用 main.c 中内置了一段真实的 RFC 7252 PDU对应如下 CoAP 报文Confirmable、POST、id65201、无 token{ type: Confirmable, code: POST, id: 65201, token: 0, options: [ Uri-Path: actuators, Uri-Path: leds, Content-Format: application/json, Uri-Query: colorg, Accept: application/json ] }其字节表示为const uint8_t pdu[] { 0x40, 0x02, 0xfe, 0xb1, 0xb9, 0x61, 0x63, 0x74, 0x75, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x04, 0x6c, 0x65, 0x64, 0x73, 0x11, 0x32, 0x37, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3d, 0x67, 0x21, 0x32, 0xff, 0x6d, 0x6f, 0x64, 0x65, 0x3d, 0x6f, 0x6e };可以看到 0xff 标记之前是选项区之后是载荷modeon。解析该 PDU 后示例依次验证了消息类别unicoap_message_code_is_request、方法字符串、Content-Format UNICOAP_FORMAT_JSON、首个 Uri-Query 与按名字取color查询等完整覆盖了 2.12.3 节的全部 API。三、Message to Bytes把消息序列化为 PDU3.1 创建消息容器因为要往 CoAP 消息里添加选项需要先分配选项缓冲区。为了免去分配辅助结构 分配缓冲区 初始化的样板代码直接使用UNICOAP_OPTIONS_ALLOC宏并给出期望容量以字节为单位UNICOAP_OPTIONS_ALLOC(options, 100);该宏在 options/base.h 中定义会为变量自动生成内部存储缓冲并完成初始化对象位于栈上。若不想关心容量还有使用默认容量CONFIG_UNICOAP_OPTIONS_BUFFER_DEFAULT_CAPACITY的UNICOAP_OPTIONS_ALLOC_DEFAULT(name)变体以及对应静态分配版本UNICOAP_OPTIONS_ALLOC_STATIC。接着初始化消息。既可以使用指定初始化器也可以使用初始化函数。下面用字符串载荷初始化一个 POST 请求并挂接上刚才的选项对象unicoap_request_init_string_with_options(message, UNICOAP_METHOD_POST, Hello, World!, options);该函数位于 message.h内部转调unicoap_message_init_string_with_options。此外还有面向字节载荷、返回新结构体unicoap_request_alloc_*系列等多个变体可按需选用。示例应用中的载荷是一个更贴近实际场景的指令const char payload[] Dear thermostat, please adjust your target temperature to 22.5 °C; unicoap_request_init_string_with_options(message, UNICOAP_METHOD_POST, payload, options);3.2 定制选项不可重复选项用unicoap_options_set系列访问器设置例如Content-Formatint res unicoap_options_set_content_format(options, UNICOAP_FORMAT_TEXT); if (res 0) { puts(Error: could not set Content-Format); }可重复选项提供两种添加方式。第一种直接提供原始连续表示如完整路径unicoap会拆分成多个选项实例int res unicoap_options_add_uri_path_string(options, /thermostat/temperature); if (res 0) { if (res -ENOBUFS) { puts(Error: options buffer too small); } puts(Error: could not add URI path); }第二种逐个添加路径组件res unicoap_options_add_uri_path_component_string(options, thermostat); if (res 0) { puts(Error: could not add path component); } res unicoap_options_add_uri_path_component_string(options, temperature); if (res 0) { puts(Error: could not add path component); }Uri-Query同理。可以一次添加多个查询分隔res unicoap_options_add_uri_queries_string(options, unitCcoolyes); if (res 0) { puts(Error: could not add URI query); }unicoap对每个此类 API 都提供两种字符串变体面向\0结尾的 C 字符串*_string后缀以及面向无终止符、需显式给出长度的字符串无后缀。例如unicoap_options_add_uri_queries带长度与unicoap_options_add_uri_queries_string或unicoap_options_add_uri_query与unicoap_options_add_uri_query_string。选项顺序规则由CONFIG_UNICOAP_OPTIONS_FULL_SUPPORT控制默认开启开启默认可以修改先前已设置的选项也可以任意顺序添加选项关闭只能按 CoAP 选项号升序添加/设置选项违反顺序时 setter 函数返回负错误码。因此在默认配置下可以在设置Content-Format为 TEXT 之后再把它改为 JSONres unicoap_options_set_content_format(options, UNICOAP_FORMAT_JSON); if (res 0) { puts(Error: could not change Content-Format); }同理也可以追加单个查询最终得到unitCcoolyestimenowres unicoap_options_add_uri_query_string(options, timenow); if (res 0) { puts(Error: could not add URI query); }示例应用在调用上述 API 后用unicoap_options_dump_all(options)把全部选项打印出来便于核对添加结果见 main.c。3.3 序列化消息首先分配一个任意容量的输出缓冲区uint8_t pdu[200];头部格式随传输而异。这里使用 CoAP over UDP / CoAP over DTLS即 RFC 7252 格式。消息属性中不携带 token设计备注此场景非常简单因此不使用 token。非常受限的节点Very constrained nodes同一时刻只处理一个请求无需用 token 区分多个未完成请求的响应。unicoap_message_properties_t properties { .token NULL, .token_length 0, .rfc7252 { .id 0xABCD, .type UNICOAP_TYPE_NON } };最后调用与该传输匹配的序列化器ssize_t res unicoap_pdu_build_rfc7252(pdu, sizeof(pdu), message, properties); if (res 0) { if (res -ENOBUFS) { puts(Error: PDU buffer too small); } puts(Error: could not serialize message); return; } printf(The final PDU has a size of % PRIuSIZE bytes.\n, res);成功时返回值即序列化后的 PDU 总字节数。从实现看unicoap_pdu_build_rfc7252先调用unicoap_pdu_build_header_rfc7252写入头部再调用unicoap_pdu_build_options_and_payload续写选项与载荷缓冲区不足时返回-ENOBUFS。如果需要零拷贝发送unicoap还提供**向量化vectored**序列化接口unicoap_pdu_buildv_rfc7252它把头部、选项与载荷组织成iolist_t链表见 pdu 文档避免拼接连续缓冲区适合配合 RIOT 的 socket/网络驱动直接发送。四、完整流程串联从解析到序列化示例应用的main()见 main.c把上述两大部分串成一个闭环_example_parse_pdu()解析内置 PDU → 读取头部属性 → 检查请求类别 → 读取 Content-Format、Uri-Query → 迭代全部选项_example_create_message()分配选项缓冲区 → 初始化 POST 消息 → 添加 Uri-Path/Uri-Query、设置 Content-Format → 打印选项 →_example_serialize_message()序列化输出 PDU 大小。这与文档 message-example.doc.md 的章节一一对应。建议按以下顺序阅读源码加深理解unicoap 消息主头文件unicoap_init/unicoap_deinit及消息处理循环入口message.h消息容器、请求/响应初始化、解析与序列化函数声明options/base.hUNICOAP_OPTIONS_ALLOC系列分配宏与选项对象定义示例应用 README运行方式说明。五、小结本文围绕unicoap消息 API 梳理了完整的数据流解析方向上unicoap_parser_result_t一揽子解决结构装配问题unicoap_message_code_is_*系列判定消息类别unicoap_options_get_*与选项迭代器覆盖不可重复、可重复选项的读取序列化方向上UNICOAP_OPTIONS_ALLOC快速分配选项区unicoap_options_add/set_*构造选项受CONFIG_UNICOAP_OPTIONS_FULL_SUPPORT影响顺序规则最后由unicoap_pdu_build_rfc7252输出可发送的字节流。这套 API 的设计取舍值得借鉴解析器直接引用 PDU 缓冲区firstgetter 返回非\0终止的视图避免了不必要的拷贝适合内存受限的物联网节点同时通过连续表示还原与按名查询等高阶访问器把常见业务模式路径拼接、查询串解析封装成一行调用。结合 message 文档 与 pdu 文档 阅读即可完整掌握unicoap消息层的设计全貌。【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考