WebSocket

聊聊OkHttp实现WebSocket细节,包括鉴权和长连接保活及其原理! - 承香墨影 - 博客园

  • 今天介绍的 WebSocket,下层和 HTTP 一样也是基于 TCP 协议,这是一种轻量级网络通信协议,也属于应用层协议。

  • 轮询的缺点也非常明显,大量空闲的时间,其实是在反复发送无效的请求,这显然是一种资源的损耗。

  • WebSocket 是真正意义上的全双工模式,也就是我们俗称的「长连接」。当完成握手连接后,客户端和服务端均可以主动的发起请求,回复响应,并且两边的传输都是相互独立的。

  • 在建立连接前,客户端还需要知道服务端的地址,WebSocket 并没有另辟蹊径,而是沿用了 HTTP 的 URL 格式,但协议标识符变成了 “ws” 或者 “wss”,分别表示明文和加密的 WebSocket 协议,这一点和 HTTP 与 HTTPS 的关系类似。

  • 以下是一些 WebSocket 的 URL 例子:

  • ws://cxmydev.com/some/path ws://cxmydev.com:8080/some/path wss://cxmydev.com:443?uid=xxx

  • 而在连接建立后,WebSocket 采用二进制帧的形式传输数据,其中常用的包括用于数据传输的数据帧 MESSAGE 以及 3 个控制帧:

  • PING:主动保活的 PING 帧;

  • PONG:收到 PING 帧后回复;

  • CLOSE:主动关闭 WebSocket 连接;

WebSocket 的特性:

  • WebSocket 建立在 TCP 协议之上,对服务器端友好;
  • 默认端口采用 80 或 443,握手阶段采用 HTTP 协议,不容易被防火墙屏蔽,能够通过各种 HTTP 代理服务器;
  • 传输数据相比 HTTP 更轻量,少了 HTTP Header,性能开销更小,通信更高效;
  • 通过 MESSAGE 帧发送数据,可以发送文本或者二进制数据,如果数据过大,会被分为多个 MESSAGE 帧发送;
  • WebSocket 沿用 HTTP 的 URL,==协议标识符是 “ws” 或 “wss”==。

WebSocket之OkHttp

建立 WebSocket 连接

  • 借助 OkHttp 可以很轻易的实现 WebSocket,它的 OkHttpClient 中,提供了 newWebSocket() 方法,可以直接建立一个 WebSocket 连接并完成通信。

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

  • 调用 newWebSocket() 后,就会开始 WebSocket 连接

  • WebSocketListener 是一个抽象类,其中定义了比较多的方法,借助这些方法回调,就可以完成对 WebSocket 的所有操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    var mWebSocket : WebSocket? = null
    fun connectionWebSockt(hostName:String,port:Int){
    // ...
    httpClient.newWebSocket(request, object:WebSocketListener(){
    override fun onOpen(webSocket: WebSocket, response: Response) {
    super.onOpen(webSocket, response)
    // WebSocket 连接建立
    mWebSocket = webSocket
    }

    override fun onMessage(webSocket: WebSocket, text: String) {
    super.onMessage(webSocket, text)
    // 收到服务端发送来的 String 类型消息
    }

    override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
    super.onClosing(webSocket, code, reason)
    // 收到服务端发来的 CLOSE 帧消息,准备关闭连接
    }

    override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
    super.onClosed(webSocket, code, reason)
    // WebSocket 连接关闭
    }

    override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
    super.onFailure(webSocket, t, response)
    // 出错了
    }
    })
    }
  • send(text):发送 String 类型的消息;

  • send(bytes):发送二进制类型的消息;

  • close(code, reason):主动关闭 WebSocket 连接;

Mock WebSocket

  • 有时候为了方便我们测试,OkHttp 还提供了扩展的 MockWebSocket 服务,来模拟服务端。
  • MockWebSocket 需要添加额外的 Gradle 引用,最好和 OkHttp 版本保持一致:
  • api ‘com.squareup.okhttp3:okhttp:3.9.1’api‘com.squareup.okhttp3:mockwebserver:3.9.1’

WebSocket 保活

  • WebSocket 建立的连接就是我们所谓的长连接,每个连接对于服务器而言,都是资源。但服务器倾向于在一个连接长时间没有消息往来的时候,将其关闭。而 WebSocket 的保活,实际上就是定时向服务端发送一个空消息,来保证连接不会被服务端主动断开。
  • OkHttp 只需要简单的配置,就可以自动的间隔发送 PING 帧和数据。
  • 在构造 OkHttpClient 的时候,通过 pingInterval() 设置 PING 帧发送的时间间隔,它的默认值为 0,所以不设置不发送。
  • val httpClient = OkHttpClient.Builder() .pingInterval(
  • 40, TimeUnit.SECONDS)
  • // 设置 PING 帧发送间隔.build()
  • 这里设置的时长,需要和服务端商议,通常建议最好设置一个小于 60s 的值。

WebSocket
http://peiniwan.github.io/2024/04/3bdfabb0f16c.html
作者
六月的雨
发布于
2024年4月6日
许可协议