人脸核身产品为了提升用户体验、提高验证速度、提高验证过程中的安全性,会引入一些实时通信技术实时地提醒用户调整姿态,引导用户做活体动作,同时实时地做活体检测。人脸核身使用了两种实时通信技术——WebSocket与WebRTC。
本文将主要介绍一下,应用在人脸核身浮层活体中的WebSocket。
浮层活体使用的核心技术——WebSocket
在浮层活体中,我们主打的特点就是“实时”——实时检测人脸距离、人脸遮挡等。在WebSocket诞生前,浏览器需要通过HTTP请求的方式去跟服务端索要数据。尽管后续的HTTP版本支持了或者聪明的开发者实现了各种“准实时”的索要数据的方案:轮询、长轮询、长连接等。但这些方式都离不开Request/Response对,即需要浏览器发起请求,服务器才有资格发送响应。
轮询与长轮询
最开始的“实时”并非真正的实时,而是由客户端每隔一段时间询问一下服务端是否有新数据产生,而客户端的轮询间隔决定了数据有多实时。
轮询的过程如下:
- 客户端发起请求
- 服务端马上响应,不论有无新数据。
- 等待n秒(即一个轮询间隔)后,客户端再次发起请求。
- 服务端依旧马上响应。
- 如此往复。
可以看到如果数据更新出现在两次轮询之间(一般来说,轮询间隔都是以秒为单位,所以数据几乎都会出现在两次轮询之间),那么最新的数据会经历一定的延迟才能送达。
于是,聪明的开发者就发明了一个长轮询方案。
长轮询的过程如下:
- 客户端发起请求。
- 服务端不马上响应,而是等待到数据更新到达后才响应客户端。(当然,一定的等待时间后还是没有数据更新的话也是会响应的。)
- 客户端处理响应后,马上发起下一个长轮询请求。
- 如此往复。
与轮询相比,长轮询的优势就在于,数据更新几乎没有延迟就能送达到客户端。同时也减少了客户端与服务端建立连接的次数,降低了连接建立的开销。
短连接与长连接
轮询与长轮询常常也会跟短连接长连接比较。总的来说,短链接就是每次请求都会建立一个新的TCP连接用于通信;而长连接则是多次请求复用同一个TCP连接。
然而,不论是短连接还是长连接、轮询还是长轮询,所有的数据更新均需要客户端发起请求索要,服务端方能发送。但是在浮层活体过程中,有很多数据更新是分批到达的,而且需要及时送达到客户端,所以需要一种更实时的通信方式。
WebSocket
WebSocket为浏览器与服务端之间提供了一种双向通信的能力。与Socket类似,它是一种基于TCP连接的应用层协议。使用HTTP协议进行连接,连接建立成功后,双端就可以主动地向对方发送信息。
WebSocket是怎么建立连接的?
借助HTTP协议,将HTTP所依赖的TCP连接抢到手,套上自己的面具,用自己的协议进行通信。
为了保持兼容HTTP服务端,WebSocket选择使用HTTP协议来建立连接。首先,客户端会发送一个HTTP Upgrade 请求,请求upgrade协议:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
这就是一个很标准的HTTP Get请求的Request。里面有一个关键的Header:
- Upgrade:upgrade是HTTP1.1中用于定义转换协议的header域。它表示,如果服务器支持的话,把当前应用层协议切换一下,但是所基于的TCP连接不动。例如换成WebSocket、HTTP2.0.
服务端收到一个协议切换请求后,就会自身能力做一个判断,如果支持该协议的话,就会回复一个Upgrade成功的Response——Switching Protocols:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
至此,这个HTTP所基于的TCP连接就被复用为WebSocket的连接了。下面就是一个nodejs版本的websocket server demo。
httpserver.on('upgrade', function upgrade(request, socket, head) {
wsserver.handleUpgrade(request, socket, head, function done(ws) {
ws.on('message', (data, isBinary) => {
ws.send('message: ' + data + 'recieved!')
})
});
})
由于WebSocket的连接建立需要依赖HTTP协议,所以有很多同学会误以为WebSocket是基于HTTP协议的一个协议。但实际上,WebSocket在连接建立完成后,就跟HTTP没有任何关系了。它跟HTTP协议一样,都是基于TCP协议的一个应用层协议。
WebSocket的帧格式
WebSocket 使用了自定义的二进制分帧格式,将每个应用消息切分成一个或多个帧,对端等到接收到完整的消息后再进行组装与处理。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
主要介绍两个关键的字段:
- FIN。占1bit。表示后续是否还有帧。一个消息可能拆分成多个帧,接收方判断为最后一帧后将前面的帧拼接组成消息。TCP没有粘包,