哪有什麼天生如此,只是咱們每天堅持。 -Zhiyuanhtml
之前我已經總結了CoAP協議的基礎理論知識。沒看過的朋友能夠出門左轉看個人文章git
關於CoAP 協議有不少開源代碼實現:你們能夠參考個人文章選擇本身最適合的:
https://segmentfault.com/a/11...github
Let's go!segmentfault
引入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 != null) { this.config = config; } else { this.config = NetworkConfig.getStandard(); } // 初始化Resource this.root = createRoot(); // 初始化MessageDeliverer this.deliverer = new ServerMessageDeliverer(root); CoapResource wellKnown = new CoapResource(".well-known"); wellKnown.setVisible(false); wellKnown.add(new DiscoveryResource(root)); root.add(wellKnown); // 初始化EndPoints this.endpoints = new ArrayList<>(); // 初始化線程池 this.executor = Executors.newScheduledThreadPool(this.config.getInt(NetworkConfig.Keys.PROTOCOL_STAGE_THREAD_COUNT), new NamedThreadFactory("CoapServer#")); // 添加Endpoint for (int port : ports) { addEndpoint(new CoapEndpoint(port, this.config)); } }
構造方法初始化了一些成員變量。其中,Endpoint負責與網絡進行通訊,MessageDeliverer
負責分發請求,Resource
負責處理請求。接着讓咱們看看啓動方法start()
又作了哪些事:ide
public void start() { // 若是沒有一個Endpoint與CoapServer進行綁定,那就建立一個默認的Endpoint ... // 一個一個地將Endpoint啓動 int started = 0; for (Endpoint ep:endpoints) { try { ep.start(); ++started; } catch (IOException e) { LOGGER.log(Level.SEVERE, "Cannot start server endpoint [" + ep.getAddress() + "]", e); } } if (started==0) { throw new IllegalStateException("None of the server endpoints could be started"); } }
啓動方法很簡單,主要是將全部的Endpoint一個個啓動。至此,服務端算是啓動成功了。讓咱們稍微總結一下幾個類的關係函數
如上圖,消息會從Network模塊
傳輸給對應的Endpoint節點
,全部的Endpoint節點都會將消息推給MessageDeliverer,MessageDeliverer
根據消息的內容傳輸給指定的Resource,Resource
再對消息內容進行處理。學習
接下來,將讓咱們再模擬一個客戶端發起一個GET請求,看看服務端是如何接收和處理的吧!客戶端代碼以下:
public static void main(String[] args) throws URISyntaxException { // 肯定請求路徑 URI uri = new URI("127.0.0.1"); // 建立客戶端 CoapClient client = new CoapClient(uri); // 發起一個GET請求 client.get(); }
經過前面分析,咱們知道Endpoint是直接與網絡進行交互的,那麼客戶端發起的GET請求,應該在服務端的Endpoint中收到。框架中Endpoint接口的實現類只有CoapEndpoint
,讓咱們深刻了解一下CoapEndpoint的內部實現,看看它是如何接收和處理請求的。
CoapEndpoint類實現了Endpoint接口,其構造方法以下:
public CoapEndpoint(Connector connector, NetworkConfig config, ObservationStore store) { this.config = config; this.connector = connector; if (store == null) { this.matcher = new Matcher(config, new NotificationDispatcher(), new InMemoryObservationStore()); } else { this.matcher = new Matcher(config, new NotificationDispatcher(), store); } this.coapstack = new CoapStack(config, new OutboxImpl()); this.connector.setRawDataReceiver(new InboxImpl()); }
從構造方法能夠了解到,其內部結構以下所示:
那麼,也就是說客戶端發起的GET請求將被InboxImpl類接收。InboxImpl
類實現了RawDataChannel
接口,該接口只有一個receiveData(RawData raw)
方法,InboxImpl類的該方法以下:
public void receiveData(final RawData raw) { // 參數校驗 ... // 啓動線程處理收到的消息 runInProtocolStage(new Runnable() { @Override public void run() { receiveMessage(raw); } }); }
再往receiveMessage(RawData raw)
方法裏看:
private void receiveMessage(final RawData raw) { // 解析數據源 DataParser parser = new DataParser(raw.getBytes()); // 若是是請求數據 if (parser.isRequest()) { // 一些非關鍵操做 ... // 消息攔截器接收請求 for (MessageInterceptor interceptor:interceptors) { interceptor.receiveRequest(request); } // 匹配器接收請求,並返回Exchange對象 Exchange exchange = matcher.receiveRequest(request); // Coap協議棧接收請求 coapstack.receiveRequest(exchange, request); } // 若是是響應數據,則與請求數據同樣,分別由消息攔截器、匹配器、Coap協議棧接收響應 ... // 若是是空數據,則與請求數據、響應數據同樣,分別由消息攔截器、匹配器、Coap協議棧接收空數據 ... // 一些非關鍵操做 ... }
接下來,咱們分別對MessageInterceptor
(消息攔截器)、Matcher
(匹配器)、CoapStack
(Coap協議棧)進行分析,看看他們接收到請求後作了什麼處理。
框架自己並無提供該接口的任何實現類,咱們能夠根據業務需求實現該接口,並經過CoapEndpoint.addInterceptor(MessageInterceptor interceptor)
方法添加具體的實現類。
】
咱們主要看receiveRequest(Request request)
方法,看它對客戶端的GET請求作了哪些操做:
public Exchange receiveRequest(Request request) { // 根據Request請求,填充並返回Exchange對象 ... }
CoapStack的類圖比較複雜,其結構能夠簡化爲下圖:
有人可能會疑惑,這個結構圖是怎麼來,答案就在構造方法裏:
public CoapStack(NetworkConfig config, Outbox outbox) { // 初始化棧頂 this.top = new StackTopAdapter(); // 初始化棧底 this.bottom = new StackBottomAdapter(); // 初始化出口 this.outbox = outbox; // 初始化ReliabilityLayer ... // 初始化層級 this.layers = new Layer.TopDownBuilder() .add(top) .add(new ObserveLayer(config)) .add(new BlockwiseLayer(config)) .add(reliabilityLayer) .add(bottom) .create(); }
迴歸正題,繼續看CoapStack.receiveRequest(Exchange exchange, Request request)
方法是怎麼處理客戶端的GET請求:
public void receiveRequest(Exchange exchange, Request request) { bottom.receiveRequest(exchange, request); }
CoapStack在收到請求後,交給了StackBottomAdapter去處理,StackBottomAdapter處理完後就會依次向上傳遞給ReliabilityLayer、BlockwiseLayer、ObserveLayer,最終傳遞給StackTopAdapter。中間的處理細節就不詳述了,直接看StackTopAdapter.receiveRequest(Exchange exchange, Request request)
方法:
public void receiveRequest(Exchange exchange, Request request) { // 一些非關鍵操做 ... // 將請求傳遞給消息分發器 deliverer.deliverRequest(exchange); }
能夠看到,StackTopAdapter最後會將請求傳遞給MessageDeliverer,至此CoapEndpoint的任務也就算完成了,咱們能夠經過一張請求消息流程圖來回顧一下,一個客戶端GET請求最終是如何到達MessageDeliverer的:
框架有ServerMessageDeliverer和ClientMessageDeliverer兩個實現類。從CoapServer的構造方法裏知道使用的是ServerMessageDeliverer類。那麼就讓咱們看看ServerMessageDeliverer.deliverRequest(Exchange exchange)方法是如何分發GET請求的:
public void deliverRequest(final Exchange exchange) { // 從exchange裏獲取request Request request = exchange.getRequest(); // 從request裏獲取請求路徑 List<String> path = request.getOptions().getUriPath(); // 找出請求路徑對應的Resource final Resource resource = findResource(path); // 一些非關鍵操做 ... // 由Resource來真正地處理請求 resource.handleRequest(exchange); // 一些非關鍵操做 ... }
當MessageDeliverer找到Request請求對應的Resource資源後,就會交由Resource資源來處理請求。(是否是很像Spring MVC中的DispatcherServlet,它也負責分發請求給對應的Controller,再由Controller本身處理請求)
還記得CoapServer構造方法裏建立了一個RootResource嗎?它的資源路徑爲空,而客戶端發起的GET請求默認也是空路徑。那麼ServerMessageDeliverer就會把請求分發給RootResource處理。RootResource類沒有覆寫handleRequest(Exchange exchange)
方法,因此咱們看看CoapResource父類的實現:
public void handleRequest(final Exchange exchange) { Code code = 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請求,那麼將會進入到RootResource.handleGET(CoapExchange exchange)
方法:
public void handleGET(CoapExchange exchange) { // 由CoapExchange返回響應 exchange.respond(ResponseCode.CONTENT, msg); }
再接着看CoapExchange.respond(ResponseCode code, String payload)
方法:
public void respond(ResponseCode code, String payload) { // 生成響應並賦值 Response response = new Response(code); response.setPayload(payload); response.getOptions().setContentFormat(MediaTypeRegistry.TEXT_PLAIN); // 調用同名函數 respond(response); }
看看同名函數裏又作了哪些操做:
public void respond(Response response) { // 參數校驗 ... // 設置Response屬性 ... // 檢查關係 resource.checkObserveRelation(exchange, response); // 由成員變量Exchange發送響應 exchange.sendResponse(response); }
那麼Exchange.sendResponse(Response response)又是如何發送響應的呢?
public void sendResponse(Response response) { // 設置Response屬性 response.setDestination(request.getSource()); response.setDestinationPort(request.getSourcePort()); setResponse(response); // 由Endpoint發送響應 endpoint.sendResponse(this, response); }
原來最終仍是交給了Endpoint去發送響應了啊!以前的GET請求就是從Endpoint中來的。這真是和達康書記同樣,從人民中來,再到人民中去。
在CoapEndpoint類一章節中咱們有介紹它的內部結構。那麼當發送響應的時候,將與以前接收請求相反,先由StackTopAdapter處理、再是依次ObserveLayer、BlockwiseLayer、ReliabilityLayer處理,最後由StackBottomAdapter處理,中間的細節仍是老樣子忽略,讓咱們直接看StackBottomAdapter.sendResponse(Exchange exchange, Response response)
方法:
public void sendResponse(Exchange exchange, Response response) { outbox.sendResponse(exchange, response); }
請求入口是CoapEndpoint.InboxImpl,而響應出口是CoapEndpint.OutboxImpl,簡單明瞭。最後,讓咱們看看OutboxImpl.sendResponse(Exchange exchange, Response response)
吧:
public void sendResponse(Exchange exchange, Response response) { // 一些非關鍵操做 ... // 匹配器發送響應 matcher.sendResponse(exchange, response); // 消息攔截器發送響應 for (MessageInterceptor interceptor:interceptors) { interceptor.sendResponse(response); } // 真正地發送響應到網絡裏 connector.send(Serializer.serialize(response)); }
經過一張響應消息流程圖來回顧一下,一個服務端響應最終是如何傳輸到網絡裏去:
經過服務端的建立和啓動,客戶端發起GET請求,服務端接收請求並返回響應流程,咱們對Californium框架有了一個總體的瞭解。俗話說,師父領進門,修行看我的。在分析這個流程的過程當中,我省略了不少的細節,意在讓你們對框架有個概念上的理解,在之後二次開發或定位問題時更能抓住重點,着重針對某個模塊。最後,也不得不讚嘆一下這款開源框架代碼邏輯清晰,模塊職責劃分明確,靈活地使用設計模式,很是值得咱們學習!
若是對你有幫助,點贊收藏手有餘香!