基于Visual C++与Npcap的网络流量监控系统实现
2026/9/8 2:21:05 网站建设 项目流程

简介:这是一份基于Visual C++开发的网络流量监控系统完整源代码,适合正在学习网络编程与Windows应用开发的中级C++开发者,可帮助解决流量采集、协议解析与界面展示的工程实现问题。压缩包共24个文件,以8个h头文件和5个cpp源文件为主,另有MFC界面资源、工程配置、图标以及一个可运行的exe,整体约67KB,结构紧凑便于查阅。目前已有595人学习下载。源码通过WinPCap库实现数据包捕获与解析,覆盖IP、TCP/UDP等常用协议,并包含多线程流量统计、带宽计算和表格展示,模块划分清晰,可直接编译调试或迁移到自己的项目中。实现中还考虑了多线程避免UI阻塞、实时告警与日志记录等细节。相比纯理论资料,这份资源能直观看到捕获、解析、统计与界面更新如何衔接,用于课程设计或网络管理工具开发都有参考价值。 上个月办公室还在为视频会议卡顿吵得不可开交的时候,我已经盘算着要自己做一套网络流量监控系统了。交换机、路由器看着都正常,可一到下午会议高峰期,延迟就往上飙,问谁都是一问三不知——不是大家不配合,是确实没有数据支撑,谁在用带宽、哪个IP流量最大,全靠猜。我当时的想法很简单:用Visual C++写一个网络流量监控系统,挂在一台机器上7x24小时采集,哪个IP、哪个端口用了多少流量,全部记录下来。于是就有了这套源代码,解压开就是完整的Visual C++工程,编译后直接能跑。

这篇内容会把这套系统的技术选型、源码主干、编译过程和实战中的坑完整过一遍。不管你是刚接触网络编程的学生,还是在办公室里被网络问题折磨的运维,应该都能从里面拿走一些直接能用的东西。

1. 先搞清楚需求,再谈代码

1.1 需求从场景里长出来:办公室网卡事件

做这套系统的直接触发点是那次“网卡事件”。当时症状很典型:早上一切正常,下午两点一过,网络延迟从20ms以内飙到300ms以上,视频会议开始出现马赛克。排查了半天,路由、交换机的资源占用率都不高,网线也换了,问题依旧。

最后我才意识到,缺的是一个能按时间维度还原“流量是谁产生的”的工具。Wireshark确实能抓包,但它是分析工具,不是监控工具。你可以用它在问题发生的10分钟里去抓,但没法让它天天挂着、自动按IP和端口累计数据、事后还能导出报表。市面上的商业监控软件又太贵,而且很多需要装agent。自己写一个,反而最可控。

1.2 功能清单:做哪些事,不做什么事

这套系统的定位很明确:不解析应用层内容,不做入侵检测,只做流量计量和会话统计。下面这个表是当时跟需求方确认下来的核心功能,后来也基本都实现了。

功能说明
实时速率显示按秒刷新各网卡的上行/下行速率,类似任务管理器但更细
连接级统计按五元组(源IP、目的IP、源端口、目的端口、协议)聚合流量
TOP会话排行实时列出占用带宽最大的前N个连接
协议分布TCP、UDP、ICMP、ARP等协议的包数和字节数占比
定时落盘每分钟把累计统计写入CSV日志,方便事后排查

不做的部分同样重要。这套代码不负责解包HTTP请求内容,不解密TLS流量,也不做应用层识别。原因很简单:一旦涉及内容解析,性能开销会成倍放大,而且法律合规风险也复杂。做流量监控,统计到连接级别对大部分网络排障来说已经完全够用。

2. 抓包引擎选型:为什么是Npcap而不是裸Socket

2.1 Windows下抓包的三种主流姿势

很多初学者拿到这个需求,第一反应是用Socket编程,创建一个SOCK_RAW原始套接字去收包。这个思路在Linux下勉强能走通,但在Windows下行不通。Windows的RAW Socket从Win2000开始就对TCP流量做了限制,你拿SOCK_RAW收到的只有ICMP和其他少数协议的数据包,TCP/UDP包根本不会递送上来。所以用裸Socket做流量监控,第一步就卡死了。

剩下的主流路线有两条。

第一条是ETW(Event Tracing for Windows),通过Microsoft-Windows-NDIS-PacketCapture这个Provider可以拿到网卡收发的数据包,好处是微软原生、不需要额外装驱动,但它的使用方式偏C++/WMI,调试起来比较绕,而且有些老版本Windows行为差异很大。

第二条是WinPcap/Npcap。WinPcap是经典的抓包库,但已经停止维护很久了,Windows 10以上的驱动兼容性全靠Npcap在撑。Npcap是WinPcap的继任者,提供完全兼容的API,装驱动后可以工作在数据链路层,拿到最原始的以太网帧。这套源码选的就是Npcap,准确说是用WinPcap API兼容模式去调Npcap。

2.2 Npcap的驱动服务和调用方式

Npcap的核心是一个内核驱动,安装后系统里会多一个叫npcap的服务。用户态的抓包程序通过pcap.dll跟驱动通信,驱动把网卡收到的数据包复制一份到用户态缓冲区,你的程序再用pcap_next_ex之类的API取出来。

这里有一个关键认知:Npcap抓包不是“截获”,而是“监听”。它不会阻止数据包正常收发,只是额外复制一份给你看。所以跑监控程序的机器本身的网络功能不受影响,这点很重要,不然监控工具就成了故障源。

2.3 一段最小可运行的抓包骨架

把Npcap接进Visual C++工程后,核心抓包逻辑其实不长。下面是这套源码里抓包线程的骨架代码,做了简化:

#include <pcap.h> // 打开网卡,promisc=1 表示混杂模式 char errBuf[PCAP_ERRBUF_SIZE] = { 0 }; pcap_t* handle = pcap_open_live("\\Device\\NPF_{7B6A3C11-...}", 65536, // snaplen,抓包长度 1, // 混杂模式 1000, // 超时,毫秒 errBuf); if (handle == NULL) { // 打印 errBuf 后退出 return -1; } // 设置过滤规则,只处理 IP 和 ARP 包,减少无效处理 struct bpf_program fcode; pcap_compile(handle, &fcode, "ip or arp", 1, PCAP_NETMASK_UNKNOWN); pcap_setfilter(handle, &fcode); // 抓包循环:在工作线程中执行 struct pcap_pkthdr* header; const u_char* pktData; while (bRunning) { int ret = pcap_next_ex(handle, &header, &pktData); if (ret == 1) { // 把数据包交给解析模块 ParsePacket(pktData, header->len, header->caplen); } } pcap_close(handle);

注意几个细节。第一,设备名是\Device\NPF_{GUID}这种格式,不能用简单的“eth0”之类,要调用pcap_findalldevs去枚举。第二,snaplen设成65536可以保证抓到完整包,但如果只做统计,设到96到128字节就够了,能省大量拷贝开销。第三,过滤规则用pcap_compile编译后,驱动层就会过滤,性能比用户态过滤好得多。

3. 源码主干流程:一个数据包从网卡到界面的完整旅程

3.1 拿到源码先看哪里:目录与阅读顺序

解压这套RAR后,你不会想一头扎进几万行代码里。源码的目录结构是这样的逻辑分层:

  • CaptureEngine/:封装Npcap调用,负责设备管理、抓包线程、统计回调
  • PacketParser/:以太网帧、IP头、TCP/UDP头的结构体定义与解析函数
  • FlowTable/:五元组哈希表,连接级统计的核心数据结构
  • UI/:MFC界面,包括设备选择、实时列表、速率曲线
  • Common/:日志模块、配置读写、字符串工具

如果你要快速理解这套代码,阅读顺序应该是:Common的公共结构体定义 ->CaptureEngine的抓包循环 ->PacketParser的解析函数 ->FlowTable的统计逻辑 -> 最后再看UI怎么把数据画出来。

3.2 协议解析:从以太网头到TCP端口

一个数据包从网卡进来的时候,是最原始的以太网帧。要拿到IP地址和端口,得一层一层拨开。这套源码里用#pragma pack(push,1)定义了紧凑排列的结构体,直接映射到内存里的数据包:

#pragma pack(push, 1) typedef struct _ETHER_HEADER { BYTE dstMac[6]; BYTE srcMac[6]; WORD etherType; // 0x0800=IPv4,0x0806=ARP } ETHER_HEADER; typedef struct _IP_HEADER { BYTE verIhl; // 高4位版本,低4位头部长度(以4字节为单位) BYTE tos; WORD totalLen; WORD id; WORD fragOffset; BYTE ttl; BYTE protocol; // 6=TCP,17=UDP,1=ICMP WORD checksum; DWORD srcAddr; DWORD dstAddr; } IP_HEADER; typedef struct _TCP_HEADER { WORD srcPort; WORD dstPort; DWORD seq; DWORD ack; BYTE offset; // 高4位头部长度 BYTE flags; WORD window; WORD checksum; WORD urgent; } TCP_HEADER; #pragma pack(pop)

解析的时候有个很容易踩的坑:网络字节序是大端,而x86是小端。从包里读出来的端口号、总长度这些字段,必须用ntohs()ntohl()转换,否则你会看到端口65535这种离谱的值。判断协议类型时不要只依赖以太网头的etherType,因为VLAN标签(Q-Tag)会改变偏移量,生产环境里多了一层VLAN头导致解析错位的例子我见过不少。这套代码里对VLAN头做了处理,判断到0x8100时会主动跳过4个字节。

3.3 五元组哈希表是统计的核心

流量统计的核心数据结构就是五元组连接表。每次解析完一个包,提取出五元组,在哈希表里查找对应的连接记录,然后累加上行或下行字节数:

typedef struct _CONN_KEY { DWORD srcAddr; DWORD dstAddr; WORD srcPort; WORD dstPort; BYTE protocol; } CONN_KEY; typedef struct _CONN_STAT { ULONGLONG upBytes; // 上行字节 ULONGLONG downBytes; // 下行字节 ULONGLONG upPackets; ULONGLONG downPackets; time_t lastActive; // 最后活跃时间,用于超时清理 } CONN_STAT;

这里有个方向判断问题:同样的五元组,源和目的互换后,在另一个方向上就是一条新记录。这套代码的处理策略是——如果源端口大于目的端口,就交换两个地址,保证一个TCP连接的正反两个方向都能归到同一条记录里。这样界面上看到的就是“一条连接,两条方向,上行多少,下行多少”。

哈希表不能无限增长。如果不做清理,监控程序跑几天后内存会被没人用的旧连接占满。源码里用一个定时器,每隔60秒扫描一次,把超过5分钟没有活跃的连接从表里移除,并把累计值写入日志后释放。

4. 编译与部署:把源码跑起来必须迈过的四道坎

4.1 开发环境与SDK准备

这套源码最早是用Visual C++ 6.0写的工程文件(.dsw/.dsp),但拿到新版Visual Studio(VS2015到VS2022都行)后可以直接打开,VS会提示做工程迁移。如果迁移后报了一堆兼容性错误,不要慌,基本都是下面三类问题:一是旧的#include <afxtempl.h>写法,二是C2447这类旧式函数声明,三是原来依赖的STL版本接口变了。

除了Visual C++本身,你还需要安装Npcap的SDK。去Npcap官网下载开发包,解压后里面有Include和Lib两个目录。在VS的工程属性里,把附加包含目录指向Include,把附加库目录指向Lib,然后在链接器的附加依赖项里手工加上wpcap.libPacket.lib

如果你在VC6时代的老工程上改,还需要在预处理定义里加上WPCAPHAVE_REMOTE,否则pcap_remote相关的接口会用不了。新版SDK一般会自动带上,但如果链接报错,首先检查的就是这两个宏。

4.2 一个最容易忽略的运行时依赖

编译成功只是第一步。运行的时候如果提示“找不到wpcap.dll”,说明目标机器上没有安装Npcap运行时。很多人忘记这一茬,把exe拷到别的机器上就跑不起来。

最简单的办法是在每台目标机器上安装Npcap的安装包,安装时候注意勾选“Install Npcap in WinPcap API-compatible Mode”。如果不方便装,也可以把wpcap.dllPacket.dll从安装了Npcap的机器上拷贝到exe同目录下。但这里有个64位适配问题:32位程序需要32位的dll,64位程序需要64位的dll,混着放会直接加载失败。建议直接用官方安装包,省心。

4.3 编译期与运行期的常见报错

我把这套源码在迁移过程中遇到的典型报错整理成了表,你如果卡住了直接对着查:

现象原因解决方案
cannot open include file: 'pcap.h'没有配置Include目录工程属性 -> VC++目录 -> 附加包含目录
unresolved external symbol pcap_open_live没有链接wpcap.lib附加依赖项里加wpcap.lib
运行时报找不到wpcap.dll目标机器没有Npcap运行时按运行时安装包,或同目录放dll
pcap_open_live返回失败,errBuf提示权限程序没有管理员权限exe右键属性勾选“以管理员身份运行”
数据包长度一直是65535snaplen设置的太大如果只做统计,snaplen改成96即可

最后一个坑值得多说一句:Npcap抓包需要管理员权限。无论是调试还是部署,程序必须提权运行,否则驱动打不开设备。在VS里调试时,也要把Visual Studio本身以管理员身份启动。

5. 实测数据对不上?这些坑我替你踩过了

5.1 混杂模式不等于“全都能抓到”

代码里pcap_open_live的第三个参数设成了1,也就是混杂模式。很多人以为开了混杂模式,就能抓到经过网卡的所有流量,这是最普遍的误解。

实际上,混杂模式只是让网卡把目的MAC不是自己的帧也收上来。在传统共享式网络里这招有用,但现在的网络基本都是交换式以太网,交换机会根据MAC地址表把帧只发到对应端口。你在监控机上开混杂模式,能抓到的仍然只是发往这台机器和广播的流量。

如果想监控整个局域网,必须在交换机上做端口镜像,把目标端口的流量复制一份到监控机所在的端口。这套代码本身做得再对,网络环境不具备端口镜像条件,数据一样不完整。排查“流量数据明显变少”的问题时,第一件事不是看代码,而是确认交换机配置。

5.2 网卡硬件卸载带来的统计偏差

到这里排查过一轮之后,你可能还会发现一个现象:系统自带的任务管理器显示的网卡速率,和这套监控软件算出来的数值差不少。一开始我还以为是抓包丢包,后来才定位到网卡硬件的TCP分段卸载和校验和卸载特性在捣鬼。

现在的网卡普遍支持Large Send Offload和Checksum Offload,数据包的拆分和校验计算是在网卡硬件里完成的。驱动交给Npcap的包,跟你实际发送的应用数据对比,可能已经被拆成了多个小包,或者校验和字段是0。这对抓包内容分析影响不大,但会让你的“包数”统计看起来偏高,“字节数”在某些场景对不上。

这个不是代码能完全解决的,能做的是在界面上加一个数据来源标注,同时关闭网卡驱动属性里的“大量发送卸载”选项,让统计更接近真实值。源码里对这块留了配置项,但老实说,大多数国产网卡驱动不一定给你关的机会,理解偏差来源比盲目调整更重要。

5.3 界面卡顿与高流量丢包

监控程序跑在千兆网络下的时候,如果网络流量比较大,你会发现界面刷新开始掉帧,CPU占用率居高不下。第一个定位到的元凶是UI线程的OnTimer里直接遍历整个五元组哈希表,二十分钟后表里有几万条连接,每秒钟全表扫描两次,卡是必然的。

优化方案是给统计表加锁,在抓包线程里做累加,把“需要显示的数据”单独快照一份。具体做法是维护一张小得多的“显示用表”,每秒由抓包线程定时汇总TopN数据进去,UI线程只读这张小表,不碰大表。这样界面刷新的开销从几万条降到了几十条,流畅度立刻不一样。

如果发现抓包本身丢包率上升,那就是pcap的缓冲区太小。默认情况下内核缓冲区只有几MB,高峰期撑不住。可以在打开设备后用pcap_setbuff把缓冲区调到64MB甚至128MB,同时把snaplen降到128字节,只保留解析需要的头部信息。这两个动作做了之后,千兆下跑满速率时丢包率能控制在很低的水平。

6. 从能用走向好用:值得动手的三个扩展方向

6.1 从“按IP统计”升级到“域名维度”

现在这套代码按五元组统计已经很清晰了,但在实际用的时候你会发现一个问题:看到IP占用流量大,你还得去查这个IP是什么业务。扩展方向很直接——通过DNS解析把IP映射到域名。具体做法是解析DNS响应包里的A记录,把域名和IP的映射关系缓存起来,然后在流量统计表里增加一个“hostname”字段。

这个扩展对带宽治理特别有用。办公室里哪个业务系统是流量大头,一眼就能看出来,不用再拿IP对着台账查半天。

6.2 告警:让系统主动找你

监控系统的终极目标不是让你天天看图表,而是出问题的时候主动告诉你。可以在FlowTable的定时扫描逻辑里加阈值判断,比如某条连接速率超过100Mbps持续30秒,就触发告警。

这套代码的架构里,告警模块可以放在统计回调之后,完全不影响原有逻辑。告警方式不必搞得很复杂,一个简单的Windows气泡通知加写事件日志就够。有条件的话,可以顺带实现一个HTTP POST把告警转发给企业微信机器人,手机上就能收到。

6.3 数据落盘与Web可视化

最后的扩展方向稍微重一点。源码里目前是每分钟把统计结果写入CSV,然后就没有然后了。想长期观测趋势的话,可以接一个时序数据库,用Python读CSV后写入并生成图表,或者直接在C++里调InfluxDB的写入接口。

我自己后来改的版本就是在CSV落盘的基础上,每天夜里写一个汇总脚本,把当天数据导成图表。虽然简陋,但连续跑了一个多月后,之前那个"每到下午就卡顿"的问题终于有了直观结论——某个备份任务固定时间全速同步,把出口带宽占满了。

最后再分享一个调试技巧:抓包程序在断点调试的时候非常容易丢包,因为断点一停,驱动缓冲区的包会溢出。遇到统计数字不对,先让程序裸奔跑几分钟再看日志,别一边断点一边判断数据正确性,否则容易把自己带进沟里。这套代码本身不算复杂,把抓包、解析、统计这条主线理顺了,往上加再多的功能都是围绕这个骨架做文章。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询