news 2026/10/16 3:27:52

QT手写Linux工控机WiFi连接程序:从wpa_supplicant到nmcli实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
QT手写Linux工控机WiFi连接程序:从wpa_supplicant到nmcli实战

简介:这份资源面向在 Linux 平台从事桌面或嵌入式开发的 Qt 学习者,提供一套可运行的 Wi-Fi 连接程序源码,帮助理解如何用 Qt 图形界面配合 NetworkManager、wpa_supplicant 完成无线网络的扫描、选择与连接。压缩包共 12 个文件,以 4 个 cpp 源文件、3 个 h 头文件、3 个 ui 界面文件为主,另有 pro 工程文件与 user 配置,整体约 9KB,结构紧凑,便于直接导入 Qt Creator 编译调试。代码将网络管理逻辑与界面分离,涵盖 Wi-Fi 列表展示、密码输入、连接与断开等交互,并处理密码错误、网络不可达等异常,可作为跨平台网络应用开发的练手项目。目前已有 4522 人学习下载,适合希望打通 Qt 信号槽机制与 Linux 底层网络管理、提升实战能力的开发者参考借鉴。

1. 一个跑在工控机上的小工具:为什么我要用 QT 手写 WiFi 连接程序

去年给一台无头工控机做现场部署,系统是精简过的 Linux,桌面环境都没装,NetworkManager 的图形前端自然也没有。客户要求设备上电后自动连上指定热点,断线还要能重连,最好再给个简单的状态显示。用nmcli写脚本当然能跑,但现场调试时改个 SSID、换个密码都得 SSH 进去敲命令,维护成本太高。于是我花了两天用 QT 写了个带界面的 WiFi 连接程序,底层调wpa_supplicant和nmcli,上层用 QT Widgets 做交互,编译出来一个可执行文件丢到设备上就能用。这个项目适合两类人:一是需要在 Linux 嵌入式设备上做网络配置界面的开发者,二是想学 QT 怎么跟系统底层服务打交道的新手。它不依赖桌面环境,只要有 X11 或者 framebuffer 就能跑,核心逻辑不到 500 行,改起来也快。

2. 技术选型与整体架构:QT 怎么跟 wpa_supplicant 对话

2.1 为什么选 QT + nmcli 而不是纯 C 调 wpa_ctrl

Linux 下管理 WiFi 连接主要有三条路:直接调wpa_supplicant的wpa_ctrl接口、用NetworkManager的 D-Bus API、或者干脆命令行调nmcli。第一种最底层,控制精细,但需要自己处理 socket 通信和协议解析,代码量大且容易出错。第二种最规范,但 D-Bus 的 XML 接口定义复杂,QT 里虽然可以用QDBus模块,调试起来比较麻烦。第三种最省事,nmcli本身就是 NetworkManager 的命令行封装,输出格式稳定,用QProcess调用后解析文本就行。

我最终选了nmcli为主、wpa_cli为辅的方案。理由很实际:现场设备上 NetworkManager 通常是预装的,nmcli一定存在;而wpa_supplicant虽然也在,但它的控制接口需要 root 权限且 socket 路径因发行版而异。用nmcli的好处是命令语义清晰,比如nmcli device wifi list列出热点,nmcli device wifi connect SSID password PASSWORD发起连接,返回结果直接可读。坏处是每次调用都要 fork 一个进程,实时性稍差,但对于配置界面来说完全够用。

整体架构分三层:界面层用QWidget和QTableWidget展示扫描到的热点列表,用户选中后弹出密码输入框;逻辑层用QProcess封装nmcli调用,把返回的文本解析成结构化的WifiInfo对象;系统层就是nmcli和wpa_supplicant本身。三层之间通过 QT 的信号槽机制通信,扫描完成发scanFinished信号,连接成功发connected信号,界面层只负责响应。

2.2 项目目录结构与关键类说明

项目用qmake管理,没有上cmake,因为 QT5 的qmake对小型项目足够友好,交叉编译时配置也简单。目录结构如下:

wifi-qt/ ├── wifi-qt.pro # qmake 工程文件 ├── main.cpp # 程序入口 ├── mainwindow.h/cpp # 主窗口,负责界面布局和交互 ├── wifimanager.h/cpp # 核心管理类,封装 nmcli 调用 ├── wifidevice.h/cpp # 热点信息数据类 └── resources/ └── icons/ # 信号强度图标等资源

WifiManager是整个项目的核心,它继承自QObject,内部持有一个QProcess指针用于执行命令。对外暴露的接口有四个:scan()触发扫描、connectToNetwork(ssid, password)发起连接、disconnect()断开当前连接、currentStatus()返回连接状态。每个接口都是异步的,调用后立即返回,结果通过信号发出。WifiDevice是个纯数据类,包含ssid、bssid、signal、security四个字段,从nmcli的输出行解析而来。

MainWindow负责把WifiManager的信号接到自己的槽函数上,比如onScanFinished(QList<WifiDevice>)里刷新表格,onConnected(QString ssid)里更新状态栏文字。界面很简单:顶部一个刷新按钮,中间一个表格显示热点,底部一个标签显示当前连接状态。密码输入用QInputDialog::getText,设置成QLineEdit::Password模式。

2.3 核心代码:扫描热点与解析 nmcli 输出

扫描功能的实现分两步:先执行nmcli -t -f SSID,BSSID,SIGNAL,SECURITY device wifi list,再用正则解析每一行。-t参数让输出用冒号分隔,方便切分;-f指定字段,避免解析多余信息。代码如下:

// wifimanager.cpp void WifiManager::scan() { if (m_process->state() != QProcess::NotRunning) { m_process->kill(); m_process->waitForFinished(1000); } // -t 表示 terse 模式,字段用冒号分隔;--rescan yes 强制重新扫描 QStringList args; args << "-t" << "-f" << "SSID,BSSID,SIGNAL,SECURITY" << "device" << "wifi" << "list" << "--rescan" << "yes"; m_process->start("nmcli", args); } void WifiManager::onProcessFinished(int exitCode) { if (exitCode != 0) { emit errorOccurred(m_process->readAllStandardError()); return; } QList<WifiDevice> devices; QString output = QString::fromLocal8Bit(m_process->readAllStandardOutput()); QStringList lines = output.split('\n', Qt::SkipEmptyParts); for (const QString &line : lines) { // BSSID 里含有冒号,所以用正则匹配而不是简单 split QRegularExpression re("^([^:]*):([0-9A-Fa-f:]{17}):(\\d+):(.*)$"); QRegularExpressionMatch match = re.match(line); if (!match.hasMatch()) continue; WifiDevice dev; dev.ssid = match.captured(1).trimmed(); dev.bssid = match.captured(2); dev.signal = match.captured(3).toInt(); dev.security = match.captured(4).trimmed(); if (!dev.ssid.isEmpty()) devices.append(dev); } emit scanFinished(devices); }

这里有个坑:nmcli -t模式下,如果 SSID 里本身含有冒号,输出会转义成\:,但 BSSID 的冒号不转义。所以不能简单用split(':'),必须用正则从右往左匹配。上面的正则先匹配 SSID(非冒号字符),再匹配 17 位 BSSID,再匹配信号强度数字,最后是安全类型。--rescan yes会强制网卡重新扫描,耗时约 2 到 5 秒,所以scan()是异步的,不能阻塞界面。

QProcess的finished信号连接到onProcessFinished,但要注意每次start()之前先kill()掉上一次的进程,否则连续点击刷新按钮会堆积多个进程,导致结果错乱。waitForFinished(1000)是给旧进程一点退出时间,避免僵尸进程。

2.4 连接逻辑与密码处理

连接比扫描简单,直接调nmcli device wifi connect就行。但有两个细节要处理:一是密码里如果有特殊字符(比如$、!),直接拼进命令行会被 shell 解释,所以要用QProcess的参数列表方式传参,而不是拼成一个字符串;二是连接失败时nmcli的返回码非零,需要把 stderr 读出来展示给用户。

// wifimanager.cpp void WifiManager::connectToNetwork(const QString &ssid, const QString &password) { QStringList args; args << "device" << "wifi" << "connect" << ssid; if (!password.isEmpty()) { args << "password" << password; } // 隐藏密码,避免 nmcli 把密码写进系统日志 args << "hidden" << "no"; m_process->start("nmcli", args); } void WifiManager::onConnectFinished(int exitCode) { if (exitCode == 0) { emit connected(m_currentSsid); } else { QString err = QString::fromLocal8Bit(m_process->readAllStandardError()); // nmcli 的报错通常是 "Error: Connection activation failed: ..." emit errorOccurred(err.trimmed()); } }

hidden no是告诉nmcli不要把这个网络标记为隐藏网络,否则下次扫描可能看不到。密码通过参数列表传递,QProcess会自己处理转义,不会经过 shell,所以$和!都是安全的。连接成功后nmcli会自动把配置写入/etc/NetworkManager/system-connections/下的一个文件,下次开机自动重连,不需要我们额外做持久化。

如果设备上没有 NetworkManager,只有wpa_supplicant,那就得换一套实现:用wpa_cli的add_network、set_network、enable_network命令,最后save_config写入wpa_supplicant.conf。代码结构类似,只是命令不同。我一般会在WifiManager里加一个enum Backend { NetworkManager, WpaSupplicant },根据系统里存在的可执行文件自动选择。

3. 编译、部署与避坑:从 x86 到 ARM 的完整流程

3.1 在 Ubuntu 上编译运行

开发环境是 Ubuntu 20.04,装好 QT5 开发包和 NetworkManager 就行。依赖安装命令如下:

sudo apt update sudo apt install qtbase5-dev qt5-qmake g++ network-manager

qtbase5-dev提供 QT Widgets 和QProcess模块,network-manager提供nmcli。如果系统里没有nmcli,WifiManager启动时会检测不到,直接弹窗提示。编译过程就是标准的qmake三步:

qmake wifi-qt.pro make -j4 ./wifi-qt

wifi-qt.pro里需要加QT += widgets和CONFIG += c++11。如果要用到网络状态变化通知,还可以加QT += dbus,但本项目没用到。编译出来的可执行文件依赖libQt5Widgets.so、libQt5Core.so等,在目标机器上要么装 QT 运行库,要么静态编译。

3.2 交叉编译到 ARM 开发板

现场设备是 ARM Cortex-A7 的板子,跑的是 Buildroot 构建的根文件系统。交叉编译需要先准备好目标平台的 QT 库和工具链。假设工具链前缀是arm-linux-gnueabihf-,QT 库已经编译好放在/opt/qt5-arm/,那么qmake要用目标平台的版本:

/opt/qt5-arm/bin/qmake wifi-qt.pro make -j4

编译出来的wifi-qt是 ARM 可执行文件,用file命令确认一下架构。拷贝到板子上时,除了可执行文件,还要把 QT 的插件目录带过去,特别是platforms/libqlinuxfb.so(framebuffer)或libqxcb.so(X11)。如果板子没有 X11,就用-platform linuxfb启动:

./wifi-qt -platform linuxfb

这时候界面会直接画在 framebuffer 上,不需要桌面环境。字体如果显示成方块,把fonts/目录也拷过去,或者在main.cpp里用QFontDatabase::addApplicationFont加载一个中文字体。

3.3 避坑记录:权限、转义与信号槽

现象一:扫描结果为空,但nmcli命令行能列出热点。原因:QProcess启动的nmcli没有继承当前用户的环境变量,或者当前用户不在netdev组里,没有权限操作 WiFi。 解决:在main.cpp里用qputenv设置PATH,或者把程序用sudo运行。更规范的做法是给可执行文件加setcap权限:sudo setcap cap_net_admin+ep ./wifi-qt。

现象二:密码里带$或!时连接失败。原因:早期版本用QProcess::execute("nmcli device wifi connect " + ssid + " password " + password),字符串拼接后经过 shell 解释,特殊字符被吞掉。 解决:改用QProcess::start(program, arguments)的参数列表形式,不经过 shell。这是血泪经验,现场调试时因为一个$符号排查了半小时。

现象三:连续点击刷新按钮,界面卡死或结果错乱。原因:QProcess是异步的,上一次扫描还没结束就启动了下一次,两个进程的输出混在一起。 解决:在scan()开头判断m_process->state(),如果不是NotRunning就先kill()并waitForFinished()。同时把刷新按钮在扫描期间设为setEnabled(false),扫描完成后再启用。

现象四:连接成功后状态栏不更新。原因:nmcli的finished信号连接到了错误的槽函数,或者exitCode判断有误。nmcli连接成功返回 0,失败返回 1 或 10。 解决:用QProcess::exitCode()判断,同时把 stderr 读出来打印到qDebug(),方便定位。信号槽连接用Qt::QueuedConnection确保跨线程安全。

现象五:ARM 板子上界面显示但触摸屏没反应。原因:QT 的触摸屏输入需要tslib库支持,且要设置QT_QPA_GENERIC_PLUGINS=tslib和TSLIB_TSDEVICE=/dev/input/event1。 解决:在启动脚本里 export 这两个环境变量,或者用-plugin tslib参数。如果触摸屏校准不准,用ts_calibrate重新校准。

4. 二次开发与改造:从能用到好用

4.1 增加自动重连与信号强度实时刷新

原版程序只有手动刷新,实际部署时客户要求断线自动重连。改造思路是加一个QTimer,每 5 秒执行一次nmcli -t -f GENERAL.STATE device show wlan0,解析返回的状态字符串。如果状态是disconnected且之前连接过某个 SSID,就自动发起重连。代码片段如下:

// 在 WifiManager 构造函数里 m_monitorTimer = new QTimer(this); connect(m_monitorTimer, &QTimer::timeout, this, &WifiManager::checkStatus); m_monitorTimer->start(5000); void WifiManager::checkStatus() { QProcess proc; proc.start("nmcli", {"-t", "-f", "GENERAL.STATE", "device", "show", "wlan0"}); proc.waitForFinished(2000); QString state = QString::fromLocal8Bit(proc.readAllStandardOutput()); if (state.contains("disconnected") && !m_lastSsid.isEmpty()) { connectToNetwork(m_lastSsid, m_lastPassword); } }

m_lastSsid和m_lastPassword在连接成功时保存,密码存在内存里,不写磁盘。如果对安全性要求高,可以用QSettings加密存储,但嵌入式设备上一般没必要。

信号强度实时刷新更简单,把scan()的定时器设成 10 秒一次,每次扫描后只更新表格里已有热点的信号列,不重建整个表格。这样界面不会闪烁,用户体验好很多。

4.2 用 QML 重写界面

如果设备性能允许,可以把QWidget换成 QML,界面更流畅,动画效果也好做。核心逻辑不用动,WifiManager注册成 QML 上下文属性即可:

// main.cpp QQmlApplicationEngine engine; WifiManager manager; engine.rootContext()->setContextProperty("wifiManager", &manager); engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

QML 里用ListView展示热点,MouseArea处理点击,密码输入用TextField加echoMode: TextInput.Password。信号槽在 QML 里用Connections接收。改造工作量大概一天,但界面美观度提升明显。

4.3 一个容易忽略的细节:nmcli 版本差异

不同发行版的nmcli版本不同,输出格式有细微差别。比如 Ubuntu 18.04 的nmcli不支持--rescan yes参数,得用nmcli device wifi rescan单独执行。我一般会在WifiManager初始化时执行nmcli --version,解析出版本号,然后根据版本决定用哪套参数。这个逻辑写在detectBackend()函数里,返回一个枚举值,后续所有命令都根据这个枚举拼参数。从那以后我每次部署到新设备上,都强制先跑一遍nmcli --version和nmcli device status,确认环境再启动程序。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 3:27:19

提示词工程系统教程:从结构化Prompt到API批量测试实战

这次我们来看的不只是一个新工具&#xff0c;而是一套覆盖 748 集的完整提示词工程教程。它从零基础讲起&#xff0c;核心目标很明确&#xff1a;把大模型从“聊天玩具”变成“能稳定干活”的工具。整条学习线覆盖了提示词怎么写、怎么写结构才可靠、怎么批量验证同一批模板、怎…

作者头像 李华
网站建设 2026/10/16 3:27:16

40天被收购的图片社交产品:文科团队如何跑通验证与增长

上线40天就被某大型科技公司收入麾下&#xff0c;两位创始人还都是文科出身——这几个词放在一起&#xff0c;乍一看像是拼运气拼出来的故事。但把相关报道、产品形态、创始团队的取舍逻辑从头到尾捋一遍之后&#xff0c;我意识到这件事的底层逻辑跟运气关系不大&#xff0c;更…

作者头像 李华
网站建设 2026/10/16 3:26:34

基于窗函数反求法的NLFM信号设计与脉压验证

简介&#xff1a;这份资源围绕传统基于逗留相位原理的窗函数反求法&#xff0c;讲解非线性调频&#xff08;NLFM&#xff09;信号设计与脉冲压缩的实现思路&#xff0c;面向雷达、通信信号处理方向的学习者与工程人员&#xff0c;帮助理解如何在高分辨率与低旁瓣之间取得平衡。…

作者头像 李华
网站建设 2026/10/16 3:26:07

AI搜索时代的企业信源权威性建设:从关键词排名到被可靠引用

1. 先把问题说清楚&#xff1a;AI搜索来了&#xff0c;但很多企业还盯着"关键词排名"今年和几个做品牌营销的朋友聊起各自的项目&#xff0c;发现大家的困惑惊人地一致&#xff1a;官网做了不少内容&#xff0c;更新频率也不低&#xff0c;但AI搜索引擎在回答"某…

作者头像 李华
网站建设 2026/10/16 3:26:00

鸣潮 ok-ww 一键日常完整指南:3 步跑通安装到自动刷声骸

鸣潮 ok-ww 一键日常完整指南&#xff1a;3 步跑通安装到自动刷声骸 【免费下载链接】ok-wuthering-waves 鸣潮 后台自动战斗 自动刷声骸 一键日常 Automation for Wuthering Waves 项目地址: https://gitcode.com/GitHub_Trending/ok/ok-wuthering-waves ok-ww 是一款鸣…

作者头像 李华