CoAP协议深入解析
目录一、引言1.1 什么是 CoAP 协议1.2 为什么需要 CoAP 协议二、CoAP 消息格式2.1 CoAP 报文头2.2 Token(令牌)、Option(选项) 与 Payload(负载)三、CoAP 的消息类型3.1 重传机制 与 Message ID3.2 拥塞控制3.3 多播与闲暇控制四、请求/响应 模型4.1 请求方法4.2 响应4.3 请求/响应码4.4 工作流程五、Option —— 选项5.1 选项格式5.2 选项定义5.3 Uri-HostUri-PortUri-PathUri-Query5.4 Proxy-Uri and Proxy-Scheme5.5 Content-Format5.6 Accept5.7 Max-Age5.8 ETag5.9 Location-Path and Location-Query5.10 If-Match and If-None-Match5.11 Size1六、CoAP 的扩展机制6.1 观察者模式6.2 块传输6.2.1 块选项6.2.2 拆分块选项6.2.3 Size2 选项一、引言在计算机网络里协议Protocol就是规矩。就像我们写信有信纸格式、发邮件有标题和正文一样两个设备要交换数据必须遵守同一个规矩否则就是对牛弹琴。CoAP这个规矩是国际互联网工程任务组IETF在2014年正式发布的国际标准官方文档链接RFC 7252 - The Constrained Application Protocol (CoAP)。 RFC 7252 是编号这不是某个公司的私有协议而是全世界工程师公认的、免费公开的通用准则。1.1 什么是 CoAP 协议CoAP全称是Constrained Application Protocol中文翻译为“受限应用协议”是专门为物联网IoT设备设计的轻量级应用层协议基于UDP协议开发。别因为 “受限” 这两个字觉得 CoAP协议很弱在计算机世界里“受限”通常不是贬义词而是代表“精打细算”。如果用一句话给你最直白的定义那就是CoAP是一种专门为 “小破烂儿” 电子设备设计的、让它们能像大电脑一样上网聊天的通信规则。CoAP其实是刻意仿照 HTTP“描”出来的比如 CoAP 也用 GET 拿数据、用 POST 提交数据、用 PUT 改数据、用 DELETE 删数据。所以有很多人把 CoAP 称为“物联网的 HTTP”。1.2 为什么需要 CoAP 协议一个联网的设备MCU主频只有几十MHz内存只有几十KB用一颗 纽扣电池供电还让它工作3年。那如何满足这种低功耗、低算力、低带宽的需求呢再假设设备要发送一条“温度 26℃”的数据这条数据本身可能只有 5 个字节。TCP 建立连接和断开连接就要进行三次握手四次挥手同时 TCP 头部最少占用 20字节。再使用一些应用层协议这些额外数据消耗的电量是传输真实数据的几十倍。CoAP协议就是为了解决这些问题CoAP 抛弃了TCP使用UDP协议作为底座。UDP 是无连接的UDP 头部固定8字节CoAP协议头部仅占用 4字节CoAP 使用请求/响应模式弥补 UDP 不可靠问题。二、CoAP 消息格式CoAP消息用二进制格式编码消息格式以固定大小的4字节头开始后面是一个可变长度的标记值长度可以在0到8字节之间。CoAP 消息格式(重点)2.1 CoAP 报文头前面反复提到 CoAP 头部只有 4字节这 4字节 把所有关键信息浓缩在一起现在讲讲头部字段的定义字段名位(bit)简要说明Ver (版本号)第 0-1 位固定为01表示 CoAP 版本 1T (消息类型)第 2-3 位标明本条消息是 CON、NON、ACK 还是 RSTTKL (令牌长度)第 4-7 位说明后面 “Token” 字段占几个字节0~8Code (方法/响应码)第 8-15 位相当于 HTTP 的请求方法GET/POST或响应状态码2.xx/4.xxMessage ID (消息ID)第 16-31 位每条消息的唯一序列号用于匹配请求和响应、以及处理重传2.2 Token(令牌)、Option(选项) 与 Payload(负载)头部的4个字节后面还跟着Token(令牌)、Option(选项) 与 Payload(负载)这几个“附加组件”这几个是跟着头部依次排列的。Token(令牌)可以把它理解为“本次对话的ID”。比如手机发个“把灯打开”的请求ID是0x7a灯泡回复时也带上0x7a代表刚才那个开灯指令的回复它的长度由TKL决定。TKL4后面就取4个字节当TokenTKL0就没有这个字段。Option(选项)类似HTTP的头部用来携带各种对请求或响应的“补充说明”和“元数据”它是一串可变长的字段。Payload(负载)负载就是真正要传递的数据。0xFF只是有效负载的前缀标记该标记指示选项的结束和有效负载的开始。只有那些需要带返回数据的操作比如GET成功的响应才有Payload很多简单的ACK确认消息连0xFF都不带这里的 Token 跟 报文头的 Message ID 虽然都是担任 “匹配任务”但是他们是完全不同的两个字段Message ID链路层/传输层级别的配对用于 重传确认 和 网络去重Token应用层级别的配对用于 多请求并发匹配 和 观察者订阅标识三、CoAP 的消息类型消息类型就是报文头中的字段T类型描述T 值CON 报文Confirmable需要被确认的报文T00NON 报文Non-Confirmable不需要被确认的报文T01ACK 报文Acknowledgement应答报文用于响应接受到的CON消息T10RST 报文Reset复位报文当接收者收到的消息出错时会发送RSTT113.1 重传机制 与 Message IDCONConfirmable 消息是需要对方确认的“挂号信”但是 UDP 又不可靠于是 CoAP 设计了重传机制而 Message ID 是贯穿重传全过程的“定海神针”。Message ID 在重传中的两个核心作用应答配对客户端发送 CON 消息时设定一个唯一的 Message ID(比如 MID100)。服务器处理的 ACK 响应中必须包含相同的 MID。客户端只有收到这个相同 MID 的 ACK才会停止重传计时器。去重过滤如果客户端重传了多次 CON 包MID 都是 100。服务器收到第 1 个包后看到第 2 个和第 3 个来自相同 IP/端口、且 MID 相同的包时会直接丢弃确保应用层只被触发一次防止重复执行指令。CoAP 重传不是无脑死循环的该机制规定了一组参数参数名默认值作用ACK_TIMEOUT2 秒发出一条 CON 后等 2 秒没回音就重传。ACK_RANDOM_FACTOR1.5每次重传的间隔不是固定翻倍而是乘以 1.0~1.5 的随机数防止大量设备同时重传造成网络风暴。MAX_RETRANSMIT4 次一条 CON 最多重传 4 次再收不到就放弃。CoAP 的重传间隔算法是下次超时时间 当前超时时间 × 2 × [1.0 ~ 1.5 的随机数]。综上所述只有收到 相同 Message ID 的 ACK报文 或 相同 Message ID 的 RST报文 后会停止重传3.2 拥塞控制如果说重传机制解决的是单个消息丢了怎么办的问题那么拥塞控制解决的是大家都别发太快否则网络会堵死的问题。一个客户端对于同一个服务器在任何时刻只能有一个尚未收到确认ACK的CON消息。参数名默认值作用NSTART1同一时刻对同一台服务器只能有 1 个 CON 在等待 ACK防止把自己撑死。PROBING_RATE1 byte/second在发送非确认消息NON或发起新通信时以每秒 1 字节的极低速率起步先探测网络是否通畅确认没问题后再逐步提速。3.3 多播与闲暇控制在 3.1 和 3.2 中我们讨论的重传机制和拥塞控制主要解决的都是单播场景下的问题 —— 也就是“一个客户端对一台服务器”的通信。但在物联网中还有一种常见的通信模式一个设备想要同时询问多个设备。这种“一对多”的通信方式叫做多播或组播。多播非常高效 —— 网关只发了一条消息所有传感器都能收到。但问题出在响应环节如果100个传感器同时回复网关网关的接收缓冲区会被瞬间塞满大量响应包会在网络中碰撞、丢失最终网关一条有效数据都收不到。这就是响应风暴Response Storm。为了解决这个“响应风暴”问题CoAP引入了一个专门的参数 —— DEFAULT_LEISURE。参数名默认值作用DEFAULT_LEISURE5 seconds在响应多播请求时每个设备必须随机等待 0 到 5 秒之间的一个时间然后再发送响应。四、请求/响应 模型4.1 请求方法CoAP完美复刻了HTTP最核心的四个动作GET获取资源。用于从服务器读取资源是安全的不会对服务器状态产生任何影响POST创建新资源或更新现有资源。用于向服务器提交数据既不安全也不幂等。PUT更新或创建指定资源。如果资源不存在服务器也可以根据请求创建一个新资源DELETE删除资源。4.2 响应在接收请求和解释请求后服务器会进行CoAP响应(ACK报文)该响应通过客户端生成的令牌与请求匹配。ACK报文的响应码一共有三类2 - 成功请求已成功接收、理解和接受4 - 客户端错误请求包含错误语法或无法实现5 - 服务器错误服务器未能完成明显有效的请求4.3 请求/响应码请求/响应码用的都是报文头的Code (方法/响应码)字段对应的结构图如下Code字段占用一个字节(8位)。上三位定义了类别下五位代表具体细节。为了便于阅读和文档书写Code被写成c.dd的格式。其中 “c” 是以十进制表示的类“dd”是以两位十进制表示的细节。详细的 CoAP方法代码和响应代码如下两张图所示不需要强行记忆CoAP 方法代码CoAP响应代码4.4 工作流程“请求 - 响应” 的模式有多种灵活的方式。携带模式ACK 的 payload 部分包含响应负载此时 ACK 中的 Message ID 与 Token 都与请求报文中的相同。携带模式分离模式当服务器处理请求非常耗时无法快速获取结果时使用。分离模式下服务器虽然会马上回复 ACK但不携带负载单纯为了响应 CON。等到服务器处理完毕后会主动发送一条新的 CON 消息将真正的数据发送给客户端服务器后发的 CON 中的 Token 与 客户端请求的 Token 相同。分离模式非确认模式客户端发送一个 NON 消息服务器收到后处理但不回复 ACK。非确认模式五、Option —— 选项请求和响应都可能包括一个或多个选项的列表。例如请求中的URI通过几个选项传输HTTP中HTTP头中携带的元数据也作为选项提供。5.1 选项格式CoAP 选项格式Option Delta -- 选项增量CoAP 的消息中可能携带多个选项每个选项都有一个预设的绝对编号但是发送时不直接发送绝对编号而是发送相对值Option Length -- 选项长度用来表示 Option Value选项值的实际数据长度Option Value -- 选项值选项所携带的具体数据Option Delta 和 Option Length 虽然都使用 4bit(值范围0~15) 表示按理来说 0~15 的数值应该使用同一个含义但事实并非如此Option Delta数值 0~12 直接代表差值为 0~12。13 和 14 触发扩展字节 (Option Delta extended)。15 是特例代表选项到此全部结束紧接着的字节就是 Payload 数据。Option Length数值 0~12 直接代表长度为 0~12。13 和 14 触发扩展字节 (Option Length extended)。15 是保留值协议规定发送方不能用接收方若收到 15视为格式错误并丢弃该包。Option Delta 和 Option Length 的值为 13 和 14 代表的含义是一样的。当值 13 时对应的扩展字段为 1byte(8位无符号整数)。实际值 13 后面这 1个字节的值最大值可表示 26813 ~ 255 13。当值 14 时对应的扩展字段为 2byte(16位无符号整数)。实际值 269 后面这 2个字节的值最大值可表示 65804269 ~ 65535 269。5.2 选项定义如下表CoAP定义了一组用于请求和响应的选项CoAP 选项表选项属性 No.选项编号C关键U不安全N无缓存键R可重复Format选项格式Length选项值长度范围。5.3 Uri-HostUri-PortUri-PathUri-Query本小节讲解Uri主机、Uri端口、Uri路径、Uri查询这几个选项用于指定对CoAP源服务器的请求的目标资源。// URI 语法 coap-URI coap: // host [ : port ] path-abempty [ ? query ] // URI 举例 coap://example.com:5683/home/led?stateoncolorred // CoAP 报文里的写法 // 注意路径和查询参数不是一次性写入的都是拆分的接收方收到后才拼接 Uri-Host example.com Uri-Port 5683 Uri-Path home Uri-Path led Uri-Query stateon Uri-Query colorredUri-Host 和 Uri-Port 主要是为了解决虚拟主机的问题。5.4 Proxy-Uri and Proxy-Scheme本小节讲解代理Uri 与 代理方案当 CoAP请求 无法直接发送到目前服务器时这两个东西就派上用场了。Proxy-Uri它的值是一个完整的绝对 URI(简单粗包)用于向转发代理发出请求。转发代理可以将请求转发到另一个代理也可以直接转发到绝对URI指定的服务器。为了避免请求循环代理必须能够识别其所有服务器名称包括任何别名、本地变体和数字IP地址。Proxy-Scheme也用于向转发代理发出请求但是比 Proxy-Uri 更灵活。代理会将原始URI最开头的协议名coap 或 coaps替换成 Proxy-Scheme 选项的值以 Proxy-Schemehttp 举例coap://example.com/led?stateon 会被替换成 http://example.com/led?stateon。5.5 Content-Format本小节讲解内容格式Content-Format 选项用于指示消息有效负载的表示格式。最常用编号如下图除了这些基础编号还有大量为特定应用定义的编号这个不讲解了。5.6 Accept在 5.5 小节中我们学习了Content-Format它解决的是“我发的是什么”的问题。但通信是双向的请求发出去之后服务器返回的数据是什么格式呢如果服务器返回的是 XML而你的小设备只能解析 JSON那就鸡同鸭讲了。Accept 选项可用于指示客户端偏好接受的内容格式它解决的是“我想要收的是什么”的问题。Accept 选项的值就是 5.5 节表格里的那些数字编号。注意Accept 只是 “偏好” 而非 “命令”服务器有自由裁量权。如果无服务器不支持对应内容格式会返回4.06 Not Acceptable错误码。5.7 Max-Age这个是 CoAP 协议中用于缓存控制的核心选项此选项只能在响应中使用用来告诉客户端或中间代理“这份资源数据你可以在本地缓存多久”。使用场景如果有多个传感器去读取同一个资源配置比如同一个温湿度数值Max-Age可以极大减少不必要的网络传输减轻服务器压力。HTTP 协议里的缓存控制 Cache-Control 通常是个很长的字符串如Cache-Control: max-age3600, public。而 CoAP 的Max-Age只有一个纯粹的整数连单位都不写就是秒。5.8 ETag这是用于资源版本标识的核心选项。它的作用相当于给服务器上的某一份资源数据打上了一个 “唯一指纹” 或 “版本号”。响应中的 ETag当客户端发送GET请求读取数据时服务器如果支持缓存会在成功的响应2.05 Content中携带ETag选项告诉客户端“当前这份数据的 ‘指纹/版本号’ 是X如果你以后要问可以直接报这个数字不用把完整数据传一遍。”请求中的 ETag当客户端缓存了携带 ETag 数据后在下次发起 GET 请求去询问数据是否变更时使用。如果服务器发现 ETag 有匹配上的直接回复2.03 Valid(有效)。如果没有 ETag 匹配上回复2.05 Content并返回最新的 数据 和 ETag。5.9 Location-Path and Location-Query本小节讲解位置路径 与 位置查询这两个选项主要用于服务器响应客户端告诉客户端“你刚刚创建或修改的资源现在在哪个具体的 URI 位置上”。简单来说它们就是告诉客户端“去哪儿能找到刚才的操作成果”。假设在某系统中客户端发出了一个POST请求去创建一盏新灯的配置1. 客户端请求POST coap://lights.example.com/ 2. 服务器处理服务器成功创建了一个新的灯配置ID 是 123。 3. 服务器响应返回 2.01 Created创建成功并在响应消息中放入以下选项 Location-Path 选项值为 lights。 Location-Path 选项值为 123。 4. 客户端后续动作客户端收到并解析这个响应后立刻知道新创建的灯的资源具体位于 coap://lights.example.com/lights/123。 随后客户端就可以使用这个URI去发起 GET请求 来获取这个新灯的状态了。 // 其实也只是拼接罢了拼接逻辑 // coap:// [Location-Host] : [Location-Port] / [Location-Path拼接] ? [Location-Query拼接]。5.10 If-Match and If-None-Match本小节讲解条件匹配 与 条件非匹配这两个选项这是 CoAP 协议中用来实现并发控制和防重复创建的关键选项。它们的核心作用是让客户端在请求中附加前置条件告诉服务器 “只有在满足特定条件时才执行我的请求如果不满足直接拒绝即可不要盲目操作”。If-Match用于“更新或删除(PUT、DELETE)”类的操作客户端在请求中带上一个或多个之前获取的 ETag(If-Match: abc123)。服务器收到请求后会比对当前资源的最新 ETag。只有当服务器当前的 ETag 与客户端提供的 ETag 中的某一个完全匹配时才会真正执行这个更新或删除操作。If-None-Match用于“创建(POST)”新资源的操作。客户端在请求中带上这个选项后(If-Match: 冒号后面是空的)服务器在当前资源完全不存在的情况下才执行创建。如果已存在服务器会拒绝该请求并返回 4.12 Precondition Failed前置条件失败错误。根据表格中的描述我们可以知道 If-Match 是用来放置多人并发控制的If-None-Match 是用来防重复创建资源的。5.11 Size1本小节讲解“请求中资源大小指示”在CoAP的基础规范 RFC 7252 中Size1 的作用很单一它主要用于请求中提供关于请求体Request Payload 的大小信息。Size1 的使用客户端使用客户端在上传数据的请求(PUT/POST)中携带“告诉服务器本次请求体的大小”服务器使用当服务器发现请求体太大无法处理时会返回 4.13(Request Entity Too Large 请求实体过大) 并在响应中携带 Size1 选项以告知客户端它所能接受的最大请求体大小。简而言之Size1 只负责 “上传” 相关的大小信息。六、CoAP 的扩展机制CoAP 有很多扩展本文章就挑一部分进行讲解不全部讲解了。6.1 观察者模式在标准 CoAP 机制中客户端想要获取资源状态只能不断循环“请求-响应”模式轮询。但这会导致巨大的网络开销和电池损耗。国际互联网工程任务组IETF发布 RFC 7641 扩展增加观察者模式选项解决此问题。观察者模式(Observe) 是 CoAP 协议最重要、最实用的一类扩展机制选项定义RFC 7641官方文档链接RFC 7641 - Observing Resources in the Constrained Application Protocol (CoAP)。观察者模式它允许客户端向服务器订阅一个资源一旦资源状态发生改变服务器就会主动向客户端推送通知无需客户端每次都发起请求。观察者工作流程三步曲1. 注册客户端发起标准的 GET 请求在请求选项中带上 Observe0注销前客户端都不可忘记 Token服务器收到后会为这个客户端和资源建立 “订阅关系”。服务器立即返回第一次 2.05 Content 响应并在响应中附带 Observe 选项值随机代表当前的序列号同时将当前资源状态发给客户端。2. 持续通知一旦资源的状态发生变化服务器会自动向客户端发起新的响应消息2.05 Content每个通知都会携带一个递增的 Observe 序列号。假设订阅时返回 Observe12通知的序列号就是 13、14...3. 注销客户端不需要接收状态更新时要发起的新的 GET 请求并带上 Observe1服务器收到后会清除该 “订阅关系”停止发送通知。6.2 块传输CoAP 协议运行在 UDP 协议之上UDP 数据报会受到 MTU(最大传输单元)的限制。当响应载荷Payload过大或请求 URI 过长时单个 UDP 包无法容纳IP 层被迫分片这会显著增加丢包概率从而导致整个事务失败。为解决此问题IETF 在RFC 7959中定义了块传输扩展机制。该机制允许将大数据拆分为多个较小的“块”每个块独立传输从而避免 IP 分片提升可靠性。官方文档链接RFC 7959 - Block-Wise Transfers in the Constrained Application Protocol (CoAP)6.2.1 块选项这次 CoAP 引入了两个专用选项Block1用于请求方向(如客户端PUT/POST上传数据)Block2用于响应方向(如服务器回复 GET 请求下载数据)选项定义6.2.2 拆分块选项虽然使用场景不同但是二者结构是相同的位拆分结构表如下这里的编码遵循 大端序(网络字节序)选项值总长度字段分配与位结构从高位到低位各字段的作用与范围1 字节(8 bits)位[7:4]NUM位[3]M位[2:0]SZXNUM最大 15M最大 1SZX最大 72 字节(16 bits)位[15:4]NUM位[3]M位[2:0]SZXNUM最大 4095M最大 1SZX最大 73 字节(24 bits)位[23:4]NUM位[3]M位[2:0]SZXNUM最大 1,048,575M最大 1SZX最大 7三个字段的具体含义NUM块编号20位当前发送的是第几块数据。从 0 开始后续块会递增NUMNUM1MMore位1位M1后面还有更多块请客户端继续发送请求获取下一块。M0当前是最后一块传输可以结束了。SZX块大小指数3位决定当前单个数据块的最大载荷字节数。计算公式提示RFC 7959 规定 7 被保留等同于 6即最大为1024 字节6.2.3 Size2 选项RFC 7959中还引入了 Size2 选项他们的结构是一样的Size2 和 Size1 功能互补分别服务于 请求 和 响应 这两个方向为了更好的理解我们将它们放在一起看特性Size1 选项Size2 选项核心用途指示请求中资源表示的大小指示响应中资源表示的大小出现位置主要出现在请求PUT/POST中可出现在请求或响应中主要场景1. 客户端在上传数据时告知服务器请求体总大小2. 服务器因请求过大返回4.13错误时告知客户端能处理的最大请求体大小。1.在请求中客户端用来主动询问服务器某个响应资源的总大小2.在响应中服务器告知客户端当前正在传输的响应资源的总大小。