OpenHarmony USB 免驱打印探索

1. 简介

       在现代智能设备生态系统中,打印功能扮演着不可或缺的角色。OpenHarmony 作为一个新兴的开源操作系统,正在快速发展并不断完善其功能生态。然而,在打印领域,OpenHarmony 仍面临着一些挑战。

        当前,OpenHarmony 主要支持两种打印方式:通过网络连接的打印机进行打印,或者通过硬件厂商为特定打印机型号开发的自定义驱动程序实现打印。这种现状限制了用户的选择,尤其是对于那些不支持网络打印或没有专门驱动的 USB 打印机而言,用户将无法在 OpenHarmony 系统中直接使用这些设备。

        为了解决这一问题,我们提出了一种创新的解决方案,实现了 OpenHarmony 系统上的 USB 免驱动打印功能。这项技术突破不仅填补了系统在打印功能方面的空白,还大大提升了用户体验。通过支持 IPP over USB 协议(IPP-USB),我们使得大多数现代打印机,尤其是中高端机型,能够在 OpenHarmony 系统上实现即插即用。用户现在可以简单地通过 USB 连接打印机,无需安装复杂的驱动程序,也不需要依赖网络连接,就能轻松完成打印任务。

        这项功能的实现不仅拓展了 OpenHarmony 支持的打印机范围,还降低了打印机厂商的开发成本,为 OpenHarmony 的开源生态贡献了宝贵的技术方案。它使得 OpenHarmony 在打印功能上更加完整和灵活,增强了系统在日常办公和家庭使用场景中的实用性。通过这项工作,我们希望能为 OpenHarmony 的功能完善和用户体验提升做出贡献,推动其在全球操作系统市场中的竞争力。

2. 背景

2.1 OpenHarmony 打印架构

        打印流程始于用户下发打印需求,打印需求会由 print framework 传给 PrintSpooler。

        常规的打印流程中需要一个打印选项界面,给用户选择打印参数,比如彩印/黑白,单面/双面等等,这些都会由 PrintSpooler 负责。PrintSpooler 是 OpenHarmony 中预置的系统应用,负责提供打印预览、发现和连接打印机、设置打印参数、下发打印任务以及管理打印任务状态等功能。

        开始打印前,需要在 PrintSpooler 中选择打印机,这就涉及了打印机发现功能。请求会通过 PrintSA(Print Service Ability)、PrintExtension,最终由 Custom PE(Print Extension)上报其发现的打印机。

        PrintSA 可以理解为打印框架提供的接口,提供诸如添加/移除打印机、开始/取消打印任务等能力。PrintExtension则负责管理所有 Custom PE,每个 Custom PE 对应一个打印机驱动,用于处理特定类型或品牌的打印机。

        打印机的发现是整个流程中的关键环节。Print 框架需要依赖 vendor 或 PrintSpooler 原生提供的 IPP 或 P2P 功能来发现打印机。这意味着各个硬件厂商需要适配自己的 PrintExtension,以便将其打印机注册到 OpenHarmony 打印框架中。

2.2 OpenHarmony 现阶段的打印版图

        OpenHarmony 现阶段的打印版图主要包括两条路线,分别针对网络打印和自定义驱动打印。

        OpenHarmony 可以通过内置的 ippprint 组件实现网络打印功能。这个组件是 PrintSpooler 内部提供的一个PrintExtension,专门用于发现和连接支持 IPP(Internet Printing Protocol)协议的打印机。IPP 是一种标准网络打印协议,允许设备通过互联网或局域网进行打印。目前,大多数现代网络打印机都支持 IPP 协议,尤其是中高端打印机。这使得 OpenHarmony 能够兼容市面上大部分网络打印设备,为用户提供便捷的网络打印体验。

        OpenHarmony 还支持通过自定义驱动的方式实现打印功能。这需要硬件厂商或开发者实现 PrintServiceAbility 接口。PrintServiceAbility 需要提供一系列核心功能,包括发现打印机、连接打印机、提交打印任务和预览打印任务等。开发者可以参考/base/print/print_fwk/interfaces/kits/jskits/@ohos.PrintExtensionAbility.d.ts文件中的接口定义来实现这些功能。

        当前,OpenHarmony当前的打印功能仍存在一些局限性,最明显的是缺乏直接通过USB连接打印机的能力。这意味着对于不支持网络打印的USB打印机,用户无法直接在 OpenHarmony 系统中使用,除非该打印机的制造商专门为OpenHarmony 开发并适配了驱动程序。

2.3 免驱打印

2.3.1 免驱打印基础

        无驱动打印针对打印的客户端,是指客户端设备(计算机、智能手机、平板电脑、笔记本电脑等)无需在客户端上安装任何静态功能文件或驱动程序(制造商特定的或其他)即可打印的能力。

        客户端有多种方法可以将作业提交到打印系统并无驱动程序打印:

  • 直接从客户端上的应用程序打印。
  • 使用 AirPrint 或 IPP Everywhere 打印。
  • 将作业作为电子邮件附件发送到特殊地址。
  • 网络印刷。文档通过 Web 表单样式界面从 Web 浏览器上传。

        传统的打印系统依赖于存储在客户端设备上的静态文件(如 PPD 文件,PostScript 打印机描述文件)来获取打印机的特性和功能信息。这种方法存在一些问题:需要在客户端上安装和维护大量文件和驱动程序,对移动设备来说尤其不便,用户每次使用新打印机时都需要进行繁琐的配置。与其他即插即用的外设相比,打印机的使用体验显得格格不入。

        为了解决这些问题,打印机厂商在他们的产品中新增了免驱打印功能。现代打印机可以通过网络自动广播自己的存在和功能,使电脑和手机能够发现同网络上的打印机,并直接获取到打印机信息。同时,客户端和打印机之间采用了标准的 IPP 协议进行通信,可以动态获取打印机的详细功能,无需预先安装驱动程序。

        这种"无驱动打印"的方法大大简化了打印机的使用过程。用户只需将设备连接到网络,就能像使用鼠标键盘一样方便地使用打印机,无需繁琐的配置步骤。这不仅提高了用户体验,也为移动设备节省了宝贵的存储空间和电池电量。

2.3.2 IPP 协议

        Internet 打印协议 (IPP) 是一种专用通信协议,用于客户端设备(计算机、移动电话、平板电脑等)和打印机(或打印服务器)之间的通信。它允许客户端将一个或多个打印作业提交到网络连接的打印机或打印服务器,并执行查询打印机状态、获取打印作业状态或取消单个打印作业等任务。

与所有基于 IP 的协议一样,IPP 可以在本地运行或通过 Internet 运行。与其他打印协议不同,IPP 还支持访问控制、身份验证和加密,使其成为比旧协议更强大、更安全的打印机制。

2.3.3 USB 免驱打印

        虽然通过网络连接打印机已经变得越来越普遍和便捷,但USB打印仍然有其存在的价值和需求。

        有些情况下,网络连接可能不可用或不稳定,而 USB 则能够提供稳定的传输。另外,对于偶尔使用的打印机来说,即插即用的 USB 往往更加方便,不用进行复杂的网络打印设置。而安全性也是一个重要因素,对某些用户来说,通过 USB 传输敏感文档比通过网络更安全。

        单纯的 IPP 协议只能在网络环境中使用,但免驱动打印不仅限于网络,还可以使用 IPP-over-USB 驱动程序,将 IPP 和 USB 结合起来。运行 IPP-over-USB 后,计算机和打印机之间无需网络就可以进行 IPP 打印。

        IPP USB 规范描述了 USB 的扩展,该扩展定义了通过 USB Print 类接口提供 IPP 的标准,旨在允许 USB 连接的设备实现与使用 IPP 的网络连接设备的功能对等。换句话说,可以通过网络连接完成的任何事情也可以通过 USB 连接完成。因此,当使用 IPP-over-USB 时,USB 连接的打印机被视为网络打印机。

2.3.4 ippusbxd

        ippusbxd 是 IPP-over-USB 类 USB 设备的用户级驱动程序。它一直 专为 Linux 设计,但使用跨平台 USB 库,允许最终 移植到 Windows 和其他非 POSIX 平台。

        ippusbxd 不是最新的 IPP-over-USB 驱动程序。有人在使用 ippusbxd 时遇到了一些 BUG,开发了代替品,如 ipp-usb 和 ippusb_bridge。但是 ipp-usb 是用 Go 编写的,ippusb_bridge 是用 Rust 编写,移植到 OpenHarmony 平台具有一定难度。

3. 实现方法

3.1 ippusbxd 技术架构

3.1.1 ippusbxd

        ippusbxd 是一个用于通过 USB 连接的 IPP 打印机的守护进程。它的工作过程涉及多个网络协议和服务发现机制,主要包括 IPP、DNS-SD、mDNS 和 Zeroconf。

当一台支持 IPP 的打印机通过 USB 连接到计算机时,ippusbxd 会启动并为该打印机创建一个虚拟的网络接口。这使得 USB 打印机可以像网络打印机一样被发现和使用。ippusbxd 会使用 DNS-SD(DNS Service Discovery)在本地网络中宣告这个 IPP 服务。DNS-SD 是 Zeroconf(零配置网络)技术的一部分,它允许设备在网络中自动发现服务,而无需手动配置。

        mDNS(Multicast DNS)是实现 DNS-SD 的关键技术之一。它允许在没有传统 DNS 服务器的情况下,在本地网络中解析主机名。当 ippusbxd 宣告 IPP 服务时,它会使用 mDNS 发布相关信息,如打印机的名称、IP 地址和支持的功能。

        在 Linux 系统中,Avahi 守护进程(avahi-daemon)通常负责处理 mDNS 和 DNS-SD 的实现。Avahi 是 Zeroconf 的一个开源实现,它可以帮助 ippusbxd 发布服务信息,同时也帮助客户端设备发现网络中的打印服务。

        Avahi 使用 D-Bus(Desktop Bus)与其他应用程序进行通信。D-Bus 是一种进程间通信(IPC)机制,它为应用程序提供了一种标准化的方式来相互通信和交换数据。通过 D-Bus,用户应用程序可以接收到 Avah i发现的网络服务和资源的通知。

        当用户想要打印文档时,OpenHarmony 的打印系统会首先使用 DNS-SD 和 mDNS 在本地网络中查找可用的 IPP 打印机。一旦发现打印机,系统就会使用 IPP 协议与打印机通信,发送打印作业或查询打印机状态。IPP 协议定义了一套标准的操作,如提交打印作业、取消打印作业、获取打印机状态等,使得不同厂商的打印机可以以统一的方式被访问和控制。

        通过这种方式,ippusbxd、Avahi、D-Bus 和 OpenHarmony 的打印系统共同工作,为用户提供了一个无感的网络打印体验,无论打印机是通过USB连接还是直接连接到网络。

3.1.2 Avahi

        Avahi 是一个开源的零配置网络实现,主要用于在局域网中自动发现设备和服务。在 ippusbxd 打印的场景中,Avahi 扮演着关键角色,它能够帮助计算机自动发现网络中的打印机,简化打印机的配置和使用过程。

        Avahi 实现了 mDNS(多播域名系统)和 DNS-SD(DNS 服务发现)协议。这些协议允许设备在无需手动配置的情况下,自动在网络中广播和发现服务。对于打印机来说,这意味着它们可以自动向网络中的其他设备宣告自己的存在和提供的服务。

        Avahi 的核心组件包括 avahi-daemon 和 avahi-client 库:

        avahi-daemon 是 Avahi 的核心服务进程。它在系统后台运行,负责管理本地设备的服务发布和远程服务的发现。在 ippusbxd 打印环境中,avahi-daemon 会监听网络上的打印机广播,收集打印机的信息,如 IP 地址、端口号和支持的打印协议等。

        avahi-client 库是一套 API,允许应用程序与 avahi-daemon 进行交互。通过这个库,应用程序可以注册新服务、浏览网络中的服务,或者获取特定服务的详细信息。在打印系统中,打印管理软件可以使用 avahi-client 库来查询可用的打印机,获取它们的配置信息。

3.1.3 D-Bus

        DBus(Desktop Bus)是一种进程间通信(IPC)机制,在 Linux 和其他类 Unix 系统中广泛使用。在 ippusbxd 打印环境中,DBus 扮演着重要角色,它为打印系统的各个组件提供了一个统一的通信平台,使得打印管理、打印队列控制和打印机状态监控等功能能够高效地协同工作。

        DBus 的核心组件包括:

  1. dbus-daemon:这是 DBus 的核心服务进程。它负责管理消息的路由,确保消息能够正确地从发送者传递到接收者。在打印系统中,dbus-daemon 运行在系统级别,协调各个打印相关服务之间的通信。
  2. libdbus 库:这是一个底层的 C 库,提供了与 DBus 系统进行交互的 API。Avahi 会直接使用 libdbus 来实现高效的通信。
  3. libexpat 库:这是一个 XML 解析库,DBus 使用它来解析配置文件和消息格式。虽然 libexpat 对于 DBus 的内部工作很重要,但对于 Avahi 和 ippusbxd 应用层来说是透明的。

        尽管移动设备通常采用 Binder 作为主要的进程间通信机制,但 ippusbxd 的设计深度依赖于 DBus。这种依赖关系使得我们不得不考虑将 DBus 移植到移动平台,而非直接使用 Binder。

        D-Bus(Desktop Bus)最初是为桌面环境设计的,主要用于 Linux 和其他类 Unix 系统。它提供了一种标准化的方式来实现系统服务和应用程序之间的通信。D-Bus 使用一个中心化的总线守护进程来管理消息的路由,支持一对一和一对多的通信模式。它的设计重点是灵活性和可扩展性,允许动态发现服务和对象,并支持复杂的数据类型。

        Binder 则是 Android 系统专门设计的 IPC 机制,针对移动设备的特点进行了优化。它直接集成在 Linux 内核中,通过内存映射实现了高效的数据传输。Binder 的设计目标是提供更好的性能和安全性,特别适合于资源受限的移动设备。

        在实现上,D-Bus 主要在用户空间运行,而 Binder 则在内核空间实现核心功能。D-Bus 的消息传递需要经过总线守护进程,可能会引入额外的开销,而 Binder 直接在内核中进行数据传输,效率更高。

3.2 移植重点难点

3.2.1 如何通过交叉编译为 OpenHarmony 生成 config.h

        config.h 包含了编译环境的关键信息,如系统调用的可用性、编译平台(GNU 或 BSD)等。这些信息可以确保代码在不同环境下都能正常配置和运行。

        然而,在 OpenHarmony 环境中生成 config.h 和在传统 Linux 中不同。传统的 Linux 系统通常使用 GNU Autotools 工具链,比如 autoconf 会生成 configure 脚本和最终的 config.h 文件。但 OpenHarmony 不支持 Autotools,就无法使用传统方法生成 config.h。

        OpenHarmony 使用的是 LLVM 编译链,位于 //prebuilts/clang/ohos/linux-x86_64/llvm 目录。这与传统 Linux 系统使用的 GCC 编译器有所不同。因此,我们首先需要为交叉编译环境正确配置 configure 脚本,具体来说,我们需要指定一系列环境变量来帮助 configure 脚本找到正确的工具链和系统根目录(sysroot)。sysroot 是交叉编译中的一个重要概念,它指定了目标系统的根文件系统路径,编译器会在这个路径下查找头文件和库文件。在 OpenHarmony 中,sysroot 通常位于 ${OHOS_SRC_HOME}/out/${YOUR_PRODUCT_NAME}/obj/third_party/musl,这里使用的是 musl libc 而不是更常见的 glibc。

        为了正确设置编译环境,我们需要导出以下变量:

OHOS_SRC_HOME=${YOUR_OHOS_SRC_PATH}
OHOS_TOOLCHAIN=${OHOS_SRC_HOME}/prebuilts/clang/ohos/linux-x86_64/llvm/bin
OHOS_SYSROOT=${OHOS_SRC_HOME}/out/${YOUR_PRODUCT_NAME}/obj/third_party/musl
export PATH=${OHOS_TOOLCHAIN}/bin:$PATH
export CC=${OHOS_TOOLCHAIN}/clang
export CXX=${OHOS_TOOLCHAIN}/clang++
export SYSROOT=${OHOS_SYSROOT}
export AR=${OHOS_TOOLCHAIN}/llvm-ar
export AS=${OHOS_TOOLCHAIN}/llvm-as
export LD=${OHOS_TOOLCHAIN}/lld
export RANLIB=${OHOS_TOOLCHAIN}/llvm-ranlib
export STRIP=${OHOS_TOOLCHAIN}/llvm-strip
export OBJDUMP=${OHOS_TOOLCHAIN}/llvm-objdump
export CFLAGS="--sysroot=$OHOS_SYSROOT -Xclang -mllvm -Xclang -instcombine-lower-dbg-declare=0 --target=aarch64-linux-ohos"
export LDFLAGS="--rtlib=compiler-rt -fuse-ld=lld"
 

        这些设置确保了 configure 脚本能够正确地找到编译器、工具链和系统根目录。

        在运行 configure 脚本时,我们需要指定目标架构:./configure --host=aarch64-linux-ohos --target=aarch64-linux-ohos

        然而,由于 OpenHarmony 是一个相对较新的操作系统,标准的 config.sub 脚本可能无法识别 "ohos" 这个操作系统标识。为了解决这个问题,我们需要修改 config.sub 文件,在 linux-android 后添加 linux-ohos 选项。

        最后,把生成的 config.h 文件放置在库源码对应目录下,并在 BUILD.gn 中添加到对应 include_dirs 中即可正常编译。

3.2.2 udev 在 OpenHarmony 中没有代替机制

        在 Linux 系统中,ippusbxd 服务依赖 udev 机制,在 USB 设备插入时自动启动。udev 是 Linux 系统中的设备管理器,负责管理 /dev 目录下的设备节点,并在设备状态发生变化时触发相应的事件。它能够动态创建设备节点,加载驱动程序,并执行自定义的规则脚本,这样 Linux 就可以灵活地管理各种设备。

        然而,OpenHarmony 操作系统并不支持 udev。即使 OpenHarmony 提供了 uevent 机制(这是一个内核到用户空间的通知系统,用于报告设备状态的变化),但 uevent 相比 udev 功能较为有限,无法直接执行复杂的设备管理任务或运行自定义脚本。

        为解决这个问题,我们在 print_service 中设计了一个专门的服务来检测 USB 设备并实现类似 udev 的功能,以支持 IPP-USB 打印机的识别和管理。这个服务本质上是在应用层模拟了 udev 的核心功能,主要包括以下几个方面:

  1. 设备监听:利用 OpenHarmony 的 usb_client_srv API,我们实现了持续监听 USB 设备插拔事件的功能,类似于 udev 的设备事件监听机制。
  2. 设备识别:为了模拟了 udev 规则匹配的功能,当检测到 USB 设备插入时,我们会检查设备的 USB 描述符。具体来说,我们查看接口描述符中的 bInterfaceClass、bInterfaceSubClass 和 bInterfaceProtocol 字段。对于 IPP-USB 设备,这些值分别为 7(打印机类),1(打印机子类),和 4(IPP-USB 协议)。
    # lsusb -a
    Bus 001 Device 005: ID 04a9:1829 Canon, Inc. TS300 series
    Device Descriptor:
      ...
      Configuration Descriptor:
        ...
        Interface Descriptor:
          ...
          bInterfaceClass         7 Printer
          bInterfaceSubClass      1 Printer
          bInterfaceProtocol      4
          ...
        ...(Other Interface Descriptor)
  3. 动作触发:一旦识别出 IPP-USB 设备,我们的服务会自动启动 ippusbxd,并传递必要的参数。这相当于 udev 规则中定义的动作。
  4. 资源管理:我们实现了一个停止机制,能在打印服务停止或系统关闭时,正确地终止 ippusbxd 进程并释放资源。这模拟了 udev 的设备移除处理。

        在实现过程中,我们在 print_service 中新增了一个名为 print_ippusbxd_helper 的辅助模块,专门用于处理 IPP-USB 设备的连接和断开。这个模块封装了上述所有功能,提供了一个类似 udev 的抽象层。

3.2.3 pthread cancel 在 musl 里被禁用

        OpenHarmony 使用的 libc 库是 musl。musl 是一种 C 标准函数库,主要使用于以 Linux 内核为主的操作系统上,目标为嵌入式系统与移动设备。

        ippusbxd 会使用多线程同时处理多个打印任务并与设备通信。然而,用于终止线程的 pthread_cancel 函数在 OpenHarmony 中被禁用了,导致无法通过编译。

        官方 musl 是支持 pthread_cancel 的,但是翻阅 OpenHarmony musl 提交日志,开发人员并没有说明为什么要禁用 pthread_cancel,这给开发移植带来很大困难,因为我们不知道能不能启用 pthread_cancel,也不知道是否有一些安全性问题。在查阅大量资料后,我们推测 OpenHarmony 系统可能延续了 Android 的做法,因为 Android 的 Bionic libc 也不支持 pthread_cancel

        为了适配 OpenHarmony,我们只能手动替换掉 pthread_cancel,并根据 ippusbxd 逻辑,自己设置机制接管线程退出。

3.2.4 printer TXT 读取不到

        在现代网络打印环境中,打印机的 TXT 记录用于在打印机的发现和配置过程中传递关键信息。TXT(文本)记录是一种 DNS 资源记录类型,对于网络打印机,TXT 记录包含了诸如打印机型号、支持的打印协议、页面描述语言(PDL)等重要参数。这些信息对于操作系统和打印驱动程序来说是必不可少的。TXT 记录通常通过网络发现协议(如 mDNS/Bonjour)广播,并被本地系统的服务发现守护进程(在我们的案例中是 avahi-daemon)捕获。

        然而,在开发过程中,我们遇到了一个棘手的问题:系统无法读取打印机的 TXT 记录。这个问题涉及到了 avahi-daemon 和 ippusbxd 两个关键模块的交互。问题的症状表现为,即使手动启动 avahi-daemon,ippusbxd 在启动时仍然报错,无法连接到本地的 60000 端口。我们首先使用 avahi-browse 工具进行初步诊断。结果有些奇怪,虽然 avahi-browse 能够探测到打印机服务,但输出与 Ubuntu 系统上的有明显差异:

# # OpenHarmony
# avahi-browse -rt _ipp._tcp
+     lo IPv4 TS300 series                                  _ipp._tcp            local
=     lo IPv4 TS300 series                                  _ipp._tcp            local
   hostname = [linux.local]
   address = [127.0.0.1]
   port = [60000]
   txt = ["Fax=F" "note=" "adminurl=" "qtotal=1" "txtvers=1" "priority=60" "usb_MDL=TS300 series" "usb_MFG=Canon" "rp=ipp/print"]

# # Ubuntu
$ avahi-browse -rt _ipp._tcp
+     lo IPv4 Canon TS300 series (USB)                      Internet Printer     local
=     lo IPv4 Canon TS300 series (USB)                      Internet Printer     local
   hostname = [Laptop-ubuntu.local]
   address = [127.0.0.1]
   port = [60000]
   txt = ["air=none" "mopria-certified=1.3" "rp=ipp/print" "priority=50" "kind=document,photo,postcard" "PaperMax=isoC-A2" "URF=V1.4,CP1,PQ4-5,RS600,SRGB24,W8,OB9,OFU0,IS1" "UUID=00000000-0000-1000-8000-001829233e1c" "Color=T" "Duplex=F" "note=" "qtotal=1" "usb_MDL=TS300 series" "usb_MFG=Canon" "usb_CMD=BJRaster3,NCCe,IVEC,URF" "ty=Canon TS300 series" "product=(Canon TS300 series)" "pdl=application/octet-stream,image/jpeg,image/urf,image/pwg-raster" "txtvers=1" "adminurl=http://localhost:60000/index.html?page=PAGE_AAP" "Fax=F" "Scan=F"]
...
=     lo IPv4 0105                                          _ipp-usb._tcp        local
   hostname = [Laptop-ubuntu.local]
   address = [127.0.0.1]
   port = [60000]
   txt = []

        特别值得注意的是,我们的系统中缺少了 _ipp-usb._tcp 服务,这在标准 Ubuntu 系统中是存在的。

        为了进一步定位问题,我们采取了以下调试步骤:

  1. 对比 Ubuntu 系统:
            我们在 Ubuntu 系统上禁用了默认的 ipp-usb 服务,改为使用 ippusbxd。通过这种方式,我们发现 ippusbxd 在 Ubuntu 上是可以正常工作的,这说明问题可能与 OpenHarmony 的系统环境有关。
  1. 网络连接验证:
            使用 netstat 命令,我们确认 60000 端口确实处于监听状态:
# netstat -tuln | grep 60000
tcp        0      0 127.0.0.1:60000         0.0.0.0:*               LISTEN
tcp6       0      0 ::1:60000               :::*                    LISTEN

        这个结果排除了端口未开放的可能性。

  1. 自定义 socket 测试:
            为了排除系统防火墙或其他网络限制的影响,我们编写了一个简单的 C 语言 socket 程序来测试基本的网络功能:
// 简化的服务器代码
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(60000);
bind(server_fd, (struct sockaddr *)&address, sizeof(address));
listen(server_fd, 3);
// 接受连接的代码...

// 简化的客户端代码
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(60000);
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);
connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

        这个测试成功建立了连接,证明基本的网络功能是正常的。

  1. ippusbxd TCP 实现分析:
            既然自定义的 socket 程序可以工作,我们将注意力转向了 ippusbxd 的 TCP 实现。我们提取了 ippusbxd 中的 TCP 相关代码,编写了一个独立的服务器程序进行测试。令人惊讶的是,这个程序也无法正常工作。通过深入分析 ippusbxd 的源码,我们发现了问题所在:
// ippusbxd 中的问题代码
if (listen(sock, 0) < 0) {
    // 错误处理
}

        listen 函数的第二个参数(backlog)被错误地设置为 0。虽然在多数 UNIX 系统中,0 会被解释为一个小的默认值(通常是 5),但这种做法可能导致不可预测的行为,特别是在不同的操作系统上。

        修改后,ippusbxd 终于能够在我们的系统上正常工作,avahi-daemon 也能够正确地探测到打印机服务并获取完整的 TXT 记录。

3.2.5 OpenHarmony netmanager 无法发现 mdns 广播

        在调通 avahi-browse 网络发现之后,我们发现 OpenHarmony 依然无法发现打印机。具体表现为,avahi-browse 工具可以正常检测到网络上的打印机,但 OpenHarmony 的 print spooler 中的 ippprint 插件却检测不到。

        调试过程中,我们首先检查了 print spooler 的日志,但发现其中的 ippprint extension 正是靠 mdns 发现网络打印机的,主要是对 mDNS 的封装,没有提供足够的信息。

    this._discoveryService.on('serviceFound', this.onServiceFound);
    this._discoveryService.on('serviceLost', this.onServiceLost);
    this._discoveryService.startSearchingMDNS();
 

        因此,我们将注意力转向了 mDNS 组件。mDNS(Multicast DNS)是一种用于本地网络服务发现的协议。它是由 netmanager_ext 封装的。通过详细的日志分析和网络监控,我们观察到一个关键现象:OpenHarmony 的 netmanager 发出的 mDNS 广播仅被自身接收,avahi-daemon 进程并未收到这些请求(如下 log 所示)。相反,当使用 avahi-browse 工具时,avahi-daemon 能够正常响应。这一观察结果初步指向了网络接口配置的潜在问题。

../../third_party/avahi/avahi-daemon/dbus-protocol.c: interface=org.freedesktop.Avahi.Server, path=/, member=GetAPIVersion
../../third_party/avahi/avahi-daemon/dbus-protocol.c: interface=org.freedesktop.Avahi.Server, path=/, member=GetState
../../third_party/avahi/avahi-daemon/dbus-protocol.c: interface=org.freedesktop.Avahi.Server, path=/, member=GetVersionString
../../third_party/avahi/avahi-daemon/dbus-protocol.c: interface=org.freedesktop.Avahi.Server, path=/, member=GetHostNameFqdn
../../third_party/avahi/avahi-daemon/dbus-protocol.c: interface=org.freedesktop.Avahi.Server, path=/, member=ServiceTypeBrowserNew
../../third_party/avahi/avahi-daemon/dbus-protocol.c: interface=org.freedesktop.Avahi.Server, path=/, member=ServiceBrowserNew
../../third_party/avahi/avahi-daemon/dbus-protocol.c: interface=org.freedesktop.Avahi.Server, path=/, member=ServiceBrowserNew
../../third_party/avahi/avahi-daemon/dbus-protocol.c: interface=org.freedesktop.Avahi.Server, path=/, member=ServiceBrowserNew
sendmsg() to ff02::fb (iface #1) failed: Network unreachable
../../third_party/avahi/avahi-daemon/dbus-protocol.c: client :1.13 vanished.

        起初,我们假设两个系统都在回环接口(lo interface)上工作,通过 127.0.0.1 进行广播。这个假设似乎合理,因为在本地网络发现中,回环接口通常扮演重要角色。我们甚至考虑使用 tcpdump 工具进行更深入的网络数据包分析。然而,回顾早期的测试数据,OpenHarmony 的打印服务能够发现其他网络中的打印机。这一事实引发了一个关键问题:虽然 avahi 确实在回环接口上运行,但 OpenHarmony 的 mDNS 实现是否也采用了相同的策略,读取回环接口上的数据?

        深入分析后,我们发现了导致 OpenHarmony netmanager 无法发现 mDNS 广播的两个关键问题。首先,OpenHarmony 的 mDNS 实现采用了一种特殊的网络接口选择策略。具体而言,它默认排除了回环接口(lo),除非系统中没有其他可用的网络接口。这与广泛使用的 avahi 守护进程形成鲜明对比,后者主要在回环接口上运行以实现本地服务发现。这种实现差异直接导致了 OpenHarmony 和 avahi 之间的通信障碍,使得两个系统无法相互发现对方的 mDNS 广播。

        其次,我们在 OpenHarmony 的网络接口处理代码中发现了一个微妙但影响重大的错误。在 Ifaceverification 函数中,开发者错误地传递了指针而非指针的指针,导致回环接口的地址未能被正确赋值。这个看似微小的编程错误实际上阻碍了系统正确识别和配置回环接口,进一步加剧了 mDNS 发现的问题。

        通过这些修复,OpenHarmony 的 mDNS 现在可以在回环接口上广播,并能够接收到 avahi-daemon 的回应。OpenHarmony 打印界面终于能够发现打印机了。

3.2.6 ippusbxd 启动与退出问题

        在开发过程中,我们遇到了 ippusbxd 无法自动启动和退出的问题。尽管我们已经开发了专门用于处理打印机热插拔的模块,但这个问题仍然存在。

首先,关于启动问题,我们发现 ippusbxd 无法正常打开 USB 设备。通过将 ippusbxd 的日志输出到 hilog,我们观察到以下错误信息:

01-01 08:11:32.781  3245  3245 E C01c00/ippusbxd: <-2009826752>Error: failed to open device

        这个错误提示我们,ippusbxd 缺少访问 USB 设备的必要权限。在 Linux 系统中,ippusbxd 通常通过 AppArmor(一个 Linux 安全模块,用于强制执行应用程序的访问控制策略)来获取权限。然而,OpenHarmony 的权限控制和 Linux 不同,我们需要另想办法。

        经过研究,我们发现 OpenHarmony 使用自己的 ueventd 来管理设备节点权限。通过修改 base/startup 目录下的 ueventd.config 文件,可以调整相应 dev 节点的 uid 和 gid,取得访问权限。

        关于退出问题,我们观察到以下现象:

  1. 进程列表中会出现一个僵尸进程。
  2. 这个僵尸进程无法提供 ipp.tcp 服务,而新启动的进程可以正常工作。

print         2344  2329 0 00:42:19 ?     00:00:00 [ippusbxd]
print         2526  2329 1 00:42:54 ?     00:00:00 ippusbxd --bus-device 2:2 --from-port 60000 --logging --verbose --no-fork

        这些现象表明,ippusbxd 在资源清理阶段存在问题。通过正确处理打印机拔出时 ippusbxd 父子进程之间的通信,我们最终解决了 ippusbxd 无法正常退出的问题。

        尽管现在 ippusbxd 能够正常启动和退出,我们也意识到当前的解决方案仍有改进空间。未来,我们需要深入研究 OpenHarmony 的设备权限管理机制,以寻求更加规范和健壮的解决方案。这不仅能提高系统的稳定性,还能为类似的设备管理问题提供更好的处理方法。

4. 结果与验证

        为了验证本研究在 OpenHarmony 系统上移植免驱动打印功能的有效性和稳定性,我们设计并执行了一系列全面的测试。这些测试不仅涵盖了各个关键组件的独立功能,还包括了整个系统的集成表现。通过这些测试,我们可以全面评估移植工作的成功程度,同时为后续的开发人员提供了一个可靠的验证框架。

  1. 我们验证了系统底层通信机制的正常运作。测试结果表明,在系统启动后,D-Bus 守护进程能够正常启动并稳定运行,确保了系统各组件间的有效通信。这为上层应用和服务的正常运行奠定了基础。
  2. 我们检测了网络服务发现功能的可用性。Avahi 守护进程在系统启动后能够正常运行,并且通过 Avahi publish 和 Avahi browse 工具的配合测试,证实了系统具备了发布和浏览网络服务的能力。这一功能对于打印机的网络发现至关重要。
  3. 在核心功能方面,我们重点验证了 USB 打印机的即插即用特性。测试显示,当 USB 打印机被插入时,print_service 服务能够准确检测到设备并自动启动 ippusbxd 进程,整个过程无需用户干预。ippusbxd 进程启动后,能够正确操作 USB 设备并发布 IPP 服务。这意味着系统成功地将物理连接的 USB 打印机转换为网络可访问的 IPP 打印服务,实现了免驱动打印的核心目标。
  4. 我们测试了系统对设备热插拔的响应能力。当 USB 设备被拔出时,ippusbxd 进程能够及时检测到这一变化并正常退出,避免了系统资源的无谓占用,保证了系统的稳定性和资源利用效率。

        这一系列测试不仅验证了移植工作的成功,也为开发人员提供了一个系统化的验证方法。通过逐项检查这些测试项目,开发者可以快速定位潜在问题,类似于单元测试的思路。如果所有测试项目都通过,则可以合理推断整个系统运作正常;如果出现异常,这个框架也有助于精确定位问题所在。

5. 总结

        在 OpenHarmony 系统上,我们成功移植了免驱动打印功能。这项工作不仅填补了系统在打印功能方面的空白,也为 OpenHarmony 的开源生态贡献了重要一笔。

        作为一个新兴的操作系统,OpenHarmony 目前还缺乏广泛的打印机厂商支持。通过实现免驱动打印,我们降低了打印机厂商的开发成本,同时为 OpenHarmony 社区提供了实用的技术方案。

        在用户体验方面,这项功能极大地扩展了 OpenHarmony 支持的打印机范围。特别是通过支持 IPP over USB 协议(IPPUSB),使得大多数现代打印机,尤其是高端机型,能够在 OpenHarmony 系统上即插即用。用户现在可以更方便地进行打印操作,无需复杂的驱动安装过程。

        我们同时支持了无线 IPP 打印和 USB 连接打印,这让 OpenHarmony 在打印功能上更加完整。这不仅增强了系统在日常办公和家庭使用场景中的实用性,也为未来在智能家居、移动办公等领域的应用奠定了基础。

        通过这项工作,我们希望能为 OpenHarmony 生态系统的发展贡献一份力量,同时提升用户的使用体验。

6. 参考资料

6.1 OpenHarmony

6.1.1 编译

6.1.2 启动

6.1.3 打印

6.2 USB 免驱打印

6.3 代码资源

6.4 其他参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值