Californium 实现CoAP协议,利用RabbitMQ实现服务器与客户端间的解耦和异步消息传递
一、引言
Constrained Application Protocol (CoAP) 是一种专为网络受限设备设计的互联网应用协议。CoAP 常用于低功耗、计算能力有限的物联网设备,能够以较小的开销实现设备和服务器之间的通信。Californium 是一个用于 CoAP 的 Java 库,用于实现 CoAP 服务器和客户端。
使用了 RabbitMQ 进行消息的异步处理,这是一种非常有效的方法,可以将接收消息和处理消息分开,提高系统的响应能力。同时,通过 RabbitMQ,服务器和客户端可以解耦,降低系统的复杂性。
二、Californium的介绍
Californium (Cf) 是一个用于构建物联网应用的强大工具,它是一个基于Java的开源框架,专门为实现Constrained Application Protocol (CoAP)设计。CoAP是一种专为小型、低功率和有限存储设备设计的网络协议,经常被用于物联网环境。
以下是一些Californium的关键特性:
- 高效性能:Californium是为高并发和大规模物联网应用设计的。由于它是基于Java的,所以可以利用Java的并发和内存管理特性,提供高效的性能。
- 模块化设计:Californium的设计是模块化的,可以根据需求选择添加或删除特定的功能。这使得它可以灵活地适应各种不同的应用场景。
- 安全性:Californium支持CoAP的DTLS安全层,这意味着可以在不安全的网络环境中安全地使用CoAP。同时,它还提供了一些其他的安全特性,比如防重放攻击和保护机密性和完整性。
- 扩展性:Californium的设计是可扩展的,可以通过添加新的模块或修改现有模块来扩展其功能。
总的来说,Californium是一个强大的CoAP实现,可以为物联网应用提供稳定、高效和安全的网络通信。
以下是项目中构建的CoAP资源,本项目构建了多层级的资源,现只展示底层资源
package com.bytecub.coap.service.resource;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bytecub.coap.domain.PropertySetRequest;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.coap.BlockOption;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.OptionSet;
import org.eclipse.californium.core.observe.ObserveRelation;
import org.eclipse.californium.core.observe.ObserveRelationFilter;
import org.eclipse.californium.core.observe.ObservingEndpoint;
import org.eclipse.californium.core.server.resources.CoapExchange;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
/**
* CoAP 设备资源
* @author gcu_gz
* @version Id: DeviceResource.java, v 0.1 2022/8/29 Exp $$
*/
@Slf4j
public class DeviceResource extends AbstractResource {
PropertySetRequest resource;
public DeviceResource(String name) {
super(name);
}
public DeviceResource(String name,PropertySetRequest request) {
super(name);
this.resource = request;
/**
* 允许被观察(底层采用观察者模式,当前类为一个subject)
*/
//添加标记,使core在get时可以看到obs:true
this.getAttributes().setObservable();
this.setObservable(true);
this.setObserveType(CoAP.Type.CON);
}
/**
* 属性由产品管理
* 每个属性下有具体设备,由设备返回当前属性的具体值
* @param exchange
*/
@Override
public void handleGET(CoapExchange exchange) {
OptionSet options = exchange.getRequestOptions();
// 判断是否发出观察请求
if (options.hasObserve()) {
this.handleObserve(exchange,options);
} else {
String dataJson = JSON.toJSONString(this.resource);
exchange.respond(CoAP.ResponseCode.CONTENT,dataJson);
}
}
private void handleObserve(CoapExchange exchange, OptionSet options) {
Integer observe = options.getObserve();
if (observe > 1) {
// 其他情况返回数据
String dataJson = JSON.toJSONString(this.resource);
exchange.respond(CoAP.ResponseCode.CONTENT,dataJson);
return;
}
ObservingEndpoint endpoint = new ObservingEndpoint(exchange.getSourceSocketAddress());
ObserveRelation relation = new ObserveRelation(endpoint,this, exchange.advanced());
/**
* 当options含有observe参数:
* observe = 0,表示注册操作
* observe = 1,表示注销操作
*/
if (observe == 0) {
// 开启订阅,注册观察
relation.setEstablished(); // 允许连接
this.addObserveRelation(relation);
log.info("客户端 {} 订阅资源 {} 成功",exchange.getSourceSocketAddress().toString(),this.getURI());
exchange.respond(exchange.getSourceSocketAddress().toString()+"subscription successful,resource:"+this.getURI());
} else if (observe == 1) {
this.removeObserveRelation(relation);
byte[] block2 = new byte[3];
block2[2] = 2;
options.setBlock2(new BlockOption(block2));
exchange.respond(exchange.getSourceSocketAddress().toString()+"unsubscribe successful,resource:"+this.getURI());
}
}
@Override
public void handlePUT(CoapExchange exchange) {
String payload = null;
try {
payload = new String(exchange.getRequestPayload(),"UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if (payload == null || payload.equals("")) {
exchange.respond(CoAP.ResponseCode.REQUEST_ENTITY_INCOMPLETE,"request payload must not be empty !");
return;
}
log.info("payload: " + payload);
JSONObject payloadObj = (JSONObject) JSON.parse(payload);
Class<? extends PropertySetRequest> clazz = this.resource.getClass();
try {
Field valueField = clazz.getDeclaredField("value");
valueField.setAccessible(true);
Object value = payloadObj.get("value");
valueField.set(this.resource,value);
Field saveTimeField = clazz.getDeclaredField("saveTimestamp");
saveTimeField.setAccessible(true);
Long saveTimestamp = System.currentTimeMillis(); // 获取当前系统时间戳
saveTimeField.set(this.resource,saveTimestamp);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
// changed()方法可以通知所有观察者,也可自己通过notifyObserverRelations实现通知
// this.changed();
// 更新后通知所有观察对象
this.notifyObserverRelations(new ObserveRelationFilter() {
@Override
public boolean accept(ObserveRelation observeRelation) {
if (observeRelation.isCanceled()) {
return false;
}
OptionSet options = observeRelation.getExchange().getRequest().getOptions();
Integer observe = options.getObserve();
if (observe == 0) {
observe = 1;
}
options.setObserve(++observe);
return true;
}
});
exchange.respond(CoAP.ResponseCode.CHANGED,payloadObj.getString("identifier")+" updated...");
}
@Override
public void handleDELETE(CoapExchange exchange) {
this.delete();
exchange.respond(CoAP.ResponseCode.DELETED,"deleted...");
}
}
三、RabbitMQ的基本概念
RabbitMQ是一个开源的消息代理和队列服务器,用来通过轻量级的消息在分布式系统或者在微服务架构中的服务之间进行通信。RabbitMQ是使用Erlang语言来编写的,并且通过AMQP协议来进行通信。
在RabbitMQ中,生产者(Producer)创建消息并发送到交换器(Exchange),交换器根据指定的路由规则,将消息路由到一个或多个队列(Queue),消费者(Consumer)则监听队列,接收队列中的消息进行处理。
四、RabbitMQ在服务间解耦中的作用
RabbitMQ 是一种消息中间件,主要用于应用程序之间的异步通信和解耦。它实现了高级消息队列协议(AMQP),并提供了丰富的特性和插件。
服务解耦是指将系统中的各个部分独立出来,使之能够独立变化和发展,而不会对整个系统产生副作用。在微服务架构中,服务解耦是非常重要的设计原则,可以提高系统的可维护性、扩展性和稳定性。
RabbitMQ 在服务间解耦中的作用主要表现在以下几点:
- 异步通信:通过使用 RabbitMQ,系统中的各个部分可以异步地发送和接收消息。这意味着发送者无需等待接收者处理消息,可以立即返回并处理其他任务。这大大提高了系统的响应能力和吞吐量。
- 缓冲压力:RabbitMQ 提供了消息缓存机制,可以缓存大量的消息,等待接收者有能力处理。这样即使在流量高峰期,也可以保证系统的稳定性。
- 扩展性:RabbitMQ 支持动态的添加和删除消息队列和消费者,这意味着可以根据系统的需要动态地扩展或缩小系统的规模。
- 容错性:RabbitMQ 提供了消息持久化、消费者确认等机制,可以防止消息丢失,提高系统的可靠性。
总的来说,RabbitMQ 可以有效地解耦服务,使系统更加灵活、可靠和易于扩展。
五、RabbitMQ在异步消息传递中的作用
RabbitMQ 是一种开源消息队列(MQ)系统,用于通过消息的形式在处理和响应系统之间进行异步通信。在异步消息传递中,RabbitMQ 扮演了非常关键的角色,下面详细描述了一些主要作用:
- 解耦生产者和消费者:在异步消息传递中,发送消息的应用(生产者)和接收消息的应用(消费者)之间是解耦的。也就是说,生产者只需要把消息发送到 RabbitMQ,而不需要直接发送到消费者。这种解耦使得生产者和消费者可以独立地进行扩展和修改,而不会对彼此产生影响。
- 缓冲消息:RabbitMQ 可以作为一个缓冲区,存储来自生产者的消息,然后在消费者准备好处理消息时,再把消息传递给消费者。这样即使生产者产生消息的速度比消费者处理消息的速度快,也不会导致消费者过载。
- 路由和分发消息:RabbitMQ 提供了丰富的消息路由和分发机制,如直接交换、主题交换、头交换和扇出交换等。这些机制可以根据需求把消息路由到一个或多个队列,实现消息的灵活分发。
- 支持持久化和消息确认:为了防止消息丢失,RabbitMQ 支持消息持久化,即将消息存储在磁盘中,即使RabbitMQ 崩溃,也能保证消息不丢失。同时,它还支持消息确认机制,消费者在成功处理消息后,向 RabbitMQ 发送确认消息,防止消息处理过程中出错而导致的消息丢失。
- 支持消息的优先级:RabbitMQ 还支持设置消息的优先级,可以根据消息的重要性进行优先处理。
总的来说,RabbitMQ 在异步消息传递中扮演了重要的角色,通过解耦生产者和消费者,缓冲消息,路由和分发消息,支持消息持久化和消息确认,以及设置消息的优先级,使得系统能够异步、灵活、可靠地进行消息传递。
六、RabbitMQ在项目中的实践
本项目中,在CoAP服务器和CoAP客户端之间加了一层消息中间件RabbitMQ,服务器与客户端两者并不之间连接,而是通过RabbitMQ进行消息的传递。
在本项目中,RabbitMQ 有可能被用在以下场景中:
- 设备消息的异步处理:在物联网中,设备会持续地发送数据,这些数据需要在后端进行处理。通过使用 RabbitMQ,你的系统可以把接收数据和处理数据分开,设备发送的数据首先被发送到 RabbitMQ,然后由后端服务异步地从 RabbitMQ 接收和处理数据。这样可以提高系统的响应速度,防止在设备发送大量数据时,后端服务处理不过来。
- 服务间的解耦:系统可能包含多个服务,如设备管理服务、消息处理服务、数据存储服务等。通过使用 RabbitMQ,这些服务可以通过消息进行通信,而不是直接调用对方的接口,这样可以减少服务间的依赖,提高系统的可维护性和可扩展性。
- 任务的调度和执行:在系统中,可能会有一些定时任务,如定时获取设备的状态、定时清理旧的数据等。这些任务可以通过发送消息到 RabbitMQ 进行调度,然后由后端服务接收消息并执行相应的任务。
- 系统的可靠性和容错性:RabbitMQ 提供了消息持久化、消息确认和消息重试等机制,可以防止消息丢失,提高系统的可靠性。即使在系统出现故障时,也可以通过 RabbitMQ 保证消息不丢失,当系统恢复正常后,可以继续处理这些消息。
通过这些方式,RabbitMQ 在你的项目中实现了消息的异步处理,服务间的解耦,任务的调度和执行,以及提高系统的可靠性和容错性。
以下是RabbitMQ在项目中的使用:
1、更新属性,更新的数据先加入RabbitMQ
package com.bytecub.coap.service.protocol;
import com.bytecub.plugin.rabbitmq.utils.RabbitMQSender;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.elements.exception.ConnectorException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
/**
* SetResource.java 2022/8/30
*/
@Slf4j
@Component
public class SetResource {
@Autowired
private RabbitMQSender rabbitMQSender ;
/**
* 更新属性资源
* @param uriString
* @param payload
*/
public void setProperty(String uriString,String payload) {
MessageProperties properties = new MessageProperties();
properties.setHeader("uri", uriString); // 设置URI
properties.setHeader("method", "put"); // 设置请求方式
Message amqpMessage = new Message(payload.getBytes(), properties);
// 此处消息异步发送
rabbitMQSender.send("coap.exchange","coap.key",amqpMessage);
}
}
2、CoAPSendClient类为CoAP客户端类,用于对RabbitMQ消费,并将数据根据CoAP具体请求类型交给不同类处理处理
package com.bytecub.coap.service.client;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.elements.exception.ConnectorException;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
@Component
@Slf4j
public class CoAPSendClient {
public static void send(String uriString, String payload, String method) {
URI uri = null;
CoapClient coapClient = null;
try {
uri = new URI(uriString);
} catch (URISyntaxException e) {
log.warn("coap url 错误...");
} catch (Exception e) {
log.warn("{}连接失败...",uri);
}
coapClient = new CoapClient(uri);
try {
switch (method) {
case "post":
// 发送post请求
PostClient.send(coapClient, payload);
break;
case "put":
// 发送put请求
PutClient.send(coapClient, payload);
break;
}
} catch (ConnectorException e) {
log.warn("连接失败...,原因:{}", e.getMessage());
} catch (IOException e) {
log.warn("IO异常...");
} finally {
if (coapClient != null) {
coapClient.shutdown();
}
}
}
}
package com.bytecub.coap.service.client;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.elements.exception.ConnectorException;
import java.io.IOException;
public class PutClient {
public static void send(CoapClient coapClient, String payload) throws ConnectorException, IOException {
// the first arg is the request body
// the second arg is the format id of the body, 0-text|50-json
// coapClient.post(payload.getBytes(), 50);
coapClient.put(payload.getBytes(),50);
}
}
七、使用libcoap实现CoAP客户端
以下只展示部分代码,实现了observer的观察
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#if 1
#include "libcoap.h"
#include "coap_dtls.h"
#endif
#include "coap.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "string.h"
#include <sys/time.h>
#include <time.h>
#include <stdio.h>
#include "cJSON.h"
#define COAP_DEFAULT_TIME_SEC 5
#define EXAMPLE_COAP_PSK_KEY CONFIG_EXAMPLE_COAP_PSK_KEY
#define EXAMPLE_COAP_PSK_IDENTITY CONFIG_EXAMPLE_COAP_PSK_IDENTITY
#define EXAMPLE_COAP_LOG_DEFAULT_LEVEL CONFIG_COAP_LOG_DEFAULT_LEVEL
#define COAP_DEFAULT_DEMO_URI CONFIG_EXAMPLE_TARGET_DOMAIN_URI
#define DHT11_PIN 15//定义DHT11的引脚
#define LED_PIN 2 //定义Led灯的引脚
#define uchar unsigned char
#define uint8 unsigned char
#define uint16 unsigned short
const static char *TAG = "CoAP_client";
void LED_Control(int i){
switch(i){
case 1 :
gpio_pad_select_gpio(LED_PIN);
gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);
gpio_set_level(LED_PIN, 1);
break;
case 2 :
gpio_pad_select_gpio(LED_PIN);
gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);
gpio_set_level(LED_PIN, 0);
break;
default:
ESP_LOGI(TAG, "错误:LED灯状态标识符识别失败!");
}
}
#ifdef CONFIG_COAP_MBEDTLS_PKI
extern uint8_t ca_pem_start[] asm("_binary_coap_ca_pem_start");
extern uint8_t ca_pem_end[] asm("_binary_coap_ca_pem_end");
extern uint8_t client_crt_start[] asm("_binary_coap_client_crt_start");
extern uint8_t client_crt_end[] asm("_binary_coap_client_crt_end");
extern uint8_t client_key_start[] asm("_binary_coap_client_key_start");
extern uint8_t client_key_end[] asm("_binary_coap_client_key_end");
#endif /* CONFIG_COAP_MBEDTLS_PKI */
// 监控响应处理
static void monitor_message_handler(coap_context_t *ctx, coap_session_t *session,
coap_pdu_t *sent, coap_pdu_t *received,
const coap_tid_t id)
{
unsigned char *data = NULL;
size_t data_len;
coap_opt_iterator_t opt_iter;
if (coap_check_option(received, COAP_OPTION_OBSERVE, &opt_iter)) {
coap_log(LOG_DEBUG,
"observation relationship established\n",
obs_seconds);
}
if (COAP_RESPONSE_CLASS(received->code) == 2) {
if (coap_get_data(received, &data_len, &data)) {
if ((int)data_len > 100) {
cJSON* root=cJSON_Parse((char *)data); //解析JSON字符串
cJSON* item=cJSON_GetObjectItem(root,"identifier"); //读取 identifier 字段
cJSON* deviceCode=cJSON_GetObjectItem(root,"deviceCode"); //读取 deviceCode 字段
//开启事件
if(strcmp(item->valuestring,"open") == 0){
ESP_LOGI(TAG, "开启设备");
LED_Control(1);
} else if(strcmp(item->valuestring,"open") == 1){
ESP_LOGI(TAG, "关闭设备");
LED_Control(2);
} else{
ESP_LOGI(TAG, "错误:调用设备服务,未知服务,该报文不做处理");
}
} else {
// printf("Received: %.*s\n", (int)data_len, data);
}
}
}
}
#ifdef CONFIG_COAP_MBEDTLS_PKI
static int
verify_cn_callback(const char *cn,
const uint8_t *asn1_public_cert,
size_t asn1_length,
coap_session_t *session,
unsigned depth,
int validated,
void *arg
)
{
coap_log(LOG_INFO, "CN '%s' presented by server (%s)\n",
cn, depth ? "CA" : "Certificate");
return 1;
}
#endif /* CONFIG_COAP_MBEDTLS_PKI */
// 监控open
static void monitor_open(void *p)
{
struct hostent *hp;
coap_address_t dst_addr;
static coap_uri_t uri;
const char *server_uri = &"coap://coap_server:5683/lqa5bihfks4fzkbn/service/open/z96g32s2i27hswxj";
char *phostname = NULL;
coap_optlist_t *optlist = NULL;
coap_set_log_level(EXAMPLE_COAP_LOG_DEFAULT_LEVEL);
while (1) {
#define BUFSIZE 100
unsigned char _buf[BUFSIZE];
unsigned char *buf;
size_t buflen;
int res;
coap_context_t *ctx = NULL;
coap_session_t *session = NULL;
coap_pdu_t *request = NULL;
optlist = NULL;
ESP_LOGI(TAG, "server_uri: %s", server_uri);
// 解析请求URI
if (coap_split_uri((const uint8_t *)server_uri, strlen(server_uri), &uri) == -1) {
ESP_LOGE(TAG, "CoAP server uri error");
break;
}
if (uri.scheme == COAP_URI_SCHEME_COAPS && !coap_dtls_is_supported()) {
ESP_LOGE(TAG, "MbedTLS (D)TLS Client Mode not configured");
break;
}
if (uri.scheme == COAP_URI_SCHEME_COAPS_TCP && !coap_tls_is_supported()) {
ESP_LOGE(TAG, "CoAP server uri coaps+tcp:// scheme is not supported");
break;
}
// 查询URI中远程主机地址
phostname = (char *)calloc(1, uri.host.length + 1);
if (phostname == NULL) {
ESP_LOGE(TAG, "calloc failed");
break;
}
// 分配内存
memcpy(phostname, uri.host.s, uri.host.length);
hp = gethostbyname(phostname);
free(phostname);
if (hp == NULL) {
ESP_LOGE(TAG, "DNS lookup failed");
vTaskDelay(1000 / portTICK_PERIOD_MS);
free(phostname);
continue;
}
char tmpbuf[INET6_ADDRSTRLEN];
// 初始化并配置地址
coap_address_init(&dst_addr);
switch (hp->h_addrtype) {
case AF_INET:
dst_addr.addr.sin.sin_family = AF_INET;
dst_addr.addr.sin.sin_port = htons(uri.port);
memcpy(&dst_addr.addr.sin.sin_addr, hp->h_addr, sizeof(dst_addr.addr.sin.sin_addr));
inet_ntop(AF_INET, &dst_addr.addr.sin.sin_addr, tmpbuf, sizeof(tmpbuf));
ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", tmpbuf);
break;
case AF_INET6:
dst_addr.addr.sin6.sin6_family = AF_INET6;
dst_addr.addr.sin6.sin6_port = htons(uri.port);
memcpy(&dst_addr.addr.sin6.sin6_addr, hp->h_addr, sizeof(dst_addr.addr.sin6.sin6_addr));
inet_ntop(AF_INET6, &dst_addr.addr.sin6.sin6_addr, tmpbuf, sizeof(tmpbuf));
ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", tmpbuf);
break;
default:
ESP_LOGE(TAG, "DNS lookup response failed");
goto clean_up;
}
if (uri.path.length) {
buflen = BUFSIZE;
buf = _buf;
// 解析请求路径
res = coap_split_path(uri.path.s, uri.path.length, buf, &buflen);
while (res--) {
coap_insert_optlist(&optlist,
coap_new_optlist(COAP_OPTION_URI_PATH,
coap_opt_length(buf),
coap_opt_value(buf)));
buf += coap_opt_size(buf);
}
}
if (uri.query.length) {
buflen = BUFSIZE;
buf = _buf;
// 解析查询
res = coap_split_query(uri.query.s, uri.query.length, buf, &buflen);
while (res--) {
// 安装查询
coap_insert_optlist(&optlist,
coap_new_optlist(COAP_OPTION_URI_QUERY,
coap_opt_length(buf),
coap_opt_value(buf)));
buf += coap_opt_size(buf);
}
}
// 创建context
ctx = coap_new_context(NULL);
if (!ctx) {
ESP_LOGE(TAG, "coap_new_context() failed");
goto clean_up;
}
if (uri.scheme == COAP_URI_SCHEME_COAPS || uri.scheme == COAP_URI_SCHEME_COAPS_TCP) {
#ifndef CONFIG_MBEDTLS_TLS_CLIENT
ESP_LOGE(TAG, "MbedTLS (D)TLS Client Mode not configured");
goto clean_up;
#endif /* CONFIG_MBEDTLS_TLS_CLIENT */
#ifdef CONFIG_COAP_MBEDTLS_PSK
session = coap_new_client_session_psk(ctx, NULL, &dst_addr,
uri.scheme == COAP_URI_SCHEME_COAPS ? COAP_PROTO_DTLS : COAP_PROTO_TLS,
EXAMPLE_COAP_PSK_IDENTITY,
(const uint8_t *)EXAMPLE_COAP_PSK_KEY,
sizeof(EXAMPLE_COAP_PSK_KEY) - 1);
#endif /* CONFIG_COAP_MBEDTLS_PSK */
#ifdef CONFIG_COAP_MBEDTLS_PKI
unsigned int ca_pem_bytes = ca_pem_end - ca_pem_start;
unsigned int client_crt_bytes = client_crt_end - client_crt_start;
unsigned int client_key_bytes = client_key_end - client_key_start;
coap_dtls_pki_t dtls_pki;
static char client_sni[256];
memset (&dtls_pki, 0, sizeof(dtls_pki));
dtls_pki.version = COAP_DTLS_PKI_SETUP_VERSION;
if (ca_pem_bytes) {
dtls_pki.verify_peer_cert = 1;
dtls_pki.require_peer_cert = 1;
dtls_pki.allow_self_signed = 1;
dtls_pki.allow_expired_certs = 1;
dtls_pki.cert_chain_validation = 1;
dtls_pki.cert_chain_verify_depth = 2;
dtls_pki.check_cert_revocation = 1;
dtls_pki.allow_no_crl = 1;
dtls_pki.allow_expired_crl = 1;
dtls_pki.allow_bad_md_hash = 1;
dtls_pki.allow_short_rsa_length = 1;
dtls_pki.validate_cn_call_back = verify_cn_callback;
dtls_pki.cn_call_back_arg = NULL;
dtls_pki.validate_sni_call_back = NULL;
dtls_pki.sni_call_back_arg = NULL;
memset(client_sni, 0, sizeof(client_sni));
if (uri.host.length) {
memcpy(client_sni, uri.host.s, MIN(uri.host.length, sizeof(client_sni)));
} else {
memcpy(client_sni, "localhost", 9);
}
dtls_pki.client_sni = client_sni;
}
dtls_pki.pki_key.key_type = COAP_PKI_KEY_PEM_BUF;
dtls_pki.pki_key.key.pem_buf.public_cert = client_crt_start;
dtls_pki.pki_key.key.pem_buf.public_cert_len = client_crt_bytes;
dtls_pki.pki_key.key.pem_buf.private_key = client_key_start;
dtls_pki.pki_key.key.pem_buf.private_key_len = client_key_bytes;
dtls_pki.pki_key.key.pem_buf.ca_cert = ca_pem_start;
dtls_pki.pki_key.key.pem_buf.ca_cert_len = ca_pem_bytes;
session = coap_new_client_session_pki(ctx, NULL, &dst_addr,
uri.scheme == COAP_URI_SCHEME_COAPS ? COAP_PROTO_DTLS : COAP_PROTO_TLS,
&dtls_pki);
#endif /* CONFIG_COAP_MBEDTLS_PKI */
} else {
// 创建session
session = coap_new_client_session(ctx, NULL, &dst_addr,
uri.scheme == COAP_URI_SCHEME_COAP_TCP ? COAP_PROTO_TCP :
COAP_PROTO_UDP);
}
if (!session) {
ESP_LOGE(TAG, "coap_new_client_session() failed");
goto clean_up;
}
// 绑定消息响应处理函数
coap_register_response_handler(ctx, monitor_message_handler);
// 根据session内容创建请求
request = coap_new_pdu(session);
if (!request) {
ESP_LOGE(TAG, "coap_new_pdu() failed");
goto clean_up;
}
request->type = COAP_MESSAGE_CON;
request->tid = coap_new_message_id(session);
request->code = COAP_REQUEST_GET; // 订阅服务器资源,发送GET请求
// 添加observe选项,开启观察者模式
uint8_t obs_buf[4];
coap_insert_optlist(&optlist,
coap_new_optlist(COAP_OPTION_OBSERVE,
coap_encode_var_safe(obs_buf, sizeof(obs_buf),
COAP_OBSERVE_ESTABLISH), obs_buf)
);
coap_add_optlist_pdu(request, &optlist);
// 发送请求
coap_send(session, request);
// 死循环检测当前context,服务器资源更新后立刻响应
while (1) {
coap_run_once(ctx, 100);
// sleep(2);
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
clean_up:
if (optlist) {
coap_delete_optlist(optlist);
optlist = NULL;
}
if (session) {
coap_session_release(session);
}
if (ctx) {
coap_free_context(ctx);
}
coap_cleanup();
break;
}
vTaskDelete(NULL);
}
void app_main(void)
{
ESP_ERROR_CHECK(nvs_flash_init() );
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(example_connect());
// 启动监控open任务
xTaskCreate(monitor_open, "monitor_open", 8 * 1024, NULL, 5, NULL);
}
八、总结
经过测试,本方案可行,通过引入RabbitMQ,提高了系统的可用性。
在本文中,RabbitMQ 扮演了非常重要的角色,它被用于实现服务间的异步通信和解耦,处理设备发送的数据,调度和执行定时任务,提高系统的可靠性和容错性。而 Californium 作为 CoAP 的 Java 实现,也提供了丰富的功能,为物联网设备提供了 REST 操作和消息观察。
libcoap客户端
copper客户端