Coap(Constrained Application Protocol)是一种在物联网世界的类web协议,它的详细规范定义在 RFC 7252。COAP名字翻译来就是“受限应用协议”,顾名思义,使用在资源受限的物联网设备上。物联网设备的ram,rom都通常非常小,运行TCP和HTTP是不可以接受的。
COAP协议有4种消息类型
CoAP的消息格式是很紧凑的,默认运行在UDP上(每个CoAP消息都是UDP数据包中的数据部分)。
CoAP也可以运行在DTLS协议上和其它传输协议上,例如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)
2-bit无符号整型,代表CoAP版本号。本文档(7252)的实现必须设置这个字段为0b01。其它的值为今后其它版本保留。对于带有未知版本号的消息,必须忽略。
类型(T)
2-bit无符号整型。代表这个消息的类型是:CON(0), NON(1), ACK(2),或RST(3)。
Token长度(TKL)
4-bit无符号整型。表示变长的Token字段(0-8字节)的长度。长度9-15是保留的,不能设置长度为9-15。如果设置了长度为9-15,必须被当作消息格式错误来处理。
列代码(Code)
8-bit无符号整型。拆分为3-bit的分类信息和5-bit详细信息。
写作”c.dd”。c是3-bit长,可以是一个从0到7的数字,dd是5-bit长,它一个两位的数字,从00到31。
分类信息c可以代表是一个请求(0),一个成功的响应(2),一个客户端错误响应(4),或者一个服务端错误响应(5)。
所有其它的值都是保留的。代码0.00是一个特殊的情况,表示一个空的消息。当消息是一个请求时,Code字段表示请求方法。当响应时,Code字段代表响应代码。Code字段所有可取的值都在下文CoAP代码表中定义了。
消息ID(Message ID)
16-bit无符号整型,网络字节序。用于检测消息重复以及匹配ACK/RST类型的消息和CON/NON类型的消息。生成消息ID和匹配消息的规则在第4章中讲述。
Token值
头部之后是Token值,可以有0到8个字节,由Token长度字段指定。这个Token值用于将某个请求和对应的响应关联。
Options
头部和Token之后,是0个或多个选项(见3.1节)。一个选项之后,有可能是消息结束,也可能是另一个选项,也可能是payload标识符和payload部分。
payload部分
在头部、token和选项之后,是payload部分(可以没有payload)。
如果有payload,并且长度不为0,那么payload之前有一个固定长度为一个字节的payload标识符(0xFF),它标志着选项部分的结束和payload部分的开始。
payload部分从标识符之后开始,一直到这个UDP数据报结束,也就是说,payload部分的长度可以根据UDP数据报的长度计算出来。
如果没有payload标识符,那么就代表这是一个0长度的payload。如果存在payload标识符但其后跟随的是0长度的payload,那么必须当作消息格式错误处理。
Coap负载包含与具体应用直接相关的内容,Coap负载包含多种不同的媒体类型,包括二进制负载、文本负载、XML负载、JSON负载、CBOR负载等。Coap所支持的媒体类型中并不包含HTML类型。
实现注意:0xFF这个值有可能出现在一个选项的长度或选项的值中,所以简单的扫描0xFF来寻找payload标识符是不可行的。作为payload标识符的0xFF只可能出现在一个选项结束之后下一个选项有可能开始的地方。
方法码(Method Codes) 这个sub-registry是“CoAP Method Codes” 每次进入这个sub-registry都必须包含在0.01-0.31范围内的Method Code,方法的名字,方法文档的参考。
初始化进入这个sub-registry如下:
+------+--------+-----------+---------------+
| Code | Name | Reference | description |
+------+--------+-----------+---------------+
| 0.01 | GET | [RFC7252] | 用于获得某资源 |
| 0.02 | POST | [RFC7252] | 用于创建某资源 |
| 0.03 | PUT | [RFC7252] | 用于更新某资源 |
| 0.04 | DELETE | [RFC7252] | 用于删除某资源 |
+------+--------+-----------+----------------+
表 5: CoAP Method Codes
其他Method Codes没有安排。每个不能被服务器识别或未被服务器支持的方法通常会导致一个4.05响应码。
互联网号码分配政策在未来为这个sub-registry额外定义描述在“IETF Review or IESG Approval” [RFC5226].
方法代码的文档需要指定这个请求的语义,包含如下属性:
响应码 这个sub-registry的名字是“CoAP Response Codes”
每个sub-registry必须包含在2.00-5.31范围内的响应码,响应码的描述,响应码的文档参考。
初始化进入这个sub-registry如下:
+------+------------------------------+-----------+
| Code | Description | Reference |
+------+------------------------------+-----------+
| 2.01 | Created | [RFC7252] |
| 2.02 | Deleted | [RFC7252] |
| 2.03 | Valid | [RFC7252] |
| 2.04 | Changed | [RFC7252] |
| 2.05 | Content | [RFC7252] |
| 4.00 | Bad Request | [RFC7252] |
| 4.01 | Unauthorized | [RFC7252] |
| 4.02 | Bad Option | [RFC7252] |
| 4.03 | Forbidden | [RFC7252] |
| 4.04 | Not Found | [RFC7252] |
| 4.05 | Method Not Allowed | [RFC7252] |
| 4.06 | Not Acceptable | [RFC7252] |
| 4.12 | Precondition Failed | [RFC7252] |
| 4.13 | Request Entity Too Large | [RFC7252] |
| 4.15 | Unsupported Content-Format | [RFC7252] |
| 5.00 | Internal Server Error | [RFC7252] |
| 5.01 | Not Implemented | [RFC7252] |
| 5.02 | Bad Gateway | [RFC7252] |
| 5.03 | Service Unavailable | [RFC7252] |
| 5.04 | Gateway Timeout | [RFC7252] |
| 5.05 | Proxying Not Supported | [RFC7252] |
+------+------------------------------+-----------+
表 6: CoAP Response Codes
响应码3.00-3.31是预留给将来使用。所有其他响应码都没有被安排。
互联网号码分配政策为这个sub-registry以后额外的定义描述在“IETF Review or IESG Approval”[RFC5226].
响应码的文档需要指定这个响应的语义,包含如下属性:
Content-Format编号内容
coap的url和HTTP的有很相似的地方,开头是“coap”对应“http”或者“coaps”对应“https”。
HTTP的默认端口是tcp 80,coap的默认端口是udp 5683(coaps是5684)。
Californium框架是一款基于Java实现的Coap技术框架,该项目实现了Coap协议的各种请求响应定义,支持CON/NON不同的可靠性传输模式。Californium 基于分层设计且高度可扩展。在同类型的 Coap技术实现中,Californium的性能表现是比较突出的,如下图:
Californiium 定义了三层架构
对于我们来讲,开发工作主要在逻辑层。
三层架构中都可以支持独立的线程池,其中网络层与协议层的线程池保持独立;
逻辑层可为每个Resource指定独立的线程池,并支持父级继承的机制,即当前Resource若没有定义则沿用父级Resource线程池;
若逻辑层未指定线程池,则默认使用协议层的线程池。
Californium框架的使用并不复杂,我们主要是调用逻辑层的一些类,最常用的一些类和接口如下:
示例介绍了一个简单的框架的应用,客户端向服务端发送一段消息,服务端接收并返回接收成功消息。代码基于JDK1.8。
import org.eclipse.californium.core.CoapResource;
import org.eclipse.californium.core.CoapServer;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.server.resources.CoapExchange;
import java.text.SimpleDateFormat;
import java.util.Date;
public class CoapServerTest
{
public static void main(String[] args) {
// 主机为localhost 端口为默认端口5683
final CoapServer server = new CoapServer();
// 默认就是udp实现传输层
server.add(new CoapResource("hello"){
// 创建一个资源为hello 请求格式为 主机:端口\hello
@Override
public void handleGET(CoapExchange exchange) {
// 重写处理GET请求的方法
exchange.respond(CoAP.ResponseCode.CONTENT, "Hello CoAP!");
}
});
server.add(new CoapResource("time"){
// 创建一个资源为time 请求格式为 主机:端口\time
@Override
public void handleGET(CoapExchange exchange) {
Date date = new Date();
exchange.respond(CoAP.ResponseCode.CONTENT,
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date));
}
});
// 启动服务
server.start();
}
}
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.Utils;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import java.net.URI;
public class CoapClientTest {
public static void main(String[] args) throws Exception {
hello();
//time();
}
public static void hello() throws Exception {
URI uri = null;
// 创建一个资源请求hello资源,注意默认端口为5683
uri = new URI("localhost:5683/hello");
CoapClient client = new CoapClient(uri);
CoapResponse response = client.get();
if(response !=null){
// 打印请求状态码
System.out.println(response.getCode());
// 选项参数
System.out.println(response.getOptions());
// 获取内容文本信息
System.out.println(response.getResponseText());
System.out.println("\nAdvanced\n");
// 打印格式良好的输出
System.out.println(Utils.prettyPrint(response));
}
}
public static void time() throws Exception {
URI uri = null;
// 创建一个资源请求hello资源,注意默认端口为5683
uri = new URI("localhost:5683/time");
CoapClient client = new CoapClient(uri);
CoapResponse response = client.post("", MediaTypeRegistry.TEXT_PLAIN);
if(response !=null){
// 打印请求状态码
System.out.println(response.getCode());
// 选项参数
System.out.println(response.getOptions());
// 获取内容文本信息
System.out.println(response.getResponseText());
System.out.println("\nAdvanced\n");
// 打印格式良好的输出
System.out.println(Utils.prettyPrint(response));
}
}
}