网络测速工具
基于PyQt的桌面测速工具,支持WIN与MAC双端
花 6 天,从零写一个跨平台的局域网测速小工具
接到一个需求:做一款局域网测速工具,覆盖 Windows 和 macOS,用户侧的操作要尽可能少。预期是打开应用,能看到局域网内可测速的设备,点一下就能开始测速。
听起来范围不大,实际做下来发现坑不少。
这篇文章是对这 11 天开发过程的一次复盘——不做代码罗列,只记录关键的技术决策、遇到的实际问题,以及事后觉得可以做得更好的地方。
选型:几项没有太多争议的决定
底层测速引擎直接用了 iperf3,这个没有太多讨论空间——它是网络吞吐量测试的事实标准,结果数据客户也认可。
GUI 框架选了 PyQt6。选型的考虑是跨平台能力、组件成熟度、以及打包生态的完整性。Electron 也在考虑范围内,但对于一个固定尺寸的桌面工具窗口来说,PyQt6 更轻量,打包体积也更可控。
设备发现层是需要自己设计的部分。iperf3 本身要求用户手动指定服务端 IP,但这与"点一下就能测"的体验目标矛盾。解决思路是设计一套基于 UDP 广播的设备发现协议——Binary RPC 格式,广播发出后解析响应,将支持测速的设备展示在列表里。
最终的技术栈:iperf3 负责测速 / PyQt6 负责界面 / 自定义协议负责发现 / PyInstaller + Inno Setup + App Bundle 负责双平台打包。
第一天:跑通核心管道
4 月 29 号,初次提交。
目标很明确:让 PyQt6 窗口运行起来,iperf3 通过子进程能够被调起,协议能够正确编解码,UDP 能够收发。第一版的交互方式是手工输入目标 IP 和 iperf 端口,界面上放置开始和停止两个按钮——先把 iperf3 的子进程管道跑通,后续的设备发现要依赖这条管道,所以底层稳定性是第一步需要确认的。

功能链路是跑通了,但界面本身还处于很原始的状态。同时这个阶段只支持了 Windows,macOS 适配尚未开始。
第二天:交付闭环
4 月 30 号,6 个提交,核心议题是让应用能够真正交付到用户手上。
首先是 URL Scheme 注册。NAS 管理界面是 Web 形态,用户在上面点击"测速"按钮后,需要自动唤起桌面应用并进入测速流程。为此注册了自定义协议 LANPerf://——Windows 侧通过 Inno Setup 将注册表写入 HKCU\Software\Classes\LANPerf,macOS 侧通过 App Bundle 的 CFBundleURLTypes 声明。
其次是安装器。Windows 端采用 Inno Setup,需要覆盖安装/升级/卸载三条路径;macOS 端需要构建 .app 并打包为 .dmg,涉及代码签名和公证流程。两个平台各有独立的一套工具链,这部分的时间投入比预想的多出不少。
还处理了一个容易被忽略的细节:任务栏图标。应用图标和公司品牌 Logo 是两份不同的资源,但在初始版本中任务栏显示的是 Logo 而非应用图标,后续做了区分。
第三天到第五天:适配真实网络环境
5 月 6 号到 8 号,9 个提交。这是开发密度最高的阶段,大部分问题在开发机的单网卡环境下根本不会暴露。
多网卡问题是首先碰到的。现代机器通常同时存在有线网卡、无线网卡、虚拟网卡和 VPN 虚拟适配器,UDP 广播默认只走默认路由指向的那一个接口,导致其他子网中的设备无法被发现。解决方案是遍历所有活跃网卡,每个接口独立发送一份广播。但这也引入了新问题:同一设备可能从多个接口收到广播并多次回复,需要在接收侧按 MAC 地址和接收接口做去重匹配。
广播地址的选择同样经历了调整。最初使用子网定向广播(如 192.168.1.255),但在部分交换机配置下不可达。最终统一改用全局广播地址 255.255.255.255,兼容性明显改善。
iperf3 的输出处理也有几个细节需要处理。实时 stdout 中偶有重复行出现,需要做去重;UDP 控制协议自身的交互日志不应出现在用户可见的测速面板中,需要按来源做过滤——用户只应看到 iperf 测速的过程输出和最终的高亮总结。
设备列表的生命周期管理是这阶段的另一个重点。设备上线后需要及时出现在列表中,离线后需要被移除。参考了 MAC 表老化机制:每个设备维护独立的计数器,连续 3 个发现周期(15 秒)未收到响应则从列表中淘汰。新增和删除都加了过渡动画——新增项从下方向上滑入,删除项左滑移除后下方项向上补位。
安装器也在持续完善:加入简体中文安装界面;升级安装前自动执行旧版本卸载;卸载前检测应用是否在运行并给出提示。每一项都需要调试对应的 Inno Setup Pascal 脚本。
最后一天:收尾
5 月 9 号,3 个提交。
多网卡广播时的序列号同步——不同网卡发出的同一轮广播,序列号需要保持一致,否则接收端会当成不同的消息处理
提前结束测速时,接收端的结果汇总此前存在数据不完整的问题
版本号迭代
从手动到自动:交互方式的彻底转变
回头看,这个工具最大的变化不在代码层面,而在交互设计上。
初始版本是一个传统的工具窗口——用户需要知道目标设备的 IP 地址和 iperf 监听端口,手动输入后点击开始,测完点停止。本质上和直接用 iperf3 命令行差别不大,只是多了一层 GUI 包装。
最终版本则完全不同。整个窗口里找不到一个输入框:设备发现协议在后台以固定间隔广播,局域网内支持测速的设备自动出现在左侧列表中,附带设备名、IP 和类型图标(PC 或 NAS)。用户的操作被简化到一步——点击目标设备,弹出确认框,确认后测速开始。


这一转变的背后是设备发现协议、状态机调度和 URL Scheme 三方协同的结果,不是单纯的 UI 改版。
视觉打磨方面:
暗色和亮色双主题,切换按钮使用太阳和月亮图标,切换过程带有约 0.8 秒的倾斜覆盖过渡动画
无边框窗口,右上角三个控制按钮(主题切换、最小化、关闭),省去传统标题栏的空间占用
设备列表项采用图标加双行文字的结构,PC 和 NAS 使用不同的图标
状态指示灯:灰色表示空闲,绿色表示测速进行中
左右两栏交界处有一条发光细线作为等待指示,不显示百分比,仅提供视觉反馈
滚动条统一处理为细圆角样式
右下角显示版本号
窗口可拖动但尺寸固定——固定尺寸对于工具型应用避免了控件拉伸后的布局问题
几点反思
打包和安装器的成本被低估了
核心的测速逻辑差不多一天写完,但安装器和跨平台打包花了将近三天。Inno Setup 的 Pascal 脚本、macOS 的代码签名和公证流程、DMG 制作——这些工具链的学习成本不容忽视。如果重来,我会在第一天就把双平台的完整打包链路跑通,而不是放在功能完成之后。
网络工具的测试需要真实拓扑
多网卡、虚拟适配器、不同交换机配置下的广播行为——这些边界情况在开发机的单网卡环境下完全不可见。直到部署到不同的网络环境后才逐一暴露。网络工具的开发中,测试环境的多样性比代码本身的质量更先决。
协议设计越简单,后续调试越省力
协议层从一开始就定了几条清晰的约束:Request 必须有 Response,Notify 单向不需要回应,error_code 和 error_msg 固定放在最前面。状态机也只设了三种状态:空闲、服务端测速、客户端测速。非空闲态下仅响应结束测速指令。这些简化让后续的调试工作少走了很多弯路。
过渡动画有实际价值
主题切换的倾斜覆盖、设备列表的滑入滑出——这些动画不只是视觉上的修饰。对于设备列表这种动态变化的内容,如果没有任何过渡,用户很容易忽略增删变化。动画提供了可感知的视觉线索。
数据一览
周期:2026 年 4 月 29 日至 5 月 9 日,其中劳动节放假 5 天,约 6 天
代码规模:约 3,200 行 Python,分布为 10 个模块
提交次数:21 次
平台支持:Windows + macOS
技术栈:Python / PyQt6 / iperf3 / UDP / PyInstaller / Inno Setup
如果再做一次
第一天就搭建双平台打包链路,与功能开发同步推进
尽早部署到真实网络环境中验证,而非在开发机上依赖假设
URL Scheme 的注册放在安装阶段完成(Windows 走 Inno Setup 注册表,macOS 走 Info.plist),而不是依赖应用首次运行时再写入——这一点是后来才修正的
总的来看,6 天交付一个覆盖双平台、支持设备自动发现和 URL Scheme 唤起的桌面测速工具,同时沉淀了一套可复用的技术方案,结果可以接受。
本文仅记录技术方案与个人思考,不包含源代码或商业敏感信息。