竹笋

首页 » 问答 » 问答 » WebSocket细节,包括鉴权和
TUhjnbcbe - 2023/6/21 19:29:00

一、序

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、ServerPush等特性,但是「请求-应答」依然是HTTP协议主要的通信方式。

WebSocket协议是由HTML5规范定义的,原本是为了浏览器而设计的,可以避免同源的限制,浏览器可以与任意服务端通信,现代浏览器基本上都已经支持WebSocket。

虽然WebSocket原本是被定义在HTML5中,但它也适用于移动端,尽管移动端也可以直接通过Socket与服务端通信,但借助WebSocket,可以利用80(HTTP)或(HTTPS)端口通信,有效的避免一些防火墙的拦截。

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

2.2WebSocket的特点

WebSocket的数据传输,是基于TCP协议,但是在传输之前,还有一个握手的过程,双方确认过眼神,才能够正式的传输数据。

WebSocket的握手过程,符合其Web的特性,是利用HTTP本身的协议升级来实现。

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

以下是一些WebSocket的URL例子:

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

PING:主动保活的PING帧;PONG:收到PING帧后回复;CLOSE:主动关闭WebSocket连接;更多WebSocket的协议细节,可以参考《WebSocketProtocol规范》,具体细节,有机会为什么再开单篇文章讲解。

了解这些基本知识,我们基本上就可以把WebSocket使用起来,并且不会掉到坑里。

我们再小结一下WebSocket的特性:

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

三、WebSocket之OkHttp

3.1建立WebSocket连接

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

我想熟悉OkHttp的朋友,对上面这端代码不会有疑问,只是URL换成了ws协议标识符。另外,还需要配置pingInterval(),这个细节后文会讲解。调用newWebSocket()后,就会开始WebSocket连接,但是核心操作都在WebSocketListener这个抽象类中。

3.2使用WebSocketListener

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

在WebSocketListener的所有方法回调中,都包含了WebSocket类型的对象,它就是当前建立的WebSocket连接实体,通过它就可以向服务端发送WebSocket消息。

如果需要在其他时机发送消息,可以在回调onOpen()这个建立连接完成的时机,保存webSocket对象,以备后续使用。OkHttp中的WebSocket本身是一个接口,它的实现类是RealWebSocket,它定义了一些发送消息和关闭连接的方法:

send(text):发送String类型的消息;send(bytes):发送二进制类型的消息;close(code,reason):主动关闭WebSocket连接;利用这些回调和WebSocket提供的方法,我们就可以完成WebSocket通信了。

3.3MockWebSocket

有时候为了方便我们测试,OkHttp还提供了扩展的MockWebSocket服务,来模拟服务端。

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

MockWebServer的使用也非常简单,只需要利用MockWebSocket类即可。

MockWebSocket服务端,依然需要用到我们前面讲到的WebSocketListener,这个就比较熟悉,不再赘述了。之后就可以通过mMockWebSocket获取到这个Mock的服务的IP和端口。

需要注意的是,这两个方法需要在子线程中调用,否者会收到一个异常。

虽然有时候在服务端完善的情况下,我们并不需要使用Mock的手段,但是在学习阶段,依然推荐大家在本地Mock一个服务端,打一些日志,观察一个完整的WebSocket连接和发送消息的过程。

3.4WebSocket如何鉴权

接下来我们聊聊WebSocket连接的鉴权问题。

所谓鉴权,其实就是为了安全考虑,避免服务端启动WebSocket的连接服务后,任谁都可以连接,这肯定会引发一些安全问题。其次,服务端还需要将WebSocket的连接实体与一个真是的用户对应起来,否者业务无法保证了。

那么问题就回到了,WebSocket通信的完整过程中,如何以及何时将一些业务数据传递给服务端?当然在WebSocket连接建立之后,立即给服务端发送一些鉴权的数据,必然是可以做到业务实现的,但是这样明显是不够优雅的。

前文提到,WebSocket在握手阶段,使用的是HTTP的协议升级,它本质上还是HTTP的报文头发送一些特殊的头数据,来完成协议升级。

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

那么实际我们在WebSocket阶段,也可以通过Header传输一些鉴权的数据,例如uid、token之类,具体方法就是在构造Request的时候,为其增加Header,这里就不举例说明了。

另外WebSocket的URL也是可以携带参数的。

wss://cxmydev.

1
查看完整版本: WebSocket细节,包括鉴权和