前言
WebAssembly是一种可以在浏览器端运行二进制格式代码的技术,它的目标则是想提供接近Native code的执行效率的技术体验,自 2015 年出现后一直受到广泛关注。对于网络平台而言,WebAssembly提供了一条途径,使得以各种语言编写的代码都可以以接近原生的速度在Web中运行,具有巨大的意义。这一特性为前端密集计算场景提供了无限的可能。WebAssembly 技术现在已经成为 W3C 的标准,众多浏览器厂商已经提供了对其 MVP 版本特性的支持。
本文对WebAssembly技术进行了深入研究,并以证券开户系统中H5开户业务为例,探索了该技术在实际业务场景中的落地方案。
01什么是Webassembly
随着JavaScript的快速发展,目前它已然成为最流行的编程语言之一,这背后正是 Web 的发展所推动的。但是随着JavaScript被广泛的应用,它也暴露了很多问题:
l 语法太灵活导致开发大型Web项目困难
l 性能不能满足一些场景的需求。
为了解决这些问题,出现一些很成功的项目,比如微软的TypeScript语言、谷歌的Dart语言,Mozilla(Firefox) 的asm.js,但是都没有真正的解决问题,或者得不到一致支持。所以在2015年,W3C 牵头推出了WebAssembly。简单的说,WebAssembly并不是一种编程语言,而是一种新的字节码格式。与 JavaScript 需要解释执行不同的是,WebAssembly字节码和底层机器码很相似可快速装载运行,因此性能相对于 JavaScript 解释执行有了很大的提升。
WebAssembly由W3C WebAssembly工作组维护, 最新的主流浏览器Chrome,Firefox,Safari,Edge,移动浏览器都对其提供了支持。各大浏览器兼容性参考下图:
WebAssembly 的设计目标:定义一个可移植,体积紧凑,加载迅速的二进制格式为编译目标,而此二进制格式文件将可以在各种平台(包括移动设备和物联网设备)上被编译,然后发挥通用的硬件性能以原生应用的速度运行。
WebAssembly编译目标:顾名思义,只要通过特定的编译器,你就能将你自己惯用的语言编译成WebAssembly,然后执行在浏览器上。目前可以通过Emscripten(LLVM to JS compiler)来编译C / C ++的程序。
02技术原理
2.1 WebAssembly架构
WebAssembly的执行包括两部分:编译器前端和后端。前端部分实现将高级语言(C、C++和Rust)编译成LLVM IR码。后端负责将LLVM IR编译成各架构(x86,AMD64,ARM)对应的机器码。
实际中常用的是Emscripten,它可以将C和C++应用程序移植到WebAssembly,原生C/C++代码编译为两个文件.wasm文件和.js文件。
wasm文件包含实际的WASM代码,.js文件包含允许JavaScript代码运行WASM的所有框架。
一份典型的.wasm文件如下所示:
Emscripten还支持很多的任务,比如将OpenGL调用转换为WebGL;为DOM API和其他浏览器和设备API提供绑定;提供可在浏览器中使用的文件系统实用程序等等。默认情况下,这些东西不能直接在WebAssembly中访问。
2.2 技术优势
wasm 文件与 Javascript 生成机器码的过程不同,如下图所示,Javascript 文件生成机器码需要经过语法解析,代码优化,最后才转换成机器码等过程,而wasm的优势是本身就是通过编译器并优化过后的二进制文件,可以直接转换为机器码,省去了Javascript需要解析,优化的工作,所以在加载和执行上本身就具有优势。
WebAssembly优势总结如下:
1、体积小:由于浏览器运行时只加载编译成的字节码,一样的逻辑比用字符串描述的 JS 文件体积要小很多;
2、加载快:由于文件体积小,再加上无需解释执行,wasm 能更快的加载并实例化,减少运行前的等待时间;
3、兼容性问题少:wasm 是非常底层的字节码规范,制订好后很少变动,就算以后发生变化,也只需在从高级语言编译成字节码过程中做兼容。可能出现兼容性问题的地方在于 JS 和 wasm 桥接的 JS 接口。
4、安全性好:wasm 描述了一个内存安全的沙箱执行环境,可以在现有的JavaScript虚拟机中实现。 当嵌入到Web中时,wasm 将强制执行浏览器的同源和权限安全策略。
03如何使用
3.1开发工具
l AssemblyScript:支持直接将TypeScript编译成WebAssembly。
l Emscripten:可以说是WebAssembly的灵魂工具不为过。将其他的高级语言,编译成WebAssembly。
l WABT:是个将WebAssembly在字节码和文本格式相互转换的一个工具,方便开发者去理解这个wasm到底是在做什么事。
3.2哪些语言适合编译为wasm
l C\C++:官方推荐的方式
l Rust:语法复杂、学习成本高,对前端来说可能会不适应。
l Kotlin:语法和 Java、JS 相似,语言学习成本低。
l Go语言:又称Golang,语法和c类似
l Java语言:TeaVM 现已支持将 Java 字节码编译成 WebAssembly
3.3移植一个C/C++程序
简单来说,编译前端 LLVM / Emscripten 流程可以获得 wasm 文件和胶水 js。然后,通过胶水 js 来加载 wasm 并转为 arrayBuffer 格式。紧接着进行编译和实例化后,即可用 JavaScript 与 WebAssembly 通信。下图为常见的使用步骤:
Step1:编写C语言模块// add.c
1. // add.c
2. #include <emscripten.h>
3. // 实现一个加法
4. EMSCRIPTEN_KEEPALIVE
5. int add(int a,int b) {
6. return a+b;
7. }
Step2:编译wasm,指定输出文件为add.js否则默认输出a.out.js
1. emcc add.c -o add.js –s
当看到add.js和add.wasm文件就说明成功了。add.wasm文件可以说是wasm文件,add.js就是wasm和js文件交互的桥梁。生成文件如下所示:
add.js
1. var Module = typeof Module !== 'undefined' ? Module : {};
2. var moduleOverrides = {};
3. var key;
4. for (key in Module) {
5. if (Module.hasOwnProperty(key)) {
6. moduleOverrides[key] = Module[key];
7. }
8. }
9.
-
Module['arguments'] = [];
-
Module['thisProgram'] = './this.program';
-
Module['quit'] = function(status, toThrow) {
-
throw toThrow;
14. };
15. ……
add.wasm
1. 0061 736d 0100 0000 0143 0b60 037f 7f7f
2. 017f 6003 7f7e 7f01 7e60 017f 017f 6001
3. 7f00 6002 7f7f 017f 6000 017f 6002 7f7f
4. 0060 0000 6004 7f7f 7f7f 017f 6004 7f7f
5. 7e7f 017e 6005 7f7f 7f7f 7f01 7f02 d003
6. 1603 656e 7612 6162 6f72 7453 7461 636b
7. 4f76 6572 666c 6f77 0003 0365 6e76 0b6e
8. 756c 6c46 756e 635f 6969 0003 0365 6e76
9. 0d6e 756c 6c46 756e 635f 6969 6969 0003
-
0365 6e76 0d6e 756c 6c46 756e 635f 6a69
-
6a69 0003 0365 6e76 075f 5f5f 6c6f 636b
-
……
Step3: web端验证
1. // test.js
2. // 如果要解注,编译的的使用请使用 emcc add.c -o add.js -s -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]'
3. let addModule = require('./add.js');
4. // let add = addModule.cwrap('add', 'number', ['number','number']);
5. addModule.onRuntimeInitialized = function() {
6. // console.log(add(1,2))
7. // console.log(addModule.ccall('add', 'number', ['number','number'], [3,4]));
8. console.log(addModule._add(5,6))
9. }
可以直接用node运行,结果如下:
1. node test.js # 结果11
04在证券开户中的应用探索
Web中高性能需求的应用等都可以利用WebAssembly技术获得接近于原生的性能, 比如:在线游戏、音乐、视频流、AR/VR、平台模拟、虚拟机、远程桌面、压缩及加密等。目前已经有很多公司用它来在网上提供更好在线服务。比如知名CAD设计软件,AutoCAD发布了在Web应用程序中运行的流行设计产品,使用WebAssembly呈现其复杂的编辑器,该编辑器(从桌面客户端代码库迁移)是使用C++构建的。
目前证券H5开户系统中涉及到人脸检测,视频加密,压缩等对性能要求较高,利用传统的web技术很难达到性能标准,可以利用WebAssembly技术将这一部分的功能由性能较高的C/C++进行实现。单向视频录制,是指客户在开户过程中,通过移动终端,单向录制一段个人视频,视频过程中根据提示正确宣读开户意愿信息,以确认为本人操作开户申请。
4.1单向视频人脸检测
H5开户流程单向视频录制过程中,需要准确检测人脸,利用C语言开发的opencv库可以很简单实现,但是利用web技术没有很好的方案。借助WebAssembly技术,我们可以将opencv人脸检测的C语言库编译为wasm及胶水代码opencv.js,由H5页面端调用,这样便可以提高人脸检测效率。
WebAssembly的出现使得 JavaScript 开发者可以高效便捷的使用 OpenCV 提供的图形处理算法,也就是说开发者仅凭借浏览器就能快速开发诸如图片风格美化、图像识别、OCR等功能的应用。
以下为利用opencv.js实现人脸检测的示例代码:
1. let src = cv.imread('canvasInput');
2. let gray = new cv.Mat();
3. cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0);
4. let faces = new cv.RectVector();
5. let faceCascade = new cv.CascadeClassifier();
6. // load pre-trained classifiers
7. faceCascade.load('haarcascade_frontalface_default.xml');
8. // detect faces
9. let msize = new cv.Size(0, 0);
-
faceCascade.detectMultiScale(gray, faces, 1.1, 3, 0, msize, msize);
-
for (let i = 0; i < faces.size(); ++i) {
-
let roiGray = gray.roi(faces.get(i));
-
let roiSrc = src.roi(faces.get(i));
-
let point1 = new cv.Point(faces.get(i).x, faces.get(i).y);
-
let point2 = new cv.Point(faces.get(i).x + faces.get(i).width,
-
faces.get(i).y + faces.get(i).height);
-
cv.rectangle(src, point1, point2, [255, 0, 0, 255]);
-
roiGray.delete(); roiSrc.delete();
-
}
-
cv.imshow('canvasOutput', src);
-
src.delete(); gray.delete(); faceCascade.delete();
借助编译后的opencv.js,利用haar分类器,可以准确检测人脸,运行结果如下:
4.2单向视频加密
H5开户过程中,需要将客户录制的单向视频进行加密并上传云存储,Web前端视频加密存在2个问题,一是性能比不上客户端加密,二是密钥暴露在web端容易被破解,很不安全。借助WebAssembly技术,我们便可以解决上述2个问题:
1、加密视频的程序交给C语言来实现,转成wasm由web端调用,提高了加密的性能; 2、加密密钥写在C语言程序中转成二进制后,暴露在web端也无法被破解,大大提高了安全性。
H5开户中视频加解密架构图如下所示:
总结
WebAssembly 的出现,使得前端不再只能使用 JavaScript 进行开发了,C、C++、Go 等等都可以为浏览器前端贡献代码。WebAssembly给web带来了更好的性能,也带来了更多的可能,随着WebAssembly的技术越来越成熟,势必会有更多的应用,从Desktop被搬到Web上,这会使本来已经十分强大的Web更加丰富和强大。