背景
openstf是一款优秀的手机集群管理平台,功能强大、性能优秀、扩展性很高,可以大幅度提高手机的使用效率,使用nodejs和angularjs开发,遵循apache licene2.0开源协议,用户可以对源码进行修改发布。(源码地址:https://github.com/openstf)
使用openstf平台可以方便的对手机进行远程管理、调试、远程手机桌面监控等操作,但是这一切都建立在平台的网络带宽环境非常好的情况下,stf对网络带宽环境要求非常高。为什么存在这种情况,看下图:
在本地搭建一台stf平台服务器,用户从另一台PC访问stf平台对手机进行管理。stf 服务器端通过流量监控软件发现单个访问的上行流量达到3.22MB/s,最高可以到达5MB/s以上。实际上的带宽占用达到5*8=40Mbps,通常情况下平台上不可能只有一个用户,按照10个用户同时使用stf服务的情况下带宽要求达到400Mbps(stf服务的带宽和普通web服务不一样,stf服务的带宽是持续占用的,后续说明)。对于局域网用户来说这个带宽要求勉强达到,但是对于把stf服务发布到公网提供给外部用户使用这种情况带宽成本就太高了,现在电信机房的20M上行带宽成本为每年10万左右。在局域网中同时支撑20个以上的用户同时访问时局域网带宽要求也非常高,不可能为了搭建一个服务改造局域网。为了兼顾用户体验和带宽成本只能从优化stf的数据传输量上找方法了。
问题分析
已经知道 stf服务对带宽占用很高,现在需要找到为什么会占用如此高的带宽,在分析带宽的时候肯定要用到抓包工具。使用chrome自带的devtool工具对stf服务进行抓包,发现stf 的web端会不停的从浏览器缓存中读取图片格式的文件。
对这些图片格式的文件分析后发现这些图片就是被管理的手机的屏幕截图
再查看抓包结果发现有个websocket的长连接一直在从后端推送二进制文件文件到web端,初步推断刚才图片格式文件就是这个websocket连接推送到前端的,前端收到二进制数据后转换成图片格式文件,再把图片展示到web界面上。
为了证明以上推断,关闭手机端的屏幕,这时候发现websocket 完全停止传输数据了,监控流量发现流量基本上为0KB/s。
现在可以断定就是这个websocket服务为了把手机端的屏幕截图实时传输给web端从而占用了大量的网络带宽。这也说明了为什么在stf平台操作手机时网络带宽是持续占用而不是像普通web服务那样有请求才占用带宽。
要减少websocket 的流量需要从源头优化,减少所传输文件的数据量,这里是一方面需要减小原始手机屏幕截图的文件大小,另一方面减少文件传输的个数。如何减少文件大小,有两个思路:降低图片质量或者降低图片分辨率。如何减少文件个数:这里只能降低图片传输的帧数。解决思路已经确定了,接下来就要分析代码、分析数据流从而优化代码流程达到效果。
工作原理
前面分析到web端的手机屏幕同步来自于对手机屏幕端的同步截图,那么stf截图并发送给web端就可能存在三种方案:
1. 手机端截图后传给stf服务端,stf服务端再传给stf web端
2. stf平台直接截图后传给stf web端
3. 手机端截图后直接传给stf web端
通过抓包发现与stf web端通讯的websoket的服务地址是 stf服务器的而不是手机的,同时考虑到手机直接传数据给stf web端对手机网络环境的限制(手机和stf 用户需要在同一网络这会严重限制平台使用环境)的要求,这就排除了第三种可能性。接下来就是查资料读代码了,通过官网资料得知,stf使用了minicap这个截图工具。minicap 是一款开源的anroid截屏工具,minicap使用c++开发工作在android ndk层,通过实时抓取手机的显示缓存的数据生成二进制的图片文件信息,因此截图效率极高(android手机屏幕刷新频率有多快minicap截图频率就有多快),这保证了stf在同步android手机的屏幕时高质量、高帧数,同时也导致了很高的带宽占用。这样就确定了stf同步手机屏幕用的是方案一。
针对方案1的优化方案有两种:
A. 修改minicap源码,在android端生成图片数据时就降低图片的大小
B. 在minicap把截图传给stf服务端后,在stf服务端降低图片大小后再传给web端
在对minicap的源码进行研究后就放弃了方案A,minicap源码工程大,逻辑太复杂,同时修改后还要考虑到不同手机的兼容性问题,总之就是工作量大、太复杂。相对的方案B可行性就高很多,只需对stf服务端代码进行修改而不用考虑各种手机的兼容性,而且stf服务端使用的nodejs脚本开发难度小很多。
确定了在stf服务端对图片大小进行降低后再传给web端这种方式接下来需要找到stf端接收minicap数据的逻辑了。在对stf工作流程和代码进行研究后,在plugins/streen/stream.j下找到了stf接收minicap数据的逻辑。
具体逻辑如下:
在手机连接到stf平台后,stf 会加载stream.js,stream.js会建立一个websocket的服务(这个服务是提供给web端的),在建立websocket服务的时候会启动一个websocket的监听者,同时启动一个minicap的实例,stf web端连接websocket服务后会启动一个websocket会话,websocket监听者监听到有屏幕同步信息时就会给minicap的实例发消息,minicap接收消息后开始截屏,在minicap实例截屏后直接通过websocket会话发送给你stf web端,这样stf web端就可以获取到手机的截图了。工作流程图如下:
由上面的流程图可以看出FrameProducer类是对minicap的原始数据进行处理的,stream.js上有这段代码
通过查阅官方代码发现这里的-Q参数可以调整minicap抓取的图片质量
再看下这个- Q参数的默认值,在stream.js 的启动函数找到了,如果没有人为设置这里的默认值是80,也就是说原始图片质量的80%
.option('screen-jpeg-quality', {
describe: 'The JPG quality touse for the screen.'
, type: 'number'
, default: process.env.SCREEN_JPEG_QUALITY || 80
})
在使用stf管理手机的时候我们主要是操作手机,对手机的显示效果并不关注,因此完全可以把显示质量降低到可用范围从而减少流量,直接把图片质量降低到50%
通过实际使用发现stf web 端50%的显示质量完全可以接受,参考下对比图:左边图片质量为80%,右边图片质量为50%
再看下这时候的实际网络带宽占用对比:左边图片质量为80%,右边图片质量为50%
流量从3.22MB/s降低到了692KB/s,效果很明显了,但是692KB/s相对来说还是不够理想。如果再降低图片质量就不太合适,只能通过降低图片数量来继续降低总的流量了。继续研究stream.js 最终找到了减少传输帧数的方法:在websocket的ws会话发送图片二进制数据到web端时丢弃1/3的数据,修改方式如下
降低帧数前后的对比效果
降低帧数后的前后流量对比
通过降低帧数数据流量从692KB/s降低到了468KB/s,实际使用中降低帧数后的体验并没有相差很远,因为工作在PC 端的浏览器上17帧的刷新屏幕也是够流畅了。
总结
下图是整个优化过程中带宽占用的变化:未优化、只优化图片质量、优化图片质量和帧数
带宽占用最终降低了10倍左右,整个优化过程中代码修改很少,修改的地方也非常简单。但是如果不对整个stf工具的前后端流程和代码进行研究,不了解底层相关的工作原理还是很难下手的。通过对前端的抓包后端的数据流分析,最终在修改了很少源码情况下起到了立竿见影的效果,节省了10倍的带宽。
展望
从3MB/s到400KB/s的带宽占用这个效果是挺明显的,但是实际上400KB/s的带宽占用还是不够优秀。上面的优化方案已经把stf平台前端显示质量和刷新频率降低到了刚好可用的状态,现在如果还要继续降低带宽占用肯定不能继续用这种方法了,有没有其他方案?在继续研究流量优化方案时,数据流媒体方案展示出来。能否做到在保证用户体验的情况下把3.2MB/s的带宽占用降低30倍达到100KB/s左右?答案是肯定的。下一篇文章“基于流媒体技术的stf平台流量优化方案”。