第3章:CoAP协议报文结构深度解析

说实话,CoAP协议最让我着迷的地方,就是它那精巧的报文结构。你想想看,要在几十个字节里塞下请求、响应、选项、载荷,这本身就是一门艺术。我在做智能照明项目时,就曾因为报文结构理解不透彻,导致设备死活连不上网关——后来发现,只是版本号字段写错了。

3.1 报文头部:4个字节定乾坤

CoAP的报文头部固定为4个字节。别小看这4个字节,它包含了协议运行的核心信息。我习惯把这4个字节拆成两部分来看:前两个字节是控制字段,后两个字节是标识字段。

字节偏移 字段 位宽 说明
0 Ver(版本号) 2位 当前固定为01(二进制)
0 T(报文类型) 2位 00=CON, 01=NON, 10=ACK, 11=RST
0 TKL(Token长度) 4位 0-8字节,表示Token字段长度
1 Code(响应码/请求方法) 8位 0.01=GET, 0.02=POST, 2.05=Content
2-3 Message ID 16位 用于匹配请求和响应

版本号陷阱:我曾经在调试时发现,有些ESP32的CoAP库默认版本号是0。这会导致服务器直接丢弃报文。记住,RFC 7252规定版本号必须是01(二进制),也就是十进制的1。

3.2 报文类型:四种角色各司其职

CoAP定义了四种报文类型。这四种类型,说白了就是设备之间对话的四种语气。

  • CON(Confirmable,可确认):需要接收方回复ACK。我习惯把它理解为「挂号信」——必须回执。
  • NON(Non-confirmable,不可确认):不需要确认。适合传感器周期性上报数据,丢了就丢了,下次再发。
  • ACK(Acknowledgement,确认):对CON报文的响应。注意,ACK必须携带和CON相同的Message ID。
  • RST(Reset,复位):告诉对方「我不认识你」或者「我处理不了」。我在项目中用RST来拒绝非法设备的连接请求。

我的经验:对于温度传感器这类数据,用NON就够了。但对于控制指令(比如开关灯),一定要用CON+ACK的可靠传输模式。否则,你想想看,灯没亮用户投诉,那可就麻烦了。

3.3 Token:给每个请求贴个标签

Token字段紧跟在头部之后,长度由TKL字段指定(0-8字节)。它的作用很简单:区分不同的请求。

为什么需要Token?因为CoAP是基于UDP的,同一个客户端可能同时发出多个请求。服务器响应时,客户端需要知道这个响应对应哪个请求。Token就是干这个的。

我建议的做法是:

  • 简单场景:用Message ID做匹配就够了,Token长度设为0
  • 复杂场景:生成随机Token,长度4字节,避免冲突
  • 安全场景:Token可以携带会话标识,但注意不要泄露敏感信息

3.4 选项(Options):CoAP的瑞士军刀

选项是CoAP最灵活的部分。它采用TLV(Type-Length-Value)格式,可以携带各种元数据。我个人最喜欢的选项是Uri-Path和Content-Format。

3.4.1 Uri-Path:资源定位的关键

Uri-Path选项用来指定资源路径。比如请求coap://server/temperature,就需要两个Uri-Path选项:

选项1: Type=11 (Uri-Path), Length=11, Value="temperature"
选项2: Type=11 (Uri-Path), Length=5,  Value="sensor"

注意,每个路径段是一个独立的选项。我在项目中踩过坑:如果路径包含中文,一定要先做URL编码,否则服务器解析会出错。

3.4.2 Content-Format:告诉对方数据长啥样

Content-Format选项指定载荷的媒体类型。常用的格式编号如下:

编号 媒体类型 适用场景
0 text/plain; charset=utf-8 纯文本数据
40 application/link-format 资源发现(/.well-known/core)
41 application/xml XML格式数据
42 application/octet-stream 二进制数据
47 application/json JSON格式(最常用)
50 application/cbor CBOR二进制JSON

避坑指南:我曾经在项目中使用Content-Format=0(纯文本)传输JSON字符串。结果客户端解析时,因为没指定编码格式,中文全部乱码。后来统一改用Content-Format=47(application/json),问题迎刃而解。

3.5 载荷(Payload):真正要传的数据

载荷是报文的最后一部分。它前面有一个特殊的标记字节:0xFF。这个字节告诉解析器:「选项结束了,后面是载荷数据」。

载荷可以是任意二进制数据。但实际项目中,我推荐使用以下格式:

  • JSON:人类可读,调试方便。适合数据量不大的场景。
  • CBOR:二进制JSON,体积更小。适合带宽受限的LoRa网络。
  • 纯文本:简单直接,但缺乏结构化。适合单值传感器。

举个例子,一个温度传感器上报数据:

// 载荷内容(JSON格式)
{"temperature": 25.6, "humidity": 68.3}

// 对应的CoAP报文(十六进制)
40 01 00 01  // 头部:Ver=1, CON, Token长度=0, Code=0.01(GET), Message ID=1
B1 74 65 6D 70 65 72 61 74 75 72 65  // Uri-Path选项:路径为"temperature"
FF  // 载荷分隔符
7B 22 74 65 6D 70 65 72 61 74 75 72 65 22 3A 20 32 35 2E 36 2C 20 22 68 75 6D 69 64 69 74 79 22 3A 20 36 38 2E 33 7D  // JSON数据

3.6 完整报文示例:从理论到实践

咱们来看一个完整的请求-响应流程。假设客户端请求coap://server/light,期望返回JSON格式的灯光状态。

请求报文(客户端→服务器):

Ver: 1 (01)
Type: CON (00)
TKL: 0 (0000)
Code: 0.01 (GET) -> 0x01
Message ID: 0x0001

选项1: Uri-Path (11), Length=5, Value="light"
选项2: Accept (17), Length=1, Value=47 (application/json)

载荷分隔符: 0xFF
载荷: 无

响应报文(服务器→客户端):

Ver: 1 (01)
Type: ACK (10)
TKL: 0 (0000)
Code: 2.05 (Content) -> 0x45
Message ID: 0x0001 (与请求相同)

选项1: Content-Format (12), Length=1, Value=47 (application/json)

载荷分隔符: 0xFF
载荷: {"status":"on","brightness":80}

调试技巧:我习惯用Wireshark抓包分析CoAP报文。在ESP32上,可以开启CoAP日志输出,打印原始十六进制数据。这样能快速定位报文结构问题。

3.7 资源受限环境下的优化建议

在ESP32这类资源受限设备上,报文结构的每个字节都值得精打细算。我总结了几条优化原则:

  1. 精简选项数量:能省则省。比如Uri-Path尽量短,用/t代替/temperature
  2. 合理选择Token长度:如果并发请求少,Token长度设为0,省下8字节。
  3. 载荷压缩:JSON用CBOR替代,体积能减少30%-50%。
  4. 避免冗余字段:比如Content-Format,如果双方约定好格式,可以省略。

嗯,说到优化,我记得有个项目需要把CoAP报文塞进LoRaWAN的51字节载荷里。经过上述优化,最终报文头部+选项+载荷总共48字节,刚好塞下。那一刻,我觉得CoAP的设计者真是天才。

好了,报文结构就讲到这里。下一章咱们聊聊CoAP的资源发现机制——这可是物联网设备自动组网的关键。