简介:一份面向ARM架构Linux开发者的网络调试助手完整源码及依赖包,适用于嵌入式设备、树莓派等场景下的网络故障排查、端口扫描与数据包分析。压缩包共135个文件,约51.25MB,主要包含可执行程序、动态链接库(.so)、配置文件和Shell脚本,其中随附的libglib、libGLdispatch等依赖库可避免因缺少运行环境而导致的编译或启动失败。已有1128人学习下载。源码主目录聚焦于64位ARM处理器,涵盖TCP/IP配置、流量监控、协议解析等核心模块,开发者既能直接使用,也能基于源码深入理解网络调试的底层实现,并通过交叉编译适配不同ARM板卡。依赖库一并打包省去手动查找安装的繁琐,适合有一定Linux基础、希望定制或学习嵌入式网络工具的开发人员。
1. Linux ARM 版网络调试助手:为什么源码和依赖比现成安装包更省事
在 ARM 嵌入式 Linux 板卡上调网络协议,最常遇见的一幕是:Windows 上的调试助手过不去,板子的镜像里也没预装能收 Hex 报文的工具,临时装一个发现架构不匹配,或者连动态库都带不全。这时候手里如果有一份 linux-arm 版网络调试助手的源码,以及一套能跑通的依赖构建记录,四五十分钟就能在板子上拥有一个趁手的 TCP/UDP 收发工具。本文讲的不是某个神秘仓库的地址,而是拿到这类源码后,怎么把依赖理清、怎么交叉编译、怎么在板子上验证收发的完整路径。适合正在做嵌入式网络联调、但又不想在 GUI 工具上反复折腾的开发者;也适合刚接触 ARM 交叉编译、想找个中等规模项目练手的人。
2. 源码骨架拆解:五个模块把 TCP/UDP 收发落到实处
拿到任何一份网络调试助手源码,先别急着编译。先把它拆成几个独立逻辑块,确认哪些代码是跨平台通用、哪些是 Linux 特有、哪些依赖外部库。我常见到一个能正常工作的 Linux ARM 网络调试助手,无论界面用什么框架,底层都离不开五个模块:协议管理、数据收发、编码转换、状态通知、界面呈现。
2.1 项目结构:一个调试助手的五个必要模块
一份典型的项目布局大概长这样:
netassist/ ├── CMakeLists.txt ├── src/ │ ├── main.cpp │ ├── core/ │ │ ├── netcore.h │ │ ├── netcore.cpp │ │ ├── hexcodec.cpp │ │ └── hexcodec.h │ ├── ui/ │ │ ├── mainwindow.h │ │ └── mainwindow.cpp │ └── util/ │ └── applog.h └── toolchain/ └── toolchain-arm.cmakecore目录里存放不依赖界面的收发逻辑,ui目录只负责展示和用户输入,util里放日志或字符串工具。这个边界很重要:跨平台编译时,core几乎不需要改,需要适配 ARM 环境的只是构建文件和界面层的几个平台调用。
协议管理模块负责记录当前是 TCP 客户端、TCP 服务端还是 UDP,管理连接状态;数据收发模块负责真正读写 socket;编码转换模块负责在 Hex 字符串和原始字节之间互转;状态通知模块把连接建立、断开、收到数据等事件抛给界面;界面呈现模块把字节流显示成 Hex 或 ASCII。把这五个模块分开,后续排查问题会容易得多。
2.2 实现 Hex 收发:字节转换与发送函数的三个关键点
网络调试助手的灵魂功能是十六进制收发。很多人在这里翻车,根因是把QString和QByteArray混用,导致0x80以上的字节被当成多字节字符。我建议所有入站出站数据统一用QByteArray,到显示层再转字符串。
// hexcodec.cpp —— Hex 字符串与字节互转 #include <QByteArray> #include <QString> #include <QRegExp> #include <QDebug> // 输入 "AA BB 0D 0A" 或 "AABB0D0A",输出原始字节 QByteArray hexStringToBytes(const QString &hexInput) { QString s = hexInput; s.remove(QRegExp("[\\s\\t\\r\\n]")); // 去掉空格和换行 if (s.size() % 2 != 0) { s.chop(1); // 奇数长度时丢弃最后半字节,避免越界 } QByteArray out; out.reserve(s.size() / 2); for (int i = 0; i < s.size(); i += 2) { bool ok = false; const uint val = s.mid(i, 2).toUInt(&ok, 16); if (ok) { out.append(static_cast<char>(val)); } else { qWarning() << "invalid hex at" << i << s.mid(i, 2); } } return out; } // 反向转换:字节流转 "AA BB 0D 0A" 纯大写 QString bytesToHexString(const QByteArray &data) { QString hex; hex.reserve(data.size() * 3); for (int i = 0; i < data.size(); ++i) { const uchar byte = static_cast<uchar>(data.at(i)); hex += QString::asprintf("%02X ", byte); } return hex.trimmed(); }逻辑说明:hexStringToBytes先做归一化,把输入里的空格、换行全删掉,再检查长度是否为偶数。chop(1)的作用是当用户输入了类似"AA B"这种残缺串时,宁可丢弃半个字节也不去拼一个错误值。重点在toUInt(&ok, 16),只有解析成功才 append,这样非法字符不会静默污染数据。bytesToHexString用%02X保证输出始终是两位大写十六进制,trimmed()把末尾空格去掉,方便直接复制到日志里。
发送函数里还有一个隐蔽点:TCP 模式下小包延迟。如果你发现板子上发出去的数据总是慢半拍,多半是 Nagle 算法在缓存。建议在连接建立后设置TCP_NODELAY,Qt 里可以这么打开:
// 关闭 Nagle 算法,降低交互式调试的延迟 void NetCore::setNoDelay(bool enabled) { if (m_tcpSocket) { m_tcpSocket->setSocketOption(QAbstractSocket::LowDelayOption, enabled ? 1 : 0); } }LowDelayOption映射到底层就是TCP_NODELAY。调试小报文时强烈建议打开,否则你会在板子上看到"我发一条,它要等 200ms 才回一条"的诡异现象,其实是 Nagle 算法在凑数据包。
2.3 TCP 与 UDP 的底层选型:连接状态机怎么设计
TCP 模式下要考虑连接建立、断开重连、服务端接受连接三种形态。我一般会封装一个枚举状态机:
enum class NetProtocol { TcpClient, TcpServer, Udp }; enum class ConnState { Idle, Connecting, Connected, Listening };TCP 客户端调connectToHost,TCP 服务端用QTcpServer::listen再加nextPendingConnection,UDP 则是QUdpSocket::bind之后直接收发。实现时有一个常见错误:在 TCP 客户端里也调bind,结果导致板子上的服务端收到来自陌生端口的连接,部分设备会因此拒绝。
void NetCore::startTcpServer(quint16 port) { if (m_server) { m_server->close(); } m_server = new QTcpServer(this); bool ok = m_server->listen(QHostAddress::Any, port); if (!ok) { emit stateChanged(QString("listen failed: %1").arg(m_server->errorString())); return; } connect(m_server, &QTcpServer::newConnection, this, [this]() { if (m_clientConnection) { m_clientConnection->disconnectFromHost(); m_clientConnection->deleteLater(); } m_clientConnection = m_server->nextPendingConnection(); connect(m_clientConnection, &QTcpSocket::readyRead, this, &NetCore::readIncoming); }); }参数说明:QHostAddress::Any表示监听板子所有网口地址,适合调试场景;如果有多网卡且想绑定特定地址,可以改成具体的QHostAddress。listen失败时一定要取出errorString(),否则你只会看到"啥反应都没有"。
deleteLater()这句很关键:新连接到来时把旧连接先关闭再延迟销毁,避免 socket 对象在事件循环里被提前释放导致崩溃。这是血泪经验,很多人在 ARM 板子上连第二次就段错误,就是少了这一步。
2.4 收发数据与界面解耦:为什么不能在 UI 线程里读 socket
调试助手的误区是把readyRead里收到的数据直接往QTextEdit里塞。当数据量一大,界面刷新就把网络吞吐拖垮。正确做法是收到QByteArray后先存进一个接收缓冲区,然后通过信号抛给界面,界面只负责展示。
void NetCore::readIncoming() { while (auto *sock = qobject_cast<QTcpSocket *>(sender())) { QByteArray chunk = sock->readAll(); if (chunk.isEmpty()) break; m_rxBuffer.append(chunk); // 先并入缓冲区 emit dataReceived(chunk); // 只发新到的这一段 } }逻辑说明:sender()在信号槽里拿到发出信号的 socket,readAll一次读完当前可用数据,避免多次触发readyRead造成重复读取。m_rxBuffer保留完整数据用于后续落盘,dataReceived信号把这一段新数据传给 UI。界面里用一个定时器刷显示,而不是每收到一个字节就刷一次,这样在高频串口或网口数据下不卡顿。
qobject_cast在这里还起保护作用:如果 socket 在发送信号前已经被deleteLater,qobject_cast返回空指针,循环直接退出,不会访问野指针。这一步在交叉编译环境下尤其重要,因为 ARM 板上的栈信息不全,段错误不好查。
3. 依赖构建:在 x86 开发机上准备 ARM 版 Qt 运行库的三要素
源码拿到了,下一步是依赖。Linux ARM 版网络调试助手的依赖通常分两类:一类是编译期依赖(头文件、.so 链接库),一类是运行期依赖(目标板上需要存在的动态库)。本末倒置的常见结果是:开发机上编译过了,拷到板子上一运行就报cannot open shared object file。所以要先梳理清楚依赖到底有哪些,以及 ARM 版 Qt 运行库怎么来。
3.1 依赖清单:Qt Core / Network / Widgets 在 ARM 上需要什么
一个 Qt Widgets 写的调试助手,最省依赖的链接方式也要涉及下面这几项:
| 依赖模块 | 作用 | 编译期形式 | 运行期形式 |
|---|---|---|---|
| Qt5Core | 事件循环、字符串处理 | libQt5Core.so 头文件 | libQt5Core.so.5 |
| Qt5Network | TCP/UDP socket | libQt5Network.so 头文件 | libQt5Network.so.5 |
| Qt5Widgets | 界面控件 | libQt5Widgets.so 头文件 | libQt5Widgets.so.5 |
| libstdc++ | C++ 标准库 | 链接期由交叉编译器提供 | libstdc++.so.6 |
| libc | Linux 系统调用 | sysroot 提供 | 板载自带 |
如果你的源码还引用了 Qt5SerialPort 做串口调试,那还要加上libQt5SerialPort.so,但这超出标题范围,先不展开。这里要注意:编译期链接的是未带版本号的.so软链,运行期实际加载的是带版本号的.so.5。拷贝依赖时只拷软链不拷真实文件,是板子上跑不起来的第一大原因。
静态链接会把 Qt 库整个编进二进制,省掉运行期动态库,代价是二进制体积从几 MB 涨到几十 MB,且部分 Qt 模块(如字体、平台插件)静态编译时配置繁琐。我的建议是:调试阶段用动态链接,把整个 Qt 目录同步到板卡;要批量部署再考虑静态。
3.2 交叉编译工具链的三要素:gcc、sysroot、Qt 库的匹配
交叉编译不是装一个编译器就能跑。三要素必须匹配:交叉 gcc 的 CPU 架构、sysroot 根文件系统、Qt 库的编译架构。常见搭配是arm-linux-gnueabihf对应 ARMv7 硬件浮点,aarch64-linux-gnu对应 ARMv8/ARM64。如果你把 ARMv7 的工具链拿去编 ARM64 的 Qt 库,链接阶段会报一堆relocation错误,看着像源码问题,其实是架构不匹配。
sysroot 指的是一个内含lib/、usr/lib、usr/include的目录树,交叉编译器在它里面找 libc 头文件和基础库。没有 sysroot,编译器会去宿主机的/usr/include找头文件,结果混入了 x86 的声明,编出来的二进制在板子上行为很怪。我通常在/opt/arm-sysroot放一个从目标板根文件系统拷贝出来的目录树,然后让 CMake 强制指定。
# 从板子上导出 sysroot(临时用 scp 或直接挂载镜像均可) mkdir -p /opt/arm-sysroot scp -r user@devboard:/lib /opt/arm-sysroot/ scp -r user@devboard:/usr/lib /opt/arm-sysroot/usr/ scp -r user@devboard:/usr/include /opt/arm-sysroot/usr/导出后检查一下/opt/arm-sysroot/usr/lib/arm-linux-gnueabihf是否存在,很多 Debian 系板子的 libc.so 放在这个路径下。如果你的板子没有现成可用,也可以从板卡镜像里用debootstrap或解包 rootfs 得到,注意架构必须匹配。
3.3 用 CMake 的交叉编译文件对接 ARM 版 Qt
Qt 的 ARM 版运行库通常以两种方式获得:一是从 Qt 官方下载对应架构的嵌入式包,二是自己用源码交叉编译。无论哪种,最终都要得到一个目录结构类似/opt/qt-arm/lib、/opt/qt-arm/include的产出,并且里面有libQt5Network.so这样的文件。
拿到之后,写一个 CMake toolchain 文件把这些路径串起来:
# toolchain/toolchain-arm.cmake —— ARM 交叉编译配置 set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR arm) # 交叉编译器前缀,根据板卡架构选 arm-linux-gnueabihf 或 aarch64-linux-gnu set(TOOLCHAIN_PREFIX arm-linux-gnueabihf-) set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}gcc) set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}g++) # sysroot 和 ARM 版 Qt 的安装根目录 set(SYSROOT /opt/arm-sysroot) set(CMAKE_SYSROOT ${SYSROOT}) set(QT_ARM_ROOT /opt/qt-arm) # 查找路径:只在 sysroot 和 Qt 目录里找库和头文件 set(CMAKE_FIND_ROOT_PATH ${SYSROOT} ${QT_ARM_ROOT}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)参数说明:CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER保证 CMake 找编译器时不去 sysroot 里找,否则它会想在板子的文件系统里找一个 gcc 出来,自然找不到。MODE_LIBRARY和MODE_INCLUDE设成ONLY,强制只搜 ARM 路径,避免误食宿主机/usr/lib/x86_64-linux-gnu里的 Qt。这两行不设,源码里明明#include <QApplication>也编不过,报的错却是找不到qglobal.h。凑齐这三样,构建配置才算完整。
4. 交叉编译与部署:从源码到 ARM 板可执行文件的完整流程
依赖准备好之后,真正的构建部署流程并不长,但每一步失败点都很典型。这章给出一套可复用流程:先写构建脚本,再决定部署形态,最后在板子上用命令行验证程序能启动、能收发。
4.1 一套可复用的交叉编译构建脚本
把交叉编译参数固化到脚本里,比每次敲一长串 cmake 命令省事,也避免换终端后参数写错。下面这份脚本我直接用在项目里:
#!/bin/bash # build-arm.sh —— 交叉编译并打包 ARM 版网络调试助手 set -e BUILD_DIR=build-arm OUTPUT_DIR=output-arm # 清理旧产物,避免 CMake 缓存串架构 rm -rf ${BUILD_DIR} ${OUTPUT_DIR} mkdir -p ${BUILD_DIR} ${OUTPUT_DIR} # 用 toolchain 文件配置 cmake -S . -B ${BUILD_DIR} \ -DCMAKE_TOOLCHAIN_FILE=${PWD}/toolchain/toolchain-arm.cmake \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_PREFIX_PATH=/opt/qt-arm # 并行编译,nproc 自动取核数 cmake --build ${BUILD_DIR} -j "$(nproc)" # 复制可执行文件到输出目录 cp ${BUILD_DIR}/netassist ${OUTPUT_DIR}/ # 用 readelf 检查动态依赖,不能直接用宿主机的 ldd arm-linux-gnueabihf-readelf -d ${OUTPUT_DIR}/netassist | grep NEEDED逻辑说明:set -e让脚本在任一步失败时立刻退出,避免半成品还被拿去部署。rm -rf清理 build 目录的关键点在于,CMake 一旦在某个架构下配置过,缓存里的编译器路径和 sysroot 不会自动更新,换板卡型号时必须从头配置。CMAKE_PREFIX_PATH指向/opt/qt-arm,让 CMake 的find_package(Qt5)能定位 ARM 版 Qt 的.cmake配置文件。
最后的readelf -d是检查动态依赖的正道。宿主机的ldd是 x86 动态加载器,用它看 ARM 二进制只会报not a dynamic executable,让人误以为编译有问题。arm-linux-gnueabihf-readelf直接解析 ELF,输出NEEDED列表,清清楚楚告诉你这个程序在板子上要哪些.so。
4.2 把程序和依赖一起部署:三种方式的取舍
编译产物拿到手,部署有三种常见方式,取舍取决于你的板卡存储和后续维护频率:
| 部署方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 全静态编译 | 单文件直接跑 | 体积大、Qt 静态配置麻烦 | 批量刷机、镜像定制 |
| 动态库 + 拷贝 Qt 目录 | 灵活、可局部升级 | 目录结构要固定、容易漏库 | 开发调试阶段 |
| 打包成 deb / opkg | 依赖声明清晰、可卸载 | 需要维护包元数据 | 多次部署到多台板卡 |
开发期我用第二种。把/opt/qt-arm/lib下所有.so*文件平铺复制到板卡的/opt/netassist/lib,然后设置LD_LIBRARY_PATH指向这个目录。注意 Qt 自身的库也有依赖链,比如libQt5Network.so依赖libQt5Core.so,所以要整个 lib 目录一起拷,不要只拷三五个文件名看起来需要的。
# 在开发机上批量拷贝 ARM 版 Qt 运行库 mkdir -p /opt/netassist/lib cp -a /opt/qt-arm/lib/libQt5{Core,Network,Widgets}* /opt/netassist/lib/ cp -a /opt/qt-arm/lib/libQt5*.so.5 /opt/netassist/lib/cp -a保留符号链接属性,这一点比cp -r靠谱。后一行把版本号链接也带上,确保运行时能解析libQt5Core.so.5。拷贝完成后,在板子上把这些文件放到对应位置,再用export LD_LIBRARY_PATH指路。
4.3 在 ARM 板上验证:启动参数、日志检查、最小联通测试
部署完成后不要急着开界面。先在无头模式下验证程序能启动,能监听端口,能回应数据。大多数 Linux ARM 板子启动 Qt 程序需要指定平台插件,常见三种:linuxfb直接写 framebuffer、eglfs走 OpenGL、vnc远程显示。调试阶段我推荐linuxfb,依赖最少。
# 在板卡上启动网络调试助手(无显示器场景) export LD_LIBRARY_PATH=/opt/netassist/lib:$LD_LIBRARY_PATH export QT_QPA_PLATFORM=linuxfb export QTDIR=/opt/qt-arm /opt/netassist/netassist -platform linuxfb &如果在命令行启动后没有报错,说明 Qt 库加载成功。接下来验证端口监听:
# 检查监听端口是否正常(假设程序默认监听 52001) ss -tulnp | grep 52001ss输出里能看到0.0.0.0:52001和进程名netassist,就说明界面和网络层都串起来了。如果ss里什么都没有,去翻程序的 stderr 输出,QSocketNotifier相关报错一般会直接打在终端里。下一步从开发机发一个 TCP 报文过去,看板卡是否原样收下并回显。
日志方面,Qt 默认把qDebug输出写到 stderr。建议在启动命令里加2>/var/log/netassist.log,这样即使板子没有显示器,也能事后翻日志排查。常见问题都能在日志里看到痕迹,比如平台插件加载失败会打印This plugin does not support propagateSizeHints(),连不上串口会打印qt.qpa.fontconftig之类。
5. 避坑与排查:ARM 版网络调试助手编译运行的四个高发问题
这部分是本文的实践精华。以下每个问题我都遇到过,有些在板子上耗了一整天才定位到根因。按"现象 → 原因 → 解决"来写,方便直接对号入座。
5.1 现象:CMake 配置时明明装了 Qt5,却报找不到 Qt5Widgets
用cmake配置 ARM 构建时,依赖报错信息里经常混着 x86 的库路径:
Could not find a package configuration file provided by "Qt5Widgets"原因分析:toolchain 文件里CMAKE_FIND_ROOT_PATH_MODE_LIBRARY没设成ONLY,或者忘了把/opt/qt-arm加进CMAKE_FIND_ROOT_PATH。CMake 优先在宿主机系统路径里找 Qt5 的 cmake 配置,找到的是 x86 版,接着又因为架构不匹配报错,绕一圈后提示找不到包。
解决办法:检查 toolchain 文件,确认set(CMAKE_FIND_ROOT_PATH /opt/arm-sysroot /opt/qt-arm)和三行MODE设置都在。然后删除 build 目录重新配置,不要只改一行后增量重跑,CMake 的缓存会保留之前的查找路径。
5.2 现象:把 Qt 库拷到板子后,程序仍报库文件缺失
启动时报错:
error while loading shared libraries: libQt5Network.so.5: cannot open shared object file原因分析:用cp复制时只拷了软链接文件,真实文件还在开发机;或者漏掉了 Qt Network 本身依赖的其他库,比如libQt5Network.so.5还需要libQt5Core.so.5。弹这个错误前,可能还有一批libQt5DBus.so、libicu之类被间接依赖的库缺失。
解决办法:在板子上运行arm-linux-gnueabihf-readelf -d /opt/netassist/netassist | grep NEEDED,把列出的库逐个在/opt/netassist/lib下对照。更推荐的做法是把整个/opt/qt-arm/lib镜像过去,而不是挑着拷,然后在启动脚本里用find递归检查:
for f in $(readelf -d /opt/netassist/netassist | grep NEEDED | awk '{print $NF}' | tr -d '[]'); do find /opt/netassist/lib -name "$f" || echo "MISSING $f" done这段会在板子上逐项检查缺失,比肉眼对照靠谱得多。
5.3 现象:程序能启动,但界面全白或字体乱码
板子上启动后窗口按预期大小出现,但上面没内容,或者中文全变成方块。原因分析:Qt 在linuxfb平台下依赖字体文件,而很多精简板卡镜像里没有中文字体,Qt 找不到任何可用字体时就只渲染控件框架,不渲染文字;乱码则多半是字体编码缺失导致的。
解决办法:确认板子上有至少一个 CJK 字体,常见做法是把wqy-microhei.ttc拷到/usr/share/fonts/或 Qt 字体目录里,然后重建字体缓存:
fc-cache -fv /usr/share/fonts/如果板子上没有fontconfig,可以直接在源码里设置QApplication::setFont,指定一个确定存在的字体路径。还有一种选择是编译 Qt 时把-qt-rpath打开,并把字体目录编译进 Qt 平台插件查找路径里,但这属于对源码的改动,调试阶段先用外部字体最省事。
5.4 现象:Hex 收发时数据字节错位,0x7F 以上字节全变样
用调试助手发"AA FF 01",对端收到的却是一串C3 AA C3 BF之类的多字节序列。原因分析:发送路径里把QByteArray转成了QString,再调.toUtf8()写进 socket。任何大于0x7F的字节,在 UTF-8 编码下都会展开成两个字节,于是数据被二次编码。
解决办法:发送函数直接使用QByteArray,禁止在收发管道里出现QString或toLatin1()/toUtf8()转换。检查式子:
void NetCore::sendRaw(const QByteArray &payload) { if (m_udp) { m_udp->writeDatagram(payload, m_peerAddr, m_peerPort); } else if (m_clientSocket && m_clientSocket->state() == QAbstractSocket::ConnectedState) { m_clientSocket->write(payload); } }所有上层输入先进hexStringToBytes转成字节流,之后再无字符编码干预。这一步纠错后,乱码现象应彻底消失。如果还出现错位,检查是否在接收侧也做了一次bytesToHexString后又把结果当成 HEX 重新解析了一遍,双重转换是常见连招。
6. 进阶:自动重连、数据落盘与无头模式回归验证
当你能在板子上稳定启动程序手动收发后,就该考虑让它更贴近真实联调场景。我通常会在源码里再叠加三样东西:TCP 断线自动重连、接收数据按天落盘、以及一个无界面回归测试入口。自动重连解决的是板子重启后程序不会自动恢复连接的问题。实现思路不复杂:在disconnected信号里启动一个QTimer,间隔 3 秒尝试重连,连上后停止定时器。
void NetCore::scheduleReconnect() { if (m_protocol != NetProtocol::TcpClient) return; m_reconnectTimer->start(3000); // 3 秒后触发重连 }接收数据落盘则用一个带日期滚动的QFile,每天一个文件,文件名用启动日期。这样可以连续跑一个星期的协议调试,最后用脚本统一分析。无头回归测试的入口我习惯藏在命令行参数里:加--selftest参数时程序不做界面初始化,直接起一个 UDP 回环服务,把收到的包原样发回。
验证脚本用 Python 写十分钟就能完成,不在板子上装任何额外运行时:
#!/usr/bin/env python3 # regress.py —— 向板卡发协商报文,检查回显 import socket, sys target = ("192.168.1.110", 52001) payload = bytes.fromhex("AA 01 02 03 FF") s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.settimeout(5) s.sendto(payload, target) data, addr = s.recvfrom(4096) if data == payload: print("PASS: echo matched") sys.exit(0) else: print("FAIL: got", data.hex()) sys.exit(1)把这段放到 CI 脚本里,每次改完源码交叉编译后,先跑回归再上板联调,比到现场才发现收发逻辑被改坏要省事得多。我个人的教训是:早期在板子上直接开界面肉眼看收发,一个字节错位的问题排查了两天;后来把所有收发验证都挪到无头模式用脚本跑,五秒钟就能确认数据链路是好的,界面问题再单独看。这个习惯让后续所有 ARM 版网络调试工具的维护都轻松了不少。希望帮到你。
本文还有配套的精品资源,点击获取