RIOT unicoap 实战教程:用 C 语言编写支持 UDP 与 DTLS 的 CoAP 服务器
2026/9/19 10:45:44 网站建设 项目流程

RIOT unicoap 实战教程:用 C 语言编写支持 UDP 与 DTLS 的 CoAP 服务器

【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT

导读

本教程基于 RIOT 操作系统(The friendly OS for IoT)的unicoap(Unified CoAP Suite)框架,带你从零编写一个完整的 CoAP 服务器应用:先是注册一个返回Hello, World!的静态资源,再实现一个从 URI 查询参数读取用户姓名、返回个性化问候的动态资源,并最终通过 DTLS(PSK 凭据)提供加密访问。全流程使用 RIOT 的native板卡在 Linux 主机上完成验证,无需任何无线硬件。读完本文,你将掌握UNICOAP_RESOURCE资源声明、请求处理器编写、矢量(iolist)载荷、选项设置、DTLS 凭据装载,以及借助 aiocoap 客户端脚本进行端到端测试的完整技能链。

本文对应的完整可运行示例位于仓库 examples/networking/coap/unicoap_server,服务端框架文档见 sys/net/application_layer/unicoap/docs/server.doc.md,unicoap的公共 API 头文件位于 sys/include/net/unicoap/server.h。


1. 环境准备与工程骨架

unicoap是 RIOT 中面向受限应用协议(Constrained Application Protocol,CoAP)的统一、模块化框架。相比 HTTP,CoAP 专为带宽受限、内存有限的物联网节点设计,支持资源发现、消息分片与端到端消息保护(参见 sys/net/application_layer/unicoap/docs/doc.md)。unicoap采用分层、模块化设计,每种传输(如 UDP、DTLS)通过独立的 driver 模块接入,后续也旨在逐步取代 RIOT 中更早期的gcoapnanocoap等实现。

1.1 编写 Makefile 并引入依赖

在应用目录下新建Makefile,首先引入unicoap及其服务器子模块。由于我们要同时支持 CoAP over UDP 与 CoAP over DTLS(DTLS 底层同样依赖 UDP),需要把对应的两个传输驱动一并加入:

USEMODULE += unicoap USEMODULE += unicoap_server USEMODULE += unicoap_driver_udp USEMODULE += unicoap_driver_dtls

其中:

  • unicoap:框架核心;
  • unicoap_server:CoAP 服务器子模块,负责资源注册与请求的异步处理;
  • unicoap_driver_udp/unicoap_driver_dtls:传输驱动,分别监听默认的 5683 / 5684 端口(端口号可在 Kconfig 中通过UNICOAP_UDP_PORTUNICOAP_DTLS_PORT调整,见 sys/net/application_layer/unicoap/Kconfig)。按需引入其中任意一个即可,也可以两者都引入。

RIOT 允许切换网络后端(network backend),本教程选择 GNRC:

# Include packages that pull up and auto-init the link layer. # NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present USEMODULE += netdev_default # Automatically initialize GNRC upon startup USEMODULE += auto_init_gnrc_netif # Specify the mandatory networking modules USEMODULE += gnrc_ipv6_default # Additional networking modules that can be dropped if not needed USEMODULE += gnrc_icmpv6_echo

最后,在main.c中引入框架头文件:

#include "net/unicoap.h"

工程实际使用的 Makefile 参见 examples/networking/coap/unicoap_server/Makefile。该文件默认BOARD ?= native,并通过LWIP_IPV4/LWIP_IPV6开关在 GNRC 与 lwIP 两套网络栈之间切换,便于移植到不同板卡。


2. 定义第一个 CoAP 资源

CoAP 中的"资源"(resource)与 HTTP 中的"端点"(endpoint)行为类似。unicoap提供了两种注册方式:一种是利用跨文件静态数组在编译期声明资源(推荐),另一种是在运行时手动创建监听器并注册资源。本教程使用前者。

2.1 静态声明资源

要使用编译期声明,需要在Makefile中引入资源声明模块:

USEMODULE += unicoap_server_resource_declarations

随后用UNICOAP_RESOURCE宏定义一个资源。宏需要一个标识符参数(例如hello),该标识符仅要求是唯一的 C 变量名,本身不参与路由,纯粹是为了支撑跨文件数组的实现(详见 sys/net/application_layer/unicoap/docs/resources-xfa.doc.md):

UNICOAP_RESOURCE(hello) { // ... };

2.2 分配路径(path)

每个资源必须被赋予一个路径,它是 CoAP URI 中主机/域名与端口之后的部分。将unicoap_resource_t.path属性设为UNICOAP_PATH_ROOT(根路径/),或使用UNICOAP_PATH构造自定义路径:

UNICOAP_RESOURCE(hello) { .path = UNICOAP_PATH_ROOT, };

例如路径/gelato/flavours/menu应写成:

UNICOAP_PATH("gelato", "flavours", "menu")

注意:单个路径组件内严禁包含斜杠,即绝不能写UNICOAP_PATH("gelato/flavours", "menu")。从 sys/include/net/unicoap/server.h 的源码可见,UNICOAP_PATH实际构造了一个unicoap_pathspec_t,把各组件以字符串字面量数组的形式保存,并在编译期通过_UNICOAP_TRY_CHECK_PATH_COMPONENTS对组件做检查;UNICOAP_PATH_ROOT则直接以NULL组件列表表示根路径,后续匹配时unicoap_path_is_root()会据此判定(见同一头文件的unicoap_path_is_rootunicoap_path_component_count内联函数)。

2.3 声明允许的 CoAP 方法

接着用UNICOAP_METHODS宏列出该资源允许的 CoAP 方法(类似 HTTP 方法)。methods属性不能留空。如果客户端后续发送的方法不在允许集合内,unicoap会以Method Not Allowed的 CoAP 响应拒绝请求:

UNICOAP_RESOURCE(hello) { .path = UNICOAP_PATH_ROOT, .methods = UNICOAP_METHODS(UNICOAP_METHOD_GET, UNICOAP_METHOD_FETCH), };

2.4 按需限制传输协议

可选地,可以用UNICOAP_PROTOCOLS将资源限制到一组传输协议,这在"加密是强制要求"的场景下尤为有用。本教程中的问候资源仍允许明文 UDP 访问:

UNICOAP_RESOURCE(hello) { // ... .protocols = UNICOAP_PROTOCOLS(UNICOAP_PROTO_DTLS, UNICOAP_PROTO_UDP), // ... };

需要说明的是:unicoap在请求与资源匹配时,会先检查当前传输是否在该资源的协议集合内;若不在,则视为该资源"不存在"。而protocols属性未设置时默认含义是"允许所有协议",如需显式表达,可使用UNICOAP_PROTOCOLS_ALLOW_ALLUNICOAP_PROTOCOLS_ALLOW_NONE(见 sys/net/application_layer/unicoap/docs/server.doc.md 的 "Request-resource matching" 一节)。

2.5 请求可靠传输

由于我们基于 CoAP over UDP(DTLS 亦然,其底层仍是 UDP),可以指示unicoap发送**可确认(confirmable,CON)**响应——unicoap会持续重传响应,直到客户端确认收到为止。为此传入UNICOAP_RESOURCE_FLAG_RELIABLE标志(该标志对非 UDP/DTLS 传输没有效果):

UNICOAP_RESOURCE(hello) { .path = UNICOAP_PATH_ROOT, .methods = UNICOAP_METHODS(UNICOAP_METHOD_GET, UNICOAP_METHOD_FETCH), .flags = UNICOAP_RESOURCE_FLAG_RELIABLE, };

2.6 绑定请求处理器

最后指定一个处理发往/的请求的函数handle_hello_request

UNICOAP_RESOURCE(hello) { .path = UNICOAP_PATH_ROOT, .methods = UNICOAP_METHODS(UNICOAP_METHOD_GET, UNICOAP_METHOD_FETCH), .flags = UNICOAP_RESOURCE_FLAG_RELIABLE, .handler = handle_hello_request };

请求处理器必须遵循unicoap_request_handler_t的函数签名,共四个参数:请求消息message、携带客户端地址等信息的辅助对象aux、响应上下文ctx,以及可选的附加参数arg(对应unicoap_resource_t.handler_arg,详见 sys/include/net/unicoap/server.h):

static int handle_hello_request(unicoap_message_t* message, const unicoap_aux_t* aux, unicoap_request_context_t* ctx, void* arg) { // ... }

2.7 处理器实现:记录请求并应答

处理器首先打印日志。借助unicoap_string_from_method取得 CoAP 方法(即 CoAP 消息码)的字符串表示,用unicoap_message_payload_get_size获取载荷字节数。这将输出类似GET /, 0 bytes的日志:

printf( "app: %s /, %" PRIuSIZE " bytes\n", unicoap_string_from_method(message->method), unicoap_message_payload_get_size(message) );

对于以\0结尾的静态字符串,unicoap提供了便捷初始化器unicoap_response_init_string。注意这里可以直接复用message参数的内存来构造响应。随后调用unicoap_send_response发送响应:

unicoap_response_init_string(message, UNICOAP_STATUS_CONTENT, "Hello, World!"); return unicoap_send_response(message, ctx);

关于返回值有一项重要的约定:返回值始终应表示成功(0)或错误(负整数)。以下两种行为均被视为致命错误:调用send_response之后却返回状态码而不返回其结果;以及既未调用send_response、又未返回状态码(详见 server.doc.md 的 "Responding" 一节)。

完整的首个资源及其处理器在示例代码 examples/networking/coap/unicoap_server/main.c 中均有对应实现,你可以直接对照学习。


3. 打造动态 CoAP 资源:个性化问候

静态资源过于简单,让我们开发一个/greeting资源,它接受名为name的查询参数。当客户端请求coap://...host.../greeting?name=RIOTeer时,服务器响应Hello, RIOTeer! Welcome to our itsy bitsy tiny CoAP server!

3.1 定义资源

UNICOAP_RESOURCE(greeting) { .path = UNICOAP_PATH("greeting"), .flags = UNICOAP_RESOURCE_FLAG_RELIABLE, .methods = UNICOAP_METHODS(UNICOAP_METHOD_GET), .handler = handle_greeting_request, };

在 CoAP 消息的层面,URIcoap://.../greeting?name=RIOTeer会被编码为两个选项:Uri-Path: "greeting"Uri-Query: "name=RIOTeer"

3.2 读取查询参数并校验

handle_greeting_request中,我们通过unicoap_options_get_first_uri_query_by_name_string提取名为name的查询参数。若访问者未提供姓名,直接返回Bad Request响应。处理器可以直接返回一个 CoAP 状态码作为快捷方式——此时unicoap会自动发送一个不带载荷的响应:

static int handle_greeting_request(unicoap_message_t* message, const unicoap_aux_t* aux, unicoap_request_context_t* ctx, void* arg) { (void)aux; (void)arg; ssize_t res = 0; const char* name = NULL; if ((res = unicoap_options_get_first_uri_query_by_name_string(message->options, "name", &name)) < 0) { printf("error: could not get 'name' query: %" PRIdSIZE " (%s)\n", res, strerror(-res)); return UNICOAP_STATUS_BAD_REQUEST; } // ... }

接着校验输入。示例代码设定了 30 个 UTF-8 字符的长度上限,并要求做 UTF-8 有效性检查(包括是否存在提前出现的\0终止符)。当校验失败时,这次我们借助unicoap_send_response与专门的错误消息,把"查询值无效"明确告知客户端:

// Validate any input. Here, we apply a 30 UTF-8 character limit. You should perform UTF-8 // validation (is_valid_name). This also includes checking if there's a premature null terminator. if (res > 30 || !is_valid_name(name, res)) { unicoap_response_init_string(message, UNICOAP_STATUS_BAD_REQUEST, "invalid 'name' query"); return unicoap_send_response(message, ctx); }

示例中的is_valid_name实现会遍历字符串长度内的每个字节,若发现任何字节为 0 则判定非法(参见 main.c)。

3.3 设置 Content-Format 选项

规范的响应应设置Content-Format选项,这里取text/plain。由于设置选项可能失败(例如缓冲区容量不足),应当最先执行。选项通过UNICOAP_OPTIONS_ALLOC在栈上分配,再用unicoap_options_set_content_format设置格式:

UNICOAP_OPTIONS_ALLOC(options, 2); // Set Content-Format option to text/plain if (unicoap_options_set_content_format(&options, UNICOAP_FORMAT_TEXT) < 0) { return UNICOAP_STATUS_INTERNAL_SERVER_ERROR; } message->options = &options;

关于缓冲容量:分配时指定的容量应当是"上界估计"。如果你确切知道 CoAP 选项在内存中的表示方式,也可以把容量设为精确的字节数——本例中只用到Content-Format这一个选项,因此设为恰好 2 字节。请注意,这个精确值也意味着无法再追加其他选项。

相关可调参数见 sys/net/application_layer/unicoap/Kconfig:UNICOAP_OPTIONS_BUFFER_DEFAULT_CAPACITYUNICOAP_OPTIONS_ALLOC未显式指定时的默认缓冲容量(默认 32 字节);UNICOAP_OPTIONS_MAX控制单条消息中允许的最大选项数(默认 16);UNICOAP_OPTIONS_FULL_SUPPORT则决定是否支持任意顺序地增删改选项(关闭后,乱序修改选项会触发运行时错误)。

3.4 用矢量(iolist)拼接动态载荷

理论上可以把Hello,、姓名、后缀! Welcome ...三段写入一个大缓冲区再发送,但当需要构造大缓冲区时,下面的技术更可取:不创建缓冲区,而是创建一个矢量——即一系列载荷chunk(片段)的链表。诀窍在于网络后端会自行把这些 chunk 拷贝进发送缓冲区,从而省去应用层的额外拷贝操作。

首先是首、尾两个静态 chunk:

#define PREFIX "Hello, " iolist_t list = { .iol_base = PREFIX, .iol_len = static_strlen(PREFIX), }; #define SUFFIX "! Welcome to our itsy bitsy tiny CoAP server!" iolist_t suffix = { .iol_base = SUFFIX, .iol_len = static_strlen(SUFFIX) };

static_strlen是示例代码中定义的辅助宏(#define static_strlen(string) sizeof(string) - 1),用于在编译期取得字符串字面量的长度,参见 main.c。

接着加入动态的中间部分——即来自查询参数的姓名:

iolist_t name_chunk = { .iol_next = &suffix, .iol_base = (void*)name, .iol_len = res }; list.iol_next = &name_chunk;

通过.iol_next = &suffix把后缀链到中间块之后,再用list.iol_next = &name_chunk把这条"两段链"追加到首个块之后,矢量组装完成。最后设置状态与载荷并发送。unicoap支持多种略有差异的响应方式以减少样板代码,可参阅 server.doc.md 的 "Responding" 一节获取扩展讲解:

unicoap_message_payload_set_chunks(message, &list); unicoap_response_set_status(message, UNICOAP_STATUS_CONTENT); return unicoap_send_response(message, ctx);

4. 为服务器开启 DTLS

DTLS(Datagram Transport Layer Security)为 UDP 数据报提供传输层安全。我们在前文已经引入了unicoap_driver_dtls驱动,但还需要包含额外头文件并向unicoap装载一个 DTLS 凭据:

#if IS_USED(MODULE_UNICOAP_DRIVER_DTLS) # include "net/sock/dtls/creds.h" # include "net/credman.h" # include "net/dsm.h" # include "unicoap_example_dtls.h" # define EXAMPLE_DTLS_CREDENTIAL_TAG 42 static const uint8_t psk_id_0[] = PSK_DEFAULT_IDENTITY; static const uint8_t psk_key_0[] = PSK_DEFAULT_KEY; static const credman_credential_t credential = { .type = CREDMAN_TYPE_PSK, .tag = EXAMPLE_DTLS_CREDENTIAL_TAG, .params = { .psk = { .key = { .s = psk_key_0, .len = sizeof(psk_key_0) - 1, }, .id = { .s = psk_id_0, .len = sizeof(psk_id_0) - 1, }, } }, }; #endif

要点说明:

  • 这里使用的是PSK(预共享密钥)凭据,身份标识为Client_identity、密钥为secretPSK,二者的默认值定义在示例头文件 unicoap_example_dtls.h 中(该文件同时提供了CONFIG_DTLS_ECC下的 ECDSA 密钥对示例,供需要非对称加密的场景参考);
  • credman是 RIOT 中统一管理 DTLS 凭据的模块(见sys下的net/credman);
  • 标签(tag)42连同凭据类型需要保持唯一,之后通过该标签把凭据绑定到 DTLS socket。

main函数中,把凭据加入系统,再通过unicoap_transport_dtls_get_socket取得 DTLS socket 并装载凭据:

int main(void) { # if IS_USED(MODULE_UNICOAP_DRIVER_DTLS) int res = credman_add(&credential); if (res < 0 && res != CREDMAN_EXIST) { /* ignore duplicate credentials */ printf("app: cannot add credential to system: %d\n", res); return 1; } sock_dtls_t* dtls_socket = unicoap_transport_dtls_get_socket(); assert(dtls_socket); if ((res = sock_dtls_add_credential(dtls_socket, EXAMPLE_DTLS_CREDENTIAL_TAG)) < 0) { printf("app: cannot add credential to DTLS sock: %d\n", res); return 1; } # endif }

credman_add返回CREDMAN_EXIST表示该凭据已存在,此处予以忽略(重复凭据不做处理)。完成之后,你的服务器就可以通过加密的 DTLS 连接访问了。

关于 DTLS 运行参数,可在 sys/net/application_layer/unicoap/Kconfig 中调整:UNICOAP_DTLS_PORT(默认 5684)、UNICOAP_DTLS_HANDSHAKE_TIMEOUT_MS(握手超时,默认 3000 ms,设为 0 表示无限等待)、UNICOAP_DTLS_MINIMUM_AVAILABLE_SESSION_SLOTS及对应的会话整理超时UNICOAP_DTLS_MINIMUM_AVAILABLE_SESSION_SLOTS_TIMEOUT_MS

4.1 关于 unicoap 初始化与运行线程

示例的main中还展示了若干可选的运行时行为,有助于理解框架:

  • 默认情况下unicoap_init()会在main()之前由auto_init_unicoap自动调用(它属于 RIOT 的DEFAULT_MODULE)。可通过DISABLE_MODULE += auto_init_unicoap关闭该默认行为,此时需要自行调用unicoap_init()
  • 通过unicoap_transport_udp_get_socket()可取得底层 UDP socket(sock API),进而用unicoap_print_endpoint打印监听端点;
  • unicoap_transport_udp_add_socket()允许额外添加第二个监听 socket(示例中监听 UDP 5682 端口);
  • 若在 Kconfig 中把UNICOAP_CREATE_THREAD设为 0,unicoap_init()不再创建专用线程,而需要你在自己的线程(如 main 线程)里调用unicoap_loop_run()驱动处理循环;CONFIG_UNICOAP_CREATE_THREAD=y时该函数不可用。示例的 app.config 默认开启了CONFIG_UNICOAP_CREATE_THREAD=y

5. 在 native 板卡上端到端测试

示例代码自带一个基于 aiocoap 的client.py测试脚本。我们将使用 RIOT 的native板卡——客户端与服务器都运行在 Linux 主机上,不涉及任何无线网络,无需天线。

5.1 编译并运行服务器

cd RIOT/examples/networking/coap/unicoap_server BOARD=native make -j flash term

运行后应看到类似如下的输出:

RIOT/dist/tools/pyterm/pyterm -ps RIOT/examples/networking/coap/unicoap_server/bin/native64/unicoap_server.elf --process-args tap0 Welcome to pyterm! Type '/exit' to exit. # RIOT native interrupts/signals initialized. # RIOT native64 board initialized. # RIOT native hardware initialization complete. # coap: registered 2 XFA resources # coap.transport.udp: zero_copy_guarantees=1 creating UDP sock, port=5683 if=0 family=inet6 # coap.transport.dtls: creating DTLS sock, port=5684 if=0 family=inet6 # main(): This is RIOT! (Version: 2025.04-devel-634-RED-unicoap-02-server-minimal) # app: listening at UDP <sock_tl_ep port=5683 netif=0 ipv6=::> # app: listening at DTLS <sock_tl_ep port=5684 netif=0 ipv6=::> # app: using credential: type=PSK id=Client_identity key=secretPSK # app: IPv6 address: fe80::c0:ff:ee

输出解读:

  • coap: registered 2 XFA resources:框架通过跨文件数组(XFA)注册了 2 个静态资源(hellogreeting);
  • 服务器同时在 UDP 5683 与 DTLS 5684 端口监听 IPv6 全地址;
  • 示例中 PSK 凭据的 identity/key 为Client_identity/secretPSK

提示:如果tap0尚未启用,可能需要先手动创建并拉起 tap 接口:

sudo ip tuntap add tap0 mode tap user ${USER} sudo ip link set tap0 up

关于调试日志unicoap会根据CONFIG_UNICOAP_DEBUG_LOGGINGCONFIG_UNICOAP_ASSIST输出调试日志。前者是包含追踪日志的完整调试版本(启用时也会自动包含辅助诊断),后者是更轻量的版本,仅记录 API 误用、缺失模块与修复建议。示例代码在 app.config 中将两者默认置为n(关闭);如需排查问题可按需打开。

5.2 发送 CoAP 请求

在第二个终端会话中运行:

python3 client.py -m GET -u "coap://[fe80::c0:ff:ee%tap0]/greeting?name=RIOTer"

应看到脚本输出的若干调试日志,最终是:

response: 2.05 Content b'Hello, RIOTer! Welcome to our itsy bitsy tiny CoAP server!'

恭喜,你的服务器已经可以正常工作了!

5.3 抓包验证 CoAP 消息交互

如果想亲眼看到实际传输的 CoAP 消息,可开启第三个终端执行tcpdump -i tap0 -w coap-greeting.pcap,然后按上文方式运行客户端脚本,最后用CTRL+C终止 tcpdump。之后可用 Wireshark 打开coap-greeting.pcap检查 CoAP 消息:应能看到一个NON(不可确认)请求、随后一个CON(可确认)响应,以及客户端回应的ACK消息——这正是UNICOAP_RESOURCE_FLAG_RELIABLE生效的体现:unicoap发送 CON 响应并等待确认。

5.4 客户端脚本能力速览

client.py 是一个基于 aiocoap 的多功能客户端,其命令行接口为:

client.py -m <GET|PUT|POST|DELETE|PATCH|iPATCH|FETCH> -u <URI> [--type <NON|CON>] [--observe] [-p <PAYLOAD>]

常用参数:

  • -m/--method:CoAP 方法(必填);
  • -u/--uri:请求 URI(必填),例如coap://[fe80::c0:ff:ee%tap0]/greeting?name=RIOTer
  • -mt/--type:消息类型,NON(默认)或CON
  • --observe/--observe-cancel:注册或取消资源观察(Observe);
  • -p/--payload:请求载荷;
  • -to/--timeout:请求超时秒数(默认 4 秒)。

脚本内置了 DTLS 客户端凭据(PSK:secretPSK/ identity:Client_identity),因此在服务器开启 DTLS 后,把 URI 协议换成coaps://即可测试加密路径。由于 CoAP over DTLS 走默认端口 5684,测试 DTLS 时请相应调整 URI 中的端口。


6. 深入理解:请求-资源匹配与三种响应方式

作为补充,理解服务器的匹配与响应机制有助于写出更健壮的处理器。

6.1 匹配顺序

unicoap按以下顺序判定请求是否命中某个资源:先遍历所有监听器(listener),再遍历该监听器内注册的所有资源。监听器的顺序遵循注册顺序,但有两个特例:第一个监听器是处理/.well-known/core(CoRE 资源发现)的内置监听器,第二个可能是承载 XFA 静态资源的监听器。一旦找到匹配资源,后续资源不再检查。在此过程中:

  1. 不包含当前传输协议的资源直接跳过(视为不存在);
  2. 然后比对路径;若设置了UNICOAP_RESOURCE_FLAG_MATCH_SUBTREE标志,注册在/foo/bar的资源也会匹配/foo/bar/zoo等子路径;
  3. 最后检查请求方法是否在允许集合内:若方法不匹配且其他资源也都不匹配,则返回Method Not Allowed;若没有找到任何匹配资源,则返回Not Found

若内置行为不符合你的场景,可以为每个监听器自定义匹配算法:设置unicoap_listener_t.request_matcher属性;默认情况下(属性未设置)使用内置匹配器(详见 server.doc.md 的 "Request-resource matching" 一节)。运行时动态注册资源的完整代码模式(构造资源数组 → 创建监听器 →unicoap_listener_register,可选unicoap_listener_deregister)同样收录在该文档中。

6.2 三种响应技术

unicoap提供多种响应方式(server.doc.md 的 "Responding" 一节):

  1. 直接返回状态码:无需响应载荷与选项时,直接return UNICOAP_STATUS_XXX;。注意这要求在此之前已完成全部请求处理;若涉及敏感操作、担心时序侧信道,建议改用其他方式;
  2. 调用unicoap_send_response:先用状态码(可选地加上载荷与选项)初始化响应消息,再传入处理器参数中的ctx发送。可以复用message参数的内存,但绝不能写载荷缓冲区或修改选项;需要选项时请用UNICOAP_OPTIONS_ALLOC分配。send_response可能失败,你需要自行处理错误(可重试,或改发Internal Server Error等)。这也是大多数应用的首选方式;
  3. 延迟响应(Deferred response):该技术目前尚未在 RIOT 中实现(文档标注 "not available yet")。

另外,处理器内的所有处理都运行在服务器处理循环中,会阻塞该循环——若处理开销较大,值得考虑将工作迁移到其他线程。


7. 小结

本文完整走通了基于 RIOTunicoap编写 CoAP 服务器的全流程:

  • 通过USEMODULE组合引入框架、服务器、资源声明与 UDP/DTLS 驱动,构建最小工程骨架;
  • UNICOAP_RESOURCE+UNICOAP_PATH/UNICOAP_PATH_ROOT+UNICOAP_METHODS声明路径与方法,用UNICOAP_PROTOCOLS限制传输、UNICOAP_RESOURCE_FLAG_RELIABLE启用 CON 可靠响应;
  • 实现静态问候资源,并进阶到读取name查询参数、校验输入、设置Content-Format、用 iolist 矢量载荷零拷贝拼接动态响应的动态资源;
  • 通过credman+ PSK 凭据装载为服务器开启 DTLS 加密访问;
  • 使用BOARD=native make -j flash term在 Linux 上运行,并用 aiocoap 客户端与 tcpdump 完成端到端验证与抓包分析。

unicoap仍处于快速演进中(doc.md 明确标注 "work in progress",部分文档先行、功能尚未完全落地),因此在实际项目中引入时,建议结合当前仓库的 sys/net/application_layer/unicoap 源码与 sys/include/net/unicoap 公共头文件核对最新 API,并留意 Kconfig 中CONFIG_UNICOAP_DEBUG_LOGGINGCONFIG_UNICOAP_ASSIST等调试开关,以降低排障成本。

【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询