7.2.4 CoAP 协议
2026/7/18大约 6 分钟常用组件组件网络CoAPUDPNB-IoT
7.2.4 CoAP 协议
📚 本节导读
学习时长:约 25 分钟
难度级别:⭐⭐⭐⭐☆
前置知识:UDP 协议基础、RESTful API 概念、第 7.2.1 节 Socket 基础
🎯 学习目标
- 理解 CoAP 协议的设计理念和适用场景
- 掌握 CoAP 与 MQTT 的差异对比
- 掌握 libcoap 客户端的使用(GET / PUT / POST / DELETE)
- 掌握 libcoap 服务端的使用(资源注册、请求处理)
- 了解 CoAP 资源发现机制
一、概述
CoAP(Constrained Application Protocol,受限应用协议)是 IETF 专为受限节点和受限网络设计的 Web 传输协议(RFC 7252)。它基于 UDP,采用 RESTful 风格,报文头极小(最小仅 4 字节),非常适合 NB-IoT、LoRa 等低功耗广域网场景。
OneOS 集成了两种 CoAP 实现:
| 实现 | 路径 | 特点 |
|---|---|---|
| libcoap v4.3.0 | components/net/protocols/coap/libcoap-v4.3.0/ | 完整 CoAP 实现,支持 RFC 7252,资源发现,块传输,DTLS 安全 |
| freecoap | components/net/protocols/coap/freecoap/ | 精简 CoAP 实现,代码量小,适合极低资源场景 |
二、CoAP vs MQTT 对比
| 特性 | CoAP | MQTT |
|---|---|---|
| 传输层 | UDP | TCP |
| 通信模型 | 请求/响应(类 HTTP) | 发布/订阅 |
| 报文头大小 | 最小 4 字节 | 最小 2 字节 |
| QoS | 可确认(CON)/ 不可确认(NON) | QoS 0/1/2 |
| 资源发现 | 内置(/.well-known/core) | 无内置 |
| 块传输 | 支持(RFC 7959) | 不支持 |
| 安全性 | DTLS | TLS |
| 适用场景 | NB-IoT、资源受限 RESTful API | 通用物联网消息推送 |
三、libcoap 核心数据结构
/* CoAP 上下文:管理所有 CoAP 会话和资源 */
typedef struct coap_context_t coap_context_t;
/* CoAP 会话:表示一个客户端-服务器之间的通信会话 */
typedef struct coap_session_t coap_session_t;
/* CoAP PDU(协议数据单元):表示一个 CoAP 报文 */
typedef struct coap_pdu_t coap_pdu_t;
/* CoAP 资源:服务端可访问的资源 */
typedef struct coap_resource_t {
coap_str_const_t *uri_path; /* 资源 URI 路径 */
int flags; /* 资源标志 */
coap_method_handler_t handler; /* 资源处理方法 */
void *user_data; /* 用户自定义数据 */
} coap_resource_t;四、libcoap 核心 API 参考
/* 创建 CoAP 上下文 */
coap_context_t *coap_new_context(const coap_address_t *listen_addr);
/* 创建客户端会话 */
coap_session_t *coap_new_client_session(coap_context_t *ctx,
const coap_address_t *local_if, const coap_address_t *server,
coap_proto_t proto);
/* 创建新的 PDU */
coap_pdu_t *coap_new_pdu(coap_session_t *session,
coap_pdu_type_t type, coap_pdu_code_t code, coap_mid_t mid);
/* 发送 PDU */
int coap_send(coap_session_t *session, coap_pdu_t *pdu);
/* 添加选项到 PDU */
int coap_add_option(coap_pdu_t *pdu, uint16_t type,
uint16_t len, const uint8_t *data);
/* 添加数据到 PDU */
int coap_add_data(coap_pdu_t *pdu, size_t len, const uint8_t *data);
/* 初始化资源 */
coap_resource_t *coap_resource_init(coap_str_const_t *uri_path, int flags);
/* 注册资源处理方法 */
void coap_register_handler(coap_resource_t *resource,
coap_request_t method,
coap_method_handler_t handler);
/* 将资源添加到上下文 */
void coap_add_resource(coap_context_t *context, coap_resource_t *resource);
/* 处理一次 CoAP IO 事件 */
int coap_run_once(coap_context_t *ctx, unsigned int timeout_ms);五、CoAP 请求方法与响应码
| 方法 | 编码 | 描述 |
|---|---|---|
| GET | 0.01 | 获取资源表示 |
| POST | 0.02 | 创建新资源 |
| PUT | 0.03 | 更新或创建资源 |
| DELETE | 0.04 | 删除资源 |
| 响应码 | 描述 |
|---|---|
| 2.01 Created | 资源创建成功 |
| 2.04 Changed | 资源修改成功 |
| 2.05 Content | 响应包含内容 |
| 4.00 Bad Request | 请求格式错误 |
| 4.04 Not Found | 资源不存在 |
| 5.00 Internal Server Error | 服务器内部错误 |
六、CoAP 客户端示例
#include <coap3/coap.h>
#include <string.h>
#include <os_kernel.h>
#include <oneos_config.h>
void coap_get_example(void)
{
coap_context_t *ctx = NULL;
coap_session_t *session = NULL;
coap_address_t dst_addr;
coap_pdu_t *pdu = NULL;
coap_mid_t mid;
/* 1. 设置目标服务器地址 */
coap_address_init(&dst_addr);
dst_addr.addr.sin.sin_family = AF_INET;
dst_addr.addr.sin.sin_port = htons(5683); /* 标准 CoAP 端口 */
inet_pton(AF_INET, "127.0.0.1", &dst_addr.addr.sin.sin_addr);
/* 2. 创建 CoAP 上下文 */
ctx = coap_new_context(NULL);
if (!ctx) {
os_kprintf("[CoAP] create context failed\r\n");
return;
}
/* 3. 创建客户端会话 */
session = coap_new_client_session(ctx, NULL, &dst_addr, COAP_PROTO_UDP);
if (!session) {
os_kprintf("[CoAP] create session failed\r\n");
coap_free_context(ctx);
return;
}
/* 4. 构建 GET 请求 PDU */
mid = coap_new_message_id(session);
pdu = coap_new_pdu(session, COAP_MESSAGE_CON, COAP_REQUEST_CODE_GET, mid);
if (!pdu) {
os_kprintf("[CoAP] create PDU failed\r\n");
coap_session_release(session);
coap_free_context(ctx);
return;
}
/* 添加 URI-Path 选项:访问 /hello 资源 */
coap_add_option(pdu, COAP_OPTION_URI_PATH,
5, (const uint8_t *)"hello");
/* 5. 发送请求 */
os_kprintf("[CoAP] sending request to /hello\r\n");
int ret = coap_send(session, pdu);
if (ret == COAP_INVALID_MID) {
os_kprintf("[CoAP] send failed\r\n");
}
/* 6. 等待响应 */
coap_run_once(ctx, 5000);
/* 7. 清理资源 */
coap_session_release(session);
coap_free_context(ctx);
os_kprintf("[CoAP] done\r\n");
}七、CoAP 服务端示例
#include <coap3/coap.h>
#include <string.h>
#include <os_kernel.h>
#include <oneos_config.h>
/* GET /hello 处理函数 */
static void hnd_get_hello(coap_resource_t *resource,
coap_session_t *session,
const coap_pdu_t *request,
const coap_string_t *query,
coap_pdu_t *response)
{
coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT);
const char *msg = "Hello from OneOS CoAP Server!\r\n";
coap_add_data(response, strlen(msg), (const uint8_t *)msg);
}
/* CoAP 服务端主函数 */
void coap_server_example(void)
{
coap_context_t *ctx = NULL;
coap_address_t addr;
coap_resource_t *resource;
/* 1. 设置监听地址 */
coap_address_init(&addr);
addr.addr.sin.sin_family = AF_INET;
addr.addr.sin.sin_port = htons(5683);
addr.addr.sin.sin_addr.s_addr = INADDR_ANY;
/* 2. 创建 CoAP 上下文 */
ctx = coap_new_context(&addr);
if (!ctx) {
os_kprintf("[CoAP Server] create context failed\r\n");
return;
}
os_kprintf("[CoAP Server] listening on port 5683\r\n");
/* 3. 注册 /hello 资源(GET) */
resource = coap_resource_init(coap_make_str_const("hello"), 0);
coap_register_handler(resource, COAP_REQUEST_GET, hnd_get_hello);
coap_add_resource(ctx, resource);
os_kprintf("[CoAP Server] resources registered: GET /hello\r\n");
/* 4. 事件循环 */
while (1) {
coap_run_once(ctx, 1000);
}
}八、CoAP 资源发现
CoAP 内置资源发现机制,客户端可以通过 GET /.well-known/core 获取服务端所有可用资源:
/* 访问 /.well-known/core 资源发现端点 */
coap_add_option(pdu, COAP_OPTION_URI_PATH,
15, (const uint8_t *)".well-known");
coap_add_option(pdu, COAP_OPTION_URI_PATH,
4, (const uint8_t *)"core");📝 本节小结
核心要点回顾
- CoAP 协议本质:基于 UDP、RESTful 请求/响应模型、报文头最小 4 字节,专为受限节点/受限网络设计(RFC 7252)。
- 两种实现:libcoap v4.3.0(完整,支持 DTLS/块传输/资源发现)与 freecoap(精简)。
- 与 MQTT 对比:CoAP 用 UDP + 请求/响应(类 HTTP),MQTT 用 TCP + 发布/订阅;CoAP 支持 CON/NON、块传输、内置资源发现、DTLS。
- 客户端流程:
coap_address_init配服务器地址 →coap_new_context→coap_new_client_session(含COAP_PROTO_UDP)→coap_new_message_id+coap_new_pdu(CON + 请求码)→coap_add_option(URI-Path)→coap_send→coap_run_once等待响应。 - 服务端流程:
coap_new_context(&listen_addr)→coap_resource_init+coap_register_handler+coap_add_resource注册资源 →coap_run_once事件循环分发请求到回调。 - 资源回调签名:
coap_method_handler_t=(coap_resource_t*, coap_session_t*, const coap_pdu_t*, const coap_string_t*, coap_pdu_t*),在回调里coap_pdu_set_code+coap_add_data填充响应。 - 资源发现:内置
/.well-known/core,客户端 GET 该端点可枚举服务端全部资源。 - 地址访问:
coap_address_t的addr联合体直接含sin/sin6/sa,IPv4 访问addr.sin.sin_addr/addr.sin.sin_port。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
包含 <oneos/os_kernel.h> | 该头文件不存在 → 应用统一包含 <oneos_config.h>(+ <os_kernel.h> 用 os_kprintf) |
地址访问写 addr.addr.sin | coap_address_t 的 addr 直接是联合体 → 用 addr.sin.sin_addr/addr.sin.sin_port |
coap_new_context(NULL) 后不释放 | 用 coap_free_context(ctx) 释放,避免泄漏 |
| PDU 创建后发送失败不清理 | coap_session_release(会话)需在结束时调用,与 coap_free_context 配套 |
忘了 coap_new_message_id | coap_new_pdu 需要 mid 参数 → 先 coap_new_message_id(session) |
| 服务端回调签名不匹配 | 回调必须严格匹配 coap_method_handler_t(含 const coap_string_t *query) |
coap_resource_init 传字面量字符串 | 第一参数是 coap_str_const_t* → 用 coap_make_str_const("...") 转换 |
coap_run_once 只调一次 | 它只处理一次 IO 事件 → 服务端要在 while(1) 中循环调用 |
| 端口不转字节序 | sin_port 用 htons(5683)(CoAP 默认端口 5683,DTLS 5684) |
下一步
- 7.2.5 Molink 蜂窝模组抽象层:CoAP 已覆盖轻量 RESTful 通信,下一节学习 Molink 蜂窝模组抽象层,掌握 AT 指令抽象与多模组适配(EC20/BC26/ESP8266/M5311/SIM7600CE),理解网络硬件层如何向协议栈提供统一的 Socket 能力。