应用协议:HTTP 与 CoAP

本篇是《Ariel OS 深度实战:Rust 库式 IoT 操作系统全解析》系列第 23 篇。上一篇:网络栈(下):TCP 客户端与回显服务,下一篇:USB 实战:串口与 HID 键盘,目录见文末。

应用协议:HTTP 与 CoAP

导读

前两篇我们把 TCP/UDP 的传输层打通了,但字节流本身没有业务语义。IoT 设备最终要说"协议话":用 HTTP 从云端拉配置、用 REST 风格上报状态、或者用为受限设备而生的 CoAP 与 IETF 标准生态对话。本篇覆盖 Ariel OS 仓库中的四个协议示例——http-client(reqwless + TLS)、http-server(picoserve)、coap-server 与 coap-client——它们代表了嵌入式应用协议的全部四种形态:加密客户端、明文/加密服务端、CoAP 服务端与 CoAP 客户端。

Ariel OS 在协议层的策略很清晰:不造轮子,只做集成与安全默认值。HTTP 走社区 crate(reqwless 客户端、picoserve 服务端),Ariel OS 提供栈句柄与 TLS 种子;CoAP 则深度集成——服务端默认启用 OSCORE + EDHOC + ACE 的加密与授权链,这是 Ariel OS "为分布式系统的微控制器节点提供健壮软件基座"愿景的直接体现。本篇会解释这套安全栈为什么长这样,以及工程上怎么启用、怎么降级到无鉴权模式做调试。

主线板卡 ESP32-C6 上四个示例都能跑(http-server 除外——它需要 nightly,而 ESP32-C6 用 stable 工具链,示例还与 ram-small 冲突;我们会说明原因并给出替代验证路径)。Wi-Fi 凭据照例用 your-ssid/your-password 占位。

为什么需要两种协议、四个角色

HTTP 与 CoAP 的关系常被简化为"CoAP 是轻量版 HTTP",但工程视角下它们解决不同的问题:

HTTP 的优势是生态:云端 API、TLS 证书体系、CDN、观测工具一应俱全。设备直连云服务的场景(固件检查、证书轮换、JSON 遥测)几乎都用 HTTP(S)。代价是内存:TLS 1.3 单条加密记录上限 16 KiB + 256 字节(RFC 8449),客户端解析请求头、缓冲响应体都要实打实的 RAM——这正是 http-client 示例声明 executor_stacksize_required: 65536 的原因。

CoAP(RFC 7252)是 IETF 为受限节点(RFC 7228 的 Class-0/1 设备)设计的 REST 协议:报文头 4 字节、跑在 UDP 上(Ariel OS 目前仅支持 UDP 传输)、支持多播与观察(Observe, RFC 7641)。它真正的杀手锏是安全栈的同构性:OSCORE(对称加密)、EDHOC(密钥交换)、ACE(授权)都是" CoAP 请求/响应"形态的通信,天然穿透代理、跨传输层提供端到端安全,且能向下缩放到最小设备。这是 Ariel OS 选择 OSCORE/EDHOC/ACE 而非 DTLS 的原因(Book 原文:它们 scale down well to the smallest devices)。

四个角色的矩阵也值得展开:客户端(发起请求,内存压力主要在缓冲与 TLS)vs 服务端(长期监听,内存压力在并发处理);安全(密钥管理、握手开销)vs 不安全(调试便利)。Ariel OS 用 laze 模块把这张矩阵编码成了可组合的开关,本篇逐个拆解。

模块地图

http-client:  features: csprng, dns, mdns, tcp + network 模块 + reqwless 依赖
http-server:  network 模块 + picoserve 依赖(需 nightly: impl_trait_in_assoc_type)
coap-server:  coap-server 模块 + 安全策略模块(demokeys / unprotected / storage 三选一)
coap-client:  coap-client 模块(当前客户端安全支持尚未成熟)

注意 coap 模块(总开关)的要求:需要 C 编译器(依赖链里有 C 代码),与 ram-small 模块冲突,且执行器栈需求追加 32768 字节。这些约束写在 laze 模块声明里,构建期即暴露,而不是运行时才炸。

代码案例:HTTP 客户端(HTTPS GET)

Cargo.toml(节选依赖与 feature):

[dependencies]
ariel-os = { path = "../../src/ariel-os", features = ["csprng", "dns", "mdns", "tcp"] }
ariel-os-boards = { path = "../../src/ariel-os-boards" }
reqwless = "0.13"   # 版本以仓库 workspace 为准

laze.yml(关键项):selects: [network],executor_stacksize_required: 65536,与 ram-small、esp32c2、esp32s2、stm32l475vg 冲突。

src/main.rs:

#![no_main]
#![no_std]

use ariel_os::{
    config,
    debug::{ExitCode, exit},
    log::*,
    net,
    reexports::embassy_net,
};
use embassy_net::{
    dns::DnsSocket,
    tcp::client::{TcpClient, TcpClientState},
};
use reqwless::{
    client::{HttpClient, TlsConfig, TlsVerify},
    request::Method,
};

// RFC8449: TLS 1.3 encrypted records are limited to 16 KiB + 256 bytes.
const MAX_ENCRYPTED_TLS_13_RECORD_SIZE: usize = 16640;
// Required by `embedded_tls::TlsConnection::new()`.
const TLS_READ_BUFFER_SIZE: usize = MAX_ENCRYPTED_TLS_13_RECORD_SIZE;
// Can be smaller than the read buffer (could be adjusted: trade-off between memory usage and not
// splitting large writes into multiple records).
const TLS_WRITE_BUFFER_SIZE: usize = 4096;

const TCP_BUFFER_SIZE: usize = 1024;
const HTTP_BUFFER_SIZE: usize = 1024;

const MAX_CONCURRENT_CONNECTIONS: usize = 2;

const ENDPOINT_URL: &str = config::str_from_env_or!(
    "ENDPOINT_URL",
    "https://crab.ariel-os.org",
    "endpoint to send the GET request to"
);

#[ariel_os::task(autostart)]
async fn main() {
    let stack = net::network_stack().await.unwrap();

    let tcp_client_state =
        TcpClientState::<MAX_CONCURRENT_CONNECTIONS, TCP_BUFFER_SIZE, TCP_BUFFER_SIZE>::new();
    let tcp_client = TcpClient::new(stack, &tcp_client_state);
    let dns_client = DnsSocket::new(stack);

    let tls_seed: u64 = rand_core::RngCore::next_u64(&mut ariel_os::random::crypto_rng());

    let mut tls_rx_buffer = [0; TLS_READ_BUFFER_SIZE];
    let mut tls_tx_buffer = [0; TLS_WRITE_BUFFER_SIZE];

    // We do not authenticate the server in this example, as that would require setting up a PSK
    // with the server.
    let tls_verify = TlsVerify::None;
    let tls_config = TlsConfig::new(tls_seed, &mut tls_rx_buffer, &mut tls_tx_buffer, tls_verify);

    let mut client = HttpClient::new_with_tls(&tcp_client, &dns_client, tls_config);

    stack.wait_config_up().await;

    if let Err(err) = send_http_get_request(&mut client, ENDPOINT_URL).await {
        error!(
            "Error while sending an HTTP request: {:?}",
            Debug2Format(&err)
        );
    }

    exit(ExitCode::SUCCESS);
}

async fn send_http_get_request(
    client: &mut HttpClient<'_, TcpClient<'_, MAX_CONCURRENT_CONNECTIONS>, DnsSocket<'_>>,
    url: &str,
) -> Result<(), reqwless::Error> {
    let mut http_rx_buf = [0; HTTP_BUFFER_SIZE];

    let mut handle = client.request(Method::GET, url).await?;
    let response = handle.send(&mut http_rx_buf).await?;

    info!("Response status: {}", response.status.0);

    if let Some(ref content_type) = response.content_type {
        info!("Response Content-Type: {}", content_type.as_str());
    }

    if let Ok(body) = response.body().read_to_end().await {
        if let Ok(body) = core::str::from_utf8(&body) {
            info!("Response body:\n{}", body);
        } else {
            info!("Received a response body, but it is not valid UTF-8");
        }
    } else {
        info!("No response body");
    }

    Ok(())
}

运行:

CONFIG_WIFI_NETWORK='your-ssid' CONFIG_WIFI_PASSWORD='your-password' \
laze -C examples/http-client build -b esp32-c6-devkitc-1 run

案例 2:CoAP 服务端与客户端

coap-server 的 main.rs 是全仓库最短的应用代码之一:

#![no_main]
#![no_std]

#[ariel_os::task(autostart)]
async fn coap_run() {
    use coap_handler_implementations::{HandlerBuilder, SimpleRendered, new_dispatcher};

    let handler = new_dispatcher()
        // We offer a single resource: /hello, which responds just with a text string.
        .at(&["hello"], SimpleRendered("Hello from Ariel OS"));

    ariel_os::coap::coap_run(handler).await;
}

laze.yml:selects: [coap-server, "?coap-server-config-demokeys"](? 前缀表示可选依赖)。

coap-client 向服务端发一个 POST:

#![no_main]
#![no_std]

use ariel_os::log::info;

#[ariel_os::task(autostart)]
async fn run_client_operations() {
    use coap_request::Stack;

    let client = ariel_os::coap::coap_client().await;

    // Corresponding to the fixed network setup, we select a fixed server address; this may need to
    // be updated on hosts that are configured differently.
    let addr = "10.42.0.1:5683"; // IPv4
    let demoserver = addr.parse().unwrap();

    info!("Sending POST to {}...", demoserver);
    let request = coap_request_implementations::Code::post()
        .with_path("/uppercase")
        .with_request_payload_slice(b"This is Ariel OS")
        .processing_response_payload_through(|p| {
            info!(
                "Uppercase response is {:?}",
                core::str::from_utf8(p).map_err(|_| "not Unicode?")
            );
        });
    let response = client.to(demoserver).request(request).await;
    info!("Response {:?}", response.map_err(|_| "TransportError"));
}

laze.yml:selects: [coap-client]。客户端示例默认把请求发到 10.42.0.1:5683——这正是静态 IPv4 配置的默认网关地址,暗示了典型的实验拓扑:主机(如树莓派或 Linux 工作站)配 10.42.0.1 并跑一个 CoAP 服务端(tests/coap 目录带了 peers.yml、client.cosekey、fauxhoc.py 等配套工具)。

逐段解析

http-client:三层客户端的装配

http-client 的主任务前半段是"装配三层客户端",值得逐层读:

let tcp_client_state =
    TcpClientState::<MAX_CONCURRENT_CONNECTIONS, TCP_BUFFER_SIZE, TCP_BUFFER_SIZE>::new();
let tcp_client = TcpClient::new(stack, &tcp_client_state);
let dns_client = DnsSocket::new(stack);

第一层是 embassy-net 的 TcpClient:注意它不是上一篇手写的 TcpSocket,而是连接池——TcpClientState<2, 1024, 1024> 预分配 2 个并发连接的槽位与缓冲,TcpClient 从池里拿。reqwless 这样的高层库可能同时开连接(重定向、连接复用),池化比单 socket 灵活。DnsSocket 提供域名解析(dns feature 启用后才存在),把 URL 里的主机名交给栈的 DNS 解析器。

第二层是 TLS:

let tls_seed: u64 = rand_core::RngCore::next_u64(&mut ariel_os::random::crypto_rng());
let tls_config = TlsConfig::new(tls_seed, &mut tls_rx_buffer, &mut tls_tx_buffer, tls_verify);

TLS 握手需要高质量随机性(ClientRandom、会话密钥),所以用的是 crypto_rng()(ChaCha20Rng CSPRNG,第 20 篇),并且只在启动时取一次种子(TLS 会话密钥由 embeddedTLS 内部继续扩展,种子只需不可预测)。缓冲常量直接引用 RFC 8449 的上限 16640 字节做读缓冲——注释解释了读缓冲必须装下整条最大加密记录,写缓冲 4096 是"内存 vs 记录拆分"的折中。TlsVerify::None 是示例的诚实妥协:真正的服务器认证需要 PSK 或证书链,Book 注释明说"would require setting up a PSK with the server"。这是示例简化,不是推荐实践——生产中至少要固定服务器公钥。

第三层 HttpClient::new_with_tls(&tcp_client, &dns_client, tls_config) 把三层缝起来。之后的请求风格是 Rust 网络客户端熟悉的 builder 链:client.request(Method::GET, url) 得到 handle,handle.send(&mut http_rx_buf) 发送并解析响应,response.body().read_to_end() 读体。http_rx_buf 是每次请求传入的缓冲——把"连接缓冲(池,长期)"与"HTTP 解析缓冲(调用方,单次)"分开,是嵌入式 HTTP 库控制内存峰值的常见手法。

ENDPOINT_URL 用 config::str_from_env_or! 编译期读取环境变量,默认 https://crab.ariel-os.org——构建期可替换测试端点而无需改代码,与前两篇的凭据环境变量同一机制。

为什么 executor 栈要 65536

http-client 在 laze.yml 声明 executor_stacksize_required: 65536,比前两篇的 16384 跃升一个量级。叠加链:reqwless 的 HTTP 解析(嵌套状态机)→ embeddedTLS 的握手(X.509 解析、密钥运算的深调用链)→ embassy-net 的 TCP 处理 → 日志宏的 defmt 格式化。64 KiB 对 MCUIoT 应用不小,但相比"上线即随机 hard fault"是便宜保险。这也是为什么示例与 ram-small(RAM ≤ 32 KiB 的板卡档位)冲突——栈加缓冲的静态需求物理上放不下。冲突列表里的 esp32c2/esp32s2/stm32l475vg 是同类内存原因。

coap-server:handler 装配与系统提供的资源

let handler = new_dispatcher()
    .at(&["hello"], SimpleRendered("Hello from Ariel OS"));
ariel_os::coap::coap_run(handler).await;

new_dispatcher() 创建按路径分发资源的处理器,.at(&["hello"], ...) 挂载路径 /hello,SimpleRendered 是最简的"GET 返回文本"实现(来自 coap-handler 系 crate)。关键在 coap_run(handler):应用 handler 被叠加进系统级 CoAP 服务——OS 还会挂载自己的资源,如 /.well-known/core(资源发现)与 /.well-known/edhoc(建立安全连接)。若不选 coap-server 模块而是只跑自己的任务,系统级 handler 不会被启动,EDHOC/发现等功能缺失——Book 明确要求应用通过 coap_run 接入而不是自起 UDP 服务。

安全策略三选一:

  • coap-server-config-demokeys:示例用硬编码密钥与策略,配套示例客户端可用,绝不用于生产。
  • coap-server-config-unprotected:完全无鉴权,任何客户端可访问任何资源,仅用于协议调试。
  • coap-server-config-storage:从 peers.yml 读取客户端身份与权限(文件格式尚在演进,见 tests/coap/peers.yml);设备首次启动时生成 EDHOC 私钥并经第 27 篇的存储机制持久化,启动时上报公钥凭据。这是生产方向的策略。

三种策略共享同一个 handler 结构——安全在 Ariel OS 的 CoAP 栈里是正交的决策层("请求是否被允许"交给 access policy),业务 handler 只管内容正确性(Book 原文提醒:文件解析器等仍须把请求内容当可能畸形的数据对待)。

coap-client:声明式请求

客户端代码用 coap_request_implementations::Code::post().with_path(...).with_request_payload_slice(...) 的 builder 链声明请求,.processing_response_payload_through(closure) 流式处理响应载荷。client.to(demoserver).request(request).await 执行。ariel_os::coap::coap_client() 是异步获取客户端句柄的入口,与服务端 coap_run 对称。Book 对客户端现状的定位要如实转述:客户端支持尚不成熟,且当前唯一可用的安全策略是"不加密请求"——生产化部署的安全模型目前落在服务端侧。

运行现象

http-client:

INFO waiting for interface to come up...(若与其他示例同构)
INFO Response status: 200
INFO Response Content-Type: text/html
INFO Response body:
<!doctype html>...(crab.ariel-os.org 的页面内容)

替换端点做对照实验:ENDPOINT_URL='https://httpbin.org/status/418' laze ...,观察 418 状态码与 Content-Type 头的处理路径;断网运行看 error! 分支与 exit(FAILURE)。

coap-server + coap-client 对联:一块 ESP32-C6 跑 coap-server(demokeys 策略),主机用 tests/coap 的工具(或 libcoap 的 coap-client 命令行)请求 coap://<设备IP>/hello:

INFO GET /hello -> "Hello from Ariel OS"

再在第二块板(或 native 目标,见第 29 篇——native + tuntap 是零成本的第二节点)跑 coap-client,指向服务端 IP:

INFO Sending POST to 10.42.0.1:5683...
INFO Uppercase response is Ok("THIS IS ARIEL OS")
INFO Response Ok(...)

/uppercase 资源演示了请求载荷的回程处理。加密路径(EDHOC 握手 + OSCORE)在 demokeys 策略下默认启用,主机侧工具需要加载配套的 client.cosekey——这也是调试 CoAP 安全比调试 HTTP 麻烦的原因,unprotected 策略就是为跳过这一步而存在的。

常见问题

Q1:http-client 在 ESP32-C6 上内存/栈压力这么大,能不能砍掉 TLS 或缩小缓冲?

成因:TLS 读缓冲 16640 字节是 RFC 8449 记录上限的硬约束(embeddedTLS 要求装下整条记录),不可直接砍;64 KiB 执行器栈是防御性声明。可以做的:只跑 HTTP(无 TLS)时用 HttpClient::new(&tcp_client, &dns_client),去掉 tls 缓冲与 csprng 依赖,端点用 http://,栈需求可显著回落;把 TLS_WRITE_BUFFER_SIZE 降到 1024(代价是发送拆更多记录);TCP_BUFFER_SIZE/HTTP_BUFFER_SIZE 按响应体大小调。但 TlsVerify::None 之上的"固定公钥"方案(PSK 或 certificate pinning)不建议砍——嵌入式设备没有用户弹窗兜底,认证缺失等于裸奔。

Q2:http-server 为什么 conflicts stable?我的 ESP32-C6 怎么跑它?

成因:http-server 源文件第一行是 #![feature(impl_trait_in_assoc_type)]——picoserve 的 Router 类型签名依赖这个 nightly 特性(关联类型里的 impl Trait),stable 工具链直接编译失败,所以示例的 conflicts 里显式排除了 stable 模块。ESP32-C6 的官方构建走 stable(RISC-V 不需要 esp 工具链),因此当前示例无法在 C6 上原生构建。解决方案:等 picoserve 更新后该特性要求移除(这是依赖演进而非 Ariel OS 限制);或在另一块 nightly 友好板(如部分 STM32/nRF 组合)验证;或用第二块板/ native 目标跑服务端,C6 跑 http-client 做对联验证。

Q3:coap-client 发请求没响应,主机侧也没看到 UDP 包?

成因排查:默认目标地址 10.42.0.1:5683 是 Ariel OS 静态配置的默认网关——你的主机如果不在 10.42.0.0/24 网段(比如设备走 DHCP 拿了 192.168.1.x),包发去了一个不存在的网关。解决方案二选一:给设备配 network-config-ipv4-static(模块顺序记得放 network 之前!)让网关真的是主机,主机再配 ip address add 10.42.0.1/24 dev <iface>;或改客户端代码里的 addr 为设备的实际网段内主机地址。主机侧先用 tcpdump -i any udp port 5683 确认包是否到达,再区分"没发出"(网络问题)与"发出无回应"(服务端/策略问题)。

Q4:coap-server 用 unprotected 策略能通,换 demokeys/storage 后主机工具握手失败?

成因:demokeys/storage 策略要求客户端具备 EDHOC/OSCORE 能力并持有对应凭据(demokeys 的硬编码密钥对、storage 策略下 peers.yml 里登记的身份)。普通 coap 命令行工具(libcoap、coap-cli)默认没有 EDHOC。解决方案:使用 tests/coap 目录提供的配套工具(fauxhoc.py、client.cosekey 等)或支持 EDHOC 的库(如 ariel-os 生态的 Rust 客户端);调试期先用 unprotected 隔离网络层问题,再逐步引入安全层;读 peers.yml 样例确认权限模型(哪个客户端身份允许对哪个路径做什么方法)。

Q5:为什么 Ariel OS 的 CoAP 选 OSCORE/EDHOC 而不是更常见的 DTLS?

成因与论证:Book 给出两条理由。其一,缩放性——OSCORE/EDHOC/ACE 能缩到最小设备(Class-0/1),对象安全(object security)不要求维护传输层会话状态,断网重连无需重新握手,这对电池设备意义巨大。其二,同构性——三者都表现为 CoAP 请求/响应,可透明穿越 CoAP 代理、在 UDP/TCP/WebSockets 等任何传输上提供端到端安全;DTLS 是传输层安全,每换一种传输就要重新设计(DTLS over TCP 是不同协议)。代价是生态:DTLS/TLS 工具链成熟度远高,OSCORE/EDHOC 的主机侧工具稀缺——这是当前 CoAP 调试体验偏重的原因,也是 RFC 层面(OSCORE/EDHOC 均为 IETF Proposed Standard)的长期投资。

面试考点

考点 1:reqwless HttpClient 的装配为什么分成 TcpClientState/TcpClient、DnsSocket、TlsConfig 三层?各层的内存归属是什么?

参考答案:分层对应三个生命周期与所有权不同的资源。TcpClientState<2,1024,1024> 是连接池:静态预分配 N 个并发连接的收发缓冲,生命周期与任务同长,由 TcpClient 借用——池化让多个请求复用连接与缓冲。DnsSocket 是栈上的解析服务句柄,自身只持有对栈的引用,解析缓冲在栈内。TlsConfig 持有握手种子与 TLS 记录缓冲(读 16640 + 写 4096),由调用方提供 'static 缓冲。HTTP 解析缓冲(http_rx_buf)则是每次请求临时传入。这个"谁的数据谁提供、生命周期显式化"的设计使所有内存都是静态或栈上的,无堆分配——嵌入式 HTTP 客户端的内存工程核心。

考点 2:解释 OSCORE + EDHOC + ACE 三件套在 CoAP 通信中的角色分工,与 TLS/DTLS 方案相比的关键差异。

参考答案:EDHOC 负责密钥交换(基于 CBOR 的精简 Diffie-Hellman,产出会话密钥);OSCORE 用这些密钥对 CoAP 报文做对象级加密与完整性保护(保护的是报文选项与载荷,外层寻址可见);ACE 负责授权(客户端凭令牌声明"我能对哪些资源做哪些方法")。与 DTLS 的关键差异:(1) 安全部位不同——OSCORE 在应用层保护对象,DTLS 在传输层保护通道,前者跨代理端到端、跨传输通用,后者绑定 UDP 会话;(2) 状态量不同——OSCORE 会话态极小,断线重连免重握手,DTLS 重连需完整握手,电池敏感场景差距显著;(3) 生态不同——DTLS/TLS 工具丰富,OSCORE/EDHOC 工具稀缺。Ariel OS 的选择偏向长期标准合规与最小设备可用性。

考点 3:Ariel OS CoAP 服务端的 access policy 与 resource handler 为什么分离?coap_run(handler) 在其中扮演什么角色?

参考答案:handler 决定"内容对不对"(路径到资源的映射、载荷格式解析、渲染响应),policy 决定"这个请求允许吗"(身份验证后的授权判断)。分离使同一业务 handler 可在 demokeys/unprotected/storage 三种安全策略下复用,安全升级不改业务代码;也使 handler 作者专注格式健壮性(把请求内容当可能畸形的数据),权限判断集中到策略层,便于审计。coap_run(handler) 把应用 handler 组装进系统 CoAP 服务:系统同时挂载 /.well-known/core(发现)与 /.well-known/edhoc(密钥协商)等 OS 级资源,并按所选策略启用安全处理。不经 coap_run 自起服务会丢失这些系统集成。

考点 4:config::str_from_env_or!("ENDPOINT_URL", ...) 这类编译期配置宏的价值与局限是什么?与运行时配置文件方案相比?

参考答案:价值:零运行时成本(值是编译期常量,直接进 rodata);配置缺失在构建期报错而非现场故障;与 laze 的 -D 变量/环境变量注入天然集成,CI 矩阵可为同一代码构建不同配置;无文件系统依赖。局限:任何配置变更都要重编译烧录,固件镜像内明文(不适合机密,除非配合安全启动与加密镜像);无法根据部署现场调整。与运行时方案(如第 27 篇 storage 存配置)相比:编译期适合"构建时已知、部署期不变"的量(端点、凭据、栈大小),运行时适合"现场可调"的量(用户偏好、上次状态)。工程上常用混合:编译期默认值 + 首次启动写入 storage 允许覆盖。

考点 5:为什么 http-client 示例要显式声明 MAX_CONCURRENT_CONNECTIONS 而不是动态扩展?这个值影响哪些资源?

参考答案:TcpClientState::<2, 1024, 1024> 在编译期决定连接池的槽数与每槽缓冲——总共 2 x 2 KiB 静态缓冲。动态扩展需要堆(Ariel OS 默认不开 alloc,且网络路径的堆碎片化在长寿命设备上是灾难)或预留上限(等于还是静态,只是虚报)。声明式并发上限把资源占用变成可审计的编译期数字:2 连接 x 2 KiB + TLS 20 KiB + HTTP 缓冲 2 KiB,加起来就是该任务的内存指纹。它还约束 reqwless 的行为——第 3 个并发请求会等待或失败,倒逼应用设计串行化请求。这与 CONFIG_NETWORK_MAX_CONCURRENT_SOCKETS(栈级 socket 槽位,默认 4)是两层并发预算,应用层池不能超过栈层槽位。

小结

本篇把传输层字节流升级为业务协议:HTTP 客户端展示了"TCP 连接池 + DNS + TLS + HTTP 解析"的四层装配与各自的内存归属;CoAP 则展示了 Ariel OS 的安全野心——OSCORE/EDHOC/ACE 默认加密、策略与业务正交、服务端资源由系统与应用共同挂载。四个示例对应的 laze 模块与 feature 组合(csprng/dns/mdns/tcp、coap-server + 策略、coap-client)可以直接搬进你的项目。内存预算是这个领域的主旋律:64 KiB 执行器栈、16.6 KiB TLS 读缓冲、按需声明的连接池,都是"确定性 > 弹性"的嵌入式取舍。

下一篇转向 USB:设备如何把自己变成串口(CDC-ACM)、键盘(HID),以及系统 USB builder 的钩子机制。

系列目录

  1. 系列总览:Ariel OS 设计理念与生态全景
  2. 环境搭建:Rust 工具链与 laze 构建系统
  3. laze 元构建系统:contexts、modules 与 builders
  4. 第一个应用:从 minimal 到 hello-world
  5. 板卡描述体系:boards yaml 与结构化配置
  6. 烧录与调试:probe-rs、RTT 与日志查看
  7. 日志系统:defmt 与 ariel-os-log
  8. 调试控制台:shell 与交互调试
  9. 异步执行器:Embassy 集成与任务宏
  10. 内存布局与全局分配器
  11. 时钟体系与启动流程
  12. 引脚与 GPIO:静态配置与运行时切换
  13. 多线程(上):抢占式线程与线程宏
  14. 多线程(下):Channel、Event 与互斥
  15. 线程与异步互操作:multicore 与定时器
  16. 低功耗与电源管理
  17. I2C 实战:总线扫描与设备通信
  18. SPI 与 UART 实战
  19. 传感器框架:sensor API 与注册表
  20. 随机数与熵源
  21. 网络栈(上):初始化、Wi-Fi 与 UDP
  22. 网络栈(下):TCP 客户端与回显服务
  23. 应用协议:HTTP 与 CoAP
  24. USB 实战:串口与 HID 键盘
  25. BLE 实战:广播与扫描
  26. 以太网、设备元数据与身份
  27. 持久化存储
  28. 测试:embedded-test 与硬件在环
  29. 原生目标、基准测试与 Ferrocene
  30. 收官:新增板卡支持、横向对比与路线图
posted @ 2026-10-28 00:45  navysummer_hardware  阅读(11)  评论(0)    收藏  举报