这是在干嘛?
起因
之前看到有开发者使用二维码+tun/tap, 实现了使用二维码扫码作为物理传输的IP网络。大家感兴趣的话传送门在这里 https://hackaday.com/2016/11/22/ip-over-qr-codes/
p叔觉得很 cool 。于是我也想用二维码来做点啥。之前也想搞想 tun/tap
,无奈node.js 上 tun/tap
的库在 macOS上不是很友好,再加上 pshu 智商和时间都不够,就没再折腾。退而求其次,在假期就用二维码做一个文件传输的工具。
搜了下资料,单个二维码能承载的最大信息量是3k左右,用来做信息传输还是有可能的。(实际情况来看,由于 pshu 用的摄像头质量比较差,超过1k 的二维码就很难识别出来)
用二维码来传输文件背后的原理十分地简单,把需要传输的文件切割成合适大小的片段,然后每一段文件转换成二维码,依次显示二维码;接收端识别这些二维码,等所有的片段都收集完整之后,重新组合成文件即可。
也不是这么简单
但是注意这里有个限制条件就是:文件的发送方和接收方之间没有任何通信方式的;如果有其他任何通信方式的话,就不用这么费劲用二维码来传输文件了。所以文件的发送方只能按照一定的频率切换不同分段对应的二维码,而接收方也只能尽量快地识别二维码,以尽快组装成原来的文件。
由于二维码的识别是需要时间的,而发送双方无法做流控,所以如果接收方丢失了一个分段的数据,就是要发送方全部显示完一轮之后,才“有机会”看到,等这个机会来临的时候接收方也不一定能识别到。所以朴素地按照一定顺序显示文件片段的二维码的方式来传输文件实际传送时效率是非常低的。
喷泉码
后来 pshu 看到一个叫做“喷泉码”的编码方式,它所带来的好处就是,只要接收方收集足够多不同的片段,就能恢复出原始的文件。就像在喷泉附近放置一个水桶,只要放的时间足够久,喷泉溅起的水珠会盛满这个水桶。喷泉码一个比较简单的实现就是 LT码( Luby transform codes ),这个实现也是pshu 要用的。
LT码的背后的原理非常地简单:
1.将需要传输的数据等分成N分段,并编号2.根据预先设定的概率分布,生成一个k(k在区间:[1,N]),然后取k个片段,将这个k 片段的数据做异或操作,发送异或后的数据,和k个片段的编号信息。
其中 k 的概率分布主要集中在1和2附近。
3.在接收方根据收到的异或后的数据和片段信息,通过异或的特性,可以逐个恢复出原本的数据片段。
举个例子,假设我们需要发送数据只有三个字节,我们区间分成3段,分别是 0x11, 0x22,0x44,
第一批发送的数据是:0,1号片段的异或,即 0x33。
第二批发送的数据是:1,2号片段的异或,即 0x66。
第三批发送的数据是:0号片段:0x11。
在接收方,首先知道0号片段是 0x11, 接着通过异或运算知道1号片段为 0x22(0x33^0x11),最后使用相同的方法计算出2号片段 0x44 (0x66^0x22)。
数据传输的大致原理就是如此。但是这里还有一个很有趣的细节,就是在选取k个数据片段的时候,需要发送方和接收方能够采用一致的方式选取k的数值和如何随机地选取这k个片段。如果直接发送k和相应选取片段的k 个编号,这种同步方法是最简单的,但是也带来了一个问题就是,这样的通信效率太低,除了发送k ,还要额外发送k个编号信息,如果k 的随机值较大这样的效率就很低。
LT码实现的时候,通信双方协商好了一个随机数生成的函数,那么确定好这个随机函数的随机种子,那么就能唯一地确定好 k,以及k个分片的编号。通过这样的协议就能大大地提高传输的效率。
上代码
原理讲清楚了,直接上代码地址 https://github.com/stormslowly/lt-node.js
如何使用
先 clone 代码,安装依赖后,执行
npx ts-node src/bin/send.ts <需要发送的文件名>
建议发500k以下的文件,如果你时间足够多足够无聊,可以帮我测测大文件的传输时间,哈哈。
这个时候命令行上就会出现,不停变化的二维码,这个就是文件喷泉码编码后的文件碎片。调整下命令行程序的字体大小,让二维码尽量大的显示。
在另外一台有摄像头的电脑上,执行
npx ts-node src/bin/wsRecev.ts
然后打开本地8080端口的页面,申请摄像头权限,使用摄像头拍摄二维码,尽量让二维码占满画面,增加解码成功率。这个时候你大概会出现一个类似网站开头的的画面了。
然后等上“亿点点”时间,接收程序会自动退出,说明文件接收完毕,当前目录下就会有一个 a.out 的文件,就是接收到的文件,重命名你想要的文件名即可。
如果想更加详细的了解背后的原理和时效,关注代码里面 Encoder
和 Decoder
两个类,也可以参考下test目录下的用例。
完
欢迎点赞转发!