Bootstrap

【基础】CoAP 通讯协议

CoAP 协议简介

CoAP 全称为 Constrained Application Protocol,即受限应用协议。顾名思义,该协议是一种专为物联网设备以及资源受限网络而设计的应用层的通讯协议。由于物联网设备大多受资源限制,如 CPU、RAM、Flash、网络带宽等资源,直接使用现有的网络通信协议如 HTTP 等进行通信的花销太大,因此为使资源受限的设备接入网络,便设计了占用资源与贷款较少的 CoAP 协议。

CoAP 协议属于应用层协议,其基于 UDP 协议开发,与 HTTP、MQTT 协议属于同级协议。

在这里插入图片描述

CoAP 协议具有以下特点:

  • 轻量级:CoAP 协议非常简单,头部与选项字段少,占用网络带宽与资源较少;

  • 基于 UDP:CoAP 协议基于 UDP 协议运行,该协议更适用于无连接、低延迟和高效率的通信。此外,CoAP 也提供了 CON 和 ACK 消息来实现可靠传输;

  • Restful 风格:支持 GET、POST、PUT、DELETE 等方法来进行资源的读取、创建、更新以及删除操作;

  • 资源标识符:使用同一的资源标识符(URI)进行资源标识;

  • IP 多播:CoAP 协议允许一对多通信,在物联网中可实现多个设备同时接收数据;

  • 可扩展性:CoAP 协议允许在消息头中包含自定义选项,以满足特定应用的需求;

CoAP 协议详解

Rest 风格

CoAP 协议采用 Rest 架构实现,Rest 风格具备下述特点:

  • 资源由 URI 来指定,即访问哪些资源由具体的路径指定;

  • 无状态通信;

  • 对资源的获取、创建、修改、删除分别对应 GET、POST、PUT、DELETE 方法;

  • 资源的形式可以是 XML、JSON 或 HTML 等;

CoAP 基于 Rest 风格,简洁、清晰且易于实现。其与 HTTP 相同,使用请求-响应模式,即客户端发送 CoAP 请求,服务器监听到对应请求后会根据请求的 URI 对资源进行定位,并按照不同的请求方法对资源进行增删改查等处理。服务器处理完请求后,将会向客户端返回一个 CoAP 响应,其中包含响应码以及响应内容。

在这里插入图片描述

CoAP 报文结构

CoAP 是一个完整的二进制应用层协议,其报文帧结构包括报文头(Head)和负载(Payload),两者之间使用单字节分隔符 0xFF 分割。一个完整的 CoAP 协议的报文结构如下。

在这里插入图片描述

其中,报文头中的版本号 Ver、报文类型 T、token 标签长度指示 TKL、请求\响应码 Code、报文序号 Message ID 为必填内容。

版本号 Ver

版本号占 2 bit,取固定值 1。

报文类型 T

报文类型占 2 bit,CoAP 协议定义了四种报文类型,分别为 CON、NON、ACK、RST。

token 标签长度指示 TKL

token 长度指示占 4 bit。当 CoAP 报文包含 token 时,TKL 取值可以为 1、2、4 分别表示 token 占据 1 字节、2 字节、4 字节长度;当 CoAP 报文不包含 token 时,TKL 取 0。

请求\响应码 Code

请求\响应码占 8 bit,其值的表示分为高三位的 class 部分以及低五位的 Detail 部分,其采用 c.dd 的格式进行指示。当 c 等于 0 时,表示该报文为一条请求报文,dd 则表示四种请求方法;当 c 不为 0 时,则表示该报文时一条响应报文。当 Code 为 0.00 时,表示空报文。

请求码与对应的含义如下:

  • 0.01:表示 GET 方法,客户端要求通过请求中的 URI 访问资源信息;

  • 0.02:表示 PUT 方法,客户端要求服务器根据请求内容创建新的资源;

  • 0.03:表示 POST 方法,客户端要求服务器根据请求中的 URI 和内容更新指定的资源信息;

  • 0.04:表示 DELETE 方法,客户端要求服务器根据请求中的 URI 删除指定资源;

响应码与对应的含义如下:

  • Success

    • 2.01:表示 Created,即资源已创建;

    • 2.02:表示 Deleted,即资源已删除;

    • 2.03:表示 Valid,即资源未更新,执行缓存(响应负载一般为空);

    • 2.04:表示 Changed,即资源已更新;

    • 2.05:表示 Content,即请求已执行(响应负载一般不为空);

  • Client Error

    • 4.00:表示 Bad Request,即请求错误,服务器无法进行处理;

    • 4.01:表示 Unauthorized,即客户端无操作权限;

    • 4.02:表示 Bad Option,即请求中包含一个或者多个错误的选项;

    • 4.03:表示 Forbidden,即服务器拒绝请求;

    • 4.04:表示 Not Found,即服务器无法找到对应的资源;

    • 4.05:表示 Method Not Allowed,即客户端请求非法;

    • 4.06:表示 Not Acceptable,即请求选项和服务器生成内容不一致;

    • 4.12:表示 Precondition Failed,即请求参数不足;

    • 4.15:表示 Unsupper Content-Type,即不支持请求中的媒体类型;

  • Server Error

    • 5.00:表示 Internal Server Error,即服务器内部错误;

    • 5.01:表示 Not Implemented,即服务器无法支持请求内容;

    • 5.02:表示 Bad Gateway,即服务器网关收到了一个错误的响应;

    • 5.03:表示 Service Unavailable,即服务器过载或维护停机;

    • 5.04:表示 Gateway Timeout,即服务器作为网关时,执行请求中发生超时错误;

    • 5.05:表示 Proxying Not Supported,即服务器不支持代理功能;

报文序号 Message ID

报文序号占 2 Byte,其用于在客户端与服务器之间建立请求-响应报文的对应关系,即起到报文确认的作用。一组对应的 CoAP 请求和响应报文使用相同的 Message ID,在同一次会话当中 ID 保持不变,会话结束后 ID 会被回收利用。

报文序号可以弥补 UDP 传输的不可靠性问题。

标签 Token

token 标签占 1、2 或者 4 Byte,其长度由 TKL 定义,通常用于身份确认。

选项 Options

CoAP 请求或响应中可携带一组或多组 Options,是 CoAP 核心协议中较为复杂的部分,但 Option 也给 CoAP 的应用带来了诸多灵活性。

Options 部分的结构如下所示:

在这里插入图片描述

其中,Option Delta 占据 4 bit,其取值存在四种情况:

  1. 一般情况下其数值在 0 - 12 之间;

  2. 若其值为 13,则 Option Delta (extended) 部分占据 1 Byte,Option Delta 取值为 13 + Option Delta (extended);

  3. 若其值为 14,则 Option Delta (extended) 部分占据 2 Byte,Option Delta 取值为 14 + 255 + Option Delta (extended);

  4. 其值为 15 时无效;

Option Length 占据 4 bit,其取值存在四种情况:

  1. 一般情况下其数值在 0 - 12 之间;

  2. 若其值为 13,则 Option Length (extended) 部分占据 1 Byte,Option Delta 取值为 13 + Option Length (extended);

  3. 若其值为 14,则 Option Length (extended) 部分占据 2 Byte,Option Delta 取值为 14 + 255 + Option Length (extended);

  4. 其值为 15 时无效;

Option Value 为确定参数的值,其长度由 Option Length 的值决定。

Option Delta 的取值与其含义的对应如下:

  • 3:表示 Uri-Host,即 CoAP 主机名称;

  • 7:表示 Uri-Port,即 CoAP 端口号,默认为 5683;

  • 11:表示 Uri-Path,即资源路由或者路径;

  • 15:表示 Uri-Query,即访问资源的参数,例如?value1=1&value2=2

  • 12:表示 Content-Format,用于指定 CoAP 的媒体类型,利用整数去定义不同的媒体类型;

  • 17:表示 Accept,用于指定 CoAP 中响应的媒体类型,其定义与 Content-Format 相同;

CoAP 媒体类型

CoAP 的负载支持多种媒体类型,并采用编号的方式对其进行定义,便害一般为 2 Byte 的无符号整数。

媒体类型编号
text/plain0
application/link-format40
application/xml41
application/octet-stream42
application/exi47
application/json50
application/cbor60

CoAP 重传机制

由于 CoAP 采用 UDP 作为其传输层协议,故在网络数据传输过程中,无论是请求还是响应,均存在丢包的风险。为此,CoAP 提供了重传机制,来弥补传输过程中的不可靠性。

CoAP 定义了四种不同类型的报文:CON、NON、ACK、RST。CON 报文需要被接受者确认,即每一个 CON 报文都对应一个 ACK 报文或 RST 报文,如果在规定的时间内客户端未接受到 ACK 报文或 RST 报文,那么客户端将启动 “重传机制”,如下所示:

在这里插入图片描述
在这里插入图片描述

CoAP 报文重传机制受制于 ACK_TIMEOUT、ACK_RAMDOM_FACTOR、MAX_RETRANSMIT 三个参数。

  • ACK_TIMEOUT 是响应等待超时时间,典型值为 2s;

  • ACK_RAMDOM_FACTOR 是一个不小于 1 的随机系数,典型值取 1.5;

  • MAX_RETRANSMIT 表示最大重传次数,典型值为 4;

对于一个 CON 报文而言,初始超时时间是 ACK_TIMEOUT 到 ACK_RAMDOM_FACTOR * MAX_RETRANSMIT 之间的随机数。重传计数器从 0 开始计数,一旦在初始超时时间内客户端未收到服务器回传的 ACK 报文或 RST 报文,那么 CON 报文将会被重新发送,重传计数器自动增加至1,并且下一次重传超时间自动递增为上一次超时时间的两倍。

假设响应等待超时时间 ACK_TIMEOUT 取 2s,随机系数 ACK_RAMDOM_FACTOR 取 1.5,最大重传次数 MAX_RETRANSMIT 为 4,则初始超时时间是 2~6s 之间的随机数。最极端的情况下,CoAP 客户端会发送一次正常的 CON 报文,并在重传 4 次后停止对该报文的传输。

CoAP 通信协议的 Java 实现

maven 依赖引入:

<dependency>
    <groupId>org.eclipse.californium</groupId>
    <artifactId>californium-core</artifactId>
    <version>3.8.0</version>
</dependency>

CoAP 服务端代码如下,服务端的定义类似于写 Controller + Service 层代码,定义对应的资源路径并编写处理方法即可。

public class CoapServerDemo {

    public static void main(String[] args) {
        final CoapServer coapServer = new CoapServer();

        coapServer.add(new CoapResource("hello") {
            @Override
            public void handleGET(CoapExchange exchange) {
                exchange.respond(CoAP.ResponseCode.CONTENT, "Hello CoAP!");
            }
        });

        coapServer.add(new CoapResource("time") {
            @Override
            public void handleGET(CoapExchange exchange) {
                exchange.respond(CoAP.ResponseCode.CONTENT, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            }
        });

        coapServer.start();
    }

}

CoAP 客户端简单示例代码如下,按照指定的方法去访问指定的资源即可。

public class CoapClientDemo {

    public static void main(String[] args) {
//        hello();
//        time();
    }

    public static void hello() throws Exception {
        URI uri = new URI("localhost:5683/hello");
        CoapClient coapClient = new CoapClient(uri);
        CoapResponse response = coapClient.get();
        if (response != null) {
            // 打印格式良好的输出
            System.out.println(Utils.prettyPrint(response));
        }
    }

    public static void time() throws Exception {
        URI uri = new URI("localhost:5683/time");
        CoapClient coapClient = new CoapClient(uri);
        CoapResponse response = coapClient.get();
        if(response !=null){
            // 打印格式良好的输出
            System.out.println(Utils.prettyPrint(response));
        }
    }

}

上述两个方法访问 CoAP 服务端的返回结果分别如下:

==[ CoAP Response ]============================================
MID    : 54289
Token  : C42D2D44BCC1DFED
Type   : ACK
Status : 2.05 - CONTENT
Options: {"Content-Format":"text/plain"}
RTT    : 503 ms
Payload: 11 Bytes
---------------------------------------------------------------
Hello CoAP!
===============================================================
==[ CoAP Response ]============================================
MID    : 60081
Token  : A0469EE1B7AB8D6F
Type   : ACK
Status : 2.05 - CONTENT
Options: {"Content-Format":"text/plain"}
RTT    : 22 ms
Payload: 19 Bytes
---------------------------------------------------------------
2024-05-30 17:02:08
===============================================================
;