热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

物联网协议之CoAP协议开发学习笔记之Californium开源框架分析(入门)

哪有什么天生如此,只是我们天天坚持。-Zhiyuan以前我已经总结了CoAP协议的基础理论知识。没看过的朋友可以出门左转看我的文章关于CoAP协议有很多开源代码实现&

哪有什么天生如此,只是我们天天坚持。 -Zhiyuan

以前我已经总结了CoAP协议的基础理论知识。没看过的朋友可以出门左转看我的文章

关于CoAP 协议有很多开源代码实现:大家可以参考我的文章选择自己最适合的:
https://segmentfault.com/a/11...

Californium

Let's go!

引入Californium开源框架的依赖californium-core

启动服务端:

public static void main(String[] args) {// 创建服务端CoapServer server = new CoapServer();// 启动服务端server.start();}

让我们从CoapServer这个类开始,对整个框架进行分析。首先让我们看看构造方法CoapServer()里面做了什么:

public CoapServer(final NetworkConfig config, final int... ports) {// 初始化配置 if (config !&#61; null) {this.config &#61; config;} else {this.config &#61; NetworkConfig.getStandard();}// 初始化Resourcethis.root &#61; createRoot();// 初始化MessageDelivererthis.deliverer &#61; new ServerMessageDeliverer(root);CoapResource wellKnown &#61; new CoapResource(".well-known");wellKnown.setVisible(false);wellKnown.add(new DiscoveryResource(root));root.add(wellKnown);// 初始化EndPointsthis.endpoints &#61; new ArrayList<>();// 初始化线程池this.executor &#61; Executors.newScheduledThreadPool(this.config.getInt(NetworkConfig.Keys.PROTOCOL_STAGE_THREAD_COUNT), new NamedThreadFactory("CoapServer#")); // 添加Endpointfor (int port : ports) {addEndpoint(new CoapEndpoint(port, this.config));}
}

构造方法初始化了一些成员变量。其中&#xff0c;Endpoint负责与网络进行通信&#xff0c;MessageDeliverer负责分发请求&#xff0c;Resource负责处理请求。接着让我们看看启动方法start()又做了哪些事&#xff1a;

public void start() {// 如果没有一个Endpoint与CoapServer进行绑定&#xff0c;那就创建一个默认的Endpoint...// 一个一个地将Endpoint启动int started &#61; 0;for (Endpoint ep:endpoints) {try {ep.start();&#43;&#43;started;} catch (IOException e) {LOGGER.log(Level.SEVERE, "Cannot start server endpoint [" &#43; ep.getAddress() &#43; "]", e);}}if (started&#61;&#61;0) {throw new IllegalStateException("None of the server endpoints could be started");}
}

启动方法很简单&#xff0c;主要是将所有的Endpoint一个个启动。至此&#xff0c;服务端算是启动成功了。让我们稍微总结一下几个类的关系

clipboard.png

如上图&#xff0c;消息会从Network模块传输给对应的Endpoint节点&#xff0c;所有的Endpoint节点都会将消息推给MessageDeliverer&#xff0c;MessageDeliverer根据消息的内容传输给指定的Resource&#xff0c;Resource再对消息内容进行处理。

接下来&#xff0c;将让我们再模拟一个客户端发起一个GET请求&#xff0c;看看服务端是如何接收和处理的吧&#xff01;客户端代码如下&#xff1a;

public static void main(String[] args) throws URISyntaxException {// 确定请求路径URI uri &#61; new URI("127.0.0.1");// 创建客户端CoapClient client &#61; new CoapClient(uri);// 发起一个GET请求client.get();}

通过前面分析&#xff0c;我们知道Endpoint是直接与网络进行交互的&#xff0c;那么客户端发起的GET请求&#xff0c;应该在服务端的Endpoint中收到。框架中Endpoint接口的实现类只有CoapEndpoint&#xff0c;让我们深入了解一下CoapEndpoint的内部实现&#xff0c;看看它是如何接收和处理请求的。

CoapEndpoint类

CoapEndpoint类实现了Endpoint接口&#xff0c;其构造方法如下&#xff1a;

public CoapEndpoint(Connector connector, NetworkConfig config, ObservationStore store) {this.config &#61; config;this.connector &#61; connector;if (store &#61;&#61; null) {this.matcher &#61; new Matcher(config, new NotificationDispatcher(), new InMemoryObservationStore());} else {this.matcher &#61; new Matcher(config, new NotificationDispatcher(), store);}this.coapstack &#61; new CoapStack(config, new OutboxImpl());this.connector.setRawDataReceiver(new InboxImpl());
}

从构造方法可以了解到&#xff0c;其内部结构如下所示&#xff1a;

clipboard.png
那么&#xff0c;也就是说客户端发起的GET请求将被InboxImpl类接收。InboxImpl类实现了RawDataChannel接口&#xff0c;该接口只有一个receiveData(RawData raw)方法&#xff0c;InboxImpl类的该方法如下&#xff1a;

public void receiveData(final RawData raw) {// 参数校验...// 启动线程处理收到的消息runInProtocolStage(new Runnable() {&#64;Overridepublic void run() {receiveMessage(raw);}});}

再往receiveMessage(RawData raw)方法里看&#xff1a;

private void receiveMessage(final RawData raw) {// 解析数据源DataParser parser &#61; new DataParser(raw.getBytes());// 如果是请求数据if (parser.isRequest()) {// 一些非关键操作...// 消息拦截器接收请求for (MessageInterceptor interceptor:interceptors) {interceptor.receiveRequest(request);}// 匹配器接收请求&#xff0c;并返回Exchange对象Exchange exchange &#61; matcher.receiveRequest(request);// Coap协议栈接收请求coapstack.receiveRequest(exchange, request);}// 如果是响应数据&#xff0c;则与请求数据一样&#xff0c;分别由消息拦截器、匹配器、Coap协议栈接收响应...// 如果是空数据&#xff0c;则与请求数据、响应数据一样&#xff0c;分别由消息拦截器、匹配器、Coap协议栈接收空数据...// 一些非关键操作...}

接下来&#xff0c;我们分别对MessageInterceptor&#xff08;消息拦截器&#xff09;、Matcher&#xff08;匹配器&#xff09;、CoapStack&#xff08;Coap协议栈&#xff09;进行分析&#xff0c;看看他们接收到请求后做了什么处理。

MessageInterceptor接口

clipboard.png

框架本身并没有提供该接口的任何实现类&#xff0c;我们可以根据业务需求实现该接口&#xff0c;并通过CoapEndpoint.addInterceptor(MessageInterceptor interceptor)方法添加具体的实现类。

Matcher类

clipboard.png

我们主要看receiveRequest(Request request)方法&#xff0c;看它对客户端的GET请求做了哪些操作&#xff1a;

public Exchange receiveRequest(Request request) {// 根据Request请求&#xff0c;填充并返回Exchange对象...}

CoapStack类

clipboard.png

CoapStack的类图比较复杂&#xff0c;其结构可以简化为下图&#xff1a;

clipboard.png

有人可能会疑惑&#xff0c;这个结构图是怎么来&#xff0c;答案就在构造方法里&#xff1a;

public CoapStack(NetworkConfig config, Outbox outbox) {// 初始化栈顶this.top &#61; new StackTopAdapter();// 初始化栈底this.bottom &#61; new StackBottomAdapter();// 初始化出口this.outbox &#61; outbox;// 初始化ReliabilityLayer...// 初始化层级this.layers &#61; new Layer.TopDownBuilder().add(top).add(new ObserveLayer(config)).add(new BlockwiseLayer(config)).add(reliabilityLayer).add(bottom).create();}

回归正题&#xff0c;继续看CoapStack.receiveRequest(Exchange exchange, Request request)方法是怎么处理客户端的GET请求&#xff1a;

public void receiveRequest(Exchange exchange, Request request) {bottom.receiveRequest(exchange, request);
}

CoapStack在收到请求后&#xff0c;交给了StackBottomAdapter去处理&#xff0c;StackBottomAdapter处理完后就会依次向上传递给ReliabilityLayer、BlockwiseLayer、ObserveLayer&#xff0c;最终传递给StackTopAdapter。中间的处理细节就不详述了&#xff0c;直接看StackTopAdapter.receiveRequest(Exchange exchange, Request request)方法&#xff1a;

public void receiveRequest(Exchange exchange, Request request) {// 一些非关键操作...// 将请求传递给消息分发器deliverer.deliverRequest(exchange);}

可以看到&#xff0c;StackTopAdapter最后会将请求传递给MessageDeliverer&#xff0c;至此CoapEndpoint的任务也就算完成了&#xff0c;我们可以通过一张请求消息流程图来回顾一下&#xff0c;一个客户端GET请求最终是如何到达MessageDeliverer的&#xff1a;

clipboard.png

MessageDeliverer接口

clipboard.png

框架有ServerMessageDeliverer和ClientMessageDeliverer两个实现类。从CoapServer的构造方法里知道使用的是ServerMessageDeliverer类。那么就让我们看看ServerMessageDeliverer.deliverRequest(Exchange exchange)方法是如何分发GET请求的&#xff1a;

public void deliverRequest(final Exchange exchange) {// 从exchange里获取requestRequest request &#61; exchange.getRequest();// 从request里获取请求路径List path &#61; request.getOptions().getUriPath();// 找出请求路径对应的Resourcefinal Resource resource &#61; findResource(path);// 一些非关键操作...// 由Resource来真正地处理请求resource.handleRequest(exchange);// 一些非关键操作...}

当MessageDeliverer找到Request请求对应的Resource资源后&#xff0c;就会交由Resource资源来处理请求。&#xff08;是不是很像Spring MVC中的DispatcherServlet&#xff0c;它也负责分发请求给对应的Controller&#xff0c;再由Controller自己处理请求&#xff09;

Resource接口

clipboard.png

还记得CoapServer构造方法里创建了一个RootResource吗&#xff1f;它的资源路径为空&#xff0c;而客户端发起的GET请求默认也是空路径。那么ServerMessageDeliverer就会把请求分发给RootResource处理。RootResource类没有覆写handleRequest(Exchange exchange)方法&#xff0c;所以我们看看CoapResource父类的实现&#xff1a;

public void handleRequest(final Exchange exchange) {Code code &#61; exchange.getRequest().getCode();switch (code) {case GET: handleGET(new CoapExchange(exchange, this)); break;case POST: handlePOST(new CoapExchange(exchange, this)); break;case PUT: handlePUT(new CoapExchange(exchange, this)); break;case DELETE: handleDELETE(new CoapExchange(exchange, this)); break;}
}

由于我们客户端发起的是GET请求&#xff0c;那么将会进入到RootResource.handleGET(CoapExchange exchange)方法&#xff1a;

public void handleGET(CoapExchange exchange) {// 由CoapExchange返回响应exchange.respond(ResponseCode.CONTENT, msg);
}

再接着看CoapExchange.respond(ResponseCode code, String payload)方法&#xff1a;

public void respond(ResponseCode code, String payload) {// 生成响应并赋值Response response &#61; new Response(code);response.setPayload(payload);response.getOptions().setContentFormat(MediaTypeRegistry.TEXT_PLAIN);// 调用同名函数respond(response);}

看看同名函数里又做了哪些操作&#xff1a;

public void respond(Response response) {// 参数校验...// 设置Response属性...// 检查关系resource.checkObserveRelation(exchange, response);// 由成员变量Exchange发送响应exchange.sendResponse(response);}

那么Exchange.sendResponse(Response response)又是如何发送响应的呢&#xff1f;

public void sendResponse(Response response) {// 设置Response属性response.setDestination(request.getSource());response.setDestinationPort(request.getSourcePort());setResponse(response);// 由Endpoint发送响应endpoint.sendResponse(this, response);}

原来最终还是交给了Endpoint去发送响应了啊&#xff01;之前的GET请求就是从Endpoint中来的。这真是和达康书记一样&#xff0c;从人民中来&#xff0c;再到人民中去。

在CoapEndpoint类一章节中我们有介绍它的内部结构。那么当发送响应的时候&#xff0c;将与之前接收请求相反&#xff0c;先由StackTopAdapter处理、再是依次ObserveLayer、BlockwiseLayer、ReliabilityLayer处理&#xff0c;最后由StackBottomAdapter处理&#xff0c;中间的细节还是老样子忽略&#xff0c;让我们直接看StackBottomAdapter.sendResponse(Exchange exchange, Response response)方法&#xff1a;

public void sendResponse(Exchange exchange, Response response) {outbox.sendResponse(exchange, response);
}

请求入口是CoapEndpoint.InboxImpl&#xff0c;而响应出口是CoapEndpint.OutboxImpl&#xff0c;简单明了。最后&#xff0c;让我们看看OutboxImpl.sendResponse(Exchange exchange, Response response)吧&#xff1a;

public void sendResponse(Exchange exchange, Response response) {// 一些非关键操作...// 匹配器发送响应matcher.sendResponse(exchange, response);// 消息拦截器发送响应for (MessageInterceptor interceptor:interceptors) {interceptor.sendResponse(response);}// 真正地发送响应到网络里connector.send(Serializer.serialize(response));}

通过一张响应消息流程图来回顾一下&#xff0c;一个服务端响应最终是如何传输到网络里去&#xff1a;

clipboard.png

总结

通过服务端的创建和启动&#xff0c;客户端发起GET请求&#xff0c;服务端接收请求并返回响应流程&#xff0c;我们对Californium框架有了一个整体的了解。俗话说&#xff0c;师父领进门&#xff0c;修行看个人。在分析这个流程的过程中&#xff0c;我省略了很多的细节&#xff0c;意在让大家对框架有个概念上的理解&#xff0c;在以后二次开发或定位问题时更能抓住重点&#xff0c;着重针对某个模块。最后&#xff0c;也不得不赞叹一下这款开源框架代码逻辑清晰&#xff0c;模块职责划分明确&#xff0c;灵活地使用设计模式&#xff0c;非常值得我们学习&#xff01;

RFC7252-《受限应用协议》中文版.md
californium 框架设计分析

如果对你有帮助&#xff0c;点赞收藏手有余香&#xff01;



推荐阅读
  • 本文基于Java官方文档进行了适当修改,旨在介绍如何实现一个能够同时处理多个客户端请求的服务端程序。在前文中,我们探讨了单客户端访问的服务端实现,而本篇将深入讲解多客户端环境下的服务端设计与实现。 ... [详细]
  • 本文详细介绍了 `org.apache.tinkerpop.gremlin.structure.VertexProperty` 类中的 `key()` 方法,并提供了多个实际应用的代码示例。通过这些示例,读者可以更好地理解该方法在图数据库操作中的具体用途。 ... [详细]
  • D17:C#设计模式之十六观察者模式(Observer Pattern)【行为型】
    一、引言今天是2017年11月份的最后一天,也就是2017年11月30日,利用今天再写一个模式,争取下个月(也就是12月份& ... [详细]
  • 本文介绍了如何使用Node.js通过两种不同的方法连接MongoDB数据库,包括使用MongoClient对象和连接字符串的方法。每种方法都有其特点和适用场景,适合不同需求的开发者。 ... [详细]
  • Zabbix自定义监控与邮件告警配置实践
    本文详细介绍了如何在Zabbix中添加自定义监控项目,配置邮件告警功能,并解决测试告警时遇到的邮件不发送问题。 ... [详细]
  • td{border:1pxsolid#808080;}参考:和FMX相关的类(表)TFmxObjectIFreeNotification ... [详细]
  • Beetl是一款先进的Java模板引擎,以其丰富的功能、直观的语法、卓越的性能和易于维护的特点著称。它不仅适用于高响应需求的大型网站,也适合功能复杂的CMS管理系统,提供了一种全新的模板开发体验。 ... [详细]
  • 问题场景用Java进行web开发过程当中,当遇到很多很多个字段的实体时,最苦恼的莫过于编辑字段的查看和修改界面,发现2个页面存在很多重复信息,能不能写一遍?有没有轮子用都不如自己造。解决方式笔者根据自 ... [详细]
  • Go语言实现文件读取与终端输出
    本文介绍如何使用Go语言编写程序,通过命令行参数指定文件路径,读取文件内容并将其输出到控制台。代码示例中包含了错误处理和资源管理的最佳实践。 ... [详细]
  • 探讨 try-finally 结构中 finally 块的执行情况
    本文深入分析了 Java 中 try-finally 结构的执行机制,特别是探讨了在不同情况下 finally 块是否会得到执行。 ... [详细]
  • 本文详细介绍了如何使用C#实现不同类型的系统服务账户(如Windows服务、计划任务和IIS应用池)的密码重置方法。 ... [详细]
  • ArcBlock 发布 ABT 节点 1.0.31 版本更新
    2020年11月9日,ArcBlock 区块链基础平台发布了 ABT 节点开发平台的1.0.31版本更新,此次更新带来了多项功能增强与性能优化。 ... [详细]
  • 1、编写一个Java程序在屏幕上输出“你好!”。programmenameHelloworld.javapublicclassHelloworld{publicst ... [详细]
  • 在1995年,Simon Plouffe 发现了一种特殊的求和方法来表示某些常数。两年后,Bailey 和 Borwein 在他们的论文中发表了这一发现,这种方法被命名为 Bailey-Borwein-Plouffe (BBP) 公式。该问题要求计算圆周率 π 的第 n 个十六进制数字。 ... [详细]
  • 本文将从基础概念入手,详细探讨SpringMVC框架中DispatcherServlet如何通过HandlerMapping进行请求分发,以及其背后的源码实现细节。 ... [详细]
author-avatar
漂亮女人群
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有