用OkHttp實現WebSocket長鏈接

點擊上方 藍字 關注我,❤git


今天給你們帶來一篇老文章,介紹WebSocket,你們能夠了解了解。github

前言

最近老闆又來新需求了,要作一個物聯網相關的app,其中有個需求是客戶端須要收發服務器不按期發出的消息。
心裏OS:
🤔 這咋整呢?經過接口輪詢?定時訪問接口,有數據就更新?
🤔 不行不行,這樣浪費資源了,還耗電,會致使不少請求都是無效的網絡操做。
🤔 那就長鏈接唄?WebSocket協議好像不錯,經過握手創建長鏈接後,能夠隨時收發服務器的消息。那就它了!
🤔 怎麼集成呢?正好前段時間複習OkHttp源碼的時候發現了它是支持Websocket協議的,那就用它試試吧!web

開淦!面試

WebSocket介紹

先簡單介紹下WebSocket。咱們都知道Http是處於應用層的一個通訊協議,可是隻支持單向主動通訊,作不到服務器主動向客戶端推送消息。並且Http是無狀態的,即每次通訊都沒有關聯性,致使跟服務器關係不緊密。服務器

爲了解決和服務器長時間通訊的痛點呢,HTML5規範引出了WebSocket協議(知道這名字咋來的吧,人家HTML5規範引出的,隨爸姓),是一種創建在TCP協議基礎上的全雙工通訊的協議。他跟Http同屬於應用層協議,下層仍是須要經過TCP創建鏈接。微信

可是,WebSocketTCP鏈接創建後,還要經過Http進行一次握手,也就是經過Http發送一條GET請求消息給服務器,告訴服務器我要創建WebSocket鏈接了,你準備好哦,具體作法就是在頭部信息中添加相關參數。而後服務器響應我知道了,而且將鏈接協議改爲WebSocket,開始創建長鏈接。websocket

這裏貼上請求頭和響應頭信息,從網上找了一張圖:網絡

3851594110877_.pic.jpg

簡單說明下參數:app

  • URL通常是以 ws或者 wss開頭, ws對應 Websocket協議, wss對應在 TLS之上的 WebSocket。相似於 HttpHttps的關係。
  • 請求方法爲GET方法。
  • Connection:Upgrade,表示客戶端要鏈接升級,不用Http協議。
  • Upgrade:websocket, 表示客戶端要升級創建 Websocket鏈接。
  • Sec-Websocket-Key:key,  這個key是隨機生成的,服務器會經過這個參數驗證該請求是否有效。
  • Sec-WebSocket-Version:13,  websocket使用的協議,通常就是13。
  • Sec-webSocket-Extension:permessage-deflate,客戶端指定的一些擴展協議,好比這裏 permessage-deflate就是 WebSocket的一種壓縮協議。
  • 響應碼101,表示響應協議升級,後續的數據交互都按照Upgradet指定的 WebSocket協議來。

OkHttp實現

添加OkHttp依賴

    implementation("com.squareup.okhttp3:okhttp:4.7.2")

實現代碼

首先是初始化OkHttpClientWebSocket實例:socket

    /**
     * 初始化WebSocket
     */

    public void init() {
        mWbSocketUrl = "ws://echo.websocket.org";
        mClient = new OkHttpClient.Builder()
                .pingInterval(10, TimeUnit.SECONDS)
                .build();
        Request request = new Request.Builder()
                .url(mWbSocketUrl)
                .build();
        mWebSocket = mClient.newWebSocket(request, new WsListener());
    }

這裏主要是配置了OkHttp的一些參數,以及WebSocket的鏈接地址。其中newWebSocket方法就是進行WebSocket的初始化和鏈接。

這裏要注意的點是pingInterval方法的配置,這個方法主要是用來設置WebSocket鏈接的保活。相信作過長鏈接的同窗都知道,一個長鏈接通常要隔幾秒發送一條消息告訴服務器我在線,而服務器也會回覆一個消息表示收到了,這樣就確認了鏈接正常,客戶端和服務器端都在線。

若是服務器沒有按時收到這個消息那麼服務器可能就會主動關閉這個鏈接,節約資源。客戶端沒有正常收到這個返回的消息,也會作一些相似重連的操做,因此這個保活消息很是重要。

咱們稱這個消息叫做心跳包,通常用PING,PONG表示,像乒乓球同樣,一來一回。因此這裏的pingInterval就是設置心跳包發送的間隔時間,設置了這個方法以後,OkHttp就會自動幫咱們發送心跳包事件,也就是ping包。當間隔時間到了,沒有收到pong包的話,監聽事件中的onFailure方法就會被調用,此時咱們就能夠進行重連。

可是因爲實際業務需求不同,以及okhttp中心跳包事件給予咱們權限較少,因此咱們也能夠本身完成心跳包事件,即在WebSocket鏈接成功以後,開始定時發送ping包,在下一次發送ping包以前檢查上一個pong包是否收到,若是沒收到,就視爲異常,開始重連。感興趣的同窗能夠看看文末的相關源碼。

創建鏈接後,咱們就能夠正常發送和讀取消息了,也就是在上文WsListener監聽事件中表現:

    //監聽事件,用於收消息,監聽鏈接的狀態
    class WsListener extends WebSocketListener {
        @Override
        public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
            super.onClosed(webSocket, code, reason);
        }

        @Override
        public void onClosing(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
            super.onClosing(webSocket, code, reason);
        }

        @Override
        public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable t, @Nullable Response response) {
            super.onFailure(webSocket, t, response);
        }

        @Override
        public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) {
            super.onMessage(webSocket, text);
            Log.e(TAG, "客戶端收到消息:" + text);
            onWSDataChanged(DATE_NORMAL, text);
           //測試發消息
            webSocket.send("我是客戶端,你好啊");
        }

        @Override
        public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString bytes) {
            super.onMessage(webSocket, bytes);
        }

        @Override
        public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) {
            super.onOpen(webSocket, response);
            Log.e(TAG,"鏈接成功!");
        }
    }
    
    
    //發送String消息
    public void send(final String message) {
        if (mWebSocket != null) {
            mWebSocket.send(message);
        }
    }
    
    /**
     * 發送byte消息
     * @param message
     */

    public void send(final ByteString message) {
        if (mWebSocket != null) {
            mWebSocket.send(message);
        }
    }    

    //主動斷開鏈接
    public void disconnect(int code, String reason) {
        if (mWebSocket != null)
            mWebSocket.close(code, reason);
    }
    

這裏要注意,回調的方法都是在子線程回調的,若是須要更新UI,須要切換到主線程。

基本操做就這麼多,仍是很簡單的吧,初始化Websocket——鏈接——鏈接成功——收發消息。

其中WebSocket類是一個操做接口,主要提供瞭如下幾個方法

  • send(text: String)發送一個String類型的消息
  • send(bytes: ByteString) 發送一個二進制類型的消息
  • close(code: Int, reason: String?)關閉WebSocket鏈接

若是有同窗想測試下WebSocket的功能可是又沒有實際的服務器,怎麼辦呢?其實OkHttp官方有一個MockWebSocket服務,能夠用來模擬服務端,下面咱們一塊兒試一下:

模擬服務器

首先集成MockWebSocket服務庫:

    implementation 'com.squareup.okhttp3:mockwebserver:4.7.2'

而後就能夠新建MockWebServer,並加入MockResponse做爲接收消息的響應。

        MockWebServer mMockWebServer = new MockWebServer();
        MockResponse response = new MockResponse()
                .withWebSocketUpgrade(new WebSocketListener() {
                    @Override
                    public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) {
                        super.onOpen(webSocket, response);
                        //有客戶端鏈接時回調
                        Log.e(TAG, "服務器收到客戶端鏈接成功回調:");
                        mWebSocket = webSocket;
                        mWebSocket.send("我是服務器,你好呀");
                    }

                    @Override
                    public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) {
                        super.onMessage(webSocket, text);

                        Log.e(TAG, "服務器收到消息:" + text);
                    }

                    @Override
                    public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
                        super.onClosed(webSocket, code, reason);
                        Log.e(TAG, "onClosed:");
                    }
                });

        mMockWebServer.enqueue(response);

這裏服務器端在收到客戶端鏈接成功消息後,給客戶端發送了一條消息。要注意的是這段代碼要在子線程執行,由於主線程不能進行網絡操做。

而後就能夠去初始化Websocket客戶端了:

        //獲取鏈接url,初始化websocket客戶端
        String websocketUrl = "ws://" + mMockWebServer.getHostName() + ":" + mMockWebServer.getPort() + "/";
        WSManager.getInstance().init(websocketUrl);

ok,運行項目

    //運行結果
    E/jimu: mWbSocketUrl=ws://localhost:38355/
    E/jimu: 服務器收到客戶端鏈接成功回調:
    E/jimu: 鏈接成功!
    E/jimu: 客戶端收到消息:我是服務器,你好呀
    E/jimu: 服務器收到消息:我是客戶端,你好啊

參考

https://github.com/square/okhttp

感謝你們的閱讀,有一塊兒學習的小夥伴能夠關注下公衆號—碼上積木❤️❤️
每日三問知識點/面試題,聚沙成塔。

天天一見

給你力量

碼上積木





點點在看,你最好看



本文分享自微信公衆號 - 碼上積木(Lzjimu)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索