当前位置: 首页 > news >正文

ImprovWifi 跨平台传输层设计:把协议层做薄,把宿主层做稳

本文基于 Sang.ImprovWifi 的实践,分享我如何把 Improv 协议层抽离成可复用内核,再通过 Windows/Linux/AOT 三种宿主实现完成跨平台落地。

1. 引言

前面我已经写过几篇关于 Improv Wi-Fi 的文章,主要是介绍其如何使用。这里我想从设计的角度聊聊我在实现 Sang.ImprovWifi 时的一些思路和教训,尤其是如何把协议层做薄、宿主层做稳,从而实现真正可维护的跨平台代码。

这篇文章就来聊聊这个设计思路的细节,在开发跨平台的服务时,经常遇到一个看似普通、实则很痛的问题:同样的业务逻辑,一换平台就要重写一遍 BLE 代码。

这件事最直接的后果,不是开发慢,而是质量不一致。Windows 版稳定,Linux 版可能刚起步;Linux 版修了一个 bug,Windows 版又漏同步。久而久之,项目维护会越来越吃力。

在设计 Sang.ImprovWifi[1]时,我的核心尝试就是把这件事反过来:

  1. 协议层只做协议

  2. 宿主层只做平台

  3. 业务层只做业务

听起来像一句“架构口号”,但实践下来,它确实能显著降低复杂度。

2. 为什么要先做协议层

Improv 本身是开放协议,核心其实不复杂:

  1. 设备暴露固定 Service 和 Characteristic

  2. 客户端写入 RPC Command(如 Identify、Wi-Fi 配置)

  3. 设备更新状态并返回 RPC Result URL

真正复杂的是“状态一致性”和“事件语义”,比如:

  • 授权前后状态怎么切

  • 校验失败时返回哪个错误码

  • 何时触发ProvisioningRequested

  • RpcResult的 payload 如何编码

这些都属于协议语义,天然应该独立于平台。

所以我在项目里先做了ImprovWifi.Protocol,把这些东西收敛成可复用模型,而不是散落在每个平台实现里。

Protocol

3. 三层结构

项目结构大致如下:

  1. ImprovWifi.Protocol

  • 协议常量

  • ImprovPacketCodec

  • ImprovService状态机

  • 事件模型与通用传输抽象

  • 平台传输层

    • ImprovWifi.Transport.Windows

    • ImprovWifi.Transport.Linux

    • ImprovWifi.Transport.Linux.Aot

  • Demo 层

    • ImprovWifi.Demo.Windows

    • ImprovWifi.Demo.Linux

    • ImprovWifi.Demo.Linux.Aot

    这个分层的核心好处,是把“变化频率不同”的代码分离。

    1. 协议变化频率低,但正确性要求高

    2. 平台实现变化频率高,且受系统能力影响大

    3. Demo 变化更快,主要用于验证场景

    把它们揉在一起,维护成本会指数上升。

    4. 事件驱动模型

    我在ImprovTransportServer里提供了一组业务友好的事件:

    1. ProvisioningRequested

    2. IdentifyRequested

    3. CurrentStateChanged

    4. ErrorStateChanged

    5. RpcResultChanged

    这组事件的价值在于:

    • 平台差异被屏蔽

    • 业务代码只关注输入和输出

    • 上层可以非常自然地接入真实联网逻辑

    例如,你可以在ProvisioningRequested里调用系统网络栈;成功后调CompleteProvisioning(url),失败就调FailProvisioning(error)。这个编程模型在 Windows 和 Linux 上是一致的。

    5. Linux AOT 版的意义:给受限设备一条工程化路径

    ImprovWifi.Transport.Linux.Aot不是“多写一个版本”,而是为了真实设备场景。

    很多 ARM 小板上,AOT + native 是更现实的组合:

    1. 启动路径更可控

    2. 依赖更可控

    3. 跨架构分发更可控

    在这个版本里,我做的不是“把 C# 全部改成 Rust”,而是只把最贴近 BlueZ 的部分放进 Rust,再通过 C ABI 暴露能力。这样我们保留了 .NET 协议层和上层业务的一致性。

    5.1 为什么选择 Rust

    早期ImprovWifi.Transport.Linux依赖的是Linux.Bluetooth,这条路径在 AOT 下问题比较明显,核心不是业务逻辑,而是运行时兼容和底层行为稳定性。

    后面我又尝试替换到Tmds.DBus路线,这一步确实解决了一部分问题,但在我这块测试板上,AOT 仍然不是“稳定可交付”的状态,尤其是在重复启动和异常恢复这类真实场景里。

    这里做了一个对比测试,使用了“假 AOT”方式,也就是单文件发布 + 裁剪,不是严格意义上的 Native AOT,这样在板子上测试发现业务流程本身是没问题的。

    也就是说,问题并不在 Improv 协议逻辑,而是在 AOT + Linux BLE 宿主这层的可控性。

    到这里,方向就很清晰了:

    1. 协议层保留在 .NET

    2. Linux BLE 宿主下沉到更可控的 native 层

    3. 用稳定 C ABI 连接两侧

    于是我最终选择了 Rust 自研这条路,要把 AOT 场景做稳,必须把最底层那部分拿回可控范围。

    5.2 Rust 项目和 C ABI 设计

    这条路也并不是一帆风顺,中间折腾了不少,经历了:

    • 打包 NuGet 里 native 文件路径不对

    • 回调递归调用导致死循环,CPU 飙升、URL 重复打印

    • 首次启动正常,二次启动就报错,最后定位到 BLE 资源释放不彻底

    这里不再展开实现细节。具体的 Rust 项目可以在 GitHub 项目的native/improvwifi_transport_linux_aot_native目录下看到。下面这段代码主要用于说明 C ABI 接口边界,核心设计思路是:

    1. Rust 负责实现 BlueZ 广播、GATT、D-Bus 对象导出

    2. 通过 C ABI 导出一组句柄式 API 给 .NET 调用

    3. .NET 通过回调表接收 native 事件,再映射回ImprovTransportServer的语义事件

    在 native 侧,我导出了一组句柄式 API,在 .NET 包装层中,通过回调表接收 native 事件:

    using System.Runtime.InteropServices; namespaceImprovWifi.Transport.Linux.Aot.Native; internalstaticpartialclassLinuxAotNativeMethods { privateconststring LibraryName = "improvwifi_transport_linux"; [StructLayout(LayoutKind.Sequential)] internalstruct CallbackTable { public IntPtr OnIdentifyRequested; public IntPtr OnProvisioningRequested; public IntPtr OnStateChanged; public IntPtr OnErrorChanged; public IntPtr OnRpcResultReady; } [LibraryImport(LibraryName, EntryPoint = "improv_server_create")] internalstaticpartial IntPtr Create(); [LibraryImport(LibraryName, EntryPoint = "improv_server_destroy")] internalstaticpartialvoidDestroy(IntPtr handle); [LibraryImport(LibraryName, EntryPoint = "improv_server_start")] internalstaticpartialintStart(IntPtr handle); [LibraryImport(LibraryName, EntryPoint = "improv_server_stop")] internalstaticpartialintStop(IntPtr handle); [LibraryImport(LibraryName, EntryPoint = "improv_server_set_callbacks")] internalstaticpartialintSetCallbacks(IntPtr handle, in CallbackTable callbacks, IntPtr userData); [LibraryImport(LibraryName, EntryPoint = "improv_server_set_authorized")] internalstaticpartialintSetAuthorized(IntPtr handle, [MarshalAs(UnmanagedType.I1)] bool authorized); [LibraryImport(LibraryName, EntryPoint = "improv_server_set_config")] internalstaticpartialintSetConfig( IntPtr handle, byte[] adapterNameUtf8, nuint adapterNameLength, byte[] deviceNameUtf8, nuint deviceNameLength, [MarshalAs(UnmanagedType.I1)] bool discoverable, [MarshalAs(UnmanagedType.I1)] bool includeTxPower); [LibraryImport(LibraryName, EntryPoint = "improv_server_complete_provisioning")] internalstaticpartialintCompleteProvisioning(IntPtr handle, byte[] urlUtf8, nuint urlLength); [LibraryImport(LibraryName, EntryPoint = "improv_server_fail_provisioning")] internalstaticpartialintFailProvisioning(IntPtr handle, byte errorCode); }

    这个模式的优势是边界非常明确:

    1. native 只关心“事件发生了”

    2. 协议层只关心“状态应该怎么变”

    3. 双方都不需要知道对方内部实现

    6. 结语

    跨平台开发最难的,从来不是“支持几个平台”,而是“在平台差异存在的前提下,仍然保持语义一致和迭代效率”。

    通过开发Sang.ImprovWifi这套实践给我的最大反馈是:当你把协议层做薄、宿主层做稳,很多复杂问题会自然变简单。

    除了让拆分和分层更易于理解与维护外,最大的好处是:在排查问题时,可以快速判断问题属于哪一层。是协议层的逻辑问题,还是宿主层的实现问题?当你在 AOT 版本上遇到问题时,就知道先从 native 层的日志和状态入手,而不是怀疑协议层的实现。

    References

    [1]Sang.ImprovWifi:https://github.com/sangyuxiaowu/Sang.ImprovWifi?wt.mc_id=DT-MVP-5005195

http://www.jsqmd.com/news/880516/

相关文章:

  • FPG平台:信息透明度建设的深度解析
  • Android 全栈体系 150 讲 - 49 深度完整版 Android 常用设计模式 + 架构模式 源码剖析、业务落地、面试精讲
  • LeetCode 每日一题笔记 日期:2026.05.22 题目:33. 搜索旋转排序数组
  • 智能控制 第六章——集成智能控制系统
  • 基于SpringBoot+用户画像的商品个性化推荐毕业设计
  • Wireshark与FTK Imager电子证据采集实战指南
  • 从零开始单细胞分析:手把手教你用Scanpy复现PBMC3K教程(附避坑指南)
  • FPG平台:行业前景下的战略定位评估
  • 2026年当下常德卫生间防水公司实力盘点:优家房屋修缮中心为何备受青睐? - 2026年企业推荐榜
  • 2026年免费图片去水印保姆级教程:不用下载软件,微信小程序一步搞定
  • 渗透测试工具认知地图:从工作流理解工具本质
  • SpringBoot+Vue校车管理信息系统源码+论文
  • 首发!美团开源最强数字人 LongCat 1.5:性能狂飙15倍,8步闪电成片!
  • 基于Simulink的四开关buck-boost变换器闭环仿真模型
  • 四川钢板生产厂家名录|2026 年 5 月行情走势与价格预测 - 四川盛世钢联营销中心
  • 保姆级教程:在AirSim中用Python实现四旋翼的实时避障(附完整代码与避坑点)
  • SpringBoot+Vue实验室研究生信息管理系统源码+论文
  • 2026年Q2四川消防维修维保品牌名录及选型指南:成都消防维修口碑/消防技术服务/消防改造公司/消防改造多少钱/选择指南 - 优质品牌商家
  • 从原理到代码:用Python仿真TOA、TDOA和RSS定位算法(附GitHub源码)
  • Django 从 0 到 1 打造完整电商平台:购物车实现方式分析与模型设计
  • 基于静态动态障碍物DWA、DWA+RRT*、改进A*、RRT* 2D和3D的路径规划算法Matlab代码
  • OpenAI 推出的 GPT-5.5 大模型,倒逼接口芯片升级迭代@ACP#IX7024应用迭代
  • SpringBoot+Vue在线智慧考公系统源码+论文
  • Agent开发五层架构详解,AI智能体开发知识点
  • 基于模糊控制算法的水位控制研究(Matlab代码实现)
  • 保姆级教程:在Ubuntu 20.04上从零跑通VINS-Fusion并用EVO评测轨迹精度
  • 5分钟快速上手:免费开源Modbus调试工具QModMaster终极指南
  • LeetCode热题100-排序链表
  • Rust错误处理最佳实践:从Result到自定义错误类型
  • GPT-5.5 智能化全面普及,@ACP# IX、GSV 系列芯片构筑全层级硬件底座