coap 协议
最近需要弄着个,就稍微简单的过了一遍整个coap,蛮多其实都没记住的,先写这些吧,可能会有后续。
消息类型
- 需要确认消息 CON 0
- 不需要确认消息 NON 1(适用于消息会重复频繁的发送,丢掉消息不对业务产生影响)
- 确认应答消息 ACK 2
- 复位消息 RST 3 (拒绝响应可以使用这个类型的消息)
可靠消息传输
主要是通过确认及重传机制来实现的,客户端发送消息后,需要等待服务器收到通知, 如果在规定时间内,没有收到需要重新发送数据。 可靠传输是基于CON消息传输的,服务器端收到CON类型的消息后,需要返回ACK消息,客户端到在指定时间ACK_TIMEOUT内收到ACK消息后,才代表这个消息以可靠到服务器端。
不可靠消息传输
客户端只管发送消息, 不管服务器端有没有收到,因此可能存在丢包。不可靠传输是基于NON消息传输的。服务器端收到NON类型的消息后,不用回复ACK消息。
请求方法
- GET
- PUT
- POST
- DELETE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27+------+--------+-----------+
| Code | Name | Reference |
+------+--------+-----------+
| 0.01 | GET | [RFC7252] |
| 0.02 | POST | [RFC7252] |
| 0.03 | PUT | [RFC7252] |
| 0.04 | DELETE | [RFC7252] |
+------+--------+-----------+
表 5: CoAP Method Codes
```
## 响应类型
1. Success 2.xx 代表客户端请求被成功接收并被成功处理
2. Client Error 4.xx 代表客户端请求有错误,比如参数错误等
3. Server Error 5.xx 代表服务器在执行客户端请求时出错。
所有的请求服务器响应可以放在CoAP CON/NON/ACK消息里面进行传输。针对CoAP 带CON消息请求,响应如果快速处理完(有些请求的处理需要耗时多,服务器无法立即响应),则可直接放在ACK消息包里面返回。对于无法立即响应的,服务器带资源ready后,会单独发一个响应消息包给客户端
8-bit无符号整型。拆分为3-bit的分类信息和5-bit详细信息。
写作”c.dd”。c是3-bit长,可以是一个从0到7的数字,dd是5-bit长,它一个两位的数字,从00到31。
分类信息c可以代表是一个请求(0),一个成功的响应(2),一个客户端错误响应(4),或者一个服务端错误响应(5)。
## 消息报文
![](https://www.adminiot.com.cn/static/index/img/meta/coap/format.png)
这里找一个实际的报文解析一下看看,首先使用这个库https://github.com/obgm/libcoap
安装完之后,打开example,不打开服务端,下载网络调试助手,然后连接127.0.0.1 5683(coap默认端口),使用libcoap的demo中的client
./coap-client -m get -o result.txt coap://localhost1
执行后显示
v:1 t:0 tkl:0 c:1 id:279401
2
3即版本号为1,类型为0,即CON,token长度为0,code为1,即GET,message id为27940
它的二进制数据如下(为方便展示这里使用十六进制)
40 01 6d 24 39 6c 6f 63 61 6c 68 6f 73 741
将其转换为2进制并按照报文格式划分
ver: 01
type: 00
tkl: 0000
code: 000000011
code的展现形式为c.dd 其中c为三位,dd为后五位,这里的code为0.01
+——+——–+———–+
| Code | Name | Reference |
+——+——–+———–+
| 0.01 | GET | [RFC7252] |
| 0.02 | POST | [RFC7252] |
| 0.03 | PUT | [RFC7252] |
| 0.04 | DELETE | [RFC7252] |
+——+——–+———–+
表 5: CoAP Method Codes1
2```
msg id: 0110110100100100 27940
字符串中没有出现ff ff,且tkl为0,所以后面的数据都是options1
39 6c 6f 63 61 6c 68 6f 73 74
首先将39转化为2进制 0011 1001,
按照option的格式来看,0011(3)即为option detla,因为这是第一个option,所以它的索引就是31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 +-----+---+---+---+---+----------------+--------+--------+----------+
| No. | C | U | N | R | Name | Format | Length | Default |
+-----+---+---+---+---+----------------+--------+--------+----------+
| 1 | x | | | x | If-Match | opaque | 0-8 | (none) |
| 3 | x | x | - | | Uri-Host | string | 1-255 | (see |
| | | | | | | | | below) |
| 4 | | | | x | ETag | opaque | 1-8 | (none) |
| 5 | x | | | | If-None-Match | empty | 0 | (none) |
| 7 | x | x | - | | Uri-Port | uint | 0-2 | (see |
| | | | | | | | | below) |
| 8 | | | | x | Location-Path | string | 0-255 | (none) |
| 11 | x | x | - | x | Uri-Path | string | 0-255 | (none) |
| 12 | | | | | Content-Format | uint | 0-2 | (none) |
| 14 | | x | - | | Max-Age | uint | 0-4 | 60 |
| 15 | x | x | - | x | Uri-Query | string | 0-255 | (none) |
| 17 | x | | | | Accept | uint | 0-2 | (none) |
| 20 | | | | x | Location-Query | string | 0-255 | (none) |
| 35 | x | x | - | | Proxy-Uri | string | 1-1034 | (none) |
| 39 | x | x | - | | Proxy-Scheme | string | 1-255 | (none) |
| 60 | | | x | | Size1 | uint | 0-4 | (none) |
+-----+---+---+---+---+----------------+--------+--------+----------+
C=Critical 临界, U=Unsafe 不安全, N=NoCacheKey 不缓存, R=Repeatable 可重复
Table 4: Options
对应的项为uri-host,而该option的length为1001(9),即长度为9,所以option的value即为1
6c 6f 63 61 6c 68 6f 73 74
根据ascii码表转换一下,就是localhost
所以这个coap包的内容就是1
2
3
4
5
6ver: 1
type: CON
tkl: 0
code: GET
msg id: 27940
uri-host: localhost
资源发现
为了实现在没有人干预的情况下正常工作,CoAP提供了资源发现机制。这就是的客户端理解哪些URI是被支持,并且客户端可以获知该URI的具体含义。
CoAP协议建议,服务器端应该支持一个/.well-known/core,该URI可以被任何客户端访问。一个专门用于资源发现的服务器必须侦听默认的5683端口。
如果访问预先协商好的URI /.well-known/core,将会获得以下形式的响应:
<sensors/temp>;sz=512;title=Temperature Sensor;ct=50,<actuators/jack>;sz=256;title=Hydraulic Jacks;ct=50
上面的例子可以帮助我们深入理解服务器如何告诉客户端此处有哪些资源可以利用,通过这个例子可以获得以下信息:
- 有一个URI为“sensors/temp”的设备,该设备的名称为“温度传感器”。有效载荷的内容类型为50字节(application/json,JSON格式)。当使用一个GET方法访问资源时,最小消息的长度为512字节。
- 除了温度之外还有另外一个资源,通过上面的例子可以发现资源和资源之间通过逗号分隔,该资源的URI为actuators/jack,该资源的名称为液压千斤顶,如果使用GET方法访问该资源的话,那么最大的数据包大小为256字节。有效载荷内容的大小为50字节(application/json,格式)。
实现注意事项
- 消息包大小
- 各种格式错误的判断
- 报文可能是无序的 (当使用单独响应时,可能会存在响应数据的包先于ack到达的情况,此时ack包的msg id和请求的相同,但是单独响应的这个包的msg id是新的,那这里怎么确定这个响应是给这个请求的?这个包可能是con类型的消息,如果提前到达的话就直接作为ack.依靠token以及额外的相关端地址来确定)
在附带响应中,CON请求和ACK的“Message ID”必须匹配,响应和原始请求的“token”必须匹配。在单独响应中,只需响应和原始请求的“token”匹配。万一信息携带异常的响应(不是认定的端,端地址、token和客户端的期望不匹配),这个响应必须被拒绝
- UDP不可靠,可能需要重传机制
参考
- https://tools.ietf.org/html/rfc7252#section-5.8.2
- https://github.com/WildDogTeam/contribute/blob/master/source/RFC7252-%E3%80%8A%E5%8F%97%E9%99%90%E5%BA%94%E7%94%A8%E5%8D%8F%E8%AE%AE%E3%80%8B%E4%B8%AD%E6%96%87%E7%89%88.md
- https://segmentfault.com/a/1190000011533594
- http://fanzhenyu.me/2017/03/01/CoAP%20%E5%8D%8F%E8%AE%AE%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/