热门标签 | 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;



推荐阅读
  • 自定义RecyclerView添加EmptyView
    你知道RecyclerView里没有Em ... [详细]
  • 找出字符串中重复字符
    2019独角兽企业重金招聘Python工程师标准packagejavaBasic;importjava.util.HashMap;importjava.util.Map; ... [详细]
  • 1.什么是hashcode方法?hashcode方法返回对象的哈希码值在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有改变& ... [详细]
  • 1.File类:文件和目录路径名的抽象表现形式2.创建对象:File(Stringpathname)通过给定的路径创建文件对象File(Stringpa ... [详细]
  • 1.背景java.util.concurrent.atomic这个包是非常实用,解决了我们以前自己写一个同步方法来实现类似于自增长字段的问题。在Java语言中,增量操作符(++)不是原子的, ... [详细]
  • 在实际开发中,现在安卓端和后台之间的数据交互,一般都是用JSON来传递数据信息。JSON大家一般都比较熟悉。我这边就以实际项目中的后台传过来的情况和大家分析下及如何处理。比如后台返 ... [详细]
  • Android性能优化检测App卡顿
    在移动APP性能评测-流畅度评测中,我们介绍了如何准确客观评价APP的流畅度,最终采用SM指标来评价应用的流畅度,在知道如何评价流畅度之后 ... [详细]
  • 我正在使用数组列表通过构建一个交互式菜单供用户选择来存储来自用户输入的值。到目前为止,我的两个选择是为用户提供向列表输入数据和读取列表的全部内容。到目前为止,我创建的代码由两个类组成。 ... [详细]
  • IDEA实用插件Lombok
    LombokLombok是一个可以通过简单的注解形式来帮助我们简化消除一些必须有但显得很臃肿的Java代码的工具,通过使用对应的注解,可以在编译源码的时候生成对应的方法。通常,我们所定义的对象和b ... [详细]
  • C模板实现的单向链表,实现了链表的初始化创建,元素插入,元素链表末尾添加,元素删除,链表清空Lists.h# ... [详细]
  • Flex中使用filter过滤数据 ... [详细]
  • 编译原理c语言词法分析器,用C语言实现一个真正的词法分析器
    词法分析,是编译器的第一个模块,也是最简单的模块。最简单,指的是相对于编译器这种大型程序而言,与一般的代码相比还是有点复杂的 ... [详细]
  • 编译linux搭建vs2015,使用Vs2015开发linux(centos7)程序
    1.首先下载vs2015withupdate32.然后下载VisualCforLinuxDevelopment3.在centos7上yuminstallopenssh-server ... [详细]
  • 在ROS系统中,参数读写一般通过xml或者yaml格式的文件,其中yaml用得比较多。这是一种可读性高,轻量级的标记语言,简单好用。对于yaml文件,ros中用的较早版本的yaml- ... [详细]
  • 本文分析HashMap的实现原理。数据结构(散列表)HashMap是一个散列表(也叫哈希表),用来存储键值对( ... [详细]
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社区 版权所有