简介:这是一份基于npcap与Qt开发的网络抓包工具源码,模仿Wireshark的核心功能,面向具备一定网络编程与C++基础的开发者,用于学习数据包捕获、协议解析与图形界面集成。资源包共86个文件,以cpp与h源码、obj与tlog编译中间文件、vcxproj工程配置、qrc资源脚本及ui界面文件为主,另含sln解决方案与可执行程序,压缩包约2.48MB,结构完整可直接导入Visual Studio继续开发。目前已有378人学习下载。读者可从中掌握npcap抓包接口调用、Qt信号与槽驱动界面刷新、数据包过滤与展示的完整实现思路,并参考工程组织方式将抓包能力嵌入自有系统,适合作为网络监控与协议分析类项目的实践参考。
1. 拿到 sniffer.zip 之后:这套 npcap+Qt 抓包工具到底能干什么
如果你手头正好有一个sniffer.zip,解压后看到sniffer.sln、sniffer目录和x64文件夹,那基本可以确定这是一套 Windows 平台下用 C++ 写的抓包工程,底层依赖 npcap,界面层用 Qt。它的定位很明确:不是让你替代 Wireshark 做日常排障,而是给你一个能自己改、能嵌进别的系统里的抓包骨架。Wireshark 强在协议解析和可视化,但它的插件体系和二次开发门槛不低;这套 sniffer 的价值在于源码可控,你可以把抓包、过滤、展示这条链路拆开,换成自己的解析逻辑或上报通道。
适合谁:需要把抓包能力集成进自有监控工具的后端开发、想研究 npcap 回调机制和 Qt 模型视图配合的 C++ 工程师,以及被 Wireshark 界面限制住、想自己控制展示字段的从业者。不适合只想点开就抓包、完全不碰代码的人。下面按“先跑起来、再看懂结构、最后改得动”的顺序拆。
2. 把工程跑起来:npcap 环境、Qt 版本与 sln 编译链路
2.1 先确认 npcap 装对了,别急着开 Qt Creator
npcap 是这套工具的命根子,它负责从网卡驱动层把数据包捞上来。很多人编译能过、一运行就报failed to create the npcap service: 0x800f0203,本质是 npcap 驱动没装好或版本不匹配。常见做法是去 npcap 官方渠道下载安装包,安装时勾选 “Install Npcap in WinPcap API-compatible Mode”,这样老代码里pcap_open_live这类 WinPcap 风格调用也能用。
装完先别开工程,用命令行验证一下:
# 查看 npcap 是否识别到网卡,以及驱动版本 npcap_check.bat # 或者直接用 Wireshark 自带的 dumpcap 列接口,能列出来说明 npcap 层通了 dumpcap -Ddumpcap -D会输出类似\Device\NPF_{GUID}的接口列表。如果这里报错,Qt 工程里再怎么调pcap_findalldevs都是白搭。注意:npcap 安装包和 SDK 是两回事,编译需要的是 SDK 里的pcap.h和wpcap.lib,运行需要的是驱动服务。两者版本尽量对齐,别用 1.60 的 SDK 配 0.99 的驱动。
2.2 Qt 版本选择:别用太新的,5.15.2 是甜点
工程里如果带.pro或.vcxproj,先看它写的 Qt 模块。这套 sniffer 常见依赖是core、gui、widgets、network,不涉及 QML 和 WebEngine。热搜里那个unknown module(s) in qt: webenginewidgets就是典型的模块没装或版本不对,跟本工程无关,别被带偏。
我一般会固定用 Qt 5.15.2 + MSVC 2019 的组合,原因是:npcap SDK 的库是 MSVC 编译的,MinGW 链接wpcap.lib容易出符号不兼容;Qt 6 对部分老式QRegExp和QString::SplitBehavior有改动,老工程直接迁会报一堆弃用错误。安装时勾选 MSVC 2019 64-bit 套件即可。
# 配置时显式指定 npcap SDK 路径,避免找不到 pcap.h qmake sniffer.pro "INCLUDEPATH+=C:/npcap-sdk/Include" "LIBS+=-LC:/npcap-sdk/Lib/x64 -lwpcap -lPacket"INCLUDEPATH指向 SDK 的 Include 目录,LIBS里-lwpcap是抓包主库,-lPacket是 npcap 特有的发包库,只抓包的话后者可以不加。路径里的反斜杠在 qmake 里建议写成正斜杠,否则容易被当转义符吃掉。
2.3 用 sniffer.sln 还是 qmake:两条编译路线的取舍
解压后看到sniffer.sln,说明作者可能用 Visual Studio 直接管理工程。这时候你有两个选择:
| 路线 | 适用场景 | 注意点 |
|---|---|---|
| 直接开 sniffer.sln | 已装 VS + Qt VS Tools | 需在 VS 里配置 Qt 版本和 npcap 库目录 |
| 用 qmake 重新生成 | 只有 Qt Creator | 需要自己补 .pro 里的库路径 |
如果走 sln,先在 VS 的 “Qt VS Tools → Qt Versions” 里加好 5.15.2 的路径,再右键项目属性,在 “链接器 → 输入” 里确认wpcap.lib在列。走 qmake 的话,把源码目录里的.cpp/.h全部纳入,重点确认pcap.h能被找到。两条路最后产出的 exe 行为一致,区别只是谁管依赖。
编译通过后第一次运行,如果界面能弹出但网卡下拉框是空的,回到 2.1 检查 npcap 服务;如果直接闪退,用 Debug 模式跑,看是不是pcap_findalldevs返回了空指针却没判空。
3. 读懂抓包主链路:从 pcap_open_live 到 Qt 表格刷新
3.1 npcap 回调线程与 Qt 主线程的边界
抓包的核心是一个死循环:pcap_loop或pcap_next_ex不断从网卡取包,每来一个包触发一次回调。这个回调运行在 npcap 自己的线程里,而 Qt 的界面控件只能在主线程更新。直接在工作线程里ui->tableWidget->setItem(...)是经典翻车点,表现是界面随机卡死或崩溃。
常见做法是回调里只做两件事:把pcap_pkthdr和包数据拷贝出来,通过信号槽抛给主线程。下面是一个可抄的结构:
// CaptureWorker 运行在独立 QThread 中 void CaptureWorker::startCapture(const QString &devName) { char errbuf[PCAP_ERRBUF_SIZE]; pcap_t *handle = pcap_open_live(devName.toLocal8Bit().data(), 65535, // snaplen:抓全包,别截断 1, // promisc:混杂模式 1000, // read timeout,毫秒 errbuf); if (!handle) { emit errorOccurred(QString("open failed: %1").arg(errbuf)); return; } pcap_loop(handle, 0, &CaptureWorker::packetHandler, reinterpret_cast<u_char*>(this)); } // 静态回调,只负责转发,不碰 UI void CaptureWorker::packetHandler(u_char *user, const pcap_pkthdr *hdr, const u_char *data) { auto *self = reinterpret_cast<CaptureWorker*>(user); QByteArray payload(reinterpret_cast<const char*>(data), hdr->caplen); emit self->packetCaptured(hdr->ts, payload); // 跨线程信号 }pcap_open_live的第二个参数snaplen设 65535 是为了不截断大包,设小了后面解析 TCP 载荷会缺字节。第三个参数promisc=1让网卡进混杂模式,能抓到非本机地址的帧,交换机镜像口场景必须开。第四个read timeout影响回调触发频率,1000ms 偏保守,实时性要求高可以降到 100。信号packetCaptured在连接时要用Qt::QueuedConnection,Qt 会自动把参数拷贝到主线程队列。
3.2 协议解析:从以太头到 IP/TCP 的偏移计算
抓到原始帧后,要模仿 Wireshark 的列表展示,至少得解出源 MAC、目的 MAC、源 IP、目的 IP、协议类型。以太网头固定 14 字节,其中前 12 字节是两个 MAC,第 13-14 字节是以太类型。类型为0x0800才是 IPv4,0x0806是 ARP,0x86DD是 IPv6。
struct EthHeader { uint8_t dst[6]; uint8_t src[6]; uint16_t type; }; struct Ipv4Header { uint8_t ver_ihl; // 高4位版本,低4位首部长度 uint8_t tos; uint16_t total_len; uint16_t id, frag; uint8_t ttl, proto; uint16_t checksum; uint8_t src[4], dst[4]; }; QString parsePacket(const QByteArray &frame) { if (frame.size() < 14) return "truncated"; const auto *eth = reinterpret_cast<const EthHeader*>(frame.constData()); uint16_t ethType = ntohs(eth->type); if (ethType != 0x0800) return QString("non-IPv4 type=0x%1").arg(ethType, 4, 16); const auto *ip = reinterpret_cast<const Ipv4Header*>(frame.constData() + 14); int ihl = (ip->ver_ihl & 0x0F) * 4; // 首部长度以4字节为单位 QString src = QString("%1.%2.%3.%4").arg(ip->src[0]).arg(ip->src[1]) .arg(ip->src[2]).arg(ip->src[3]); // 传输层从 14 + ihl 开始,proto=6 是 TCP,17 是 UDP return QString("%1 -> %2 proto=%3").arg(src).arg("...").arg(ip->proto); }ver_ihl低 4 位乘 4 才是 IP 首部真实字节数,因为 IP 选项会让首部超过 20 字节,写死 20 是常见错误。ntohs处理网络字节序,Windows 下要#include <winsock2.h>,且必须在windows.h之前包含,否则会报重定义。解析函数只做偏移和取值,别在里面做字符串拼接以外的重活,否则高流量下表格刷新会拖垮抓包线程。
3.3 过滤规则:BPF 表达式在 pcap_compile 里怎么用
Wireshark 的过滤框背后就是 BPF。npcap 提供pcap_compile把表达式编译成内核过滤指令,再pcap_setfilter挂上去。这样不匹配的包在驱动层就被丢掉,不用传到用户态,性能差别很大。
struct bpf_program fp; // 只抓 80 端口且目标是 192.168.1.10 的 TCP 包 const char *filter = "tcp port 80 and dst host 192.168.1.10"; if (pcap_compile(handle, &fp, filter, 0, PCAP_NETMASK_UNKNOWN) == -1) { emit errorOccurred(QString("compile filter failed: %1") .arg(pcap_geterr(handle))); return; } if (pcap_setfilter(handle, &fp) == -1) { emit errorOccurred("set filter failed"); return; } pcap_freecode(&fp); // 编译结果挂上去后即可释放pcap_compile第四个参数optimize=1会做表达式优化,建议开。第五个netmask在抓本机网段广播时有用,不确定就传PCAP_NETMASK_UNKNOWN。pcap_freecode必须在setfilter之后调用,否则内存泄漏。注意过滤器语法和 Wireshark 显示过滤器不是一套,ip.addr == x这种是显示过滤,BPF 里要写host x。
4. 避坑与排查:编译、运行、抓包三类高频问题
4.1 编译期报 cannot find -lwpcap 或找不到 pcap.h
现象:链接阶段报cannot find -lwpcap,或编译报pcap.h: No such file。 原因:npcap SDK 没装,或 qmake/VS 里的库路径写错,32/64 位混用。 解决:确认 SDK 解压目录下有Include/pcap.h和Lib/x64/wpcap.lib;qmake 里LIBS的-L路径指向Lib/x64,工程套件也必须是 64 位。32 位工程配 64 位库必然链接失败。
4.2 运行报 failed to create the npcap service: 0x800f0203
现象:程序启动或点“开始抓包”时弹这个错误码。 原因:npcap 驱动服务没注册,或安装时没勾 WinPcap 兼容模式,或被杀软拦了驱动加载。 解决:重装 npcap,勾选兼容模式;用管理员权限运行一次;确认系统服务里npcap处于运行状态。这个错误码和代码无关,别去改源码。
4.3 界面卡死或抓一会儿就闪退
现象:抓包开始后界面无响应,或几分钟后进程消失。 原因:在 npcap 回调线程里直接操作了 Qt 控件;或包数据没做深拷贝,回调返回后指针失效。 解决:回调里只emit信号,槽函数在主线程更新 UI;QByteArray构造时带上长度做深拷贝,别只存裸指针。高流量下还要限制表格行数,比如只保留最近 1000 条,否则内存和刷新都扛不住。
4.4 抓不到任何包,但 Wireshark 能抓
现象:接口列表正常,开始抓包后一条不显示。 原因:选了错误的网卡;过滤器写太严;或没开混杂模式导致只看到本机流量。 解决:先用空过滤器抓,确认链路通;再逐步加 BPF 条件。虚拟机场景注意选对虚拟网卡,抓物理网卡是看不到虚拟机内部流量的。
4.5 Qt 版本混用导致 fatal: cannot mix incompatible Qt library
现象:运行时报cannot mix incompatible Qt library (version 0x...)。 原因:exe 旁边混入了不同版本的 Qt DLL,或系统 PATH 里有另一套 Qt。 解决:用windeployqt把依赖收拢到 exe 同目录,运行前清掉 PATH 里的其他 Qt 路径。发布时别只拷 exe,platforms/qwindows.dll必须一起带。
5. 二次开发与验证:把 sniffer 改成你自己的抓包模块
5.1 加一个自定义字段列:从解析到表格映射
想模仿 Wireshark 的 “Info” 列,把 TCP 标志位或 HTTP 方法显示出来,改动点集中在三处:解析函数多返回一个字段、信号多带一个参数、表格多插一列。下面以提取 TCP 目的端口为例:
// 在 parsePacket 里,proto==6 时继续往下解 TCP 头 const uint8_t *tcp = reinterpret_cast<const uint8_t*>(frame.constData()) + 14 + ihl; uint16_t dstPort = (tcp[2] << 8) | tcp[3]; // TCP 头第3-4字节是目的端口 // 信号改为携带结构体 struct PacketInfo { QString src, dst; quint16 dport; QString proto; }; emit packetCaptured(info);表格初始化时setColumnCount(5),在槽函数里setItem(row, 4, new QTableWidgetItem(QString::number(info.dport)))。注意setItem每次 new 一个 item,旧 item 由表格接管释放,但行数上限要自己控制,超出时removeRow(0)。
5.2 验证抓包正确性:和 Wireshark 对拍
改完解析逻辑,别凭感觉说“对了”。我一般会做一次对拍:同一张网卡、同一时间段,sniffer 和 Wireshark 同时抓,然后比对包数量和关键字段。步骤是:
- 在 sniffer 里清空过滤,开始抓包,同时 Wireshark 选同一网卡开始。
- 制造固定流量,比如
ping -n 20 8.8.8.8或访问一个固定 HTTP 地址。 - 停止后看两边 ICMP/TCP 包数量是否一致,源目 IP、端口是否对得上。
- 若 sniffer 少包,检查 snaplen 是否截断、过滤器是否误伤、回调是否丢包。
对拍能快速定位是解析错还是抓取漏。曾经有一次我这边 TCP 端口全是 0,对拍后发现是ihl没乘 4,偏移算错,血泪经验。
5.3 发布与依赖收拢:别让用户装 Qt
这套工具最终要给别人用,不能要求对方装 Qt。用windeployqt自动收依赖:
windeployqt --release --no-translations sniffer.exe执行后 exe 同目录会出现Qt5Core.dll、platforms/qwindows.dll等。npcap 的wpcap.dll和Packet.dll如果没在系统目录,也要一起拷。最后在一台没装 Qt、只装了 npcap 的干净机器上跑一遍,确认能启动、能列网卡、能抓包,才算真正交付。
从那以后我每次改完抓包逻辑,都强制走一遍“Wireshark 对拍 + 干净机启动”这两步,不再靠单机自测下结论。希望帮到你。
本文还有配套的精品资源,点击获取