十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Qt与NetworkManager的Linux WiFi图形连接工具开发实践

基于Qt与NetworkManager的Linux WiFi图形连接工具开发实践 简介Linux下基于QT框架编写的Wi-Fi连接程序是一套面向Ubuntu等Linux环境中开发图形化网络管理工具的中初级开发者的完整Qt工程源码解决如何利用QNetworkConfigurationManager等接口完成无线网络扫描、选择与连接并反馈状态的问题。压缩包共12个文件以.cpp逻辑实现、.h类定义、.ui界面布局三类源码为主另含.pro工程文件与.user用户配置整体仅9KB代码量非常精简适合快速拆解学习。程序实现了Wi-Fi扫描列表展示、密码输入对话框、连接与断开操作等核心交互并演示了QNetworkConfigurationManager与wpa_supplicant/iwconfig底层工具的协作方式以及如何通过信号槽机制更新界面状态、将网络管理逻辑封装为独立类避免UI与业务耦合。目前已有4517人学习下载适合作为Linux网络编程、Qt GUI设计与跨平台开发的一手参考小项目。 在Linux上折腾过WiFi连接的朋友多半经历过这种场景设备刚刷好系统桌面环境没装或者装了个精简版窗口管理器结果连个WiFi都要开终端敲命令。nmcli dev wifi connect xxx password yyy这条命令我敲了没有一百次也有八十次说实话在开发机上调试点一下无所谓但如果你做的是嵌入式设备、工业触摸屏、迷你主机这类要交付给普通用户的产品总不能让客户去学Linux命令行。所以在实际项目中我用Qt写了一个带图形界面的WiFi连接程序底层走的是Linux上最常见的NetworkManager管理栈。这篇文章把整个开发过程、核心代码思路、以及踩过的坑都整理出来分享给有同样需求的开发者。1. 动手之前先想清楚WiFi连接这件事1.1 Linux上连接WiFi的常规路径很多人第一次接触Linux下连接WiFi网上一搜方法五花八门其实归结起来就三条路NetworkManager系用nmcli命令或者GNOME/KDE的网络设置面板连接这是桌面发行版默认的方式服务叫NetworkManager几乎所有主流发行版Ubuntu、Debian、Fedora、CentOS、Deepin等都内置了。iwd/iwctl系Intel开发的无线守护进程在Arch、Manjaro等发行版中比较流行命令风格和NetworkManager不太一样但也能实现相同功能。wpa_supplicant系最底层的WiFi管理工具直接和无线网卡驱动打交道配置文件要手写适合极简系统或者嵌入式环境。我当时的情况是给一个触摸屏工控机做控制界面系统是Debian跑一个自定义的Qt界面程序但WiFi配置还得临时切到命令行去操作非常打断体验。于是就想把WiFi连接功能直接做成界面里的一个模块。1.2 为什么用Qt而不是其他框架选Qt主要出于几个实际考虑一是Qt的Widgets模块写这类工具界面非常轻量不用引入Web前端那一套编译出来就是个独立的二进制二是Qt的信号槽机制对异步任务很友好后面配合QProcess操作nmcli命令、监听连接状态都比较自然三是项目里其他模块本来就是Qt开发的保持技术栈统一维护成本最低。1.3 核心技术选型封装nmcli而不是直接用D-Bus接着就遇到一个关键问题程序怎么和系统里的WiFi管理服务通信我直接把三种方案对比了一下方案实现方式开发效率可控性依赖环境适用场景nmcli封装QProcess执行nmcli命令高代码量小中基于命令行的输出解析必须有NetworkManager普通桌面、工控机、产品原型D-Bus直调通过QDBusInterface调用NetworkManager的接口低代码繁琐高可以精细控制所有属性必须有NetworkManager需要深度定制网络逻辑wpa_supplicant直连直接读写wpa_supplicant.conf并发送控制命令低要自己处理状态机最高不依赖NetworkManager只需要wpa_supplicant极简嵌入式系统从表格可以很直观地看到封装nmcli是性价比最高的起点。我当时的选择就是先把nmcli这条路走通保证功能完整、界面友好后续如果真有D-Bus级别的需求再逐步替换底层通信模块但界面逻辑可以完全不动。2. 环境准备和程序整体架构2.1 开发前需要准备的东西动手写代码之前先把基础环境确认好。你需要以下几样东西Qt开发环境我用的是Qt 5.15 LTS安装qtbase、qmake或cmake即可。Qt6也完全兼容后面代码里的模块都是标准模块。NetworkManager服务确认系统里装了并且服务在运行。检查命令是systemctl status NetworkManager如果没装用系统的包管理器装一下就行。无线网卡物理层面得有一块网卡并驱动正常ip link能看到类似wlan0的无线接口。基础工具nmcli要先测试一下能不能正常执行可以手动跑一遍nmcli dev wifi list看看能不能扫出周围的WiFi热点。检查时我习惯用一条命令快速判断无线网卡的状态rfkill list如果输出里有Soft blocked: yes或Hard blocked: yes说明无线开关被禁用了需要先rfkill unblock wifi解除软封锁。这一步很多初学者容易漏掉导致后面扫描列表永远是空的。2.2 程序模块划分这个WiFi连接程序在大结构上可以拆成五个模块各自职责单一耦合度也比较低扫描模块调用nmcli扫描周围热点把结果解析成数据结构。列表展示模块将扫描结果显示在界面上带信号强度、加密方式的标注。连接模块根据用户选择的热点和输入的密码调用nmcli发起连接。状态监听模块定时检查当前无线网卡的状态把已连接/未连接的状态反馈到界面。记录管理模块方便的话可以保存一下历史连接下次点一下就能重连。从代码实现上讲这五个模块并不复杂核心就是QProcess执行命令再解析输出以及QListWidget展示数据。下面逐个章节把关键代码过一遍。3. 核心功能开发扫描、连接、状态检测3.1 先定义WiFi热点数据结构为了不把界面代码和命令解析逻辑搅在一起我先定义了一个简单的结构体来承载一个WiFi热点struct WifiInfo { QString ssid; // 热点名称 int signalLevel 0; // 信号强度0~100 QString security; // 加密方式如 WPA2、WEP、空开放网络 bool connected false; // 当前是否已连接 };这个结构体后面贯穿整个程序扫描完成后填满一个QListWifiInfo界面循环拿去渲染双击某一项时再从中取出SSID等字段发起连接。3.2 扫描WiFi列表QProcess执行nmcli并解析输出扫描是程序的入口命令就一条nmcli -t -f SSID,SIGNAL,SECURITY dev wifi list参数的含义说明一下-t是使用机器可读的纯文本格式输出字段间用冒号分隔-f指定要输出的字段dev wifi list列出周围可见的热点。直接用QProcess执行这条命令QListWifiInfo WifiManager::scanWifi() { QListWifiInfo results; QProcess proc; proc.start(nmcli, QStringList() -t -f SSID,SIGNAL,SECURITY dev wifi list); if (!proc.waitForFinished(5000)) { qWarning() nmcli scan timeout; return results; } const QString output QString::fromUtf8(proc.readAllStandardOutput()); const QStringList lines output.split(\n, Qt::SkipEmptyParts); for (const QString line : lines) { // 每行格式: SSID:信号强度:加密方式 // 注意SSID本身可能包含冒号所以不能用简单的split(:)取第一个字段 int firstColon line.indexOf(:); if (firstColon 0) continue; int secondColon line.indexOf(:, firstColon 1); if (secondColon 0) continue; WifiInfo info; info.ssid line.left(firstColon); info.signalLevel line.mid(firstColon 1, secondColon - firstColon - 1).toInt(); info.security line.mid(secondColon 1); results.append(info); } return results; }这里有一个非常关键的细节也是很多人容易踩的坑SSID无线网络名称本身可能包含冒号。比如某个热点名字叫“TP-LINK:5G”如果直接按冒号把整行切三段SSID就会变成“TP-LINK”不仅显示不对后面连接时也会匹配失败。所以我用了indexOf找前两个冒号的位置SSID取第一个冒号之前的内容信号强度取第一、二个冒号之间的内容剩余部分才是加密方式。另一个坑是编码。某些WiFi热点是中文名称nmcli在终端里直接打印时可能会做转义处理。如果你在Qt程序里发现中文SSID显示成了乱码或者一堆\x开头的转义符那就需要在启动命令时加上两个环境变量或者给nmcli传-e yes参数强制它输出UTF-8编码。实测下来在大部分UTF-8系统的终端环境下不加也能正常处理但为了稳妥扫描数据做的第一件事就是统一按UTF-8解码。3.3 连接WiFi密码对话框和nmcli连接命令用户双击一个热点后程序要判断这个热点是否加密。判断逻辑很简单security字段为空就直连否则弹一个输入密码的对话框。nmcli连接命令如下# 无密码热点 nmcli dev wifi connect SSID名 # 有密码热点 nmcli dev wifi connect SSID名 password 密码但直接调用时千万别用字符串拼接的方式构造命令。假如密码里包含空格、$、引号之类的特殊字符一条QString command nmcli dev wifi connect \ ssid \ password \ password \;的命令大概率会出错。QProcess支持直接传入参数列表它内部会处理好引号转义bool WifiManager::connectWifi(const WifiInfo info, const QString password) { QStringList args; args dev wifi connect info.ssid; if (!password.isEmpty()) { args password password; } QProcess proc; proc.start(nmcli, args); if (!proc.waitForFinished(8000)) { proc.kill(); return false; } const QString err QString::fromUtf8(proc.readAllStandardError()); if (proc.exitCode() ! 0) { // 密码错误时nmcli通常会输出 Error: secrets were required, but not provided // 或 Failed to add/activate new connection qWarning() nmcli connect failed: err; return false; } return true; }关于密码错误的判断nmcli的退出码和错误信息在不同版本里会有细节差异这里不能只看exitCode还要把标准错误输出打出来看一眼方便后续给用户提示。比如密码错误典型输出是Error: Connection activation failed、(bad password)之类的关键信息可以在界面上直接弹一个提示框。3.4 连接状态监听与反馈连是连上了但界面怎么知道连接成功我用的方式非常简单但不失可靠用一个QTimer每2秒查询一次当前网络状态查询命令是nmcli -g GENERAL.STATE dev show wlan0-g是获取指定字段的快捷参数输出就是类似connected或disconnected的字符串。在代码里我把这个状态映射成一个枚举再借助Qt的信号槽机制通知界面刷新enum class NetState { Disconnected, Connecting, Connected }; void WifiManager::startMonitor(const QString iface) { if (m_timer) m_timer-stop(); m_timer new QTimer(this); connect(m_timer, QTimer::timeout, this, []() { QProcess proc; proc.start(nmcli, QStringList() -g GENERAL.STATE dev show iface); proc.waitForFinished(2000); const QString state QString::fromUtf8(proc.readAllStandardOutput()).trimmed(); NetState cur state.startsWith(connected) ? NetState::Connected : NetState::Disconnected; emit stateChanged(cur); }); m_timer-start(2000); }这里没有用QProcess::execute这种阻塞式写法而是放到定时器回调里执行原因很简单nmcli命令本身很快几十毫秒不会给界面带来明显卡顿而且定时器天然就解决了“手动刷新”的问题。如果你追求更实时的状态也可以考虑监听NetworkManager的D-Bus信号StateChanged但那样代码量会多不少对于这个程序的定位来说轮询已经够用了。4. 界面设计与交互细节4.1 主界面布局界面我用最经典的QListWidget做列表展示每个条目用自定义的Widget渲染包含三部分信息左侧是SSID名称中间是一个信号格图标根据signalLevel字段换算格数右侧是加密标识。布局结构大致是这样顶部一个工具条WiFi开关按钮 刷新按钮。中间主体QListWidget显示扫描到的热点列表。底部一个状态栏显示“已连接到XXX”或“未连接”再配一个IP地址。信号格图标我懒得找切图直接在paintEvent里画了几道圆弧信号值从signalLevel映射成0~4格。这个实现不复杂效果也比较原生。双击条目触发连接这是一个很直觉的交互同时也放了一个连接按钮在右键菜单里贴点老派桌面的使用习惯。4.2 WiFi总开关的实现WiFi总开关本质上就是控制无线网卡的射频开关和手机顶部下拉菜单里的“WiFi开关”是一个概念命令是# 开启WiFi nmcli radio wifi on # 关闭WiFi nmcli radio wifi off界面维护一个bool m_wifiEnabled切换时执行命令并刷新按钮文字。注意开关和扫描是两回事开关控制的是“设备能不能连WiFi”扫描是在开启的前提下“看看周围有哪些WiFi”。4.3 状态提示和用户反馈这部分虽然简单但对使用体验影响特别大。我做了两层反馈第一层是连接中在UI上加一个菊花转圈的loading图标或者禁用列表交互第二层是结果反馈连接成功时状态栏直接显示“已连接IP: 192.168.x.x”失败时弹一个QMessageBox::warning提示内容来自nmcli的错误输出尽量让用户看懂是密码错、找不到热点还是网卡被禁用。获取IP地址我用的也是一条nmcli命令nmcli -g IP4.ADDRESS dev show wlan0拿到的是类似192.168.1.100/24的字符串显示时把掩码段去掉就行。5. 实测中踩过的坑以及调试技巧5.1 权限问题普通用户操作nmcli受限最典型的场景程序在root用户下跑得好好的换到普通用户启动就“扫描列表为空”或者点击连接没反应。原因是NetworkManager通过D-Bus管理权限普通用户执行nmcli命令时能不能连接WiFi取决于系统的Polkit策略。解决方式有三种我按推荐程度排一下把用户加入netdev或wheel组不同发行版组名略有差别然后在Polkit规则里放行。这个方法最干净。在/etc/polkit-1/rules.d/里写一条允许指定用户操作WiFi的规则效果类似但更细化。程序以pkexec方式启动提权但每次启动要输密码体验不好只适合自己调试用。我最终采用的是第一种在目标机器的部署脚本里加了用户组配置这样交付后程序启动即普通用户身份不需要任何提权操作。5.2 扫描列表始终为空怎么排查这个问题遇到过不止一次排查路径几乎固定rfkill list # 1. 看无线开关是否被软件或硬件禁用 nmcli radio # 2. 看WiFi是否开启 ip link show wlan0 # 3. 看网卡是否存在、up没有 systemctl status NetworkManager # 4. 看NetworkManager服务状态按这个顺序排查90%的问题能定位到是无线网卡被rfkill锁住或者NetworkManager服务没起来。测试时我习惯在终端里先手动执行nmcli dev wifi list如果命令行都扫不到那程序里扫不到就一定不是代码问题而是系统环境还没准备好。5.3 界面卡死QProcess阻塞主线程这个坑很隐蔽。最开始写扫描函数时我图省事直接用QProcess::execute()它是同步等待命令执行完的遇到网络状态不佳时nmcli可能好几秒才返回程序界面就直接“未响应”了。解决方案有两个思路一个是用waitForFinished()配合超时时间我前面扫描函数里就是3000ms/5000ms超时不等待这个方法简单且能预防极端情况另一个更彻底的是把QProcess放到工作线程通过信号槽把结果抛回主线程适合后续命令变多的情况。我在程序里对扫描和连接都用了超时等待实测一两秒内都能返回没有造成界面卡顿。5.4 密码含特殊字符导致连接失败这是我在调试一个测试热点时发现的。密码是类似abc$123的字符串第一次用字符串拼接命令怎么都连不上后来改成QStringList传参就成功了。原因就是shell解析时把$当成了变量引用。所以这里再强调一遍所有需要外部输入的命令一律用QProcess的参数字符串列表不要自己拼命令。不仅是Qt任何编程语言调用外部命令时都应该遵守这个原则。5.5 连接成功后IP获取为空有一种比较尴尬的情况nmcli显示connected但IP地址字段是空的。这通常发生在连接企业级WiFi如802.1X认证或者需要二次认证的公共热点时链路通了但还没拿到IP。我程序里的处理是IP为空时状态栏仍然显示“已连接”但额外标注“未获取IP”让用户知道网络可能还不通。如果后续要做得更完善可以再加一个ping网关的连通性检测不过那就是另一个话题了。6. 一些后续的扩展思路这个程序跑通之后再往上扩展的方向其实挺多。比如把连接过的WiFi信息存到本地配置文件中下次打开程序时自动连接已知热点再比如支持WPA3/Enterprise企业级认证的高级连接对话框还有把网络断开后自动重连的守护逻辑集成进来。这些用现有的WifiManager类和nmcli方案都能继续延伸不需要推翻重来。从工程角度说这个项目用最小的代码量解决了一个实际场景里的痛点而且技术方案完全开放依赖的系统服务也都是标准组件个人开发者和产品团队拿去改改都能用得上。本文还有配套的精品资源点击获取
返回列表