因为业务需求,接触了ws协议以及在实时语音识别中的运用,总体的感觉还是挺有意思,并且了解到很多人其实是没有用过这个协议的,所以还是值得分享记录一下。
本文将分为以下几个模块去讲述WebSocket,1.以实际场景引入来分析传统方式实现实时数据传输存在的弊端,由此引出ws的作用和特点。2.介绍ws协议的工作流程,握手过程以及对于心跳机制的一些理解。3.结合实际的业务场景去看看ws协议是怎么使用的,并且实现了什么样的价值。
实际场景
大家在玩股票的时候,不知道有没有仔细的想过一个问题,它这个价格的实时变动是怎么做的呢?说两种比较常见的方式,第一种方式就是今天要讲的ws协议,还有一种方式是grpc框架的服务端流模式,我们平时soa的接口其实是grpc一种最常见的简单模式,他还有几种方式,如:客户端流模式,服务断流模式,客户端服务端流模式,这几种方式后面大家都可以了解一下。那么在没有ws或者grpc服务端流模式的时候,最古老的方式是怎么去做实时的响应呢?如图示:
http短轮询:
以周期性的方式,去不断地访问服务端,问服务端是否有结果给返回,现在两种情况,假设访问的周期过长,那么实际上已经对实时性产生了影响,假如访问的周期很短,不断的请求是很容易造成资源浪费的,因为并不是每次的请求都能拿到实时的数据。
http长轮询:
作为短轮询的一种演进方式,让请求在服务端去等待,直到拿到结果,或者请求时长达到了超时时间自动断开,这种演进可以说是在一定程度上减少了请求的频次,假设服务端无更新,就不会一直重新发起请求。但其实缺点依旧明显,如果sever不更新的话,这个请求就会一直在服务端hold住,依旧是资源浪费。
总结下:其实这两种方式在client数量很多的时候,是存在连接数容易被顶满的风险的。
那么我们希望有一种全双工的通信协议,使得服务端和客户端都能够主动或者被动的发送或者接受消息,并且能够减少类似于http请求的频次,发送数据的时候又不会每次都需要重新建立连接上下文(如请求头),那ws协议正好能满足这些要求。
ws协议的出现其实是为了弥补http请求存在的缺陷的,主要解决了http的两个痛点。1.http协议的被动性,因为http协议他是个请求-响应模型,一个请求触发一个响应,在没有请求的前提下,响应不会被触发。这就是被动性。2.http的无状态性,所谓无状态性,其实就是每次请求-响应完成,其实他的生命周期已经结束了,如果开始下一个生命周期,那必须要重新建立连接上下文。
很多人会问,http1.1不是支持长连接吗,为何每次连接还是要重新建立连接上下文呢?其实http的长连接他是一个"伪"长连接,他的长连接只是基于一个tcp通道的复用而已,而应用层的http连接依旧是需要每次连接断开,再连,断开,再连。
其实说到这里大家应该很明白,ws的特点和优势在于哪里了,那下面就主要介绍下,ws协议的基本工作流程,握手流程以及对于ws协议应用层心跳机制的一些理解。
ws协议介绍
总体说下ws的工作流程,ws协议需要利用http进行一次握手,那么首先就得进行三次握手进行tcp连接,然后在进行http的握手操作,在握手完成之后其实就没有http的什么事情了,这里ws连接就可被建立,sever端和client端都可进行OnMessage的消息互传,完成以后,server和client都可以进行主动或者被动的关闭连接,触发tcp四次挥手。
关于ws协议的握手:
关于ws协议心跳机制:
这里抛两个问题,其一,我们都知道tcp他自身是存在keep-alive机制的,那为何我们还需要去做应用层的心跳检测(原因如123),这里得解释下,这个keep-alive和http那个keep-alive不是一个概念,tcp的keep-alive是为了检测client和server是否处于存活状态。
1.第一个原因是tcp本身默认的keep-alive-time是2h,这也就以为着,当两端无数据传输之后,要等待两个小时,然后keep-alive-probe告诉tcp检测断开的次数,并且中间必须要间隔keep-alive-inval的时间,就从这个过程来说是否流程太长又太复杂。
2.网络本身其实是很不稳定的,当client遇到断电,断网的时候,就很容易出现tcp假死的情况,客户端其实已经挂了,而服务端这个时候还认为这个连接活着,于是依旧不断去发送数据。
3.java的封装机制使得在应用层去操作传输层不太方便。
其二,当客户端异常断开的时候,各端的处理机制,如图表:
client | sever处理方式 | client处理方式 | 处理思路 |
---|---|---|---|
进程被杀死 | 触发onerror和onclose方法 | / | 此时端口被关闭,触发tcp四次挥手。外部断开的tcp会触发异常 |
断电断网 | 检测client最后心跳上报时间 | 触发onclose | 如果无心跳检测机制,此时网络恢复,客户端发起重连,那么就是一个新session,假死的tcp依旧在发数据,相反,server可及时关闭旧session |
ws在语音识别中的运用
先介绍下业务背景流程:
当用户在打开页面的时候,麦克风其实就已经在监听的状态了,当说出唤醒词的时候,功能被唤醒,后面需要去指挥这个功能去干点什么事情,比如开锁,关锁,导航等命令词,前端会进行音频的分包切片传输,在通一个通道发送给asr识别引擎,后经过一个规则过滤或模型增强(针对特殊场景的过滤和优化),最后识别出指令,触发相关的动作。
前期踩过的坑
前期是直接和服务端对接的,是希望以soa的方式去提供服务,前期尝试的方式如下图,两种方式,一种方式是忽略了语音数据他其实是个连续的并且存在上下文关系特征的数据,第二种方式是耗时太久了,这种架构一旦设计起来,问题也会一堆。
现在的问题是,假设我们要去用soa这种方式去调用接口,我们要做些什么?第一,保证一个用户的所有pcm切片是有序传输的。第二,要自己去做负载,保证一个用户所有的pcm片是打到一台asr引擎上的,综合这两点,成本其实很高,那其实就是以soa这种方式其实已经不合适了。
系统的整体设计
用户首先经过一层的握手鉴权,然后进行录音采集,并且分包发送,数据是在一个通道传输的,最后是经过asr引擎的识别,随着输入的不断完善,识别的出的结果也在不断完善,并且可以根据后面的语境去纠正之前的识别结果,最后我们会把相关的数据进行存储,以便后期的模型强化。这里以两个用户为例的目的是为了说明,两个用户是各自拥有自己的通道和task的,互不干扰。
WS-JAVA服务端的实现方式(两种)
1.基于tomcat的websocket实现,注解@ServerEndPoint("url"),几个重要的方法:onopen,onerror,onclose,onmessage。缺点:握手拦截如鉴权,比较困难。相应的功能如图:
2.基于Springboot的WebSocket实现,几个重要的方法:afterConnectionEstablished,handleMessage,handleTransportError,afterConnectionClosed。优点:能够在握手前后,拓展自己的业务逻辑。分别对应于tomcat的onopen,onmessage,onerror,onclose。
各个方法的执行顺序:
(本文作者:张俊杰)
本文系哈啰技术团队出品,未经许可,不得进行商业性转载或者使用。非商业目的转载或使用本文内容,敬请注明“内容转载自哈啰技术团队”。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。