![](http://static.javashuo.com/static/loading.gif)
點擊上方 藍字 關注我,❤git
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
今天給你們帶來一篇老文章,介紹WebSocket,你們能夠了解了解。github
前言
最近老闆又來新需求了,要作一個物聯網相關的app
,其中有個需求是客戶端須要收發服務器不按期發出的消息。
心裏OS:
🤔 這咋整呢?經過接口輪詢?定時訪問接口,有數據就更新?
🤔 不行不行,這樣浪費資源了,還耗電,會致使不少請求都是無效的網絡操做。
🤔 那就長鏈接唄?WebSocket協議
好像不錯,經過握手創建長鏈接後,能夠隨時收發服務器的消息。那就它了!
🤔 怎麼集成呢?正好前段時間複習OkHttp
源碼的時候發現了它是支持Websocket
協議的,那就用它試試吧!web
開淦!面試
WebSocket介紹
先簡單介紹下WebSocket
。咱們都知道Http是處於應用層的一個通訊協議
,可是隻支持單向主動通訊,作不到服務器主動向客戶端推送消息。並且Http是無狀態
的,即每次通訊都沒有關聯性,致使跟服務器關係不緊密。服務器
爲了解決和服務器長時間通訊的痛點呢,HTML5
規範引出了WebSocket
協議(知道這名字咋來的吧,人家HTML5
規範引出的,隨爸姓),是一種創建在TCP
協議基礎上的全雙工通訊的協議。他跟Http
同屬於應用層協議,下層仍是須要經過TCP創建鏈接。微信
可是,WebSocket
在TCP
鏈接創建後,還要經過Http
進行一次握手,也就是經過Http
發送一條GET請求
消息給服務器,告訴服務器我要創建WebSocket鏈接
了,你準備好哦,具體作法就是在頭部信息中添加相關參數。而後服務器響應我知道了,而且將鏈接協議改爲WebSocket
,開始創建長鏈接。websocket
這裏貼上請求頭和響應頭信息,從網上找了一張圖:網絡
![](http://static.javashuo.com/static/loading.gif)
簡單說明下參數:app
-
URL通常是以 ws
或者wss
開頭,ws
對應Websocket
協議,wss
對應在TLS
之上的WebSocket
。相似於Http
和Https
的關係。 -
請求方法爲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")
實現代碼
首先是初始化OkHttpClient
和WebSocket
實例: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
感謝你們的閱讀,有一塊兒學習的小夥伴能夠關注下公衆號—
碼上積木
❤️❤️
每日三問知識點/面試題,聚沙成塔。
天天一見
給你力量
碼上積木
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
點點在看,你最好看
本文分享自微信公衆號 - 碼上積木(Lzjimu)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。