websocketwss怎么配置_聊聊OkHttp实现WebSocket细节,包
括鉴权和长。。。
⼀、序
OkHttp 应该算是 Android 中使⽤中使⽤最⼴泛的⽹络库了,我们通常会利⽤它来实现 HTTP 请求,但是实际上它还可以⽀持WebSocket,并且使⽤起来还⾮常的便捷。
那本⽂就来聊聊,利⽤ OkHttp 实现 WebSocket 的⼀些细节,包括对 WebSocket 的介绍,以及在传输前如何做到鉴权、长连接保活及其原理。
⼆、WebSocket 简介
2.1 为什么使⽤ WebSocket?
我们做客户端开发时,接触最多的应⽤层⽹络协议,就是 HTTP 协议,⽽今天介绍的 WebSocket,下层和 HTTP ⼀样也是基于 TCP 协议,是⼀种轻量级⽹络通信协议,也属于应⽤层协议。
WebSocket 与 HTTP/2 ⼀样,其实都是为了解决 HTTP/1.1 的⼀些缺陷⽽诞⽣的,⽽ WebSocket 针对的就是「请求-应答」这种"半双⼯"的模式的通信缺陷。
「请求-应答」是"半双⼯"的通信模式,数据的传输必须经过⼀次请求应答,这个完整的通信过程,通信的同⼀时刻数据只能在⼀个⽅向上传递。它最⼤的问题在于,HTTP 是⼀种被动的通信模式,服务端必须等待客户端请求才可以返回数据,⽆法主动向客户端发送数据。
前端websocket怎么用
这也导致在 WebSocket 出现之前,⼀些对实时性有要求的服务,都是基于轮询(Polling)这种简单的模式来实现。轮询就是由客户端定时发起请求,如果服务端有需要传递的数据,可以借助这个请求去响应数据。
轮询的缺点也⾮常明显,⼤量空闲的时间,其实是在反复发送⽆效的请求,这显然是⼀种资源的损耗。
虽然在之后的 HTTP/2、HTTP/3 中,针对这种半双⼯的缺陷新增了 Stream、Server Push 等特性,但是「请求-应答」依然是 HTTP 协议主要的通信⽅式。
WebSocket 协议是由 HTML5 规范定义的,原本是为了浏览器⽽设计的,可以避免同源的限制,浏览器可以与任意服务端通信,现代浏览器基本上都已经⽀持 WebSocket。
虽然 WebSocket 原本是被定义在 HTML5 中,但它也适⽤于移动端,尽管移动端也可以直接通过 Socket 与服务端通信,但借助WebSocket,可以利⽤ 80(HTTP) 或 443(HTTPS)端⼝通信,有效的避免⼀些防⽕墙的拦截。
g
WebSocket 是真正意义上的全双⼯模式,也就是我们俗称的「长连接」。当完成握⼿连接后,客户端和服务端均可以主动的发起请求,回复响应,并且两边的传输都是相互独⽴的。
2.2 WebSocket 的特点
WebSocket 的数据传输,是基于 TCP 协议,但是在传输之前,还有⼀个握⼿的过程,双⽅确认过眼神,才能够正式的传输数据。WebSocket 的握⼿过程,符合其 "Web" 的特性,是利⽤ HTTP 本⾝的 "协议升级" 来实现。
在建⽴连接前,客户端还需要知道服务端的地址,WebSocket 并没有另辟蹊径,⽽是沿⽤了 HTTP 的 URL 格式,但协议标识符变成了"ws" 或者 "wss",分别表⽰明⽂和加密的 WebSocket 协议,这⼀点和 HTTP 与 HTTPS 的关系类似。
以下是⼀些 WebSocket 的 URL 例⼦:
ws://cxmydev/some/pathws://cxmydev:8080/some/pathwss://cxmydev: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
连接并完成通信。
fun connectionWebSockt(hostName:String,port:Int){ val httpClient = OkHttpClient.Builder() .pingInterval(40, TimeUnit.SECONDS) // 设置 PING 帧发送间隔
我想熟悉 OkHttp 的朋友,对上⾯这段代码不会有疑问,只是 URL 换成了 "ws" 协议标识符。另外,还需要配置 pingInterval(),这个细
节后⽂会讲解。
调⽤ newWebSocket() 后,就会开始 WebSocket 连接,但是核⼼操作都在 WebSocketListener 这个抽象类中。
3.2 使⽤ WebSocketListener
WebSocketListener 是⼀个抽象类,其中定义了⽐较多的⽅法,借助这些⽅法回调,就可以完成对 WebSocket 的所有操作。
var mWebSocket : WebSocket? = nullfun connectionWebSockt(hostName:String,port:Int){ // ... wWebSocket(request, object:WebSocketListe
在 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 版本保持⼀致:
api 'com.squareup.okhttp3:okhttp:3.9.1'api 'com.squareup.okhttp3:mockwebserver:3.9.1'
Mock WebServer 的使⽤也⾮常简单,只需要利⽤ MockWebSocket 类即可。
var mMockWebSocket: MockWebServer? = nullfun mockWebSocket() { if (mMockWebSocket != null) { return } mMockWebSocket = MockWebServer()
Mock WebSocket 服务端,依然需要⽤到我们前⾯讲到的 WebSocketListener,这个就⽐较熟悉,不再赘述了。
之后就可以通过 mMockWebSocket 获取到这个 Mock 的服务的 IP 和端⼝。
val hostName = mMockWebSocket?.getHostName()val port = mMockWebSocket?.getPort()val url = "ws:${hostName}:${port}"
需要注意的是,这两个⽅法需要在⼦线程中调⽤,否者会收到⼀个 NetworkOnMainThreadException 异常。
虽然有时候在服务端完善的情况下,我们并不需要使⽤ Mock 的⼿段,但是在学习阶段,依然推荐⼤家在本地 Mock ⼀个服务端,打⼀些
⽇志,观察⼀个完整的 WebSocket 链接和发送消息的过程。
3.4 WebSocket 如何鉴权
接下来我们聊聊 WebSocket 连接的鉴权问题。
所谓鉴权,其实就是为了安全考虑,避免服务端启动 WebSocket 的连接服务后,任谁都可以连接,这肯定会引发⼀些安全问题。其次,服务端还需要将 WebSocket 的连接实体与⼀个真实的⽤户对应起来,否则业务也⽆法保证了。
那么问题就回到了,WebSocket 通信的完整过程中,如何以及何时将⼀些业务数据传递给服务端?当然在 WebSocket 连接建⽴之后,⽴即给服务端发送⼀些鉴权的数据,必然是可以做到业务实现的,但是这样明显是不够优雅的。
前⽂提到,WebSocket 在握⼿阶段,使⽤的是 HTTP 的 "协议升级",它本质上还是 HTTP 的报⽂头发送⼀些特殊的头数据,来完成协议升级。
例如在 RealWebSocket 中,就有构造 Header 的过程,例如 Upgrade、Connection 等等。
public void connect(OkHttpClient client) { // ... final Request request = wBuilder() .header("Upgrade", "websocket") .header("Conne
那么实际我们在 WebSocket 阶段,也可以通过 Header 传输⼀些鉴权的数据,例如 uid、token 之类,具体⽅法就是在构造 Request 的时候,为其增加 Header,这⾥就不举例说明了。
另外 WebSocket 的 URL 也是可以携带参数的。
wss://cxmydev:443?uid=xxx&token=xxx
3.5 WebSocket 保活
WebSocket 建⽴的连接就是我们所谓的长连接,每个连接对于服务器⽽⾔,都是资源。⽽服务器倾向于在⼀个连接长时间没有消息往来的时候,将其关闭。⽽ WebSocket 的保活,时机上就是定时向服务端发送⼀个空消息,来保证连接不会被服务端主动断开。
那么我们⾃⼰写个定时器,固定间隔向服务端 mWebSocket.send() ⼀个消息,就可以达到保活的⽬的,但这样发送的其实是 MESSAGE 帧数据,如果使⽤ WebSocket 还有更优雅的⽅式。
前⽂我们提到,WebSocket 采⽤⼆进制帧的形式传输数据,其中就包括了⽤于保活的 PING 帧,⽽ OkHttp 只需要简单的配置,就可以⾃动的间隔发送 PING 帧和数据。
我们只需要在构造 OkHttpClient 的时候,通过 pingInterval() 设置 PING 帧发送的时间间隔,它的默认值为 0,所以不设置不发送。
val httpClient = OkHttpClient.Builder() .pingInterval(40, TimeUnit.SECONDS) // 设置 PING 帧发送间隔 .build()
这⾥设置的时长,需要和服务端商议,通常建议最好设置⼀个⼩于 60s 的值。
具体的逻辑在 RealWebSocket 类中。
public void initReaderAndWriter(String name, Streams streams) throws IOException { synchronized (this) { // ... if (pingIntervalMillis != 0) { executor
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 协议的讲解。
在头条号私信我。我会送你⼀些我整理的学习资料,包含:Android反编译、算法、设计模式、虚拟机、Linux、Kotlin、Python、爬⾍、Web项⽬源码。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论