最佳实践 | 使用WebSocket做个实时人脸活体比对服务

2023-04-06 14:47:11 浏览数 (2)

人脸核身产品为了提升用户体验、提高验证速度、提高验证过程中的安全性,会引入一些实时通信技术实时地提醒用户调整姿态,引导用户做活体动作,同时实时地做活体检测。人脸核身使用了两种实时通信技术——WebSocket与WebRTC。

本文将主要介绍一下,应用在人脸核身浮层活体中的WebSocket。

浮层活体使用的核心技术——WebSocket

在浮层活体中,我们主打的特点就是“实时”——实时检测人脸距离、人脸遮挡等。在WebSocket诞生前,浏览器需要通过HTTP请求的方式去跟服务端索要数据。尽管后续的HTTP版本支持了或者聪明的开发者实现了各种“准实时”的索要数据的方案:轮询、长轮询、长连接等。但这些方式都离不开Request/Response对,即需要浏览器发起请求,服务器才有资格发送响应。

轮询与长轮询

最开始的“实时”并非真正的实时,而是由客户端每隔一段时间询问一下服务端是否有新数据产生,而客户端的轮询间隔决定了数据有多实时。

轮询的过程如下:

  1. 客户端发起请求
  2. 服务端马上响应,不论有无新数据。
  3. 等待n秒(即一个轮询间隔)后,客户端再次发起请求。
  4. 服务端依旧马上响应。
  5. 如此往复。

可以看到如果数据更新出现在两次轮询之间(一般来说,轮询间隔都是以秒为单位,所以数据几乎都会出现在两次轮询之间),那么最新的数据会经历一定的延迟才能送达。

于是,聪明的开发者就发明了一个长轮询方案。

长轮询的过程如下:

  1. 客户端发起请求。
  2. 服务端不马上响应,而是等待到数据更新到达后才响应客户端。(当然,一定的等待时间后还是没有数据更新的话也是会响应的。)
  3. 客户端处理响应后,马上发起下一个长轮询请求。
  4. 如此往复。

与轮询相比,长轮询的优势就在于,数据更新几乎没有延迟就能送达到客户端。同时也减少了客户端与服务端建立连接的次数,降低了连接建立的开销。

短连接与长连接

轮询与长轮询常常也会跟短连接长连接比较。总的来说,短链接就是每次请求都会建立一个新的TCP连接用于通信;而长连接则是多次请求复用同一个TCP连接。

然而,不论是短连接还是长连接、轮询还是长轮询,所有的数据更新均需要客户端发起请求索要,服务端方能发送。但是在浮层活体过程中,有很多数据更新是分批到达的,而且需要及时送达到客户端,所以需要一种更实时的通信方式。

WebSocket

WebSocket为浏览器与服务端之间提供了一种双向通信的能力。与Socket类似,它是一种基于TCP连接的应用层协议。使用HTTP协议进行连接,连接建立成功后,双端就可以主动地向对方发送信息。

WebSocket是怎么建立连接的?

借助HTTP协议,将HTTP所依赖的TCP连接抢到手,套上自己的面具,用自己的协议进行通信。

为了保持兼容HTTP服务端,WebSocket选择使用HTTP协议来建立连接。首先,客户端会发送一个HTTP Upgrade 请求,请求upgrade协议:

代码语言:txt复制
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:

  1. Upgrade:upgrade是HTTP1.1中用于定义转换协议的header域。它表示,如果服务器支持的话,把当前应用层协议切换一下,但是所基于的TCP连接不动。例如换成WebSocket、HTTP2.0.

服务端收到一个协议切换请求后,就会自身能力做一个判断,如果支持该协议的话,就会回复一个Upgrade成功的Response——Switching Protocols:

代码语言:txt复制
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。

代码语言:javascript复制
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 使用了自定义的二进制分帧格式,将每个应用消息切分成一个或多个帧,对端等到接收到完整的消息后再进行组装与处理。

代码语言:txt复制
  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没有粘包,粘包是不合理的应用层协议设计导致的问题。
  • opcode。占4bit。
    • 8表示close(关闭连接)帧,主动关闭连接时需要发送这个控制指令。否则websocket会报1006错误,这个错误码可以用于区分连接是正常关闭的,还是其他异常情况。
    • 9表示ping帧,10表示pong帧。ping/pong机制是为了在长时间无消息通信时,检测连接是否断开。目前只能由服务器发ping给浏览器,浏览器返回pong消息。浏览器目前没有开放发送控制指令的接口。

利用WebSocket实现一个简单的实时比对服务

我们可以简单地使用人脸检测与分析接口与人脸比对接口做一个实时的人脸检测与比对服务。

AI能力方面,我们会使用到腾讯云提供的两个接口人脸检测与分析接口与人脸比对:

  • 人脸检测与分析接口用于检测人脸位置与人脸遮挡,根据接口返回,提示用户调整姿态。
  • 人脸比对接口用于对前端传入的截帧与服务端存储的比对照进行比对,得出一个相似度,用于判断是否同一人。

前端方面,我们使用getUserMediaAPI打开摄像头用于获取视频流;使用WebSocketAPI与服务端建立WebSocket连接。连接建立成功后,就可以从视频流中截取帧,发送到服务端进行检测。

服务端方面,我们可以用Nodejs ws这个npm包搭建一个简单的WebSocket服务端。服务端接到截帧之后就可以调用腾讯云提供的接口进行检测与验证。

体验浮层活体

人脸核身浮层活体也是基于上述方案实现的一个实时活体检测方案,同时还处理了更多的细节问题,让体验更丝滑。可以按照下面的步骤申请与体验人脸核身浮层活体服务。

1. 开通人脸核身服务

在腾讯云官网了解到 腾讯云AI 人脸核身 产品,点击申请免费试用即可体验。

2. 申请业务流程,获取RuleId

人脸核身服务开通成功后,就可以到控制台创建业务流程:https://console.cloud.tencent.com/faceid

选择“微信H5(浮层/普通模式)”,输入测试用的公众号名称后,点击下一步。

随后按控制台的提示填写相关信息。

申请完成之后,在控制台处查看自己的RuleId。

3. 调用前置鉴权接口,获取体验连接

我们可以使用API Explorer来调用实名核身鉴权接口,获取体验连接。RuleId入参填入上一步申请到的RuleId。点击发起调用。

4. 用微信打开体验连接

调用DetectAuth接口成功后,回包中有一个URL,使用微信打开,即可体验。

代码语言:json复制
{
    "Response": {
        "BizToken": "CE661F1A-0F1E-45BD-BE13-34C05CEA7681",
        "Url": "https://xxxxxxxxxxxxx",
        "RequestId": "f904f4cf-75db-4f8f-a5ec-dc4f942c7f7a"
    }
}

参考文档

  • WebSocket介绍及其go实现
  • 刨根问底HTTP和WebSocket协议

0 人点赞