在写这个系列第一篇文章的时候,有些朋友可能不知道wasm可以使用在什么场景,虽然在评论里面有说起,可是没有上具体的例子,这篇文章,使用一个具体的例子来示范下wasm的使用场景。这篇文章主要会讲如下几个方面的东西:html
md5 简单的说就是它是一种散列算法,在之前有不少网站或者系统,都是使用 md5 来加密的,md5 的特征是,任意长度的输入,它均可以给你生成一个128位的结果出来,并且只要输入不同,输出的结果确定不同(如今听说会有hash碰撞,不过咱们这里不讨论)。128位使用16进制的数字表示就是32位了。不过,在如今的系统中,应该是没有再使用md5来加密密码的了,由于 md5,使用如今的硬件或者机器的话,是能够被破解出来的。前端
关于 md5 的具体算法能够参考: zh.wikipedia.org/wiki/MD5linux
在mac下,可使用 md5 的命令获取指定文件的md5值:git
md5 文件路径
复制代码
在linux下可使用 md5sum 命令来获取:github
md5sum 文件路径
复制代码
下面来看下,在 Go 里面,怎么计算文件的 md5 值,直接来看下代码:golang
func main() {
s := time.Now();
// 打开文件
f, err := os.Open("1.mp4")
if err != nil {
log.Fatal(err)
}
defer f.Close()
// 实例化一个Hash对象
h := md5.New()
if _, err := io.Copy(h, f); err != nil {
log.Fatal(err)
}
e := time.Now();
fmt.Println(e.Sub(s));
// 计算,而且输出给定文件的md5结果
fmt.Println(fmt.Sprintf("%x",h.Sum(nil)))
}
复制代码
目录结构:web
运行结果:算法
在上面的代码中,咱们计算了1.mp4这个文件的 md5 的值,而且输出了计算 md5 值的耗时,能够看到耗时为55ms。这个文件的大小看下:bash
文件的大小为37M的样子。app
这里实际上是一个 Go 计算文件 md5 的 demo,后面会用来编译成 wasm,跟前端交互。这里暂且到这里先。
咱们知道,js 也能够计算文件的 md5 值,这里咱们使用一个比较成熟的开源库来作实例。
开源库的github地址是: github.com/satazor/js-…
但这里为了方便,咱们直接使用cdn上的库:
<!DOCTYPE html>
<html lang="en">
<body>
<form method="POST" enctype="multipart/form-data" onsubmit="return false;"><input id="file" type="file" placeholder="select a file"></form>
</body>
<script src="//cdn.rawgit.com/satazor/SparkMD5/master/spark-md5.min.js"></script>
<script>
var log=document.getElementById("log");
document.getElementById("file").addEventListener("change", function() {
// 记录计算开始时间
const s = Date.now();
var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
file = this.files[0],
chunkSize = 2097152, // read in chunks of 2MB
chunks = Math.ceil(file.size / chunkSize),
currentChunk = 0,
spark = new SparkMD5.ArrayBuffer(),
frOnload = function(e){
spark.append(e.target.result); // append array buffer
currentChunk++;
if (currentChunk < chunks)
loadNext();
else {
// 没有下一个chunk了,输出耗时
console.log('time:', Date.now() - s);
}
},
frOnerror = function () {
};
function loadNext() {
var fileReader = new FileReader();
fileReader.onload = frOnload;
fileReader.onerror = frOnerror;
var start = currentChunk * chunkSize,
end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
};
loadNext();
});
</script>
</html>
复制代码
这里的例子实际上是这个开源项目给的例子,我改了一下,加了一个耗时的输出,去掉了其余的提示,官方的demo地址为: 9px.ir/demo/increm…
使用上面的代码,咱们一样计算下刚刚那个1.mp4文件的md5,看下耗时:
从耗时来看,咱们发现,使用js来计算文件 md5 的话,比使用 Go 来计算文件的 md5 值,耗时多了差很少8倍。这对于C端的应用来讲,确定是没法接受的。因此,这也是为何,我能须要使用 wasm 的缘由。
若是你们用过百度网盘或者其余的相似的网盘,确定有秒传这样的功能。那网盘的秒传功能是怎么样实现的呢?难道真的是上传速度快,实现的吗?确定不是,由于可能你上传一个几十G的文件,也能够实现秒传。
秒传的实现其实很简单,就是利用文件的md5来跟云端的文件的md5作对比,若是相同,说明你要上传的这个文件,云端已经存在了,那么这个时候,就不须要上传了,直接标识上传完成就行,后面若是你须要下载,就提供云端的文件给你下载就行了。是否是很简单?
下面咱们来作一个实例,利用Go来计算文件的md5,经过前端页面来选择文件,而后将文件给到Go编译成的wasm去计算,算完以后,返回给到js使用。注意,这里并无要实现文件秒传的完整功能,由于须要和服务端交互,后面获取到md5以后,发送给服务端,服务端校验文件是否在云端存在,这些步骤,就再也不这篇文章说了,由于后面的内容,在前端这里,好像也没有什么能够再细说的了。若是有特殊的问题,能够再问我。
简单画了一个草图,js端用来选择文件,而后将文件传递给Go端,Go端计算好文件的md5值以后,再返回给js端,js端拿到结果以后,想干吗就随意了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form method="POST" enctype="multipart/form-data" onsubmit="return false;"><input id="file" type="file" placeholder="select a file"></form>
</body>
<script src="./wasm_exec.js"></script>
<script>
// 全局的target对象,供go端访问
var target = {
// go端会调用该方法来传递计算的结果
callback(md5) {
// 打印结果到控制台
console.log('文件的md5为:', md5);
}
}
document.getElementById("file").addEventListener("change", function() {
const file = this.files[0];
const go = new Go()
WebAssembly.instantiateStreaming(fetch('main.wasm'), go.importObject)
.then(async result => {
go.run(result.instance);
// 获取文件的ArrayBuffer对象
const buffer = await file.arrayBuffer();
// 转换为Uint8Array
const bytes = new Uint8Array(buffer)
// 调用target对象上的calcMd5方法(这个方法由Go提供,挂载到target上)
target.calcMd5(bytes);
});
});
</script>
</html>
复制代码
在上面的代码中,咱们监听了input的change事件,而且在这个事件的回调函数中,能够经过this.files访问到选择的文件对象,这里咱们直接取了files[0],表示获取第一个文件对象,若是你须要获取多个文件对象,能够本身改一下。下面的Go的wasm初始化代码,以前在第一篇文章的时候说过,这里就再也不说明了。 在下面的:
// 获取文件的ArrayBuffer对象
const buffer = await file.arrayBuffer();
// 转换为Uint8Array
const bytes = new Uint8Array(buffer)
复制代码
这里为何须要将ArrayBuffer对象转换为Uint8Array对象呢?由于在Go接收js传递给它的数据的时候,咱们须要经过一个叫作CopyBytesToGo的方法,来拷贝数据到go的对象中,这样在go里面才可使用js的数据,这个方法要求的咱们必须传递Uint8Array类型的数据过去,不然会报下面的错误:
这个错误仍是比较清楚的,对不对。
最后一行代码:
// 调用target对象上的calcMd5方法(这个方法由Go提供,挂载到target上)
target.calcMd5(bytes);
复制代码
这里咱们调用了target对象上的calcMd5方法,而后将bytes做为第一个参数传递过去,注意,这里的calcMd5方法,是在Go里面声明的,而且挂载到了target对象上面,你能够看到咱们的js代码,并无任何地方给target对象声明一个calcMd5方法。
上面的代码是js端的实现,代码比较简单,可是若是你对Go的wasm不太熟悉的话,就很容易掉坑里。
下面再来看下Go端的代码:
package main
import (
"crypto/md5"
"fmt"
"syscall/js"
)
func main() {
// 声明一个函数,用来导出到js端,供js端调用
calcMd5 := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
// 声明一个和文件大小同样的切片
buffer := make([]byte, args[0].Length())
// 将文件的bytes数据复制到切片中,这里传进来的是一个Uint8Array类型
js.CopyBytesToGo(buffer, args[0])
// 计算md5的值
res := md5.Sum(buffer)
// 调用js端的方法,将结构返回给js端
js.Global().Get("target").Get("callback").Invoke(fmt.Sprintf("%x", res))
return nil
})
// 获取js端全局对象中的target对象,设置target的calcMd5方法为上面的calcMd5实现
js.Global().Get("target").Set("calcMd5", calcMd5)
// 阻止go程序退出,由于退出了,js端就不能再调用了
signal := make(chan int)
<-signal
}
复制代码
上面的代码中,首先咱们声明了一个calcMd5函数,注意这里的函数跟普通的go函数不太同样,须要使用js.FuncOf包裹一下,在函数中:
buffer := make([]byte, args[0].Length())
复制代码
这行代码,咱们使用make函数来建立了一个byte类型的切片,make函数是go语言内置的一个函数,不了解的能够直接看go的官方文档。经过make函数,建立了一个叫作buffer的切片,而后切片的长度为js端传进来的Uint8Array的长度大小,还记得上面的js代码吗,里面调用calcMd5函数,传的第一个参数就是一个Uint8Array的数据,咱们经过arg[0]来获取第一个参数。
js.CopyBytesToGo(buffer, args[0])
复制代码
这行代码,将js端传递进来的Uint8Array的数据,复制到了咱们建立的buffer切片中,这样在后面的go代码中,才可以使用,否则是没有办法直接使用的。 既然有CopyBytesToGo方法,那有没有CopyBytesToJS方法呢?那确定有,能够看这里: golang.org/pkg/syscall…
// 计算md5的值
res := md5.Sum(buffer)
// 调用js端的方法,将结构返回给js端
js.Global().Get("target").Get("callback").Invoke(fmt.Sprintf("%x", res))
复制代码
上面的代码中,首先计算md5的值,而后使用fmt.Sprintf("%x", res)
来将res转换为16进制的字符串数据,自己md5.Sum方法返回的是一个byte类型的切片。 下面的一行代码,调用上面js端声明的target对象中的callback方法,将md5的值做为参数传递过去。这样在js端就可以拿到计算的md5的结果,去作后续的事情了。
最后,来看下执行结果:
对比下上面咱们经过md5命令执行的结果,是同样的,说明没有问题。
在这篇文章中,主要简单介绍了下md5是什么,而后经过实现一个demo,经过这个demo,你们应该就知道,文件秒传是如何实现的了,而且,咱们也看到了,使用wasm来计算文具的md5的速度,是要比js计算快不少的。这也是为何在以前的文章中,我说wasm通常用在文件上传,计算等场景下,大部分场景用不上的缘由。
好了,这篇文章就到这里,后面若是有须要,可能还会继续写一篇关于使用wasm来作计算的文章,好比使用wasm来实如今线excel的函数计算等场景。
文章中若有不对的地方,欢迎指正,🙏。
参考连接: