最近的一个项目中用到了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代码来进行组包与拆包了