ShadowCat:用 QR 码在浏览器间离线传输文件
posts posts 2026-05-23T03:15:00+08:00ShadowCat 是一个单文件 HTML 页面,通过 QR 码实现两台设备间的纯离线文件传输。协议将文件切分为 header + chunk 数据块,接收方实时追踪缺失块并重组文件。本文详解其协议设计、分块策略及适用边界。技术笔记QR码, 浏览器, 离线传输, 光学传输, 协议设计ShadowCat:用 QR 码在浏览器间离线传输文件
有时候,两台设备之间能用来通信的只剩下摄像头——比如一部老手机的射频模块(BLE、NFC)早已损坏,但摄像头和浏览器还能用。ShadowCat 解决的正是这个问题:在一个纯 HTML 页面里,把任意文件编码成一连串 QR 码,供另一台设备扫码接收。整个过程不需要网络、不需要蓝牙、不需要 NFC,只要两个浏览器和两张能看见对方的屏幕。
核心判断
ShadowCat 的价值不在"快",在"通"。在所有无线协议都不可用的场景下,它是唯一可用的选项。协议设计得很克制:header + 顺序数据块的线性结构,换来了接收端极简的追踪逻辑;Base64 编码避免了自定义字符集,直接用 | 分隔符做解析。整个方案可以总结为一句话——把文件切碎,用 QR 码一张一张地发,收端自己拼回去。
系统地图
发送端 接收端
─────────────────────────── ───────────────────────────
Encode file to Base64 ──► Camera scan QR frames
Split into chunks ──► Parse QRX1|D|<idx>|<data>
Loop: [header, chunk1…N] Track received chunk indices
@ chosen FPS Show missing-chunks grid
@ chosen chunk size Request resend of missing frames
@ chosen ECC level On complete: verify CRC → Download协议只有两种帧类型,header 携带文件元信息,数据帧按顺序编号,接收端独立追踪每块的到达状态。
协议设计
帧格式
ShadowCat 定义了两种 QR 帧格式:
Header 帧(发送时只发一次):
QRX1|H|<total>|<filename>|<sizeBytes>|<crc32hex>total:本次传输的总帧数filename:原始文件名sizeBytes:文件原始大小(字节)crc32hex:整个文件的 CRC-32 校验值(十六进制),用于最终校验
Data 帧(循环发送):
QRX1|D|<idx>|<base64chunk>idx:帧序号,从 1 开始base64chunk:对原始文件分块后的 Base64 编码片段
Base64 字符集中不包含 |,所以解析逻辑极为简单——split('|') 即可完成全部解析工作。这也是协议设计的一个小巧之处:用 Base64 自带的字符边界替代了自定义的块边界协议。
接收端逻辑
接收端的核心是一个缺失块追踪器。流程如下:
- 扫描到 header 帧后,提取总帧数和 CRC,开始追踪
- 每收到一个数据帧,按
idx记录,重复帧直接忽略(已收到的帧不会触发任何状态变化) - 界面上用网格实时展示缺失帧的序号(0 = header)
- 发送端可以通过"Show frame"功能单独显示某一帧,供接收端请求补发缺失块
- 所有帧收齐后,计算 CRC 与 header 中的值比对,一致则提供下载按钮
整个接收逻辑没有复杂的滑动窗口或重传协商——发端循环播放,收端按序号自己拼图。 这在协议层面极度简化,但也意味着传输效率受限于相机帧率和 QR 码密度。
分块策略与性能约束
README 给出了一些实测参考值:
| 参数 | 典型值 | 说明 |
|---|---|---|
| 每帧字符数 | 500 | 受 QR 码密度限制 |
| 传输速率 | ~1.1 KB/s(Base64)/~0.83 KB/s(原始) | 取决于帧率和编码量 |
| 100 KB 文件 | 约 2 分钟完成一轮循环 | 接收端通常需要 1-2 轮 |
影响传输速度的三个变量:
- Chunk size(块大小):越大越快,但 QR 码密度越高,老设备解码失败率上升
- FPS(帧率):越高越快,但接收端相机帧率不够时会漏帧
- ECC(纠错级别):越高越抗损,但同样会增大 QR 码密度
速度、可靠性、兼容性三者不能同时最优。 README 建议在老设备上降低 FPS、升高 ECC、缩小 chunk 到约 300 字符,这是针对兼容性的保守配置。
适用边界
ShadowCat 适合以下场景:
- 老旧手机之间需要传递文件,但没有网络或蓝牙可用
- 在网络受限环境下两台设备需要交换小文件(纯离线)
- 作为教学案例,理解"分块 + 确认 + 重传"的朴素传输模型
不适合以下场景:
- 需要传输大文件(MB 级别):QR 码传输速度太慢,不现实
- 追求高可靠性传输:高纠错 QR 码密度极高,相机对焦稍有问题就大量丢帧
- 网络可用时:直接用 localtunnel、Python http.server 或 anywire 等工具都远比 QR 码高效
阅读路径
如果想深入理解或改造 ShadowCat,建议按以下顺序阅读源码:
qrcode.html中 Encode/Decode 函数,理解 Base64 分块逻辑- Protocol 注释,理解帧格式定义
- 接收端缺失块追踪器的状态管理逻辑
- 结合 README 的 Practical notes,调整分块参数验证不同设备上的表现
总结
ShadowCat 是一个用朴素工程思维解决的问题:没有网络、没有蓝牙,只有屏幕和摄像头。那就用屏幕发,用摄像头收。协议设计值得注意的地方不在"多巧妙",在"多克制"——只有两种帧格式、解析只要 split('|')、接收端逻辑就是一张序号集合。简单到可以直接在浏览器控制台里复现核心逻辑,这对于学习和调试来说本身就是价值。