前言
CoAP是受限制的应用协议(Constrained Application Protocol)的代名词。在当前由PC机组成的世界,信息交换是通过TCP和应用层协议HTTP实现的。但是对于小型设备而言,实现TCP和HTTP协议显然是一个过分的要求。为了让小设备可以接入互联网,CoAP协议被设计出来。 CoAP是一种应用层协议,它运行于 UDP协议之上而不是像HTTP那样运行于TCP之上。CoAP协议非常小巧,最小的数据包仅为4字节。
CoAP协议综述
和其他TCP IP协议簇中的协议一样,CoAP协议总是以“头”的形式出现在负载之前,而负载和CoAP头之间使用单字节0xFF分离。学习CoAP协议最好的方法便是结合RFC文档,详细分析CoAP协议报文格式的每一部分,便是CoAP协议报文结构示意图。
CoAP协议报文各部分
【Ver】 版本编号,指示CoAP协议的版本号。类似于HTTP 1.0 HTTP 1.1。版本编号占2位,取值为01B。
【T】报文类型,CoAP协议定了4种不同形式的报文,CON报文,NON报文,ACK报文和RST报文。
【TKL】CoAP标识符长度。CoAP协议中具有两种功能相似的标识符,一种为Message ID(报文编号),一种为Token(标识符)。其中每个报文均包含消息编号,但是标识符对于报文来说是非必须的。
【Code】功能码/响应码。Code在CoAP请求报文和响应报文中具有不同的表现形式,Code占一个字节,它被分成了两部分,前3位一部分,后5位一部分,为了方便描述它被写成了c.dd结构。其中0.XX表示CoAP请求的某种方法,而2.XX、4.XX或5.XX则表示CoAP响应的某种具体表现。
【Message ID】报文编号
【Token】标识符具体内容,通过TKL指定Token长度。
【Option】报文选项,通过报文选项可设定CoAP主机,CoAP URI,CoAP请求参数和负载媒体类型等等。
【1111 1111B】CoAP报文和具体负载之间的分隔符。
Code部分详解
Code部分被分成了两部分,为了便于阅读,Code被描述为c.dd形式。具体内容可参考RFC7252
请求
在CoAP请求中,Code被定义为CoAP请求方法,这些方法有GET、POST、PUT和DELETE,这些方法和HTTP协议非常相似。
请求码 | 含义 |
0.01 | GET方法——用于获得某资源 |
0.02 | POST方法——用于创建某资源 |
0.03 | PUT方法——用于更新某资源 |
0.04 | DELETE方法——用于删除某资源 |
响应
在CoAP响应中,Code被定义为CoAP响应码,类似于HTTP 200 OK等等。
响应码 | 含义 |
2.01 | Created |
2.02 | Deleted |
2.03 | Valid |
2.04 | Changed |
2.05 | Content。类似于HTTP 200 OK |
4.00 | Bad Request 请求错误,服务器无法处理。类似于HTTP 400 |
4.01 | Unauthorized 没有范围权限。类似于HTTP 401 |
4.02 | Bad Option 请求中包含错误选项 |
4.03 | Forbidden 服务器拒绝请求。类似于HTTP 403 |
4.04 | Not Found 服务器找不到资源。类似于HTTP 404 |
4.05 | Method Not Allowed 非法请求方法。类似于HTTP 405 |
4.06 | Not Acceptable 请求选项和服务器生成内容选项不一致。类似于HTTP 406 |
4.12 | Precondition Failed 请求参数不足。类似于HTTP 412 |
4.15 | Unsuppor Conten-Type 请求中的媒体类型不被支持。类似于HTTP 415 |
5.00 | Internal Server Error 服务器内部错误。类似于HTTP 500 |
5.01 | Not Implemented 服务器无法支持请求内容。类似于HTTP 501 |
5.02 | Bad Gateway 服务器作为网关时,收到了一个错误的响应。类似于HTTP 502 |
5.03 | Service Unavailable 服务器过载或者维护停机。类似于HTTP 503 |
5.04 | Gateway Timeout 服务器作为网关时,执行请求时发生超时错误。类似于HTTP 504 |
5.05 | Proxying Not Supported 服务器不支持代理功能 |
Option部分详解
CoAP支持多个Option,CoAP的Option的表示方法比较特殊,采用增量的方式描述,细节可参考RFC7252
一般情况下Option部分包含Option Delta、Option Length和Option Value三部分。
【Option Delta】表示Option的增量,当前的Option的具体编号等于之前所有Option Delta的总和。
【Option Length】表示Option Value的具体长度。
【Option Value】表示Option具体内容
CoAP中所有的Option都采用编号的方式,这些Option及编号的定义如下图所示。
在这些option中,Uri-Host、Uri-Port、Uri-Path和Uri-Query等和资源“位置”和参数有关。
【3】Uri-Host:CoAP主机名称,例如iot.eclipse.org
【7】Uri-Port:CoAP端口号,默认为5683
【11】Uri-Path:资源路由或路径,例如\temperature。资源路径采用UTF8字符串形式,长度不计第一个"\"。
【15】Uri-Query:访问资源参数,例如?value1=1&value2=2,参数与参数之间使用“&”分隔,Uri-Query和Uri-Path之间采用“?”分隔。 在这些option中,Content-Format和Accept用于表示CoAP负载的媒体格式
【12】Content-Format:指定CoAP复杂媒体类型,媒体类型采用整数描述,例如application/json对应整数50,application/octet-stream对应整数40。
【17】Accept: 指定CoAP响应复杂中的媒体类型,媒体类型的定义和Content-Format相同。
CoAP协议中支持多个Option,例如 第一个Option Delta=11,表示该Option表示Uri-Path(11) 第二个Option Delta=1,表示该Option=1+11,表示Content-Format(12) 第三个Option Delta=3,表示该Option=3+1+11,表示Uri-Query(15) CoAP采用这样的方式表示多个Option,而每种Option都可以在HTTP协议中找到对应项。
在这里举一个实际情况的例子:
我有一个get请求需要发送(使用openthread上的CoAP组件),这个get请求的options里有Url-Path与Observe这两个option (观察特性是在RFC 7641里定义的,所以上表没有,它的编号是6).
一开始,我的代码是这样的:
// 创建一个COAP包
ret = coap_packet_init(&request, ucCoapMsgBuf, COAP_HANDLER_MAX_COAP_MSG_LEN, 1, COAP_TYPE_NON_CON, 6, mac_addr, COAP_METHOD_GET, coap_next_id());
if (ret < 0)
{
LOG_ERR("Failed to init CoAP message");
goto end;
}
// 添加URL选项
ret = coap_packet_append_option(&request, COAP_OPTION_URI_PATH, cUrl, strlen(cUrl));
// 添加OBSERVE选项
ret = coap_packet_append_option(&request, COAP_OPTION_OBSERVE, &ucObserveOption,sizeof(ucObserveOption));
创建了CoAP包与包头后先添加了Url Path,再添加Observe.其中Url Path的值是"Rev2Ser",一共7个字节,Observe的值是0x00,一个字节.然后出来的包实际是这样的:
I: 56 01 63 c6 b3 e8 71 7f |V.c…q.
I: d9 9a b7 52 65 76 32 53 |…Rev2S
I: 65 72 00 00 00 00 00 00 |er……
可以看出来,options部分只有Url Path,并没有Observe.其中的0xB7按上面的协议解析,高位4字节是Option Delta,低位4字节是Option Length,这个没问题,Url Path的编号确实是11,也就是0x0B,它携带的值是"Rev2Ser"所以长度是7,这个也没问题.
因为options中的多个option编号是叠加递增(Delta)的,那么,要在11上叠加递增成Observe的编号6,是不可能的,所以Observe就丢失了,那么改一下添加option代码的执行顺序,变成这样:
// 添加OBSERVE选项
ret = coap_packet_append_option(&request, COAP_OPTION_OBSERVE, &ucObserveOption,sizeof(ucObserveOption));
// 添加URL选项
ret = coap_packet_append_option(&request, COAP_OPTION_URI_PATH, cUrl, strlen(cUrl));
再运行一遍,结果就是这样的:
I: 56 01 4e 73 b3 e8 71 7f |V.Ns..q.
I: d9 9a 61 00 57 52 65 76 |..a.WRev
I: 32 53 65 72 00 00 00 00 |2Ser….
这么看,0x61就是编号6,长度1字节的option,就是Observe了,后面跟的0x00就是Observe的值,再后面的0x57,用高位的0x05+前面的Observe的编号0x06,就是0x0B,就是编号11的Url Path了,低位的0x07就是Url Path的数据长度,再后面跟着的就是Url Path的数据本体了.
Content-Format描述
CoAP支持多种媒体类型,具体可参考RFC7252。从下图的信息可以发现,CoAP协议中关于媒体类型的定义比较简单,未来应该会根据实际情况扩展。
【text/plain】 编号为0,表示负载为字符串形式,默认为UTF8编码。
【application/link-format】编号为40,CoAP资源发现协议中追加定义,该媒体类型为CoAP协议特有。
【application/xml】编号为41,表示负载类型为XML格式。
【application/octet-stream】编号为42,表示负载类型为 二进制格式。
【application/exi】编号为47,表示负载类型为“精简XML”格式。(翻译不一定准确)
另外,还有一种格式也北IANA认定,也会在CoAP协议中广泛使用那便是CBOR格式,该格式可理解为 二进制JSON格式。
【applicaiton/cbor】编号为60。
示例
该示例来自于RFC7252。
【流程描述】
CoAP客户端通过GET方法从Server端获得温度传感器数据,CoAP URI如下
coap:// www.server.com/temperautre
CoAP请求采用CON报文,Server接收到CON报文必须返回一个ACK报文。CoAP请求采用0.01 GET方法,若操作成功CoAP Server返回2.05 Content,相当于HTTP 200 OK。请求和响应的MID必须完全相同,此处为0x7d34。请求响应中的Token域为空。CoAP请求中包含Option,该Option的类型为Uri-Path,那么Option Delta的值为0+11=11,Option Value的值为字符串形式的“temperature”。CoAP返回中包含温度数据,使用字符串形式描述,具体值为"22.3"。
【格式描述】
观察机制
在RFC 7641里有定义了CoAP的观察机制,这个机制主要是用于当CoAP服务器需要有消息主动通知CoAP客户端的时候,正常情况下CoAP只能是客户端通过不同类型请求发送给服务器并携带要上行到服务器的数据,服务器收到请求作出处理后给予回应,并携带要下行到客户端的数据,若客户端不知道服务器何时有数据要下发给客户端,则客户端只能一直做轮询操作,这在CoAP的使用场景下是无法接受的,所以就有了观察机制.
该机制基于著名的观察者设计模式,在此设计模式中,成为"观察者"的组件在称为"主体"的特定已知提供者处注册,它们(观察者)有兴趣在主体经历状态更改时收到通知.主体负责管理其注册观察者列表.如果观察者对多个对象感兴趣,则观察者必须为所有对象单独注册.
观察者设计模式在CoAP中实现如下:
- 主题:在CoAP的上下文中,主题是CoAP服务器命名空间中的资源.资源的状态可以随时间变化,从不频繁的更新到持续的状态转换.
- 观察者:观察者是一个CoAP客户端,它对在当前所拥有的资源表示感兴趣.
- 注册:客户端通过以下方式注册其对资源的兴趣:向服务器发起扩展的GET请求,除了返回目标资源的表示外,此请求还会导致服务器将客户端添加到资源的观察者列表中.
- 通知:每当资源的状态发生变化时,服务器都会通知资源观察者列表中的每个客户端.每个通知都是由服务器发送额外的CoAP响应,以回复上面注册时的单个扩展GET请求,并包括新资源状态的完整,更新表示.
下面的图片显示了CoAP客户端注册其对资源的兴趣并接收3个通知的示例.
第一个通知是在注册时,然后在更改资源状态时有两个通知.注册请求和通知都通过CoAP包中Options里的观察选项来标识.在通知中,Observe选项额外提供了一个用于重新排序检测(或重复接收检测)的序列号.所有通知都带有客户端指定的令牌(Token),因此客户端可以轻松地将它们与请求(一开始的Get请求)相关联.
带有通知的CoAP消息可能会丢失,这将导致客户端在收到新通知之前采用旧状态.而且服务器端可能会错误地得出客户端不再对资源感兴趣的结论,这将导致服务器停止发送通知,直到客户端再次注册.
观察选项
观察选项具有以下属性,它的含义取决于它是否包含在GET请求(客户端向服务器发送的注册或者取消注册的GET请求)中还是包含在响应(服务器发送给注册的客户端的通知)中.
当包含在GET请求中时,观察选项扩展了GET方法,因此它不仅检索目标资源的当前表示,还请求服务器根据资源的观察者列表执行添加或删除条目的动作.列表条目由客户端端点和客户端在请求中指定的令牌(Token)组成,可能的值为:
- 0:(注册)将条目添加到列表中,如果之前列表中不存在该条目.
- 1:(注销)将条目从列表中删除.
观察选项对于处理请求并不重要,如果服务器不愿意或无法将新条目添加到列表中.则服务器将观察者的注册请求回退到正常的GET请求并且响应包中不包含观察选项.
观察选项不是Cache-Key的一部分,在请求中使用观察选项活的的可缓存响应可用于满足没有观察选项的请求,反之亦然.当带有观察选项的存储响应应用于正常的GET请求时,必须在返回响应之前删除该选项.
当包含在响应中时,观察选项将消息标识为通知.这意味着观察者列表中已经添加完成或已存在匹配条目,并且服务器将通知客户端资源状态的变化.选项值是用于重新排序检测的序号.
观察选项的值使用可变字节数以网络字节顺序编码为无符号整数.
客户端观察请求
客户端通过发出GET请求并将观察选项设置为0来注册对其资源的兴趣.如果服务器返回包含观察选项的2.xx响应,则服务器已成功将带有客户端端点和请求令牌(Token)的条目添加到了目标资源的观察者列表中,若资源有更改,客户端将收到资源状态的通知.另外,若客户端多次注册同一个目标资源,服务器应当不得为其对同一个目标资源多次注册.
服务器给客户端的通知与正常相应之间唯一的区别是存在观察选项.通知通常是2.05(内容)相应码,通知包括一个观察选项用于重新排序检测,和一个与初始相应具有相同内容格式的有效负载.
如果资源变化的方式会导致当时正常的GET请求返回非2.xx响应(例如,当资源被删除时),服务器会发送带有适当相应代码的通知(例如4.04 Not Found)并从资源的观察者列表中删除客户端的条目.非2.xx响应不应包含观察选项.
通知数据包到达的顺序
为了给客户端提供通知之间的顺序,服务器将每个通知中的观察选项的值设置为严格递增的24 bit序列号.要使本次通知比上次通知更加新的序列号条件如下:
(V1 < V2 and V2 - V1 < 2^23) or
(V1 > V2 and V1 - V2 > 2^23) or
(T2 > T1 + 128 seconds)
其中V1是迄今为止最新通知中观察选项的值,V2是当前通知的观察选项的值,T1是迄今为止最新通知的客户端本地时间戳,T2是当前通知的客户端本地时间戳.
这三个条件中前两个用于验证V1小于V2(考虑了24 bit溢出归零的情况),第三个条件确保如果服务器根据本地时钟生成序列号,则两个通知之间经过的时间不会太大,以至于V1和V2之间的差异超出有意义的范围(V1被V2多次在24bit的范围套圈(溢出)),换句话说,在最新通知的128秒后如果有新的通知,则不看序列号,直接当成有新的通知.
————————————————
版权声明:本文为CSDN博主「xukai871105」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xukai871105/article/details/45167069
Comments | NOTHING