2026年5月

嵌入式、物联网技术交流分享

CoAP 通信协议

CoAP(Constrained Application Protocol,受限应用协议)是一种计算机协议,专门用于资源受限的物联网设备,具有轻量级、可靠传输和高效数据交换的特点,适用于资源受限的环境。 由于物联网设备的内存和计算能力有限,传统的 HTTP 协议在这些设备上显得过于庞大和复杂。因此,IETF 的 CoRE 工作组提出了基于 REST 架构的 CoAP 协议( RFC 7252)。 CoAP 协议有两个不同的层:消息负载和请求/响应层。消息层处理 UDP 和异步消息,而请求/响应层基于请求/响应消息来管理请求/响应交互。CoAP 消息模型是 CoAP 的最低层,处理端点之间的 UDP 交换消息。每个 CoAP 消息都有一个唯一的 ID,这有助于检测消息重复。CoAP 协议使用两种消息:确认消息和不可确认的消息。确认消息是可靠消息,客户端可以使用这种消息确保消息将到达服务器。而不可确认的消息则不需要服务器确认。 协议特点 CoAP 协议网络传输层由 TCP 改为 UDP,以适应资源受限设备的需要。 它基于 REST 架构,这意味着服务器上的资源地址与互联网中的 URL 格式类似。客户端可以使用类似于 HTTP 的 POST、GET、PUT 和 DELETE 方法来访问服务器,这使得对 HTTP 的请求进行了简化。 CoAP 采用二进制格式,相比之下,HTTP 是文本格式,因此 CoAP 更加紧凑,可以减少传输的数据量。 CoAP 协议轻量化,最小长度仅为 4B,相比之下,一个 HTTP 头部可能达到几十个字节。 CoAP 支持可靠传输、数据重传和块传输,确保数据可靠到达。 CoAP 还支持 IP 多播,这意味着可以同时向多个设备发送请求。 CoAP 采用非长连接通信,适用于低功耗物联网场景。这与 TCP 的机制类似,对方必须确认收到消息,以实现可靠的消息传输。 消息类型 CoAP 协议有 4 种消息类型:CON、NON、ACK、RST CON:需要被确认的请求,如果 CON 请求被发送,那么对方必须做出响应。 NON:不需要被确认的请求,如果 NON 请求被发送,那么对方不必做出回应。这适用于消息会重复频繁的发送,丢包不影响正常操作。 ACK:应答消息,对应的是 CON 消息的应答。 RST:复位消息,可靠传输时候接收的消息不认识或错误时,不能回 ACK 消息,必须回 RST 消息。 消息格式 消息头 必备,固定 4 个 byte。 Ver:2 bit,版本信息,当前为 0x01。 T: 2 bit, 消息类型,包括 CON, NON, ACK, RST 这 4 种。 TKL:4 bit,token 长度, 当前支持 0 ~ 8 B 长度,其他长度保留将来扩展用。 Code:8 bit,分成前 3 bit(0 ~ 7)和后 5 bit(0 ~ 31),前 3 bit 代表类型。 0.00 Indicates an Empty message 0.01-0.31 Indicates a request 1.00-1.31 Reserved 2.00-5.31 Indicates a response. 6.00-7.31 Reserved Message ID:16 bit, 代表消息 MID,每个消息都有一个 ID ,重发的消息 MID 不变。在一次会话中 ID 总是保持不变。但在这个会话之后该 ID 会被回收利用。 token 可选,用于将响应与请求匹配。 token 值为 0 到 8 字节的序列。每个请求都带有一个客户端生成的 token, 服务器在任何结果响应中都必须对其进行回应。token 类似消息 ID,用以标记消息的唯一性。token 还是消息安全性的一个设置,使用全 8 字节的随机数,使伪造的报文无法获得验证通过。标记是 ID 的另一种表现。 option 可选,0 个或者多个。请求消息与回应消息都可以有零到多个 options。 主要用于描述请求或者响应对应的各个属性,类似参数或者特征描述,比如是否用到代理服务器,目的主机的端口等。CoAP 选项类似于 HTTP 请求头,它包括 CoAP 消息本身,例如 CoAP 端口号,CoAP 主机和 CoAP 查询字符串等。 payload 可选。实际携带数据内容, 若有, 前面加 payload 标识符 “0xFF”,如果没有 payload 标识符,那么就代表这是一个 0 长度的 payload。如果存在 payload 标识符但其后跟随的是 0 长度的 payload,那么必须当作消息格式错误处理。 请求方法(requests) 0.01 GET 方法——用于获得某资源 0.02 POST 方法——用于创建某资源 0.03 PUT 方法——用于更新某资源 0.04 DELETE 方法——用于删除某资源 URI 一个 CoAP 资源可以被一个 URI(Uniform Resource Identifier,统一资源标识符)所描述,例如一个设备可以测量温度,那么这个温度传感器的 URI 被描述为:coap://machine.address/sensors/temperature CoAP 的 URL 和 HTTP 的有很相似的地方,开头是 “coap” 对应 “http” 或者 “coaps” 对应 “https”。coap 默认端口 UDP 5683,coaps 默认端口是 UDP 5684。默认端口号可以省略。 规则与 http 的 URL 是一样的,比如不区分大小写等。 注:URL(Uniform Resource Locator,统一资源定位器)是一种特定的 URI,这种 URI 还含有如何获取资源的信息(例如 http、coap 等)。 安全性 CoAP 的安全性是用 DTLS 加密实现的。DTLS 的实现需要的资源和带宽较多,如果是资源非常少的终端和极有限的带宽下可能会跑不起来。DTLS 仅仅在单播情况下适用。 通信过程 上面以 GET 流程为例子,有没有发现,其实跟 HTTP 相类似。 Client 发起请求,类型是 CON,0.01 代表 GET 请求,MID 是请求消息 ID,URI-Path:temperature 代表请求温度。例如: coap://example.com:5683/~sensors/temperature Server 收到请求后,就会返回应答 2.05, MID 保持不变,并且返回具体参数 payload 温度 22.3 C 双向收发 基于消息的双向通信(M2M),CoAP Client 与 CoAP Server 双方都可以独立向对方发送请求。双方可当 client 或者 server 角色。传统的 HTTP 协议是主机与 web 服务器之间是单向通信的(用 websocket 除外)。而 CoAP 系统中 CoAP Client 与CoAP Server 是可以双向通信,双方都可以主动向对方发送请求。这也是 CoAP 协议解决物联网场景问题的关键所在。 订阅与发布 MQTT 协议是基于订阅与发布模型的,CoAP 通过扩展协议方式也简单的实现了订阅与发布模型。 当一个客户端需要定期去查询服务器端某个资源的最新状态时,订阅与发布模型就非常有用,不用这个模型,客户端就要周期的不断发送请求到服务器端。 模型框架: 关键概念: 主题 Subject: 代表 CoAP 的某个资源 观察者 Observer:代表对某个 CoAP 资源感兴趣的客户端 登记 Registration: 观察者需要向服务器登记感兴趣的主题 通知 Notification:当观察者感兴趣的主题发生内容变化时,服务器主动通知到观察者 观察协议在 CoAP 基础协议上增加了 1 个 Observe option, 其值为整数,通过该 options 来实现订阅与发布模型管理 在 GET 请求消息里面: oberser value 为 0: 代表向服务器端订阅一个主题。 oberser value 为 1: 代表向服务器端移除一个已订阅主题。 在 notification 消息里面: oberser value 代表 主题发生变化时,检测到顺序,以便客户端可以知道状态变化的先后。 客户端向服务器端登记感兴趣的主题 /temperature 当 temperature 发生状态改变时,服务器端主动通知到客户端。 客户端根据 token,就可以与之前订阅主题关联起来,以此确定是哪个主题订阅的。 一个 CoAP Client 可以分次向 CoAP Server 订阅多个资源主题。 一个 CoAP Server 上的主题可以被多个观察者(CoAP Client)订阅。 这样就通过了 CoAP Server 实现了 CoAP Client 之间直接数据转发通信。 可以通过灵活设计服务器上的资源链接,来实现对某个主题的条件订阅(类似触发器或者阀值等)。 比如订阅主题是: coap://server/temperature/critical?above=42 当温度超过 42,CoAP Server 需要发送通...

GitHub Markdown 提示框样式(Note/Tip/Warning)写法

在 GitHub 的 Markdown(GFM)中,实现提示、警告等醒目提示框非常简单,它基于块引用(Blockquote)语法进行了扩展。你只需要掌握一个固定的格式即可。 基本语法格式 核心格式是在块引用的第一行使用特殊标记 [!TYPE],后续内容正常以 >开头: > [!TYPE] > 这里放你的提示内容,支持**加粗**、*斜体*、`代码`、列表等大部分 Markdown 语法。 注意:[!TYPE] 中的类型关键字(如 NOTE, WARNING)必须大写。 支持的 5 种类型 GitHub 目前官方支持以下 5 种提示类型,它们在渲染时会带有不同的颜色、图标和标题: [!NOTE](蓝色) 用途:补充说明,用户快速浏览时也应注意的有用信息。 [!TIP](绿色) 用途:建议或小贴士,帮助用户更轻松或更好地完成操作。 [!IMPORTANT](紫) 用途:用户达成目标必须知晓的关键信息。 [!WARNING](黄色/橙色) 用途:急需用户注意的内容,避免潜在问题或风险。 [!CAUTION](红色) 用途:强烈警告,提示某些动作可能导致负面后果(如数据丢失)。 代码示例: > [!NOTE] > 这是一条补充信息。 > [!TIP] > 这是一个小技巧。 > [!IMPORTANT] > 这是关键信息。 > [!WARNING] > 警告,存在潜在风险。 > [!CAUTION] > 危险操作,请极其小心。 解析效果(取决于解释器): Note 这是一条补充信息。 Tip 这是一个小技巧。 Important 这是关键信息。 Warning 警告,存在潜在风险。 Caution 危险操作,请极其小心。 使用注意事项 适用范围:支持在 README.md、.md文件、Issues、Pull Requests、Discussions、Gists 中渲染。 内容支持:提示框内部支持完整的 Markdown 语法,包括段落、列表、代码块、图片等。 使用建议:官方建议仅在对用户成功至关重要时才使用,且尽量限制每篇文章 1 - 2 个,避免连续堆叠使用,防止读者视觉疲劳。 兼容性:这是 GitHub 的扩展语法。在不支持该特性的其他 Markdown 渲染器(如某些旧版编辑器)中,它会降级显示为普通的块引用,不会破坏文档结构...

告别 UUID?试试这个更优雅的分布式 ID 方案:ULID

在分布式系统和数据库设计中,“如何生成一个唯一的ID” 一直是个经典话题。我们最熟悉的可能是 UUID,但它也有不少槽点:无法排序、存储占用大、可读性差。今天给大家介绍一个非常有潜力的替代品 —— ULID(Universally Unique Lexicographically Sortable Identifier)。 什么是 ULID? ULID 的全称是 通用唯一词典分类标识符。简单来说,它是一种既能保证全局唯一,又天然支持按时间排序 的标识符。 它的设计目标很明确: 全局唯一:不怕分布式环境下的冲突 时间有序:生成顺序 = 时间顺序 紧凑可读:比 UUID 更短、更友好 ULID 长什么样? 一个标准的 ULID 看起来是这样的: 01H4Z7X8J7F9VYMQK7Z9G9Z9Z9 它是基于 Crockford's Base32 编码的(包含 0–9和 A–Z,去除了 I、L、O、U 以避免混淆),生成的字符串通常统一显示为大写,解码时大小写不敏感。 ULID 的结构 ULID 一共 128 位(16 字节),分为两部分: 部分 长度 作用 时间戳 48 位 Unix 毫秒时间(1970–2088) 随机数 80 位 保证唯一性 ┌─────────────── Timestamp (48 bits) ───────────────┐ ┌───────────────────────────────────────────────────┐ │ Time │ Randomness │ └───────────────────────────────────────────────────┘ 为什么要选 ULID? 1、全局唯一性 ULID 结合了时间 + 随机数,即使在高并发的分布式环境中,碰撞概率也极低。 2、天生支持排序 因为时间戳在最前面,ULID 天然按时间递增。你可以直观地看到,随着时间推移,前面的字符会变大: 01H4Z...(较早) 01H5A...(较晚) 这意味着: 数据库按主键排序 ≈ 按时间排序 不需要额外的时间字段 查询更快,索引更高效 Tip 不要试图在 Windows 11 的资源管理器里按文件名排序来验证 ULID 文件名的时间排序,因为该资源管理器目前只支持自然排序。可以尝试换其他资源管理器或在命令行中列出文件。 3、比 UUID 更紧凑 类型 长度 示例 UUID 36 字符 550e8400-e29b-41d4-a716-446655440000 ULID 26 字符 01H4Z7X8J7F9VYMQK7Z9G9Z9Z9 更短 不含 - 更适合 URL / 文件名 4、高性能 & 去中心化 ULID 的生成: 不需要中心服务器 不需要锁 本地即可生成,非常适合微服务、消息队列、日志系统 5、跨平台支持 主流语言几乎都有成熟实现:Java / Go / Python / Rust / JavaScript。 什么时候不该用 ULID? 虽然 ULID 很香,但并不是万能药。以下场景请慎重考虑: ❌ 需要完全随机且无规律的 ID 如果你的业务要求 ID 不能泄露任何时间信息(例如用于订单号防止被爬虫推测销量),ULID 的前缀时间戳可能会暴露生成频率和时间。这种情况下,UUIDv4 这种完全随机的方案更安全。 ❌ 需要绝对连续且无空洞的 ID ULID 是“趋势递增”,不是“绝对连续”。由于随机部分的存在,它会有空洞。如果你的系统要求 ID 必须严格连续(1, 2, 3...),ULID 不适合。 ❌ 存储空间极度敏感 虽然比 UUID 短,但如果是海量数据且不需要排序,单纯的整型自增 ID(Int/Long)仍然是最省空间的。 ULID 适合用在哪些场景? 日志系统 按时间生成、天然有序,非常适合做日志追踪 ID。 分布式系统 不依赖数据库自增 ID,避免单点瓶颈。 数据库主键 B+Tree 索引友好,写入性能更好。 消息队列 消息 ID 自带时间顺序,消费端更容易处理。 总结 ULID 是一个兼顾“唯一性、时间排序、紧凑性”的现代分布式 ID 方案。如果你正在寻找一个比 UUID 更优雅、比雪花算法更简单的方案,不妨试试 ULI...