Hotdry.

Article

浏览器内 Linux VM 通过 WebUSB+USB/IP 桥接实现扫描仪直通:协议转发与设备虚拟化工程解析

解析 yes-we-scan 项目的技术架构,探讨浏览器内 Linux VM 如何通过 WebUSB 与 USB/IP 协议桥接实现旧扫描仪的驱动兼容,涵盖协议转发、设备直通与工程化参数。

2026-05-21systems

浏览器作为应用运行环境的能力边界正在不断扩展。从早期的 JavaScript 计算到 WebAssembly 运行原生代码,再到如今直接操作硬件设备,浏览器正在从 "沙盒中的渲染引擎" 向 "通用计算平台" 演进。yes-we-scan.app 项目展示了一种颇具启发性的技术路径:在浏览器内运行 Linux 虚拟机,通过 WebUSB API 与 USB/IP 协议的桥接,让失去驱动支持的旧扫描仪重新获得生命力。

这项技术的核心在于解决了两个层面的问题:一是如何在浏览器环境中获得对 USB 设备的访问能力,二是如何将这种访问能力延伸到虚拟机内部,让原本需要内核级驱动的扫描仪设备能够被用户空间的应用程序所使用。

架构设计:三层协议桥接模型

整个系统的架构可以概括为 "浏览器 - 桥接层 - 物理设备" 的三层模型。在浏览器层面,WebUSB API 提供了 JavaScript 访问 USB 设备的入口点;在中间层,USB/IP 协议充当了设备转发的桥梁;在底层,Linux 内核的 USB/IP 驱动实现了设备的虚拟化与网络传输。

WebUSB API 的设计遵循了浏览器安全模型的基本原则:必须在安全上下文(HTTPS 或 localhost)中运行,需要用户显式授权才能访问设备,且对设备类型有明确的限制。根据 MDN 文档和 Chrome 的实现,WebUSB 不会枚举 HID 类设备(人机接口设备)和大容量存储设备,这意味着扫描仪如果呈现为 HID 键盘设备,将无法通过 WebUSB 直接访问。

USB/IP 协议则是 Linux 内核原生支持的 USB over IP 解决方案。该协议采用客户端 / 服务器架构,服务器端导出物理 USB 设备,客户端导入设备后在本地创建虚拟主机控制器,使得远程设备对操作系统而言如同本地连接一般。

USB/IP 协议详解:内核级设备转发

USB/IP 协议的当前版本为 v1.1.1(二进制表示为 0x0111),其消息格式定义了完整的设备生命周期管理。协议的核心消息类型包括设备发现(OP_REQ_DEVLIST/OP_REP_DEVLIST)、设备导入(OP_REQ_IMPORT/OP_REP_IMPORT)以及 URB 传输(USBIP_CMD_SUBMIT/USBIP_RET_SUBMIT、USBIP_CMD_UNLINK/USBIP_RET_UNLINK)。

在设备发现阶段,客户端向服务器发送 OP_REQ_DEVLIST 请求,服务器返回 OP_REP_DEVLIST 响应,其中包含每个导出设备的详细信息:设备路径(256 字节)、总线 ID(32 字节)、总线号、设备号、速度、厂商 ID、产品 ID 以及接口描述符等。这些信息构成了设备导入的基础。

设备导入成功后,TCP 连接保持打开状态用于传输 URB(USB Request Block)数据。USBIP_CMD_SUBMIT 消息用于提交传输请求,包含传输标志、缓冲区长度、起始帧、包数量、间隔时间以及设置数据等字段。服务器处理完成后返回 USBIP_RET_SUBMIT,携带状态码和实际传输长度。

协议设计中的一个关键细节是序列号(seqnum)机制。每个请求都携带递增的序列号,响应中回显相同序列号以实现请求 - 响应匹配。对于 UNLINK 操作,协议规定成功取消的 URB 将不会收到对应的 USBIP_RET_SUBMIT 响应,这是实现异步取消语义的基础。

WebUSB 接入:权限模型与设备发现

在浏览器端,WebUSB 的接入流程遵循 "请求 - 授权 - 访问" 的三阶段模型。网页通过调用 navigator.usb.requestDevice() 触发设备选择对话框,用户选择设备并授权后,浏览器将设备句柄暴露给 JavaScript 代码。

设备授权是持久化的:Chrome 会按来源(origin)存储用户的设备授权决策,下次访问时可通过 navigator.usb.getDevices() 直接获取已授权设备列表,无需重复弹窗。这一设计对于扫描仪这类需要频繁连接的设备尤为重要。

然而,WebUSB 的设备类限制给扫描仪接入带来了挑战。许多扫描仪为了兼容性,会同时注册为 HID 设备(表现为键盘输入)和厂商自定义设备。如果扫描仪仅呈现为 HID 类,WebUSB 将无法枚举到该设备。此时需要采用替代方案:使用 WebHID API(专门用于 HID 设备)或在虚拟机内部运行代理服务,通过 WebSocket 将扫描数据转发给浏览器。

工程实现要点:超时、重连与设备类处理

在实际部署中,USB/IP 连接的稳定性和 WebUSB 的权限管理是需要重点关注的工程问题。

连接超时与重连策略:USB/IP 依赖 TCP 连接传输 URB 数据,网络中断或设备热插拔都会导致连接断开。建议实现指数退避重连策略:首次断连后等待 1 秒重试,后续每次等待时间翻倍,上限设为 30 秒。同时,在 UI 层提供明确的连接状态指示,避免用户在设备不可用时进行无效操作。

设备导入的生命周期管理:USB/IP 客户端导入设备后,会在本地创建虚拟 USB 设备节点(通常为 /dev/bus/usb/XXX/YYY)。应用层需要监听该设备节点的存在性,当检测到设备消失时触发重新导入流程。在容器化或虚拟机环境中,还需要处理设备节点权限问题,确保运行扫描服务的进程对 USB 设备节点有读写权限。

设备类兼容性处理:对于同时支持 HID 和自定义接口的扫描仪,优先尝试通过 WebUSB 访问自定义接口,失败时回退到 WebHID。如果扫描仪仅支持 HID,则需要在虚拟机内部运行扫描守护进程,通过 libusb 直接与设备通信,再通过 WebSocket 将扫描结果发送到浏览器。

缓冲区大小与传输性能:USB/IP 协议中,URB 传输缓冲区的大小直接影响扫描图像的传输效率。对于高分辨率扫描仪,建议将传输缓冲区设置为 64KB 或更大,减少 URB 提交次数。同时,注意处理 ISO(等时)传输的特殊格式:ISO 包描述符紧跟在传输缓冲区之后,每个描述符包含偏移量、长度和状态字段。

局限与替代方案

尽管 WebUSB+USB/IP 的架构具有创新性,但也存在明确的局限性。

HID 设备限制:如前所述,WebUSB 有意排除了 HID 类设备的访问权限,这是出于安全考虑的设计决策。如果扫描仪仅注册为 HID 设备,必须使用 WebHID API 或代理层方案。

性能瓶颈:USB/IP 将 USB 协议封装在 TCP 之上,引入了额外的协议开销和网络延迟。对于需要高带宽、低延迟的 USB 3.0 设备,性能可能不如原生 USB 连接。在局域网环境下,延迟通常在毫秒级,可以接受;但在广域网或高延迟网络中,USB 设备的响应可能变得不可接受。

浏览器兼容性:WebUSB 目前主要在 Chromium 系浏览器(Chrome、Edge)中得到完整支持,Firefox 和 Safari 的支持程度有限。这意味着该方案无法覆盖所有用户群体。

替代方案方面,如果浏览器支持受限,可以考虑使用原生应用作为 USB 代理,通过本地 HTTP 或 WebSocket 服务与网页通信。这种方案牺牲了 "纯浏览器" 的便利性,但获得了更广泛的设备兼容性和更好的性能表现。

结语

yes-we-scan.app 项目展示的技术路径 —— 浏览器内虚拟机 + WebUSB + USB/IP 协议桥接 —— 代表了一种有趣的探索方向:将浏览器作为硬件抽象层,让网页应用获得接近原生应用的设备访问能力。

这种架构的价值不仅在于解决旧扫描仪的驱动兼容问题,更在于提供了一种通用的 "浏览器硬件直通" 模式。类似的思路可以延伸到 3D 打印机、CNC 机床、工业传感器等设备,让 Web 技术栈进入传统上由原生应用主导的领域。

当然,这条路还面临安全模型、性能优化、跨浏览器兼容性等多重挑战。但随着 WebAssembly 的成熟和浏览器 API 的演进,浏览器作为通用计算平台的边界还将继续扩展。


资料来源

  • yes-we-scan.app — 浏览器内扫描仪复活项目主页
  • Linux Kernel USB/IP Protocol Documentation — USB/IP 协议规范 v1.1.1

systems

内容声明:本文无广告投放、无付费植入。

如有事实性问题,欢迎发送勘误至 i@hotdrydog.com