最近的一个项目中用到了coap,为了防止时间过长导致遗忘,将具体的关键知识写在这里,同时也为后来人减少一些坑。
Coap
coap是一种符合REST规范(主要是幂等性)的,适用于物联网通讯的数据协议。具体使用起来和http比较类似,同样有着GET、POST、PUT、DELETE四种操作,不过coap是基于udp的,并且数据包是按字节码拼接的,甚至有些信息是按位标识的,不像是http一样用各种字符串来表达各个部分。
省流量、计算少、适合物联网,但是在当前ipv6没有普及之时,实时性不如mqtt。
coap数据包格式的资料
在查找相关资料时,发现官方的资料长达一百多页(https://tools.ietf.org/html/rfc7252),虽然很全面,但是我并不需要如此详细的文档,另外还不知道coap基本长啥样呢。
好在我在GitHub上找到了一个国外网友制作整理的备忘笔记,简单明了,可以在https://github.com/chenxuuu/coap-cheatsheet/blob/master/coap-cheatsheet.md查看我fork并转为markdown格式的这个文档
我就基于这个笔记,来简单解释一下整个coap数据包的格式,并在最后实际测试一下几个实例数据包。
数据包的整体结构
coap的数据包格式类似于下面这样:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Ver| T | TKL | Code | Message ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Token (if any, TKL bytes) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options (if any) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 1 1 1 1 1 1 1| Payload (if any) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
coap版本(Ver)
coap版本信息占用为2位(1/4字节)
现在的coap版本来说,固定是01
消息类型(T)
消息类型占用的大小位2位(1/4字节),类型如下四种:
| 具体数值 | 消息类型名称 |
|---|---|
| 0 | CONfirmable |
| 1 | NON-confirmable |
| 2 | ACKnowledgement |
| 3 | ReSeT |
CON类型的消息为主动发出消息请求,并且需要接收方作出回复
NON类型的消息为主动发出消息请求,但是不需要接收方作出回复
ACK类型的消息为接收方作出回复
Res类型为发出CON消息后,在还没收到请求时,主动通知不需要再回复
token长度(token length,TKL)
token长度占用4位(0.5字节),表示后面的token所占的字节数。
可以为0,表示token不存在。
coap状态码(Code)
状态码占用1个字节,分为两种:发送和接收
发送方的状态码
| 状态码 | 请求类型 | 实际数值 |
|---|---|---|
| 0.00 | EMPTY | 0x00 |
| 0.01 | GET | 0x01 |
| 0.02 | POST | 0x02 |
| 0.03 | PUT | 0x03 |
| 0.04 | DELETE | 0x04 |
一般empty只在消息类型为ReS的时候才会用到
响应方的状态码
类型:Success
| Code | Description |
|---|---|
| 2.01 (65, 0x41) | Created |
| 2.02 (66, 0x42) | Deleted |
| 2.03 (67, 0x43) | Valid |
| 2.04 (68, 0x44) | Changed |
| 2.05 (69, 0x45) | Content |
| 2.31 (95, 0x5F) | Continue |
类型:Client Error
| Code | Description |
|---|---|
| 4.00 (128, 0x80) | Bad Request |
| 4.01 (129, 0x81) | Unauthorized |
| 4.02 (130, 0x82) | Bad Option |
| 4.03 (131, 0x83) | Forbidden |
| 4.04 (132, 0x84) | Not Found |
| 4.05 (133, 0x85) | Method Not Allowed |
| 4.06 (134, 0x86) | Not Acceptable |
| 4.08 (136, 0x88) | Request Entity Incomplete |
| 4.12 (140, 0x8C) | Precondition Failed |
| 4.13 (141, 0x8D) | Request Entity Too Large |
| 4.15 (143, 0x8F) | Unsupported Content-Format |
类型:Server Error
| Code | Description |
|---|---|
| 5.00 (160, 0xA0) | Internal Server Error |
| 5.01 (161, 0xA1) | Not Implemented |
| 5.02 (162, 0xA2) | Bad Gateway |
| 5.03 (163, 0xA3) | Service Unavailable |
| 5.04 (164, 0xA4) | Gateway Timeout |
| 5.05 (165, 0xA5) | Proxying Not Supported |
其中a.bb为响应代码,转成二进制的代码格式如下:
0
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|class| detail |
+-+-+-+-+-+-+-+-+
如2.31,拆开分成2进制为:
010 11111
转成16进制则为:0x5f
一般返回2.xx即为响应成功,其他一般都是失败,和http协议比较类似
消息编号(Message ID)
消息编号占用2字节,代表了该消息的编号,如果是CON类型的消息,在返回时消息编号也应当与发送时相同
Token
Token的字节数由之前的token长度(token length,TKL)决定,如果前面的TKL值是0,则数据包中不包含token
Options
Options占用的字节数不定,如果包尾遇到payload标识符0xff则表示Options数据结束
Options类似于http协议中的Options,有Content-Format、Uri-Path之类的信息,格式解析与组包较为复杂,具体结构与其代表的数值如下:
0 1 2 3 4 5 6 7
+---------------+---------------+
| Option Delta | Option Length | 1 byte
+---------------+---------------+
/ Option Delta / 0-2 bytes
(extended)
+-------------------------------+
/ Option Length / 0-2 bytes
(extended)
+-------------------------------+
/ Option Value / 0 or more bytes
+-------------------------------+
| No. | Name | Format | Length | Default |
|---|---|---|---|---|
| 1 | If-Match | opaque | 0-8 | (none) |
| 3 | Uri-Host | string | 1-255 | (see note 1) |
| 4 | ETag | opaque | 1-8 | (none) |
| 5 | If-None-Match | empty | 0 | (none) |
| 7 | Uri-Port | uint | 0-2 | (see note 1) |
| 8 | Location-Path | string | 0-255 | (none) |
| 11 | Uri-Path | string | 0-255 | (none) |
| 12 | Content-Format | uint | 0-2 | (none) |
| 14 | Max-Age | uint | 0-4 | 60 |
| 15 | Uri-Query | string | 0-255 | (none) |
| 17 | Accept | uint | 0-2 | (none) |
| 20 | Location-Query | string | 0-255 | (none) |
| 28 | Size2 | uint | 0-4 | (none) |
| 35 | Proxy-Uri | string | 1-1034 | (none) |
| 39 | Proxy-Scheme | string | 1-255 | (none) |
| 60 | Size1 | uint | 0-4 | (none) |
Content-Formats参数的具体数值:
| Media type | Id. |
|---|---|
| text/plain;charset=utf-8 | 0 |
| application/link-format | 40 |
| application/xml | 41 |
| application/octet-stream | 42 |
| application/exi | 47 |
| application/json | 50 |
| application/cbor | 60 |
Option Delta
Option Delta占用4位(0.5字节)
Option Delta代表Option的类型,该值代表了上表中Option类型的代码值与上一个Option代码值之间的差值
(如果该Option为第一个Option,则直接表达该Option的Option Delta)
由于Option Delta只有4位,最大只能表达15,为了解决这个问题,coap协议有着如下规定:
- 当
Option Delta号码<=12时:Option Delta位为实际的Option Delta值 - 当
Option Delta号码<269时:Option Delta位填入13;并且在后面的Option Delta(extended)位会占用1字节,并且填入的数为实际Option Delta值减去13 - 当
Option Delta号码<65804时:Option Delta位填入14;并且在后面的Option Delta(extended)位会占用2字节,并且填入的数为实际Option Delta值减去269
特别注意,填入的Option Delta值不可能为15(0x0f)当遇到15时,该包无效
Option Length
Option Length占用4位(0.5字节)
Option Length代表该option所包含数据(value)的长度,该值的表示方法类似于Option Delta,如下:
- 当
Option Length号码<=12时:Option Length位为实际的Option Length值 - 当
Option Length号码<269时:Option Length位填入13;并且在后面的Option Length(extended)位会占用1字节,并且填入的数为实际Option Length值减去13 - 当
Option Length号码<65804时:Option Length位填入14;并且在后面的Option Length(extended)位会占用2字节,并且填入的数为实际Option Length值减去269
填入的Option Length值不可能为15,当遇到15时,该包无效
Option Delta(extended)和Option Length(extended)
Option Delta(extended)和Option Length(extended)的意义在上面已经解释过了,在不需要这两个值的情况下,这两个部分的数据便不存在
多个option的情况
当有多个option时,这些option必须是按option代码值(No.)的顺序**从小到大**排列的,不然会导致Option Delta的值出错
每个部分的option各自按格式组包,最后按顺序拼到一起,并入字节流中
负载内容(Payload)
Payload占用字节数不定
当Payload不存在时,数据包末尾不能加上0xff的分隔符
如果存在0xff的分隔符,则分隔符后的数据便是Payload
解释结束,举个实际例子
在网上有一个coap测试服务器,域名为coap.me
我们假设一个例子,用串口工具将这个例子发出
假设需要请求coap.me,5683端口的coap数据,GET请求,网址如下:
coap://coap.me:5683/path/sub1
首先不管服务器,先分析一下数据包格式:
版本号:01(二进制)
消息类型:00(二进制,表示CON)
token长度:0010(二进制,表示2字节)
请求码:0x01(GET)
消息id:0x1234(随便编的)
token:0x5678(随便编的)
到了Options这里,我们就要再小小分析一下了,这个请求有两个Options:
Uri-Path:path
Uri-Path:sub1
所以数据包格式接着上面:
Option Delta:1011(二进制,表示编号11)
Option Length:0100(二进制,表示4位数据)
Option Delta(extended):空
Option Length(extended):空
Option Value:0x70 61 74 68(字符串path)Option Delta:0000(二进制,表示上一个编号11 + 0)
Option Length:0100(二进制,表示4位数据)
Option Delta(extended):空
Option Length(extended):空
Option Value:0x73 75 62 31(字符串sub1)
该请求没有payload,所以也就不去构造payload了
数据完整的16进制代码为:
42 01 12 34 56 78 B4 70 61 74 68 04 73 75 62 31
我们用工具测试一下:
收到的回复为:
62 45 12 34 56 78 48 CB B0 EF 05 63 11 E3 84 80 FF 54 44 5F 43 4F 52 45 5F 43 4F 41 50 5F 30 39 20 73 75 62 31
解包分析一下:
01:协议版本号
10:消息类型,ACK
0010:token长度2字节
0x45:响应代码2.05,Content
0x12 34:消息id
0x56 78:token内容
0x48:option类型ETag,8字节
0xCB B0 EF 05 63 11 E3 84:ETag内容
0x80:option类型Content-Format,0字节
0xFF:和payload消息的分隔符
0x54 44 5F 43 4F 52 45 5F 43 4F 41 50 5F 30 39 20 73 75 62 31:payload内容,解码结果为TD_CORE_COAP_09 sub1
分析完毕,下一篇文章就可以讲解用lua代码来进行组包与拆包了
















