成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

聊聊OkHttp實現WebSocket細節,包括鑒權和長連接保活及其原理!

開發 開發工具
那本文就來聊聊,利用 OkHttp 實現 WebSocket 的一些細節,包括對 WebSocket 的介紹,以及在傳輸前如何做到鑒權、長連接保活及其原理。

 [[319183]]

一、序

OkHttp 應該算是 Android 中使用最廣泛的網絡庫了,我們通常會利用它來實現 HTTP 請求,但是實際上它還可以支持 WebSocket,并且使用起來還非常的便捷。

那本文就來聊聊,利用 OkHttp 實現 WebSocket 的一些細節,包括對 WebSocket 的介紹,以及在傳輸前如何做到鑒權、長連接保活及其原理。

二、WebSocket 簡介

2.1 為什么使用 WebSocket?

我們做客戶端開發時,接觸最多的應用層網絡協議,就是 HTTP 協議,而今天介紹的 WebSocket,下層和 HTTP 一樣也是基于 TCP 協議,是一種輕量級網絡通信協議,也屬于應用層協議。

WebSocket 與 HTTP/2 一樣,其實都是為了解決 HTTP/1.1 的一些缺陷而誕生的,而 WebSocket 針對的就是「請求-應答」這種"半雙工"的模式的通信缺陷。

「請求-應答」是"半雙工"的通信模式,數據的傳輸必須經過一次請求應答,這個完整的通信過程,通信的同一時刻數據只能在一個方向上傳遞。它最大的問題在于,HTTP 是一種被動的通信模式,服務端必須等待客戶端請求才可以返回數據,無法主動向客戶端發送數據。

這也導致在 WebSocket 出現之前,一些對實時性有要求的服務,都是基于輪詢(Polling)這種簡單的模式來實現。輪詢就是由客戶端定時發起請求,如果服務端有需要傳遞的數據,可以借助這個請求去響應數據。

輪詢的缺點也非常明顯,大量空閑的時間,其實是在反復發送無效的請求,這顯然是一種資源的損耗。

雖然在之后的 HTTP/2、HTTP/3 中,針對這種半雙工的缺陷新增了 Stream、Server Push 等特性,但是「請求-應答」依然是 HTTP 協議主要的通信方式。

WebSocket 協議是由 HTML5 規范定義的,原本是為了瀏覽器而設計的,可以避免同源的限制,瀏覽器可以與任意服務端通信,現代瀏覽器基本上都已經支持 WebSocket。

雖然 WebSocket 原本是被定義在 HTML5 中,但它也適用于移動端,盡管移動端也可以直接通過 Socket 與服務端通信,但借助 WebSocket,可以利用 80(HTTP) 或 443(HTTPS)端口通信,有效的避免一些防火墻的攔截。  

 WebSocket 是真正意義上的全雙工模式,也就是我們俗稱的「長連接」。當完成握手連接后,客戶端和服務端均可以主動的發起請求,回復響應,并且兩邊的傳輸都是相互獨立的。

2.2 WebSocket 的特點

WebSocket 的數據傳輸,是基于 TCP 協議,但是在傳輸之前,還有一個握手的過程,雙方確認過眼神,才能夠正式的傳輸數據。

WebSocket 的握手過程,符合其 "Web" 的特性,是利用 HTTP 本身的 "協議升級" 來實現。

在建立連接前,客戶端還需要知道服務端的地址,WebSocket 并沒有另辟蹊徑,而是沿用了 HTTP 的 URL 格式,但協議標識符變成了 "ws" 或者 "wss",分別表示明文和加密的 WebSocket 協議,這一點和 HTTP 與 HTTPS 的關系類似。 

 以下是一些 WebSocket 的 URL 例子: 

  1. ws://cxmydev.com/some/path 
  2. ws://cxmydev.com:8080/some/path 
  3. wss://cxmydev.com:443?uid=xxx 

而在連接建立后,WebSocket 采用二進制幀的形式傳輸數據,其中常用的包括用于數據傳輸的數據幀 MESSAGE 以及 3 個控制幀:

  • PING:主動保活的 PING 幀;
  • PONG:收到 PING 幀后回復;
  • CLOSE:主動關閉 WebSocket 連接;

更多 WebSocket 的協議細節,可以參考《WebSocket Protocol 規范》,具體細節,有機會為什么再開單篇文章講解。

了解這些基本知識,我們基本上就可以把 WebSocket 使用起來,并且不會掉到坑里。

我們再小結一下 WebSocket 的特性:

  1. WebSocket 建立在 TCP 協議之上,對服務器端友好;
  2. 默認端口采用 80 或 443,握手階段采用 HTTP 協議,不容易被防火墻屏蔽,能夠通過各種 HTTP 代理服務器;
  3. 傳輸數據相比 HTTP 輕量,少了 HTTP Header,性能開銷更小,通信更高效;
  4. 通過 MESSAGE 幀發送數據,可以發送文本或者二進制數據,如果數據過大,會被分為多個 MESSAGE 幀發送;
  5. WebSocket 沿用 HTTP 的 URL,協議標識符是 "ws" 或 "wss"。

那接下來我們就看看如何利用 OkHttp 使用 WebSocket。

三、WebSocket之OkHttp

3.1 建立 WebSocket 連接

借助 OkHttp 可以很輕易的實現 WebSocket,它的 OkHttpClient 中,提供了 newWebSocket() 方法,可以直接建立一個 WebSocket 連接并完成通信。 

  1. fun connectionWebSockt(hostName:String,port:Int){ 
  2.   val httpClient = OkHttpClient.Builder() 
  3.       .pingInterval(40, TimeUnit.SECONDS) // 設置 PING 幀發送間隔 
  4.       .build() 
  5.   val webSocketUrl = "ws://${hostName}:${port}" 
  6.   val request = Request.Builder() 
  7.       .url(webSocketUrl) 
  8.       .build() 
  9.   httpClient.newWebSocket(request, object:WebSocketListener(){ 
  10.     // ... 
  11.   }) 

我想熟悉 OkHttp 的朋友,對上面這段代碼不會有疑問,只是 URL 換成了 "ws" 協議標識符。另外,還需要配置 pingInterval(),這個細節后文會講解。

調用 newWebSocket() 后,就會開始 WebSocket 連接,但是核心操作都在 WebSocketListener 這個抽象類中。

3.2 使用 WebSocketListener

WebSocketListener 是一個抽象類,其中定義了比較多的方法,借助這些方法回調,就可以完成對 WebSocket 的所有操作。 

  1. var mWebSocket : WebSocket? = null 
  2. fun connectionWebSockt(hostName:String,port:Int){ 
  3.   // ... 
  4.   httpClient.newWebSocket(request, object:WebSocketListener(){ 
  5.     override fun onOpen(webSocket: WebSocket, response: Response) { 
  6.       super.onOpen(webSocket, response) 
  7.       // WebSocket 連接建立 
  8.       mWebSocket = webSocket 
  9.     } 
  10.  
  11.     override fun onMessage(webSocket: WebSocket, text: String) { 
  12.       super.onMessage(webSocket, text) 
  13.       // 收到服務端發送來的 String 類型消息 
  14.     } 
  15.  
  16.     override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { 
  17.       super.onClosing(webSocket, code, reason) 
  18.       // 收到服務端發來的 CLOSE 幀消息,準備關閉連接 
  19.     } 
  20.  
  21.     override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { 
  22.       super.onClosed(webSocket, code, reason) 
  23.       // WebSocket 連接關閉 
  24.     } 
  25.  
  26.     override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { 
  27.       super.onFailure(webSocket, t, response) 
  28.       // 出錯了 
  29.     } 
  30.   }) 

在 WebSocketListener 的所有方法回調中,都包含了 WebSocket 類型的對象,它就是當前建立的 WebSocket 連接實體,通過它就可以向服務端發送 WebSocket 消息。

如果需要在其他時機發送消息,可以在回調 onOpen() 這個建立連接完成的時機,保存 webSocket 對象,以備后續使用。

OkHttp 中的 WebSocket 本身是一個接口,它的實現類是 RealWebSocket,它定義了一些發送消息和關閉連接的方法:

  • send(text):發送 String 類型的消息;
  • send(bytes):發送二進制類型的消息;
  • close(code, reason):主動關閉 WebSocket 連接;

利用這些回調和 WebSocket 的方法,我們就可以完成 WebSocket 通信了。

3.3 Mock WebSocket

有時候為了方便我們測試,OkHttp 還提供了擴展的 MockWebSocket 服務,來模擬服務端。

Mock 需要添加額外的 Gradle 引用,最好和 OkHttp 版本保持一致: 

  1. api 'com.squareup.okhttp3:okhttp:3.9.1' 
  2. api 'com.squareup.okhttp3:mockwebserver:3.9.1' 

Mock WebServer 的使用也非常簡單,只需要利用 MockWebSocket 類即可。 

  1. var mMockWebSocket: MockWebServer? = null 
  2. fun mockWebSocket() { 
  3.   if (mMockWebSocket != null) { 
  4.     return 
  5.   } 
  6.   mMockWebSocket = MockWebServer() 
  7.   mMockWebSocket?.enqueue(MockResponse().withWebSocketUpgrade(object : WebSocketListener() { 
  8.  
  9.     override fun onOpen(webSocket: WebSocket, response: Response) { 
  10.       super.onOpen(webSocket, response) 
  11.       // 有客戶端連接時回調 
  12.     } 
  13.  
  14.     override fun onMessage(webSocket: WebSocket, text: String) { 
  15.       super.onMessage(webSocket, text) 
  16.       // 收到新消息時回調 
  17.     } 
  18.  
  19.     override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { 
  20.       super.onClosing(webSocket, code, reason) 
  21.       // 客戶端主動關閉時回調 
  22.     } 
  23.  
  24.     override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { 
  25.       super.onClosed(webSocket, code, reason) 
  26.       // WebSocket 連接關閉 
  27.     } 
  28.  
  29.     override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { 
  30.       super.onFailure(webSocket, t, response) 
  31.       // 出錯了 
  32.     } 
  33.   })) 

Mock WebSocket 服務端,依然需要用到我們前面講到的 WebSocketListener,這個就比較熟悉,不再贅述了。

之后就可以通過 mMockWebSocket 獲取到這個 Mock 的服務的 IP 和端口。 

  1. val hostName = mMockWebSocket?.getHostName() 
  2. val port = mMockWebSocket?.getPort() 
  3. val url = "ws:${hostName}:${port}" 

需要注意的是,這兩個方法需要在子線程中調用,否者會收到一個異常。

雖然有時候在服務端完善的情況下,我們并不需要使用 Mock 的手段,但是在學習階段,依然推薦大家在本地 Mock 一個服務端,打一些日志,觀察一個完整的 WebSocket 鏈接和發送消息的過程。

3.4 WebSocket 如何鑒權

接下來我們聊聊 WebSocket 連接的鑒權問題。

所謂鑒權,其實就是為了安全考慮,避免服務端啟動 WebSocket 的連接服務后,任誰都可以連接,這肯定會引發一些安全問題。其次,服務端還需要將 WebSocket 的連接實體與一個真實的用戶對應起來,否則業務就無法保證了。

那么問題就回到了,WebSocket 通信的完整過程中,如何以及何時將一些業務數據傳遞給服務端?當然在 WebSocket 連接建立之后,立即給服務端發送一些鑒權的數據,必然是可以做到業務實現的,但是這樣明顯是不夠優雅的。

前文提到,WebSocket 在握手階段,使用的是 HTTP 的 "協議升級",它本質上還是 HTTP 的報文頭發送一些特殊的頭數據,來完成協議升級。

例如在 RealWebSocket 中,就有構造 Header 的過程,例如 Upgrade、Connection 等等。 

  1. public void connect(OkHttpClient client) { 
  2.   // ... 
  3.   final Request request = originalRequest.newBuilder() 
  4.     .header("Upgrade""websocket"
  5.     .header("Connection""Upgrade"
  6.     .header("Sec-WebSocket-Key"key
  7.     .header("Sec-WebSocket-Version""13"
  8.     .build(); 
  9.   //.... 

那么實際我們在 WebSocket 階段,也可以通過 Header 傳輸一些鑒權的數據,例如 uid、token 之類,具體方法就是在構造 Request 的時候,為其增加 Header,這里就不舉例說明了。

另外 WebSocket 的 URL 也是可以攜帶參數的。 

  1. wss://cxmydev.com:443?uid=xxx&token=xxx 

3.5 WebSocket 保活

WebSocket 建立的連接就是我們所謂的長連接,每個連接對于服務器而言,都是資源。而服務器傾向于在一個連接長時間沒有消息往來的時候,將其關閉。而 WebSocket 的保活,時機上就是定時向服務端發送一個空消息,來保證連接不會被服務端主動斷開。

那么我們自己寫個定時器,固定間隔向服務端 mWebSocket.send() 一個消息,就可以達到保活的目的,但這樣發送的其實是 MESSAGE 幀數據,如果使用 WebSocket 還有更優雅的方式。

前文我們提到,WebSocket 采用二進制幀的形式傳輸數據,其中就包括了用于保活的 PING 幀,而 OkHttp 只需要簡單的配置,就可以自動的間隔發送 PING 幀和數據。

我們只需要在構造 OkHttpClient 的時候,通過 pingInterval() 設置 PING 幀發送的時間間隔,它的默認值為 0,所以不設置不發送。 

  1. val httpClient = OkHttpClient.Builder() 
  2.       .pingInterval(40, TimeUnit.SECONDS) // 設置 PING 幀發送間隔 
  3.       .build() 

這里設置的時長,需要和服務端商議,通常建議最好設置一個小于 60s 的值。

具體的邏輯在 RealWebSocket 類中。 

  1. public void initReaderAndWriter(String name, Streams streams) throws IOException { 
  2.   synchronized (this) { 
  3.     // ... 
  4.     if (pingIntervalMillis != 0) { 
  5.       executor.scheduleAtFixedRate( 
  6.         new PingRunnable(), pingIntervalMillis, pingIntervalMillis, MILLISECONDS); 
  7.     } 
  8.     // ... 
  9.   } 
  10.   // ... 

PingRunnabel 最終會去間隔調用 writePingFrame() 用以向 WebSocketWriter 中寫入 PING 幀,來達到服務端長連接保活的效果。

四、小結

到這里本文就介紹清楚 WebSocket 以及如何使用 OkHttp 實現 WebSocket 支持。

這里還是簡單小結一下:

  1. WebSocket 是一個全雙工的長連接應用層協議,可以通過它實現服務端到客戶端主動的推送通信。
  2. OkHttp 中使用 WebSocket 的關鍵在于 newWebSocket() 方法以及 WebSocketListener 這個抽象類,最終連接建立完畢后,可以通過 WebSocket 對象向對端發送消息;
  3. WebSocket 鑒權,可以利用握手階段的 HTTP 請求中,添加 Header 或者 URL 參數來實現;
  4. WebSocket 的保活,需要定時發送 PING 幀,發送的時間間隔,可以通過 pingInterval()方法設置;

額外提一句,OkHttp 在 v3.4.1 中添加的 WebSocket 的支持,之前的版本需要 okhttp-ws 擴展庫來支持,但是那畢竟已經是 2016 年的事了,我想現在應該沒有人在用那么老版本的 OkHttp 了。

本文對你有幫助嗎?留言、轉發、點好看是最大的支持,謝謝!如果本文各項數據好,之后會再分享一篇 OkHttp 中針對 WebSocket 的實現以及 WebSocket 協議的講解。

參考:

  • WebSocket教程:http://www.ruanyifeng.com/blog/2017/05/websocket.html
  • The WebSocket Protocol:https://tools.ietf.org/html/rfc6455#page-37

 

 

責任編輯:武曉燕 來源: 51CTO
相關推薦

2021-02-26 12:37:39

WebSocketOkHttp連接

2019-09-23 08:27:15

TCP長連接心跳

2024-05-28 08:24:18

2022-10-24 08:03:04

MySQL數據庫

2020-02-25 16:48:35

AndroidGoogle 移動系統

2019-01-07 12:02:02

TCP長連接Java

2021-10-26 11:42:51

系統

2017-07-11 13:58:10

WebSocket

2025-04-27 02:00:00

實時通信Nacos服務端

2019-05-20 14:57:35

Tomcat容器安全

2024-01-11 08:53:58

2024-08-05 11:14:45

2024-12-23 15:05:29

2024-07-16 10:25:27

2021-04-27 18:12:22

WebSocket持久化連接HTTP

2023-03-30 07:48:46

接口鑒權SpringBoot

2022-03-18 10:43:12

WebSocketHTML5TCP 連接

2021-07-30 15:06:05

鴻蒙HarmonyOS應用

2024-08-07 08:22:27

2019-12-31 09:11:01

后臺Android系統
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 精品视频一区二区三区在线观看 | 日韩欧美久久 | 欧美精品91爱爱 | 日日天天 | 综合九九| 精品一区二区三区在线观看 | 天天干狠狠操 | 国产精品久久国产精品久久 | 国产乱码精品一品二品 | 精品国产一区二区三区久久久四川 | 日日夜夜精品视频 | 一级片成人 | 国产精品日日做人人爱 | 国产精品免费观看视频 | 欧美另类视频 | 欧美视频成人 | 国产黄色麻豆视频 | 爱综合| 亚洲成人免费网址 | 999热精品 | 国产国语精品 | 日韩精品一区二区久久 | 亚洲精品68久久久一区 | 日韩视频在线免费观看 | 日韩1区2区| 99视频在线免费观看 | 日本一区二区三区四区 | 中文字幕在线剧情 | 欧美精品一区二区免费 | 精品一区二区三区四区五区 | 天堂久久久久久久 | 欧日韩在线 | 国产线视频精品免费观看视频 | 精品久久久久一区二区国产 | 天堂av资源| 亚洲精品一级 | 亚洲国产精品精华素 | 91九色婷婷| av天天看 | 国产成人jvid在线播放 | 欧美精品久久久久久久久久 |