物聯網協議之CoAP協議開發學習筆記之Californium開源框架分析(入門)

哪有什麼天生如此,只是咱們每天堅持。 -Zhiyuanhtml

之前我已經總結了CoAP協議的基礎理論知識。沒看過的朋友能夠出門左轉看個人文章git

關於CoAP 協議有不少開源代碼實現:你們能夠參考個人文章選擇本身最適合的:
https://segmentfault.com/a/11...github

Californium

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一個個啓動。至此,服務端算是啓動成功了。讓咱們稍微總結一下幾個類的關係函數

clipboard.png

如上圖,消息會從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類

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());
}

從構造方法能夠了解到,其內部結構以下所示:

clipboard.png
那麼,也就是說客戶端發起的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協議棧)進行分析,看看他們接收到請求後作了什麼處理。

MessageInterceptor接口

clipboard.png

框架自己並無提供該接口的任何實現類,咱們能夠根據業務需求實現該接口,並經過CoapEndpoint.addInterceptor(MessageInterceptor interceptor)方法添加具體的實現類。

Matcher類

clipboard.png

咱們主要看receiveRequest(Request request)方法,看它對客戶端的GET請求作了哪些操做:

public Exchange receiveRequest(Request request) {

    // 根據Request請求,填充並返回Exchange對象
    ...

}

CoapStack類

clipboard.png

CoapStack的類圖比較複雜,其結構能夠簡化爲下圖:

clipboard.png

有人可能會疑惑,這個結構圖是怎麼來,答案就在構造方法裏:

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的:

clipboard.png

MessageDeliverer接口

clipboard.png

框架有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本身處理請求)

Resource接口

clipboard.png

還記得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));
    
}

經過一張響應消息流程圖來回顧一下,一個服務端響應最終是如何傳輸到網絡裏去:

clipboard.png

總結

經過服務端的建立和啓動,客戶端發起GET請求,服務端接收請求並返回響應流程,咱們對Californium框架有了一個總體的瞭解。俗話說,師父領進門,修行看我的。在分析這個流程的過程當中,我省略了不少的細節,意在讓你們對框架有個概念上的理解,在之後二次開發或定位問題時更能抓住重點,着重針對某個模塊。最後,也不得不讚嘆一下這款開源框架代碼邏輯清晰,模塊職責劃分明確,靈活地使用設計模式,很是值得咱們學習!

RFC7252-《受限應用協議》中文版.md
californium 框架設計分析

若是對你有幫助,點贊收藏手有餘香!

相關文章
相關標籤/搜索