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
13fun 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
31var 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 的值。