简介这是一份基于npcap与Qt开发的网络抓包工具源码模仿Wireshark的界面与操作方式面向具备一定网络编程与C基础的开发者用于将数据包捕获与分析能力集成到自有系统中。资源包共86个文件以cpp源码、h头文件、obj编译产物、vcxproj工程文件及sln解决方案为主另含ui界面文件、qrc资源文件、png图标与log日志压缩包约2.48MB可直接用Visual Studio打开工程继续开发。工具依托npcap访问底层网络流量支持数据包捕获、过滤与解码并借助Qt的信号与槽机制构建图形界面便于定制过滤规则与解析算法。目前已有378人学习下载适合想研究抓包原理、二次开发网络监控工具或学习Qt与底层网络库结合的中高级开发者参考。1. 从零造一个抓包工具npcap Qt 到底能做出什么很多人第一次动了“自己写个抓包工具”的念头都是因为 Wireshark 太重、太全而自己只想盯住某几个端口、某几个 IP 的流量顺手做个可视化统计。这个标题——基于 npcap Qt 模仿 Wireshark 的网络抓包工具——说的正是这件事用 npcap 拿到网卡上的原始数据帧用 Qt 做界面和交互最后拼出一个能选网卡、能起停抓包、能实时刷新报文列表、能看协议树和十六进制的小工具。它解决的不是“替代 Wireshark”而是“把抓包能力嵌进自己的程序里”。适合两类人一是想学网络协议栈和 Win32 抓包接口的开发者二是需要在自有软件里内置流量监控、协议调试、设备通信分析功能的工程师。读完你应该能判断这套方案值不值得投入以及怎么在本地把它跑起来。2. 环境搭建npcap 驱动、Qt 版本与工程骨架2.1 为什么选 npcap 而不是 WinPcap在 Windows 上做抓包底层绕不开驱动。WinPcap 是上一代方案已经停止维护在 Win10/Win11 上安装经常报兼容性问题而且不支持 NDIS 6 之后的很多特性。npcap 是它的继任者由 Nmap 项目维护支持 Win7 到 Win11支持回环抓包、VLAN 标签保留、更细的过滤语法安装时还能选择“WinPcap API 兼容模式”。如果你要模仿 WiresharkWireshark 本身在 Windows 上默认用的就是 npcap所以选它最省心。安装时有两个选项要留意一是“Install Npcap in WinPcap API-compatible Mode”勾上之后老代码也能跑二是“Support loopback traffic”做本机进程间通信调试时很有用。安装完在C:\Windows\System32\Npcap下能看到wpcap.dll和Packet.dll这两个就是后面要链接的库。Qt 版本方面Qt 5.15.2 是长期支持版里比较稳的一个社区版免费在线安装器里勾选 MSVC 2019 64-bit 套件即可。不建议用 Qt 6 的早期版本因为部分第三方库对 Qt 6 的兼容还在补。安装完确认qmake -v能输出版本号Qt Creator能正常创建 Widgets Application。2.2 工程文件怎么写.pro 里的链接与包含路径新建一个 Qt Widgets Application工程名随意比如MiniSniffer。打开.pro文件加入 npcap 的头文件和库路径。npcap 安装后头文件在C:\Windows\System32\Npcap不一定有通常需要下载 npcap 的 SDK把pcap.h、pcap目录放到工程里或者直接引用 Wireshark 开发包里的头文件。稳妥做法是把 SDK 里的Include和Lib目录拷到工程旁边。QT core gui widgets TARGET MiniSniffer TEMPLATE app # npcap SDK 头文件路径按实际位置改 INCLUDEPATH $$PWD/npcap-sdk/Include # 64 位用 x6432 位用 x86 LIBS -L$$PWD/npcap-sdk/Lib/x64 -lwpcap -lPacket # 如果开了 WinPcap 兼容模式Packet.lib 可以不加 DEFINES WPCAP HAVE_REMOTE SOURCES main.cpp mainwindow.cpp HEADERS mainwindow.h FORMS mainwindow.ui-lwpcap是核心抓包库-lPacket提供更底层的网卡操作。WPCAP和HAVE_REMOTE是 npcap 头文件里用来开启远程抓包相关声明的宏不加的话某些函数原型不会暴露。编译时如果报cannot find -lwpcap九成是路径写错或者位数不匹配——32 位 Qt 配 64 位库或者反过来都会链接失败。2.3 用 Qt Designer 拖出主界面打开mainwindow.ui用 Qt Designer 拖出这几个控件顶部一个QComboBox用来选网卡旁边一个QPushButton做“开始/停止”下面一个QTableWidget显示报文列表右侧一个QTreeWidget显示协议树底部一个QPlainTextEdit显示十六进制。布局用QVBoxLayout和QHBoxLayout嵌套保证拉伸时比例正常。对象名要记牢comboBoxDev、btnStart、tablePackets、treeDetail、textHex。后面代码里全靠这些名字访问。表格列先设五列序号、时间、源地址、目的地址、协议。协议树按帧、以太网、IP、TCP/UDP 分层。这些设计不是随便定的Wireshark 的默认布局就是这样用户迁移过来没有学习成本。3. 抓包核心从 pcap_open_live 到实时刷新表格3.1 枚举网卡与打开设备npcap 提供pcap_findalldevs来列出所有可抓包的网卡。返回的是一个链表每个节点包含网卡名、描述、地址列表。网卡名是\Device\NPF_{GUID}这种形式不能直接显示给用户要显示描述字段。#include pcap.h #include QDebug void MainWindow::refreshDevices() { pcap_if_t *alldevs nullptr; char errbuf[PCAP_ERRBUF_SIZE] {0}; // 枚举所有网卡PCAP_IF_LOOPBACK 可过滤回环 if (pcap_findalldevs(alldevs, errbuf) -1) { qDebug() findalldevs failed: errbuf; return; } comboBoxDev-clear(); for (pcap_if_t *d alldevs; d ! nullptr; d d-next) { // 描述为空时用网卡名兜底 QString desc d-description ? QString::fromLocal8Bit(d-description) : QString::fromLocal8Bit(d-name); comboBoxDev-addItem(desc, QString::fromLocal8Bit(d-name)); } pcap_freealldevs(alldevs); }pcap_findalldevs失败通常是因为 npcap 服务没启动或者程序没有管理员权限。返回的alldevs必须用pcap_freealldevs释放否则每次刷新都漏内存。addItem的第二个参数是用户数据存网卡名后面打开设备时直接取不用再解析显示文本。打开设备用pcap_open_livebool MainWindow::openDevice(const QString devName) { char errbuf[PCAP_ERRBUF_SIZE] {0}; // snaplen 65536 保证整帧都能抓到promisc 1 开混杂模式 // to_ms 1000 表示超时 1 秒readTimeout 用 handle pcap_open_live(devName.toLocal8Bit().constData(), 65536, 1, 1000, errbuf); if (!handle) { qDebug() open_live failed: errbuf; return false; } return true; }snaplen设 65536 是常见做法保证再大的帧也不会被截断。promisc设 1 表示混杂模式能收到经过网卡但不是发给本机的帧做分析时通常需要。to_ms是读超时设 1000 毫秒意味着pcap_next_ex最多阻塞一秒就返回方便在循环里检查停止标志。3.2 抓包线程与界面线程的分离不能在 UI 线程里死循环调pcap_next_ex否则界面直接卡死。标准做法是开一个QThread把抓包循环放进去每抓到一帧就通过信号发给主线程刷新表格。信号参数用自定义结构体注册qRegisterMetaType才能跨线程传递。// packetinfo.h struct PacketInfo { quint64 index; QString time; QString src; QString dst; QString protocol; QByteArray raw; }; Q_DECLARE_METATYPE(PacketInfo)抓包线程的run函数void CaptureThread::run() { while (!m_stop) { struct pcap_pkthdr *header nullptr; const u_char *data nullptr; int ret pcap_next_ex(m_handle, header, data); if (ret 0) continue; // 超时继续 if (ret -1) break; // 出错 if (ret -2) break; // 离线文件读完 PacketInfo info; info.index m_counter; info.time QDateTime::currentDateTime() .toString(HH:mm:ss.zzz); info.raw QByteArray(reinterpret_castconst char*(data), header-caplen); parsePacket(info, data, header-caplen); emit packetCaptured(info); } }pcap_next_ex返回值有三种1 表示抓到包0 表示超时-1 表示错误-2 表示读到文件尾。循环里必须处理 0否则超时会被当成错误退出。caplen是实际抓到的长度len是帧原始长度两者在没截断时相等。emit出去的PacketInfo按值传递Qt 会自动做深拷贝不用担心data指针失效。主线程连接信号connect(captureThread, CaptureThread::packetCaptured, this, MainWindow::onPacketCaptured, Qt::QueuedConnection);Qt::QueuedConnection保证槽函数在 UI 线程执行。如果表格刷新太频繁可以攒一批再刷比如每 50 毫秒合并一次避免高频抓包时界面卡顿。3.3 协议解析以太网头、IP 头、TCP/UDP 头拿到原始帧后按偏移量逐层解析。以太网头 14 字节前 6 字节目的 MAC接着 6 字节源 MAC最后 2 字节类型。类型 0x0800 是 IPv40x0806 是 ARP0x86DD 是 IPv6。void MainWindow::parsePacket(PacketInfo info, const u_char *data, int len) { if (len 14) return; quint16 ethType (data[12] 8) | data[13]; int offset 14; if (ethType 0x0800 len offset 20) { // IP 头 int ihl (data[offset] 0x0F) * 4; quint8 proto data[offset 9]; info.src QString(%1.%2.%3.%4) .arg(data[offset12]).arg(data[offset13]) .arg(data[offset14]).arg(data[offset15]); info.dst QString(%1.%2.%3.%4) .arg(data[offset16]).arg(data[offset17]) .arg(data[offset18]).arg(data[offset19]); int l4 offset ihl; if (proto 6 len l4 20) { info.protocol TCP; quint16 sport (data[l4] 8) | data[l41]; quint16 dport (data[l42] 8) | data[l43]; info.src QString(:%1).arg(sport); info.dst QString(:%1).arg(dport); } else if (proto 17 len l4 8) { info.protocol UDP; quint16 sport (data[l4] 8) | data[l41]; quint16 dport (data[l42] 8) | data[l43]; info.src QString(:%1).arg(sport); info.dst QString(:%1).arg(dport); } else { info.protocol QString(IP(%1)).arg(proto); } } else if (ethType 0x0806) { info.protocol ARP; } else { info.protocol QString(ETH(0x%1)) .arg(ethType, 4, 16, QChar(0)); } }ihl是 IP 头长度单位是 4 字节所以乘 4。TCP 头固定 20 字节UDP 头固定 8 字节。端口号是大端序用移位拼。这段代码只解析了最外层实际做协议树时还要处理 TCP 选项、IP 分片、VLAN 标签类型 0x8100 时偏移要再加 4。VLAN 报文是很多人第一次写抓包工具时翻车的地方——忘了判断 0x8100结果所有带 VLAN 的帧都解析成未知协议。4. 避坑与排查npcap 服务、Qt 插件、内存泄漏4.1 报 “failed to create the npcap service: 0x800f0203”现象程序启动调pcap_findalldevs返回空或者安装 npcap 时直接弹这个错误码。原因0x800f0203 是 Windows 更新组件相关的错误通常出现在系统缺少某个补丁或者 npcap 安装包版本和系统不匹配。解决先确认系统是 Win7 SP1 以上然后去 npcap 官网下最新稳定版右键以管理员身份运行安装。如果还不行检查系统是否禁用了“Windows Installer”服务或者用sfc /scannow修复系统文件。装完重启一次再跑程序。4.2 Qt 程序发布后报 “could not find the qt platform plugin”现象在开发机上跑得好好的拷到别的机器双击就闪退命令行运行提示找不到windows平台插件。原因Qt 的动态库和插件目录没跟着 exe 一起拷。解决用windeployqt工具在 Qt 命令行里执行windeployqt MiniSniffer.exe它会自动把Qt5Core.dll、Qt5Gui.dll、platforms\qwindows.dll等拷到 exe 旁边。注意platforms目录必须存在里面要有qwindows.dll少一个都起不来。如果报的是linuxfb找不到那是把 Linux 的插件配置带到 Windows 了检查QT_QPA_PLATFORM环境变量删掉或改成windows。4.3 抓包线程退出时崩溃现象点“停止”后程序偶尔崩或者关窗口时报访问违例。原因pcap_next_ex还在阻塞主线程就把handle用pcap_close关了线程再去读就是野指针。解决停止时先设m_stop true然后调pcap_breakloop(handle)打断阻塞等线程wait()返回后再pcap_close。顺序不能反。pcap_breakloop是 npcap 提供的安全中断方式比直接关句柄可靠得多。4.4 表格刷新导致内存持续上涨现象抓包跑几分钟任务管理器里内存一直涨。原因每来一帧就new QTableWidgetItem但表格行数没上限旧行不删。解决设一个最大行数比如 10000 行超过就删最前面的行。或者用QAbstractTableModel自己管理数据只保留最近 N 条。另外PacketInfo里的raw如果每帧都存完整数据内存也会涨做实时显示时只存解析后的字段原始字节按需再取。4.5 混杂模式抓不到本机回环流量现象想抓本机两个进程之间的 TCP 通信选物理网卡抓不到。原因回环流量不走物理网卡走的是 Windows 的 loopback 接口。解决安装 npcap 时勾选“Support loopback traffic”装完后网卡列表里会多出一个Npcap Loopback Adapter选它就能抓到 127.0.0.1 的流量。如果列表里没有重新运行安装程序勾上那个选项再装一遍。5. 进阶过滤器语法、离线分析与性能调优5.1 用 pcap_compile 做内核级过滤抓包时如果不过滤所有流量都往用户态送CPU 占用很高。npcap 支持 BPF 过滤语法在驱动层就把不想要的包丢掉。用法是先pcap_compile编译表达式再pcap_setfilter应用。struct bpf_program fp; // 只抓 80 端口和 443 端口的 TCP 流量 const char *filter tcp port 80 or tcp port 443; if (pcap_compile(handle, fp, filter, 0, PCAP_NETMASK_UNKNOWN) -1) { qDebug() compile failed: pcap_geterr(handle); } else { pcap_setfilter(handle, fp); pcap_freecode(fp); }pcap_compile的第四个参数是优化开关设 1 会做表达式优化。PCAP_NETMASK_UNKNOWN用于没有子网掩码的场景。编译成功后必须pcap_freecode释放否则漏内存。过滤表达式支持host、net、port、portrange、src、dst以及and、or、not组合和 Wireshark 的捕获过滤器语法一致。注意这是捕获过滤器不是显示过滤器写错了直接抓不到包不会报语法错。5.2 离线 pcap 文件读取与回放除了实时抓包pcap_open_offline能打开之前保存的.pcap文件用同样的pcap_next_ex循环读。这样可以把抓包和分析解耦现场只负责存文件回来慢慢分析。保存文件用pcap_dump_open和pcap_dump在抓包循环里顺手写盘。pcap_dumper_t *dumper pcap_dump_open(handle, capture.pcap); // 在循环里 pcap_dump(reinterpret_castu_char*(dumper), header, data); // 结束时 pcap_dump_close(dumper);离线读取时pcap_next_ex返回 -2 表示文件读完循环要正常退出。时间戳来自文件头不是当前时间显示时要用header-ts转换。做回放测试时可以按时间戳差值sleep模拟真实速率。5.3 性能调优的几个参数参数默认值建议值说明snaplen6553665536小于 1500 会截断大帧to_ms1000100越小界面越跟手但 CPU 略高缓冲区系统默认4MBpcap_setbuff可调高流量时减少丢包表格行数无限制10000超过删旧行防内存涨刷新间隔每帧50ms 合并高频抓包时降低 UI 压力pcap_setbuff要在pcap_open_live之后、开始抓包之前调。高流量场景下默认缓冲区可能只有几百 KB瞬间打满就丢包。调到 4MB 能明显改善。另外抓包线程的优先级可以适当提高但别设成实时否则可能拖死系统。5.4 一个我常犯的错误早期写这个工具时我总想把所有解析逻辑塞进抓包线程觉得这样主线程轻松。结果协议树构建、十六进制转换这些耗时操作全在抓包线程里做pcap_next_ex的调用间隔被拉长高流量时丢包严重。后来改成抓包线程只做最轻量的帧拷贝和信号发射所有解析、格式化、界面更新都放主线程丢包率立刻降下来。抓包线程的职责越单一越好它只负责“拿到帧、发出去”别的都别管。希望帮到你。本文还有配套的精品资源点击获取