在 RIOT 中用 Rust 与 gcoap 构建 CoAP 服务器:rust-gcoap 进阶示例全解析 在 RIOT 中用 Rust 与 gcoap 构建 CoAP 服务器rust-gcoap 进阶示例全解析【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT导读本篇文章围绕 RIOT 官方语言支持示例中的进阶 Rust 应用rust-gcoap展开讲解如何在 RIOT 上编写一个完全以 Rust 为主体、C 代码混编为辅的 CoAP 服务器应用。读完本文你将掌握Rust 应用与 C 源码在同一构建系统中混合编译的方式、利用riot-wrapperscrate 安全地操作 gcoap/ztimer/gnrc 等 RIOT 组件的方法、CoAP 资源树的模块化组织思路以及如何在本地构建、烧录并验证一个可被aiocoap-client访问的.well-known/core服务端点。该示例位于 examples/lang_support/official/rust-gcoap/属于official官方维护目录下三个 Rust 示例rust-hello-world、rust-gcoap、rust-async中的进阶篇入门基础可对照 examples/lang_support/official/rust-hello-world/ 阅读。示例概览进阶 Rust 应用演示了哪些能力rust-gcoap在基础示例rust-hello-world之上集中演示了三项关键能力这也是本文后续逐一展开的主线C 代码与 Rust 代码可以轻易混编应用中的任意 C 文件会像普通无 Rust 的 RIOT 应用一样被编译并链接本文中的 vfs.c 即是一个活生生的例子。Rust 应用代码可以按模块拆散即便代码构建在 RIOT 组件之上依然可以组织成清晰的 crate 模块结构。示例中的 CoAP 处理器把通用的 CoAP 组件来自coap_message_demos与 RIOT 特有的组件来自riot-coap-handler-demos组合在一起。RIOT 的众多特性通过riot-wrapperscrate 暴露给 Rustriot-wrappers为 RIOT 的数据结构提供了安全封装。在本示例中CoAP 处理器运行在 gcoap 服务器之上封装层将其适配到平台无关的处理器接口同时用 ztimer 休眠等待网络接口就绪再遍历可用网络接口查询其 IPv6 地址方便在缺乏交互式 shell 的场景下快速定位 CoAP 请求的目标地址。工程结构一个Rust 为主体的 RIOT 应用长什么样rust-gcoap示例的完整目录结构如下examples/lang_support/official/rust-gcoap/ ├── README.md # 本文讲解的官方文档 ├── Makefile # RIOT 应用构建入口含模块声明与链接参数 ├── Makefile.ci # CI 测试声明 ├── Cargo.toml # Rust crate 元数据与依赖声明 ├── Cargo.lock # Rust 依赖锁定文件 ├── vfs.c # 演示 C/Rust 混编的 C 源文件 └── src/ └── lib.rs # Rust 应用主体入口与 CoAP 处理器Makefile如何触发Rust 构建与普通 RIOT 应用相比Makefile 中最关键的一行是APPLICATION rust_gcoap APPLICATION_RUST_MODULE rust_gcoap其中APPLICATION_RUST_MODULE的取值来自Cargo.toml的package.name注意将-替换为_。该变量的存在会触发构建系统在编译应用中的 C 代码之外额外构建 Rust 代码。这一点在示例文档中被明确强调The presence of this triggers building Rust code contained in this application in addition to any C code.其余为常规 RIOT 应用配置默认板子BOARD ? nativeRIOTBASE ? $(CURDIR)/../../../..指向仓库根目录随后通过USEMODULE依次启用 gcoap、网络栈netdev_default、auto_init_gnrc_netif、gnrc_ipv6_default、gnrc_icmpv6_echo、定时器ztimer及ztimer_usec/msec/sec、gnrc_netapi_callbacks以及为vfs功能准备的vfs与constfs为saul功能准备的saul_default。Cargo.tomlRust 侧的关键声明src 的 Cargo.toml 中有几处值得特别说明[lib]/crate-type [staticlib]这是 RIOT 之后把 C 与 Rust 两部分链接在一起的前提在rust-hello-world文档中同样强调这一节是necessary的。[profile.release]中设置了lto true、codegen-units 1、opt-level s注释说明这是帮助 Rust 二进制保持小巧的典型选项组合。依赖riot-wrappers { version ^0.9.0, features [ set_panic_handler, panic_handler_format, with_coap_message, with_coap_handler, provide_critical_section_1_0 ] }安全封装 RIOT 结构并启用 CoAP 相关特性与 panic 处理。rust_riotmodules { path ../../../../sys/rust_riotmodules/ }虽然本示例目前没有使用任何以 Rust 实现的 RIOT 模块但官方文档明确指出任何拥有顶层 Rust crate 的 RIOT 应用都应从RIOTBASE内引入rust_riotmodules这属于最佳实践。[patch.crates-io]中对try-lock做了 git 补丁替换用于那些没有原子操作的平台同时需要riot-wrappers的provide_critical_section_1_0与portable-atomic的critical-section特性来弥合差距。仓库中的 sys/rust_riotmodules/Cargo.toml 展示了 RIOT 自带 Rust 模块的聚合方式目前包含riot-module-lsm303agr驱动与riot-module-shell-democommandsshell 演示命令并通过_all特性聚合。对应 sys/rust_riotmodules/src/lib.rs 中pub use了这两个 crate供 Rust 应用以rust_riotmodules::lsm303agr之类的方式直接访问。而 sys/rust_riotmodules/Makefile.dep 中的逻辑表明只有当应用没有自带 crateifeq (,${APPLICATION_RUST_MODULE})时才会回退到rust_riotmodules_standalone伪模块同时FEATURES_REQUIRED rust_target表明此类应用要求工具链提供 Rust 目标支持。C 与 Rust 混编do_vfs_init()的实践示例文档指出虽然理论上可以为混编的 C 代码准备头文件但在实践中C 应用中也常这么做更简单的做法是手工翻译入口函数的签名do_vfs_init()函数正是如此处理的。C 侧挂载一个 constfs 只读文件系统vfs.c 实现了一个简单的 VFS 初始化定义三个constfs文件/hello-world、/hello-riot和用于测试大文件读写的/large把它们挂在/const挂载点并导出 C 函数void do_vfs_init(void) { int res vfs_mount(const_mount); if (res 0) { puts(Error while mounting constfs); } else { puts(constfs mounted successfully); } }Rust 侧手工声明并调用在 src/lib.rs 中Rust 侧没有 include 任何头文件而是直接以extern C声明签名并在unsafe块中调用extern C { fn do_vfs_init(); } unsafe { do_vfs_init() };这种手工翻译签名的方式让 Rust 代码可以零成本复用现有 C 模块同时把跨语言边界的安全责任明确地交到调用者手中这也是调用发生在unsafe块中的原因。模块化的 CoAP 资源树把处理器拆进 crate示例文档强调Rust 应用的代码可以铺开到多个模块即使它构建在 RIOT 组件之上。在main()中CoAP 资源树由通用 crate 与 RIOT 相关 crate 共同拼装而成let handler coap_message_demos::full_application_tree(None) .below([ps], riot_coap_handler_demos::ps::ps_tree()) .below([led], riot_coap_handler_demos::led::all_leds()) .below([vfs], riot_coap_handler_demos::vfs::vfs()) .below([saul], riot_coap_handler_demos::saul::SaulHandler::new([saul])) .below([netif], riot_coap_handler_demos::netif::netif()) .below([nib, neigh], riot_coap_handler_demos::nib::neighbor_cache()) .below([ping], riot_coap_handler_demos::ping::ping_tree(PINGS)) .at([pinged], riot_coap_handler_demos::ping_passive::resource(PING_PASSIVE)) .with_wkc();coap_message_demos::full_application_tree(None)提供通用的应用树骨架来自外部 cratecoap-message-demos一系列.below(...)/.at(...)调用把ps、led、vfs、saul、netif、NIB 邻居缓存、主动ping与被动pinged等 RIOT 特有资源挂到树下.with_wkc()自动生成/.well-known/core资源发现端点这是下文用aiocoap-client验证服务的基础。这些资源处理器来自riot-coap-handler-demoscrategit 依赖自https://codeberg.org/RIOT/rust-module-examples并启用了vfs、saul、nib、ping特性与 Makefile 中启用的vfs、constfs、saul_default等模块一一对应。接入 gcoap安全封装与线程模型用封装器把处理器挂到 gcoap 上构建好平台无关的处理器树后需要把它接入 RIOT 的 gcoap 服务器let mut handler riot_wrappers::coap_handler::v0_2::GcoapHandler(handler); let mut listener gcoap::SingleHandlerListener::new_catch_all(mut handler);示例文档特别指出正是riot-wrappers提供的适配层把这个平台无关的处理器接口跑在了 gcoap 服务器上。随后在gcoap::scope中注册监听器并进入主循环gcoap::scope(|greg| { greg.register(mut listener); // ... 打印就绪信息、等待网络、遍历接口 ... loop { PINGS.tick(); sectimer.sleep(ztimer::Ticks(1)); } })注释说明了为何主线程必须永久休眠cant return or the Gcoap handler would need to be deregistered (which it cant)——即main一旦返回已注册的 Gcoap 处理器将无法注销因此用无限循环让出 CPU 交由 RIOT 调度器处理。网络就绪等待与地址枚举在没有交互式 shell 的场景下用户需要自行得知往哪个地址发 CoAP 请求。示例的处理方式是let sectimer ztimer::Clock::sec(); sectimer.sleep(ztimer::Ticks(2)); for netif in gnrc::Netif::all() { println!(Active interface from PID {:?} ({:?}), netif.pid(), netif.pid().get_name().unwrap_or(unnamed)); match netif.ipv6_addrs() { Ok(addrs) { for a in addrs { println!( Address {:?}, a); } } _ { println!( Does not support IPv6.); } } }这里用到了riot-wrappers对三类 RIOT 组件的封装ztimer::Clock::sec()与ztimer::Ticks以秒为单位休眠对应 Makefile 中的ztimer/ztimer_sec等模块gnrc::Netif::all()遍历 RIOT 的网络接口对应gnrc_ipv6_default等网络模块netif.ipv6_addrs()查询接口的 IPv6 地址列表。此外示例还通过static_cell::StaticCell为PingPool与PingHistoryMutex分配静态存储并用gnrc::netreg::callback::Slot注册回调槽位演示了在#![no_std]环境中持有 RIOT 回调资源的惯用法对应 Makefile 中的gnrc_netapi_callbacks模块。构建与运行从make到 CoAP 请求编译、烧录并进入终端在examples/lang_support/official/rust-gcoap/目录下执行$ make all flash term该命令会依次完成C 部分vfs.c与 RIOT 内核/驱动与 Rust 部分rust_gcoapcrate 及其依赖的编译链接、烧录到目标板示例默认BOARD ? native可在环境变量中覆盖并打开串口终端。文档给出的典型启动输出为main(): This is RIOT! (Version: 2022.01-devel-560-g7f8ed-rust-application) constfs mounted successfully CoAP server ready; waiting for interfaces to settle before reporting addresses... Active interface from PID KernelPID(6) (gnrc_netdev_tap) Address fe80:0000:0000:0000:1234:56ff:fe78:90ab Address 2a02:0b18:c13b:8018:1234:56ff:fe78:90ab其中constfs mounted successfully即来自 C 函数do_vfs_init()Rust 在启动早期调用随后是 gcoap 服务器就绪提示以及每个活动网络接口此处为 native 平台的gnrc_netdev_tap的链路本地与全局 IPv6 地址。用 aiocoap-client 验证.well-known/core等服务器就绪后另开一个终端把命令中的地址替换为上面枚举到的实际 IPv6 地址$ aiocoap-client coap://[2a02:0b18:c13b:8018:1234:56ff:fe78:90ab]/.well-known/core该请求会命中with_wkc()生成的资源发现端点返回示例暴露的全部 CoAP 资源列表/ps、/led、/vfs、/saul、/netif、/nib/neigh、/ping、/pinged等从而验证整个 C/Rust 混编 gcoap 接入链路工作正常。栈空间的针对性调整值得留意的是Makefile 中为 Rust 示例额外调大了两个线程栈CFLAGS -DGCOAP_STACK_SIZE(THREAD_STACKSIZE_DEFAULTDEBUG_EXTRA_STACKSIZEsizeof(coap_pkt_t)12288) CFLAGS -DTHREAD_STACKSIZE_MAIN(THREAD_STACKSIZE_DEFAULTTHREAD_EXTRA_STACKSIZE_PRINTF1024)注释说明Rust 示例比 gcoap 预期的占用更多栈原因尚未完全查明且并非字符串格式化导致main线程还需要额外栈空间用于打印地址此处受字符串格式化影响。这一细节体现了 Rust 应用落地到资源受限的嵌入式环境时必须进行的实际调优。安全说明默认未启用安全机制的取舍示例文档给出了明确的安全警告Note that no security is enabled by default so far; this is considered a bug.也就是说该示例默认不启用任何安全机制官方将其视为一个 bug。为缓解该问题示例刻意不暴露任何预期会造成危害的控制项——例如虽然暴露了 LED 资源但刻意不暴露 GPIO 引脚因为 GPIO 可能根据所接硬件的不同而被驱动到某个危险电平。在自行扩展该示例时应把这一约束作为底线凡是可能触发物理伤害的硬件控制项都不应在未启用安全的前提下通过 CoAP 暴露。小结rust-gcoap是理解用 Rust 编写 RIOT 应用这条技术路线的最佳进阶样本它以APPLICATION_RUST_MODULE打通了 C/Rust 混编的构建链路用riot-wrappers的安全封装把 gcoap、ztimer、gnrc 等 RIOT 组件暴露给#![no_std]的 Rust 世界并通过模块化 crate 组织出一棵完整的 CoAP 资源树。无论是想在自己的 RIOT 应用里引入 Rust、还是想为节点搭建一个可资源发现的 CoAP 服务都可以直接以此为起点并在此基础上按需开启安全机制。【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考