CoAP 与 LwM2M 受限设备协议

本文讲解受限设备上的 CoAP 与 LwM2M 协议栈,覆盖 RFC 7252 的 4 字节报文头、方法码与响应码、ACK_TIMEOUT 与 MAX_RETRANSMIT 重传参数、Observe 通知与分块传输。文章给出 DTLS 与 OSCORE 的安全取舍、LwM2M 对象实例资源模型、Bootstrap 与注册流程,以及 Leshan 与 Wakaama 实现栈选型与完整注册交互示例。

引言

不是所有设备都能负担 MQTT。一个 NB-IoT 水表每 6 小时上报一次,电池要撑十年,模块内存只有几十 KB,链路带宽以百字节计。在这种约束下,TCP 三次握手加 TLS 握手的开销就已经超出预算,长连接的心跳更是持续耗电。CoAP 就是为这类设备设计的:UDP 之上、报文头仅 4 字节、支持请求响应与观察通知。

单有 CoAP 还不够,设备管理需要一套统一的对象模型与生命周期流程,LwM2M 补上了这一层。它定义了 Security、Server、Device、Firmware Update 等标准对象,规定注册、更新、注销与读取写入执行的交互,让不同厂商的设备能被同一个平台管理。

本文先讲 CoAP 的报文结构与交互机制,再覆盖安全与传输扩展,然后进入 LwM2M 的对象模型与注册流程,最后给出实现栈选型与一次完整的注册交互示例。MQTT 与 CoAP 的定位差异在接入篇已说明,报文与 QoS 细节见 MQTT 协议与 Broker 实践 ;LPWAN 承载层的选择见 LoRa、NB-IoT 与 LPWAN 技术选型 。

目录

  1. 为什么受限设备需要 UDP 上的 REST
  2. CoAP 报文格式
  3. 方法码与响应码
  4. 重传与可靠性参数
  5. Observe 模式
  6. Block-wise 传输
  7. CoAP 安全:DTLS 与 OSCORE
  8. CoAP over TCP 与 WebSocket
  9. LwM2M 对象模型
  10. Bootstrap 与注册流程
  11. 实现栈与部署
  12. 一次完整的注册交互示例

1. 为什么受限设备需要 UDP 上的 REST

HTTP 与 MQTT 都建立在 TCP 之上。TCP 的可靠性对受限设备是双刃剑:它保证了有序不丢,但也意味着建连握手、拥塞控制与重传队列都要占用内存与时间。一个 TLS over TCP 的完整握手在网络良好时也要两三个往返,弱网下可能十几秒,期间设备必须持续供电。

UDP 之上做轻量可靠,把可靠性控制权交还给应用层,是更划算的选择:CoAP 用可选的确认机制实现必要时的可靠,不需要时不付出代价。请求响应模型让交互天然适合 REST 语义,设备资源被抽象成 URI,读取温度就是 GET /3303/0/5700。

与 MQTT 的取舍可以归纳为:

  • 功耗:CoAP 无长连接心跳,稀疏上报场景更省电。
  • 双向性:MQTT 天生支持下行推送,CoAP 需要 Observe 或设备轮询。
  • 复杂度:CoAP 需自行处理重传与去重,MQTT 由 TCP 与 Broker 承担。
  • 语义:CoAP 是资源导向,MQTT 是消息导向,设备管理场景资源导向更自然。

2. CoAP 报文格式

CoAP 报文由一个 4 字节定长头、可变的 Token、若干 Option 与可选 Payload 组成。

 0               1               2               3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Ver| T |  TKL  |      Code     |          Message ID           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   Token (0..8 bytes) ...                                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   Options (delta-encoded) ...                                 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 1 1 1 1 1 1 1|    Payload (可选) ...                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

字段含义:

字段位宽说明
Version2 bit固定为 1
Type2 bitCON=0、NON=1、ACK=2、RST=3
TKL4 bitToken 长度,0 到 8
Code8 bit方法码或响应码,高 3 位为类别
Message ID16 bit用于匹配 ACK 与去重
Token0 到 8 字节匹配请求与响应,跨代理保持不变
GET /3303/0/5700 的报文(CON,MessageID=0x7d34,Token=0x71)
40 01 7d 34 71 b4 74 65 6d 70
40          -- Ver=1, Type=CON, TKL=1
01          -- Code=0.01 GET
7d 34       -- Message ID
71          -- Token
b4 74 65 6d 70 -- Option: Uri-Path "temp"

Type 的四种取值决定交互语义:CON 需要 ACK,NON 不确认,ACK 是对 CON 的应答,RST 表示收到了无法处理的报文。

3. 方法码与响应码

Code 字段用 类别.详情 表示,类别 0 为方法,2 到 5 为响应。

码值名称说明
0.01GET读取资源
0.02POST创建或触发
0.03PUT更新或替换
0.04DELETE删除资源
2.01Created创建成功
2.02Deleted删除成功
2.04Changed更新成功
2.05ContentGET 成功,带载荷
4.00Bad Request请求格式错误
4.01Unauthorized未授权
4.04Not Found资源不存在
5.03Service Unavailable服务不可用

一个读取温度的交互是 GET 得到 2.05 Content,载荷为 23.5;若资源不存在则回 4.04 Not Found。响应的 Token 必须与请求一致,这是客户端匹配响应的依据,Message ID 只用于 CON 的 ACK 匹配。

4. 重传与可靠性参数

CoAP 的可靠传输只在 CON 上生效。客户端发出 CON 后启动定时器,超时未收到 ACK 就重传,重传次数与间隔由四个参数控制。

参数默认值含义
ACK_TIMEOUT2 s初始超时时间
ACK_RANDOM_FACTOR1.5超时随机化系数,避免同步重传
MAX_RETRANSMIT4最大重传次数
NSTART1到同一端点的并发未确认请求数
DEFAULT_LEISURE5 s多播场景随机延迟上限
EXCHANGE_LIFETIME247 s报文去重状态保留时长

首次超时在 ACK_TIMEOUT 到 ACK_TIMEOUT × ACK_RANDOM_FACTOR 之间随机取,即 2 到 3 秒;之后按指数退避翻倍,直到 MAX_RETRANSMIT 次后放弃。理论最长等待约 45 秒,加上处理时间,EXCHANGE_LIFETIME 取 247 秒作为去重窗口。

重传时间线(CON)
t=0     发送
t=2..3  首次超时,重传 1
t=6..9  重传 2
t=14..21 重传 3
t=30..45 重传 4
t>45    放弃,向应用层报超时

NB-IoT 的典型 RTT 在 1 到 3 秒,默认 ACK_TIMEOUT 偏紧,容易触发无谓重传。工程上常把 ACK_TIMEOUT 调到 3 到 5 秒,并把 MAX_RETRANSMIT 保持 4,以容忍高延迟链路。

5. Observe 模式

Observe(RFC 7641)让客户端订阅资源变化,服务端在变化时主动通知,用一条 GET 加 Observe Option 建立。

请求:GET /3303/0/5700  Observe: 0
响应:2.05 Content       Observe: 12  Payload: 23.5
通知:2.05 Content       Observe: 13  Payload: 23.7
通知:2.05 Content       Observe: 14  Payload: 23.6

Observe 值是递增序号,客户端用它判断通知的新旧与乱序。取消订阅发 Observe: 1 或直接 RST。

通知的传输方式可选 CON 或 NON。NON 通知省流量但不保证送达,CON 通知可靠但增加功耗。工程上按数据重要性选择:告警类用 CON,周期遥测用 NON 并配合较大的通知间隔。

服务端要为每个观察者维护状态,观察者数量大时内存压力显著。RFC 建议用「资源驱动」而非「客户端驱动」的观察者表实现,并对静默观察者做超时清理。周期性资源还应设置最小通知间隔,避免高频变化时把网络打满。

6. Block-wise 传输

受限设备的最大传输单元通常只有几百字节,而固件块、配置文件的传输远超此限。Block-wise(RFC 7959)把载荷切块,每块用 Option 声明序号、大小与是否还有后续。

请求:GET /5/0/0  Block2: NUM=0, M=0, SZX=2(64B)
响应:2.05 Content Block2: NUM=0, M=1, SZX=2  <64 字节>
请求:GET /5/0/0  Block2: NUM=1, M=0, SZX=2
响应:2.05 Content Block2: NUM=1, M=1, SZX=2  <64 字节>
...
  • Block1 用于请求方向的分块(上传),Block2 用于响应方向的分块(下载)。
  • SZX 编码块大小,2 表示 64 字节,6 表示 1024 字节。
  • M 为 1 表示还有后续块,为 0 表示最后一块。
  • 块大小可协商,服务端可在首个响应中调小 SZX 以适应链路。

固件下载走 Block2,配合 ETag 做块级校验与断点续传,是 LwM2M FOTA 的基础。与设备侧固件升级的整体流程配合见 OTA 固件升级工程 。

7. CoAP 安全:DTLS 与 OSCORE

CoAP 的安全有两个层次:传输层用 DTLS,应用层用 OSCORE。

DTLS 1.2 是 CoAP 最早的安全方案,用 coaps:// 标识,默认端口 5684。它提供与 TLS 等价的机密性与完整性,但握手开销大,且需要在设备侧维护证书或预共享密钥(PSK)。PSK 模式最省资源,适合内存受限设备,代价是密钥分发与轮换的管理成本。

DTLS 1.3 精简了握手,支持 0-RTT 恢复,弱网重连场景收益明显。RFC 9147 定义了 DTLS 1.3,主流实现已陆续支持。

OSCORE(RFC 8613)在应用层加密,只保护 CoAP 载荷与部分 Option,端到端有效,且能穿越代理。它的优势是可以在 DTLS 之上叠加,或者替代 DTLS 用于无法承担握手的场景。

方案层次端到端开销适用
DTLS 1.2 PSK传输层否中设备到网关一跳
DTLS 1.3传输层否中低弱网重连频繁
OSCORE应用层是低需穿越代理
无安全无否无仅内网调试

安全设计的通用原则(密钥生命周期、最小权限、吊销流程)见 网络安全基础 。

8. CoAP over TCP 与 WebSocket

CoAP 并非只能跑在 UDP 上。RFC 8323 定义了 CoAP over TCP、TLS 与 WebSocket,用于三种场景。

第一,设备侧只能走 TCP 的网络(如某些蜂窝 APN 屏蔽 UDP)。第二,需要穿越只允许 HTTP 的企业代理,此时用 CoAP over WebSocket。第三,设备本身资源充裕,用 TCP 换取更简单的可靠性实现。

over TCP 的报文格式去掉了 Message ID 与 Type,因为 TCP 本身保证有序与不丢,重传逻辑不再需要。这简化了实现,但也失去了 UDP 的低开销优势。

选择口径:受限设备优先 UDP 上的 CoAP;需要穿越代理或复用现有 TCP 基础设施时用 WebSocket 承载;不要为了「统一」而把所有设备都改成 over TCP。

9. LwM2M 对象模型

LwM2M 用「对象 / 实例 / 资源」三级模型描述设备能力,URI 形如 /对象ID/实例ID/资源ID。

对象 ID名称用途
0Security服务器地址、安全模式、密钥
1Server注册服务器、生命周期、通知策略
3Device制造商、型号、固件版本、电量
4Connectivity Monitoring网络类型、信号强度、IP
5Firmware Update固件包、状态、升级结果
6Location经纬度、高度、时间戳
3303Temperature传感器值、单位、最小值最大值
3304Humidity湿度传感器

几个具体路径:

  • /3/0/0 设备对象实例 0 的资源 0,制造商名称。
  • /3/0/3 固件版本。
  • /3303/0/5700 温度传感器实例 0 的传感器值。
  • /1/0/1 服务器对象的生命周期(Lifetime),单位秒,决定注册有效期。

资源有读、写、执行三种操作权限。/5/0/2 固件包的下载执行用 Execute 触发,/3/0/13 重启设备也是 Execute。

LwM2M 资源权限示例
/3/0/0    R     制造商名称,只读
/1/0/1    RW    Lifetime,可读写
/5/0/2    E     固件下载,执行
/3303/0/5700 R  传感器值,只读,可 Observe

自定义对象用 1024 到 2047 的 ID 区间,避免与标准对象冲突。

10. Bootstrap 与注册流程

LwM2M 的设备生命周期分两个阶段:引导(Bootstrap)与注册(Registration)。

引导阶段让设备获得服务器地址与安全凭据。三种模式:

  • Factory Bootstrap:出厂预置,简单但不可远程变更。
  • Client Initiated Bootstrap:设备主动向引导服务器请求配置。
  • Server Initiated Bootstrap:服务器推送配置,需要设备已在引导服务器注册。

引导完成后设备向管理服务器注册:

POST /rd?ep=urn:imei:8675&lt=3600&lwm2m=1.2&b=U
Content-Format: application/link-format
</3/0>,</1/0>,</5/0>,</3303/0>
  • ep 是端点名,通常用 IMEI 或序列号,全局唯一。
  • lt 是 Lifetime,到期前必须更新注册。
  • b 声明支持的绑定模式,U 为 UDP,UQ 为带队列的 UDP。
  • 载荷是对象列表,声明设备支持哪些对象,服务器据此决定可用的管理操作。

注册成功后服务器返回位置路径(如 /rd/5a3f),后续更新与注销都用这个路径。更新用 POST 带新 Lifetime,注销用 DELETE。

更新:POST /rd/5a3f?lt=3600
注销:DELETE /rd/5a3f

服务器侧对设备的操作有 Read、Write、Execute、Discover、Write-Attributes 与 Observe。Observe 用于订阅资源变化,Write-Attributes 用于设置通知的最小间隔与阈值,例如温度变化超过 0.5 度才通知。

11. 实现栈与部署

实现角色语言特点
Eclipse Leshan服务端Java功能完整,支持 1.0/1.1/1.2
Eclipse Wakaama客户端C内存占用小,适合 MCU
Anjay客户端C商用支持,对象丰富
libcoap通用C纯 CoAP,可作为基础库
Californium通用JavaCoAP 协议栈,Leshan 依赖
aiocoap通用Python适合测试与原型

典型部署是设备侧跑 Wakaama 或 Anjay,服务端用 Leshan。服务端需要处理三件事:注册表的存储与过期清理、观察关系的维护、下行操作的请求响应匹配。

LwM2M 部署拓扑
设备(Wakaama) --CoAP/DTLS--> 网关(CoAP 代理) --CoAP/DTLS--> Leshan 服务端
                                                              |
                                                        业务系统(REST API)

规模化时要注意注册表容量与 Lifetime 分布。若所有设备 Lifetime 都是 3600 秒且同时上线,会出现周期性的注册更新风暴,工程上应加随机抖动。

12. 一次完整的注册交互示例

以下是一次典型的 DTLS PSK 引导加注册的完整时序。

1) DTLS 握手(PSK 模式,coaps://)
   Client -> Server: ClientHello (PSK identity hint)
   Server -> Client: ServerHello (cipher suite)
   Client -> Server: ClientKeyExchange (PSK identity)
   Server -> Client: Finished
   [握手完成,后续报文加密]

2) 注册
   Client -> Server: POST /rd?ep=urn:imei:8675&lt=3600&lwm2m=1.2&b=U
                     </3/0>,</1/0>,</4/0>,</5/0>,</3303/0>
   Server -> Client: 2.01 Created  Location: /rd/5a3f

3) 读取设备型号
   Server -> Client: GET /3/0/1
   Client -> Server: 2.05 Content  "TH-01"

4) 订阅温度
   Server -> Client: GET /3303/0/5700  Observe: 0
   Client -> Server: 2.05 Content  Observe: 7  23.5
   Client -> Server: 2.05 Content  Observe: 8  23.7   (资源变化时主动通知)

5) 写 Lifetime 并更新注册
   Server -> Client: PUT /1/0/1  "7200"
   Client -> Server: 2.04 Changed
   Client -> Server: POST /rd/5a3f?lt=7200
   Server -> Client: 2.04 Changed

6) 触发固件升级
   Server -> Client: POST /5/0/2  (Execute)
   Client -> Server: 2.04 Changed
   [设备按 Block2 分块下载固件,状态通过 /5/0/3 上报]

排查注册失败时按顺序核对:DTLS 握手是否成功、端点名是否唯一、Lifetime 是否合理、对象列表是否与服务器预期一致。多数失败集中在端点名重复与安全凭据不匹配两处。

权衡取舍

  • UDP 与 TCP:UDP 省电省内存但需自行处理重传,TCP 简单但开销大,受限设备优先 UDP。
  • CON 与 NON:CON 可靠但耗电,NON 省电但不保证送达,按数据重要性分别配置。
  • Observe 与轮询:Observe 省流量但服务端要维护观察者状态,观察者数量大时内存吃紧。
  • DTLS 与 OSCORE:DTLS 成熟但握手重,OSCORE 轻量且端到端但需额外密钥管理。
  • 块大小:块越小越适应窄带但往返次数越多,64 到 256 字节是常见折中。
  • 对象模型自定义:自定义对象灵活但失去标准工具支持,优先复用标准对象。

常见坑清单

  • 端点名重复:现象是后注册的设备顶掉先注册的,原因是 ep 用了非唯一值,规避方法是用 IMEI 或带唯一后缀。
  • Lifetime 过短:现象是注册频繁超时,原因是 lt 小于网络 RTT 加处理时间,规避方法是按链路延迟放大取值。
  • 所有设备同刻更新:现象是周期性注册风暴,原因是 Lifetime 相同且上线时间集中,规避方法是加随机抖动。
  • ACK_TIMEOUT 沿用默认:现象是 NB-IoT 上大量无谓重传,原因是默认 2 秒小于实际 RTT,规避方法是调到 3 到 5 秒。
  • 忽略 EXCHANGE_LIFETIME:现象是重复请求被当新请求处理,原因是去重窗口过短,规避方法是保持 247 秒默认值。
  • Observe 观察者泄漏:现象是服务端内存持续上涨,原因是静默观察者未清理,规避方法是设置观察超时并清理。
  • NON 通知当可靠用:现象是告警丢失,原因是 NON 无确认,规避方法是重要通知改 CON。
  • Block 大小不协商:现象是大载荷传输失败,原因是链路 MTU 小于块大小,规避方法是服务端在首响应中调小 SZX。
  • PSK 密钥全网相同:现象是一台设备泄露即全站失守,原因是密钥未一机一密,规避方法是按设备派生密钥。
  • 明文 CoAP 上公网:现象是数据可被窃听篡改,原因是用了 coap 而非 coaps,规避方法是强制 DTLS 并禁明文端口。

小结

CoAP 的价值在于把 REST 语义压缩到 4 字节头部,让受限设备以极低开销完成资源读写与观察。理解 Type、Code、Token 与 Message ID 的分工,掌握 ACK_TIMEOUT 与 MAX_RETRANSMIT 的调参口径,是把它用稳的前提。Observe 与 Block-wise 分别解决了主动通知与大载荷传输,两者与 FOTA 强相关。

LwM2M 在 CoAP 之上补齐了设备管理的标准语义:对象实例资源模型让不同厂商设备可被同一平台管理,注册与生命周期流程让设备上下线可被追踪,Bootstrap 让凭据与服务器地址可远程配置。安全上,DTLS 解决传输层保护,OSCORE 解决端到端保护,两者按场景取舍。

下一步建议先理清承载层选择,理解 NB-IoT 与 LoRaWAN 在功耗与覆盖上的差异;再把 CoAP 的注册流程与 OTA 的固件分发串起来,形成从引导、注册、观察到升级的完整设备管理闭环。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「物联网」更多文章

  1. 工业物联网协议与网关
  2. 边缘 AI 推理
  3. 设备配网与批量运维