1. CoAP协议基础:从诞生到核心设计

各位同学好,我是你们这趟CoAP安全之旅的向导。今天咱们先不急着谈加密、握手那些硬核东西,先把地基打牢——CoAP协议到底是个什么玩意儿?它为什么会出现?跟HTTP比有什么不同?

我个人习惯是,学任何协议之前,先搞清楚它要解决什么问题。CoAP的诞生,说白了就是一句话:物联网设备太“穷”了。

1.1 诞生背景:HTTP太重了

你想想看,一个温度传感器,可能只有几十KB的RAM,几MHz的主频,跑个完整的HTTP协议栈?那简直是让小学生扛大鼎。HTTP的报文头动不动几百字节,TCP三次握手、TLS握手,一套流程下来,设备电池可能就掉了一格电。

我记得2015年做智能照明项目时,用HTTP控制灯泡开关,一个简单的“开灯”指令,HTTP请求+响应要来回好几趟,延迟高不说,功耗还大。后来换成CoAP,同样的功能,报文只有几十字节,响应时间从秒级降到百毫秒级。嗯,这就是CoAP诞生的直接动力——为受限节点和低功耗网络而生。

CoAP由IETF的CoRE工作组制定,标准是RFC 7252。它专门针对那些内存小、CPU弱、可能还用电池供电的设备,以及丢包率高的无线网络(比如6LoWPAN)。

核心定位: CoAP = HTTP的“瘦身版” + UDP的“轻量级” + M2M的“专用优化”。

1.2 CoAP vs HTTP:一张表看懂差异

很多新手问我:“CoAP是不是就是HTTP over UDP?” 其实没那么简单。我整理了一张对比表,你看完就明白了。

对比维度 HTTP CoAP
传输层 TCP(可靠、有序) UDP(不可靠,但轻量)
报文开销 头部通常几百字节 头部仅4字节,总报文可压缩到几十字节
请求/响应模型 同步阻塞(通常) 异步非阻塞(基于消息ID匹配)
资源发现 无标准机制(靠文档) 内置 /.well-known/core 资源发现
观察模式 WebSocket/长轮询 原生 Observe 选项(订阅推送)
安全层 TLS(TCP之上) DTLS(UDP之上)
典型场景 Web浏览器、API服务 传感器、执行器、智能家居节点

看到没?CoAP把HTTP的“重装备”全卸了,只保留核心功能。但它不是简单粗暴地砍功能,而是做了精巧的适配。

我的经验: 如果你在做一个网关设备,既要对接云端HTTP,又要对接本地CoAP设备,建议在网关上做协议转换。我曾经在树莓派上用 libcoap 做代理,效果不错。

1.3 RESTful设计思想:资源就是一切

CoAP继承了HTTP的RESTful风格。什么意思呢?就是把物理世界里的“东西”抽象成资源,每个资源有一个URI,通过统一的接口来操作。

举个例子:一个灯泡,在CoAP世界里就是一个资源:

coap://192.168.1.100:5683/light/bulb1

对它做操作,就是标准的CRUD(增删改查),映射到CoAP方法上:

  • GET — 读取资源状态(比如查询灯泡是开还是关)
  • PUT — 更新资源(比如把灯泡亮度调到80%)
  • POST — 创建子资源(比如新增一个定时任务)
  • DELETE — 删除资源(比如移除这个灯泡)

这种设计的好处是:接口统一,语义清晰。不管你是温度传感器、门锁还是电机,操作方式都一样。我在做智能楼宇项目时,把空调、窗帘、灯光都抽象成资源,客户端代码复用率极高。

注意: CoAP的PUT和POST在语义上比HTTP更严格。PUT要求幂等(多次执行结果相同),POST不要求。我曾经踩过坑——用PUT去触发一个非幂等的动作(比如“增加温度1度”),结果重传时温度被加了两次。正确的做法是用POST。

1.4 消息类型:CON、NON、ACK、RST

这是CoAP最核心的机制之一。因为跑在UDP上,没有TCP的自动重传和确认,所以CoAP自己实现了四种消息类型来保证可靠性。我画了一张流程图,帮你理清它们的关系。

CoAP消息类型交互流程图 CoAP 四种消息类型交互流程 客户端 (Client) 服务端 (Server) 场景1:可靠请求(CON + ACK) CON (MsgID=0x01) ACK (MsgID=0x01) 场景2:不可靠请求(NON) NON (MsgID=0x02) NON (MsgID=0x02) — 可选响应 场景3:拒绝消息(RST) CON (MsgID=0x03) RST (MsgID=0x03) — 拒绝,不处理 图例: CON(需确认) ACK(确认) NON(无需确认) RST(复位/拒绝)

好,咱们逐个拆解:

1.4.1 CON — 可靠消息(Confirmable)

CON消息要求接收方必须回复ACK。如果发送方超时没收到ACK,就会重传。重传策略是二进制指数退避——第一次等2秒,第二次4秒,第三次8秒,直到达到最大重传次数(默认4次)。

典型场景: 控制指令(开锁、关阀门),必须确保对方收到。

1.4.2 NON — 不可靠消息(Non-confirmable)

NON消息不需要ACK,发出去就不管了。适合那些丢了也无所谓的场景,比如周期性的温度上报——丢了这一帧,下一帧马上就来。

典型场景: 传感器数据上报、心跳包。

1.4.3 ACK — 确认消息(Acknowledgement)

ACK是对CON的回应。注意:ACK必须携带和CON相同的Message ID,这样发送方才能把ACK和对应的CON匹配上。

这里有个细节:ACK可以“捎带”响应数据。比如服务端收到一个CON GET请求,可以直接在ACK报文里把响应数据带回去,省一次交互。这叫piggybacked response,我特别喜欢这个设计,省流量又省电。

1.4.4 RST — 复位消息(Reset)

RST表示接收方无法处理这条消息,或者明确拒绝。比如服务端不认识这个请求,或者客户端收到了一个它没发过的CON,都可以回RST。

我曾经在调试时遇到过:设备A给设备B发CON,但B已经重启了,状态丢了。B收到CON后,发现Message ID对不上,直接回RST。A收到RST后就知道B不认这个会话,需要重新建立。

避坑指南: 我曾经在项目中把NON和CON用反了。控制指令用了NON,结果丢包了设备没反应,排查了半天。后来统一规则:所有“写操作”用CON,“读操作”或“上报”用NON。这个原则一直沿用至今。

1.5 小结:CoAP的哲学

CoAP的设计哲学,我总结为三句话:

  • 够用就好 — 不追求HTTP的全功能,只做物联网场景需要的
  • UDP之上建可靠 — 用CON/ACK/RST机制,在不可靠的UDP上实现可控的可靠性
  • 资源即一切 — RESTful风格,让设备接口像网页一样清晰

嗯,这一章的内容就到这里。你可能会问:“这些基础跟安全有什么关系?” 关系大了去了——CoAP的安全层DTLS就是建立在UDP之上的,而消息类型直接影响了重传攻击、重放攻击的防御策略。这些我们后面章节会一一展开。