在物联网项目里,设备端往往只有几十 KB 内存、靠电池供电,如果直接套用 HTTP 协议,一个请求头动辄几百字节,再加上 TCP 三次握手,电量和流量都扛不住。CoAP(Constrained Application Protocol)就是为这种场景而生的,它跑在 UDP 之上,头部固定 4 字节,报文结构紧凑,还内置了消息重传机制来弥补 UDP 的不可靠性。服务端这边如果已经用 Spring Boot 搭建了业务系统,能不能直接把 CoAP 能力整合进去?答案是肯定的,借助 Eclipse Californium 这套成熟的 Java 实现,几行依赖就能跑起来。

CoAP 协议核心机制解析
CoAP 在设计上刻意对标 HTTP,采用了类似的请求/响应模型,也支持 GET、POST、PUT、DELETE 四个方法,但它把整套协议压缩到了 UDP 传输层之上。一个最基础的 CoAP 报文头部只有 4 字节:版本号 2 位、报文类型 2 位、TKL 长度 4 位、请求码 8 位、消息 ID 16 位。对比 HTTP 动辄几百字节的头部,这在 NB-IoT 这类按流量计费的网络里能省下不少成本。
CoAP 的消息模型分为两层:消息层和请求响应层。消息层定义了四种报文类型,其中 CON(Confirmable)类型需要接收方返回 ACK 确认,如果发送方在超时时间内没收到 ACK,会按指数退避策略重传,默认最多重传 4 次;NON(Non-confirmable)类型则是发出去就不管了,适合传感器周期性上报这类允许少量丢包的场景。请求响应层则负责承载方法、URI 路径和选项字段。
另外两个值得关注的特性是 Observe 观察机制和资源发现。Observe 机制类似一个轻量的发布订阅:客户端在 GET 请求中带上 Observe 选项,服务端就把这个客户端登记为观察者,后续资源状态变化时主动推送通知,避免客户端反复轮询。资源发现则是访问 /.well-known/core 路径,服务端会返回一份 Link Format 格式的资源列表,客户端可以据此自动发现可用资源。
Spring Boot 整合 Californium 搭建 CoAP 服务端
Eclipse Californium 是 CoAP 协议在 Java 生态里的事实标准实现,Embbedded 设备端常用的也是它的同门项目 Cf-embedded。在 Spring Boot 中整合它的思路很简单:引入依赖,然后通过配置类把 CoapServer 的生命周期交给 Spring 容器管理。
先在 pom.xml 中加入依赖,Californium 核心包和 californium-proxy 按需选择即可,这里只需要核心包:
<dependency>
<groupId>org.eclipse.californium</groupId>
<artifactId;californium-core</artifactId>
<version>3.9.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.californium</groupId>
<artifactId>element-connector</artifactId>
<version>3.9.0</version>
</dependency>注意上面代码块中第二个依赖的 artifactId 标签故意展示了转义写法,实际编写时请使用正确的 <artifactId> 标签闭合。接下来定义一个 CoAP 资源类,继承 CoapResource 并重写 handleGET 和 handlePOST 方法:
public class TemperatureResource extends CoapResource {
private final DeviceDataService deviceDataService;
public TemperatureResource(DeviceDataService service) {
// 资源名会映射为 URI 路径 /sensor/temperature
super("temperature");
getAttributes().setTitle("温度传感器资源");
// 声明该资源可被观察
setObservable(true);
setObserveType(Type.CON);
this.deviceDataService = service;
}
@Override
public void handleGET(CoapExchange exchange) {
String deviceId = exchange.getQueryParameter("deviceId");
String value = deviceDataService.readTemperature(deviceId);
exchange.respond(CoAP.ResponseCode.CONTENT,
value, MediaTypeRegistry.APPLICATION_JSON);
}
@Override
public void handlePOST(CoapExchange exchange) {
String payload = exchange.getRequestText();
deviceDataService.saveReport(payload);
exchange.respond(CoAP.ResponseCode.CREATED);
}
// 主动向所有观察者推送最新数据
public void pushToObservers(String data) {
changed(data);
}
}然后把 CoapServer 注册成 Spring Bean,监听默认的 5683 端口。为了配合 Spring Boot 的生命周期,实现 SmartLifecycle 接口是个干净的做法:
@Configuration
public class CoapServerConfig implements SmartLifecycle {
private CoapServer coapServer;
private boolean running = false;
@Autowired
private DeviceDataService deviceDataService;
@Override
public void start() {
coapServer = new CoapServer(5683);
// 资源树:根下挂 sensor,sensor 下挂 temperature
TemperatureResource temp = new TemperatureResource(deviceDataService);
CoapResource sensorRoot = new CoapResource("sensor");
sensorRoot.add(temp);
coapServer.add(sensorRoot);
coapServer.start();
running = true;
}
@Override
public void stop() {
if (coapServer != null) {
coapServer.stop();
}
running = false;
}
@Override
public boolean isRunning() {
return running;
}
}启动 Spring Boot 应用后,用配套的 Copper 插件(Firefox 浏览器)或者命令行工具 coap-cli 访问 coap://服务器IP:5683/sensor/temperature 就能拿到响应了。这里有个容易踩的坑:CoAP 走 UDP,云服务器安全组除了放行 5683 端口,还要确认防火墙对 UDP 协议放行,很多人只开了 TCP 导致死活连不上。
客户端接入与 Observe 推送实战
服务端搭好后,设备端和网关侧的客户端代码同样基于 Californium 编写。最基础的同步 GET 请求写法如下:
CoapClient client = new CoapClient("coap://192.168.0.1:5683/sensor/temperature");
// CON 模式请求,阻塞等待响应
CoapResponse response = client.get();
if (response != null && response.isSuccess()) {
System.out.println("响应码: " + response.getCode());
System.out.println("内容: " + response.getResponseText());
}
client.shutdown();更符合物联网场景的用法是 Observe 订阅。客户端发送一个带 Observe 选项的 GET,服务端返回第一个响应后连接保持打开,之后每次资源调用 changed() 方法都会触发推送。客户端通过实现 CoapHandler 回调来接收通知:
CoapClient client = new CoapClient("coap://192.168.0.1:5683/sensor/temperature");
Request request = Request.newGet().setURI(client.getURI());
request.setObserve();
ObserveRelation relation = client.observe(request, new CoapHandler() {
@Override
public void onLoad(CoapResponse response) {
// 首次响应和后续推送都会走到这里
String text = response.getResponseText();
System.out.println("收到数据: " + text);
if (response.getOptions().hasObserve()) {
System.out.println("这是第 " + response.getOptions().getObserve() + " 次通知");
}
}
@Override
public void onError() {
System.err.println("观察关系断开,准备重连");
}
});Observe 的通知序号(Observe option 值)是递增的,客户端可以据此判断通知是否乱序或重复,如果发现序号回绕异常,应当主动取消当前关系并重新发起订阅,这是官方 RFC 7641 规定的行为。
可靠性、安全性与生产环境注意事项
UDP 天生不可靠,CoAP 虽然在协议层用 CON 重传做了补偿,但业务侧还是要根据数据特性选择合适的消息类型。传感器周期上报建议用 NON,丢一两条无所谓,换来的收益是不用等 ACK、设备可以更快进入休眠;控制指令下发、固件升级触发这类不容有失的操作必须用 CON,必要时配合应用层的去重逻辑,因为 CoAP 的重传可能导致同一请求被业务处理两次。
安全方面,CoAP 定义了 DTLS 作为传输层加密方案,Californium 对 DTLS 1.2 支持完善。开启 DTLS 后端口一般换成 5684,需要配置证书和密钥:
DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder()
.setAddress(new InetSocketAddress(5684))
.setIdentity(privateKey, certificateChain, CertificateType.RAW_PUBLIC_KEY)
.setTrustStore(trustedCerts)
.setClientAuthenticationRequired(true);
DTLSConnector dtlsConnector = new DTLSConnector(builder.build());
CoapEndpoint.Builder endpointBuilder = new CoapEndpoint.Builder();
endpointBuilder.setConnector(dtlsConnector);
coapServer.addEndpoint(endpointBuilder.build());除了加密认证,还有几个生产环境的关键点。第一,消息去重:Californium 内部基于 Message ID 维护了去重窗口,默认足够用,但如果网关侧做了 NAT 转换,要留意 Message ID 冲突导致的响应被误判为重复。第二,心跳与休眠协调:低功耗设备通常采用深度休眠加定时唤醒的策略,服务端不应该假设连接长期存在,观察关系的过期时间要设置合理值,避免观察者列表越积越多。第三,网关架构:如果系统同时需要对接 HTTP 业务端,可以在网关层做 CoAP 到 HTTP 的协议转换,Californium 官方的 californium-proxy 模块提供了现成的翻译能力,把设备数据桥接到已有的 REST 接口上。
整体来看,Spring Boot 加 Californium 的组合在服务端开发效率和协议完整度上都表现不错,核心 API 学习成本低,一个下午就能搭出原型。真正的难点在于针对具体设备类型做功耗与可靠性的取舍,这需要结合设备的供电方式、网络制式和数据价值来综合判断,建议在项目初期就明确每种报文的 QoS 策略并写入设备接入规范,避免后期返工。
Spring BootCoAP协议物联网通信修改时间:2026-09-06 12:19:11