WebSocket 是一个网络通信协议,它提供了一个全双工通信渠道,通过一个单一的长期连接允许服务器主动向客户端发送数据。这项技术是 HTML5 的一部分,意在被整合到所有现代浏览器中。WebSocket 协议在2008年被提出,并在2011年成为国际标准。
WebSocket 的主要特点包括:
- 全双工通信:在WebSocket连接中,客户端和服务器都可以在任何时候发送数据,不需要等待对方的指令。这类似于电话通话,双方可以同时说话。
- 持久连接:一旦WebSocket连接建立,它会保持开放状态,直到客户端或服务器决定关闭连接。
- 实时性:由于WebSocket提供了实时的双向通信能力,它非常适合需要快速响应的应用,如在线游戏、实时聊天应用和股票交易更新。
- 基于TCP:WebSocket 连接是基于TCP协议的,握手可靠,提供了可靠的数据传输服务。
- 头部开销小:与HTTP相比,WebSocket的数据传输头部开销较小,这使得它在传输大量数据时更加高效。
- 跨域通信:WebSocket 默认支持跨域通信,但也可以设置跨域策略。
WebSocket 的使用场景:
- 聊天应用:如即时通讯软件,需要实时显示消息。
- 游戏:多人在线游戏需要实时的数据交换。
- 股票行情:实时更新股票价格和市场动态。
- 协作工具:如实时代码编辑器或文档编辑工具。
- 物联网(IoT) :设备与服务器之间的实时数据交换。
WebSocket 的基本概念:
- 握手:在WebSocket通信开始之前,客户端和服务器通过HTTP请求进行握手。如果服务器支持WebSocket,它将响应一个Upgrade头部,然后将TCP连接升级到WebSocket连接。
- 帧:WebSocket 数据传输是通过帧进行的,每个帧可以包含一个或多个消息。
- 关闭连接:WebSocket 连接可以通过发送一个关闭帧来关闭,这是一个有秩序的过程,双方都有机会发送最后一个消息并清理资源。
okhttps 框架实现 Android Websocket
class WebSocketUtils {
private var webSocket: WebSocket? = null
private lateinit var client: OkHttpClient
companion object {
val instance: WebSocketUtils by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { WebSocketUtils() }
}
fun init() {
client = OkHttpClient()
// val token: String =
// GlobalApp.instance.companyInfoBean?.access_token
// ?: ""//SharedPreferencesUtil.getInfo(SharedPreferencesUtil.TOKEN)
val request: Request = Request.Builder().url(WSS_DOMAIN).build()
//- WebSocket 通过 `ws://`(非加密)或 `wss://`(加密)协议标识符进行通信,服务器网址就是 URL。
//- WebSocket API 相对简单,例如在客户端使用 `new WebSocket('ws://localhost:8080')` 即可创建一个新的连接
webSocket = client.newWebSocket(request, object : WebSocketListener() {
override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
super.onMessage(webSocket, bytes)
LogUtils.d("----websocket----onMessage $bytes")
}
override fun onMessage(webSocket: WebSocket, text: String) {
super.onMessage(webSocket, text)
LogUtils.d("----websocket----onMessage str $text")
}
override fun onOpen(webSocket: WebSocket, response: Response) {
super.onOpen(webSocket, response)
LogUtils.d("----websocket----onOpen response $response")
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
super.onFailure(webSocket, t, response)
LogUtils.d("----websocket----onFailure response ${t.message}")
}
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
super.onClosed(webSocket, code, reason)
LogUtils.d("----websocket----onClosed reason $reason code $code")
}
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
super.onClosing(webSocket, code, reason)
LogUtils.d("----websocket----onClosing reason $reason code $code")
}
})
}
fun connect() {
//封装成 stomp 协议的格式进行通讯
val token: String =
GlobalApp.instance.companyInfoBean?.access_token ?: ""
webSocket?.apply {
val pingSecs = 2
val pongSecs = 2
val cHeaders: MutableList<Header> = ArrayList()
cHeaders.add(Header(Header.VERSION, "1.1"))
cHeaders.add(
Header(
Header.HEART_BEAT,
(pingSecs * 1000).toString() + "," + pongSecs * 1000
)
)
cHeaders.add(Header(DZ_AUTH, "$BEARER $token"))
val msg = MsgCodecImpl().encode(Message(Commands.CONNECT, cHeaders, null))
LogUtils.e(msg)
send(msg)
}
}
fun sendByteString(msg: ByteString) {
webSocket?.apply {
send(msg)
}
}
fun close(code: Int = 0, reson: String) {
webSocket?.apply {
close(code, reson)
}
}
fun subMessage(userId:String) {
webSocket?.apply {
val isAutoAck = true
val headers: MutableList<Header> = java.util.ArrayList()
headers.add(Header(Header.ID, UUID.randomUUID().toString()))
headers.add(Header(Header.DESTINATION, "/user/$userId/queue/message"))
val ackNotAdded = true
// if (this.headers != null) {
// for (header in this.headers) {
// if (Header.ACK == header.key) {
// ackNotAdded = false
// }
// val key = header.key
// if (Header.ID != key && Header.DESTINATION != key) {
// headers.add(header)
// }
// }
// }
if (ackNotAdded) {
headers.add(
Header(
Header.ACK,
if (isAutoAck) Stomp.AUTO_ACK else Stomp.CLIENT_ACK
)
)
}
val msg = MsgCodecImpl().encode(Message(Commands.SUBSCRIBE, headers, null))
LogUtils.e(msg)
send(msg)
// val headers1: MutableList<Header> = java.util.ArrayList()
// headers1.add(Header(Header.ID, UUID.randomUUID().toString()))
// headers1.add(Header(Header.DESTINATION, "/user/$userId/queue/workMobile"))
// val msg1 = MsgCodecImpl().encode(Message(Commands.SUBSCRIBE, headers1, null))
// LogUtils.e(msg1)
// send(msg1)
}
}
}