由于物联网中的很多设备都是资源受限型的,即只有少量的内存空间和有限的计算能力,所以传统的HTTP协议应用在物联网上就显得过于庞大而不适用。IETF的CoRE工作组提出了一种基于REST架构的CoAP协议。CoAP是6LowPAN协议栈中的应用层协议。

01

什么是RESTful

REST 这个词,是 Roy Thomas Fielding 在他 2000 年的博士论文中提出的,Fielding 将他对互联网软件的架构原则,定名为 REST,即 Representational State Transfer 的缩写。我对这个词组的翻译是"表现层状态转化"。

如果一个架构符合 REST 原则,就称它为 RESTful 架构。

要理解 RESTful 架构,最好的方法就是去理解 Representational State Transfer 这个词组到底是什么意思,它的每一个词代表了什么涵义。如果你把这个名称搞懂了,也就不难体会 REST 是一种什么样的设计。

资源(Resources)

REST 的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。

所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个 URI(统一资源定位符)指向它,每种资源对应一个特定的 URI。要获取这个资源,访问它的 URI 就可以,因此 URI 就成了每一个资源的地址或独一无二的识别符。所谓"上网",就是与互联网上一系列的"资源"互动,调用它的 URI。

表现层(Representation)

"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。

比如,文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式、JSON 格式表现,甚至可以采用二进制格式;图片可以用 JPG 格式表现,也可以用 PNG 格式表现。

URI 只代表资源的实体,不代表它的形式。严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而 URI 应该只代表"资源"的位置。它的具体表现形式,应该在 HTTP 请求的头信息中用 Accept 和 Content-Type 字段指定,这两个字段才是对"表现层"的描述。

状态转化(State Transfer)

访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。互联网通信协议 HTTP 协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。

客户端用到的手段,只能是 HTTP 协议。具体来说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源(也可以用于更新资源),PUT 用来更新资源,DELETE 用来删除资源。

综述

综合上面的解释,我们总结一下什么是 RESTful 架构:

(1)每一个 URI 代表一种资源;

(2)客户端和服务器之间,传递这种资源的某种表现层;

(3)客户端通过四个 HTTP 动词,对服务器端资源进行操作,实现"表现层状态转化"。

用一句话概括就是:

URL 用来定位资源(表现层),HTTP 动词 (GET、POST、PUT、DELETE) 用来描述对该资源的操作。

02

基于RESTful的CoAP概述

为什么要在物联网中实现 REST 架构,原因有下:

1、不用保持长连接

在物联网设备中,有些设备不需要保持一直在线,使用 MQTT 协议造成资源的大量浪费, 比如智能水表,智慧运输,只需要每隔一定时间将数据上报即可,所以保持一直在线没有必要;

2、幂等性

由于嵌入式设备的特殊性,经常容易造成断网、重启等状况,所以要保证服务器的资源无论被操作多少次之后都是一样的,不会被改变(指异常改变);

3、轻量级

嵌入式设备中存储资源和带宽都是受限的,所以协议的数据包一定要紧凑,节省,高效;

4、裁剪性强

物联网设备上报数据和下发命令在出厂后可能要进行升级,协议要能方便的扩展指令或者删减指令,在 REST 架构中,这一点完全可以通过设计合适的 URI 解决。

综合以上需求,CoAP 协议诞生了,Constrained Application Protocol,即受限制的应用协议,上面这些需求,也正是 CoAP 的特点,在 CoAP 协议中:

  • 使用请求/响应的通信机制(不保持长连接);

  • 使用UDP来传输数据报文(减小报文长度);

  • 通过URI来唯一确定云端资源;

  • 通过PUT/POST/GET/DELETE方法对云端资源进行操作;

最后举一个生动形象的例子,比如要使用 CoAP 协议上报温度数据的设计可以是:设备发起请求,使用 PUT 方法向 URI<服务器地址>:5683/data/temperature处的资源上传数据30,表示当前上报温度值 30,然后等待服务端的回应即可。

03

什么是CoAP

CoAP 是 受限制的应用协议(Constrained Application Protocol)的代名词。最近几年专家们预测会有更多的设备相互连接,而这些设备的数量将远超人类的数量。在这种大背景下,物联网和M2M技术应运而生。虽然对人们而言,连接入互联网显得方便容易,但是对于那些 微型设备 而言接入互联网非常困难。在当前由PC机组成的世界,信息交换是通过TCP和应用层协议HTTP实现的。但是小型物联网设备的ram,rom都通常非常小,运行TCP和HTTP是不可以接受的。为了让小型物联网设备可以接入互联网,CoAP协议被设计出来。CoAP是一种应用层协议,它运行于UDP协议之上而不是像HTTP那样运行于TCP之上。CoAP协议非常小巧,最小的数据包仅为4字节。

CoAP协议的特点:

  1.满足资源受限的网络需求。
  2.无状态HTTP映射,可以通过HTTP代理实现访问CoAP资源,或者在CoAP智商构建HTTP接口。
  3.使用UDP实现可靠IP单播和最大努力IP多播。
  4.异步消息交换
  5.很小的消息头载荷及解析复杂度。
  6.支持URI和内容类型(Content-type).
  7.支持代理和缓存.
  8.内建资源发现.
  9.可以使用DTLS作为安全加密层。
  10.资源消耗低,所需RAM和ROM资源均小于10KB。
  11.其双层(事务层,请求/响应层)处理方式可支持异步通信.
  12.支持观察模式。
  13.支持块传输

04

CoAP的交互模型

CoAP协议的交互模型与HTTP的客户端/服务端模型类似。然而,在M2M的交互场景中,一个使用CoAP协议的设备通常既是客户端又是服务端。CoAP中的请求与HTTP协议中的请求相同,是由客户端发起的,请求一个位于服务端的资源(用URI标识),执行一个动作(用Method Code标识)。然后服务端发回一个响应,带有一个响应代码(Response Code),这个响应中有可能也包含一个资源的表现(附带响应格式)。

与HTTP协议不同的是,CoAP的交互是异步的,构建于面向数据报的传输协议,如UDP。交互是通过一个消息层来实现的,消息层提供了可选的可靠性支持(采用指数回退)。

CoAP协议中定义了四种类型的消息:CON, NON, ACK和RST。(下文有介绍)这四种类型的消息中包含有请求和响应标识码,标识着这些消息是请求还是响应。请求可以包含在CON和NON两种类型中,而响应则除了可以包含在CON和NON之中,还可以包含在附带响应的ACK中。

从逻辑上,可以把CoAP协议划分为两层:

1、消息层,用于处理UDP数据包和异步;

2、请求/响应层,使用Method和Response Code,具体见图1。

图1 CoAP中的抽象层次

当然,CoAP是一个协议,消息和请求/响应仅仅是其头部特性。

CoAP的四种消息类型

CON——需要被确认的请求,如果CON请求被发送,那么对方必须做出响应。
NON——不需要被确认的请求,如果NON请求被发送,那么对方不必做出回应。
ACK——应答消息,接受到CON消息的响应。
RST——复位消息,当接收者接受到的消息包含一个错误,接受者解析消息或者不再关心发送者发送的内容,那么复位消息将会被发送。

CoAP的消息结构

CoAP的消息格式是很紧凑的,默认运行在UDP上(每个CoAP消息都是UDP数据包中的数据部分)。CoAP也可以运行在DTLS协议上(见9.1节)和其它传输协议上,例如SMS,TCP或SCTP,这些不属于本文档的范畴(CoAP不支持UDP-lite[RFC3828]和UDP zero checksum[RFC6936])。

CoAP消息用二进制格式进行编码。 这个消息格式以一个固定4个字节的头部开始。此后是一个长度在0到8字节之间的Token。Token值之后是0个或多个Type-Length-Value(TLV)格式的选项(Option)。之后到整个数据报的结尾都是payload部分,payload可以为空。

【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报文和具体负载之间的分隔符。

Option的格式

CoAP支持多个Option,CoAP的Option的表示方法比较特殊,采用增量的方式描述,一般情况下Option部分包含Option Delta、Option Length和Option Value三部分。消息中的每个option都有一个option编号,option值长度,和option值。消息中的option号(TLV格式中的T)并不是直接指定option编号的。所有的option必须按实际option编号的递增排列,某一个option和上一个option之间的option编号差值为delta;每一个TLV格式的option号都是delta值(数据包中第一个option的delta即它的option编号)。同一个编号的option再次出现时,delta的值为0。option编号由“CoAP option编号”表维护

一个option之中的各个字段的含义如下:

 Option Delta:
表示Option的增量,当前的Option的具体编号。
4-bit无符号整型。值0-12代表option delta。其它3个值作为特殊情况保留:

当值为13:有一个8-bit无符号整型(extended)跟随在第一个字节之后,本option的实际delta是这个8-bit值加13。

当值为14:有一个16-bit无符号整型(网络字节序)(extended)跟随在第一个字节之后,本option的实际delta是这个16-bit值加269。

当值为15:为payload标识符而保留。如果这个字段被设置为值15,但这个字节不是payload标识符,那么必须当作消息格式错误来处理。

Option Length:
表示Option Value的具体长度。
4-bit无符号整数。值0-12代表这个option值的长度,单位是字节。其它3个值是特殊保留的:

当值为13:有一个8-bit无符号整型跟随在第一个字节之后,本option的实际长度是这个8-bit值加13。

当值为14:一个16-bit无符号整型(网络字节序)跟随在第一个字节之后,本option的实际长度是这个16-bit值加269。

当值为15:保留为将来使用。如果这个字段被设置为值15,必须当作消息格式错误来处理。

Option Value 共(option Length)个字节。
option值字段的长度和格式取决于具体的option,有可能定义变长的值。3.2节讲述了本文档所使用的option格式。其它文档中定义的option可能使用其它option值的格式

CoAP中所有的Option都采用编号的方式,这些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协议中找到对应项。

Content-Format描述

CoAP支持多种媒体类型,具体可参考RFC7252 #12.3。从下图的信息可以发现,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。

05

基于CoAPthon3实现CoAP

首先建立一个CoAP服务器demoCoAPServer

from coapthon.server.coap import CoAP
from coapthon.resources.resource import Resource


Host = "127.0.0.1"      # 本机IP地址
Port = 5683             # 端口号


class BasicResource(Resource):
    def __init__(self, name="BasicResource", coap_server=None):
        super(BasicResource, self).__init__(name, coap_server, visible=True,
                                            observable=True, allow_children=True)
        self.payload = "Basic Resource"


    def render_GET(self, request):
        return self


    def render_PUT(self, request):
        self.payload = request.payload
        return self


    def render_POST(self, request):
        res = BasicResource()
        res.location_query = request.uri_query
        res.payload = request.payload
        return res


    def render_DELETE(self, request):
        return True


class CoAPServer(CoAP):
    def __init__(self, host, port):
        CoAP.__init__(self,(host, port))
        self.add_resource('iot', BasicResource()) #添加一个节点


def main():
    print("CoAPServer IP addr {} port {} ".format(Host,Port))
    server = CoAPServer(Host,Port)
    try:
        server.listen(10)  #设置超时时间10s
    except KeyboardInterrupt:
        print("Server Shutdown")
        server.close()
        print("Exiting...")


if __name__ == '__main__':
    main()


创建一个客户端demoCoAPClient,定义一个URI_PATH为iot

import time
from coapthon.client.helperclient import HelperClient


host = "127.0.0.1"
port = 5683
path = "iot"


def main():
    client = HelperClient(server=(host, port))
    while True :
        response = client.put(path,"Hello 物联全栈")
        print(response.pretty_print())
        time.sleep(2) # 每隔2秒发送一次数据
    client.stop()


if __name__ == '__main__':
    main()


分别启动coapserver和coapclient,可以通过日志看到数据收发正常

客户端发送消息:

服务端接收消息:

听说关注公众号的都是大牛 

 

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐