简介:这是一份面向C#开发者与网络运维人员的网络抓包工具包,基于SharpPcap开源库实现数据包的捕获与分析,可用于网络故障诊断、实时流量监控、安全威胁排查等场景。资源共17个文件,压缩包大小2.24MB,内容包含可直接运行的exe可执行程序、核心的动态链接库dll、xml配置文件、png界面截图以及docx说明文档,同时附带SharpPcap依赖包和WinPcap驱动安装程序,方便快速搭建抓包环境。目前已有303人学习下载,适合想要入手网络编程或进行流量分析的读者。通过研究源码,可以系统掌握C#中调用SharpPcap完成网卡枚举、数据包捕获、协议解析等核心操作;而“直接使用”目录下的可执行文件则让非开发人员也能一键启动工具,配合截图与参考资料快速上手。整体而言,这是一份兼顾学习与实战的轻量级C#网络编程案例,既能作为初学者的实践教材,也能为有经验的开发者提供可直接复用的抓包思路。 做工业上位机这几年,我遇到过不少类似的场景:设备说明书上写着Modbus TCP报文应该长成某个样子,可真到联调那天,设备就是不按套路回包。想确认问题到底是出在我解析代码写错了,还是设备回包本来就不对,第一反应肯定是开Wireshark抓包。但Wireshark抓的是整个网卡的全部流量,几十上百条连接里翻一条目标报文,全靠肉眼找;更麻烦的是,抓包和上位机自测是两套独立流程,我没法在程序里自动比对抓到的字段、自动判断协议是否合规。后来我把SharpPcap的抓包逻辑直接集成进C#工具里,把“抓包、解析、断言”做成一条链,联调效率明显上来了。这篇文章就围绕这个C#网络抓包程序,把核心思路、关键源码、还有实测踩过的坑一次讲清楚,适合自己写上位机、做嵌入式联调、以及需要把抓包能力嵌进自动化测试工具的朋友参考。
1. 动手之前先想清楚:自己做抓包到底解决了什么
1.1 Wireshark替你做不了的三件事
Wireshark在单次问题分析上确实是神器,界面、解码器、过滤表达式都很成熟,可放到实际工程里,有三类场景它帮不上忙。
第一是自动化回归测试。固件发版前要批量验证协议字段是否符合规范,靠人肉开Wireshark一包一包看,既不现实又容易漏,必须用代码读包、自动断言,这只有程序化的抓包能力才能做到。
第二是私有协议解析。不少设备厂商在标准Modbus TCP之上做了扩展,字段偏移量、数据长度只有自己清楚。Wireshark不会为你的私有字段单独写解析器,但自己的C#工具里写几十行解析代码,就能把事务ID、功能码、寄存器地址这些关键信息直接抽出来。
第三是长时间运行的统计口径。做稳定性压测时,关心的往往不是某一条包,而是心跳超时次数、平均响应延迟、异常重连频率这类指标。把SharpPcap接进程序,统计直接在内存里完成,比事后打开pcap文件分析要轻松得多,还能在指标异常时实时告警。
1.2 这个项目适合什么人直接用
我做这个程序的直接原因是,现场有一台PLC设备走Modbus TCP,联调时需要对每一次请求-响应做耗时统计,还要自动校验事务ID、功能码、寄存器数量。如果你也是做上位机开发、嵌入式联调或者网络设备测试的C#工程师,下面这套代码基本可以抄到自己的项目里。它和Wireshark不是替代关系,更像是一个自动化补充:
| 对比项 | Wireshark | SharpPcap自制工具 |
|---|---|---|
| 单次临时抓包 | 非常方便 | 需要写代码 |
| 批量自动化测试 | 不支持 | 可集成到测试框架 |
| 私有协议解析 | 需要写插件 | C#代码直接解析 |
| 长时间指标统计 | 弱 | 强 |
| 部署到现场环境 | 安装包大、难集成 | 拷贝exe即可 |
2. SharpPcap的抓包原理:它比Socket低了半层
2.1 数据是怎么“绕过”协议栈被拿出来的
SharpPcap本质上是libpcap的.NET封装,在Windows下底层依赖Npcap(或老旧的WinPcap)驱动。libpcap这套架构的高明之处在于,它在网卡驱动层就把数据帧复制了一份到内核缓冲区,应用层再通过环形缓冲逐包读取,完全不干扰正常的协议栈收发。
可以这样理解:普通Socket就像在快递站等着取自己名下的包裹,而libpcap是站在传送带旁边,把所有经过的包裹都看一遍,只看不拿走。开启混杂模式,就是让网卡连“写给别人地址”的包裹也复制一份,这是抓包工具能捕获大规模流量的基础。很多人以为抓包是“半路拦截”,其实它从头到尾都没影响正常通信,只是多抄了一份副本,所以抓包程序崩溃也不会把网络搞挂。
2.2 内核缓冲区、BPF过滤器和回调的关系
一次完整的抓包流程是:网卡收到帧 → 驱动按BPF过滤器判断是否留用 → 留用的帧写入内核缓冲区 → 应用层从缓冲区读出来触发回调。注意,“是否留用”在内核态就决定了,所以过滤器写得越靠前,CPU负担越低。
SharpPcap常用的接口是OnPacketArrival事件,包到达缓冲区后自动触发,本质上是事件驱动模型,你不用自己轮询缓冲区,这对写UI程序特别友好。在6.x版本里,事件参数改成了PacketCapture结构,代码里直接调用e.GetPacket()拿到原始包。这套模型和老的WinPcap时代相比变化不小,如果你在网上搜到旧教程,看到构造参数对不上,基本就是版本差异导致的。
2.3 为什么不用C#的Raw Socket
有人会问,C#里直接建一个Socket,套上SocketOptionHeaderIncluded不也能收包吗?Windows下的原始套接字天生受限,而且经过系统的包在网络层、传输层会留下加工痕迹,Ethernet头的很多细节拿不全,回环包处理也麻烦。SharpPcap直接在链路层做原始拷贝,头一个字节都不落,后面自己写的解析代码才有完整依据。做工业通信调试,差别往往就藏在这些底层字节里。
3. 从装驱动到跑通Demo:环境准备与核心代码骨架
3.1 装驱动、装包:跑通之前的两件小事
写代码之前,先确认两件事。第一,安装Npcap驱动,安装时记得勾选“Support loopback traffic”选项,否则Windows本机进程之间的回环流量抓不到。第二,在NuGet里安装SharpPcap,它会自动把PacketDotNet作为依赖拉进来,后面解析报文全靠这个库。
提示:项目平台架构建议明确选x64或x86,不要图省事选AnyCPU。老版本SharpPcap对平台匹配很敏感,AnyCPU在64位系统上偶尔会蹦出BadImageFormatException,虽然新版适配好一些,但踩一次坑的代价远大于提前指定平台。
3.2 枚举网卡、打开设备的完整代码
下面是最小闭环代码,先跑通它,再谈解析:
using SharpPcap; using PacketDotNet; var devices = CaptureDeviceList.Instance; for (int i = 0; i < devices.Count; i++) { Console.WriteLine($"[{i}] {devices[i].Description}"); } Console.Write("请选择网卡编号: "); if (!int.TryParse(Console.ReadLine(), out var idx) || idx >= devices.Count) return; using var device = devices[idx]; device.OnPacketArrival += (sender, e) => { var packet = Packet.ParsePacket(e.GetPacket()); Console.WriteLine(packet.ToString()); }; device.Open(new DeviceConfiguration { Mode = DeviceModes.Promiscuous, ReadTimeout = 1000, Snaplen = 65535 }); device.Filter = "tcp port 502"; device.StartCapture(); Console.ReadLine(); device.StopCapture();如果你的SharpPcap版本较老,没有DeviceConfiguration这个类,就用老写法device.Open(DeviceModes.Promiscuous, 1000)直接打开,再通过device.KernelBufferSize属性调整内核缓冲区大小,效果一样。
3.3 事件回调和主动读取怎么选
SharpPcap提供两种取包方式,实际使用按场景选:
- 事件回调:OnPacketArrival事件自动触发,适合UI程序和大多数联调工具,代码简单,不容易漏包。
- 主动读取:在循环里调GetNextPacket逐个处理,适合后台服务、需要严格背压控制的场景。
我默认用事件回调,因为上位机工具通常还要同时刷新界面、响应用户输入,事件模型不会阻塞主逻辑。只有做高吞吐的协议转换服务时,我才换成主动读取,以便在流量高峰时自己决定丢弃哪些低优先级包。
3.4 BPF过滤器语法:最容易踩的隐性坑
设置过滤器用的是BPF(Berkeley Packet Filter)语法,不是Wireshark界面里那套显示过滤语法。很多新手把tcp.port == 502直接填进Filter属性,结果一个包都抓不到。正确的是raw版的tcp port 502,中间没有点号也没有双等号。这俩一个是捕获过滤器、一个是显示过滤器,作用阶段完全不同,写混了要么抓不到包,要么程序直接报语法错误。
| 想抓的流量 | 正确填写的Filter值 |
|---|---|
| 所有Modbus TCP报文 | tcp port 502 |
| 某个IP的全部通信 | host 192.168.1.100 |
| 排除SSH的其他TCP | tcp and not port 22 |
| 一段端口范围 | portrange 2000-5000 |
| 只想看ARP | arp |
| UDP广播 | udp and broadcast |
4. 把字节变成看得懂的报文:数据包解析实战
4.1 PacketDotNet的解析链
SharpPcap拿到的原始数据只是字节数组,直接看是没法读的。PacketDotNet按协议栈的顺序,把Ethernet帧、IPv4/IPv6包、TCP/UDP段一层层剥开。解析链的代码模式很固定,而且能直接在官方包类型上做类型判断:
void OnPacketArrival(object sender, PacketCapture e) { var packet = Packet.ParsePacket(e.GetPacket()); if (packet is not EthernetPacket eth) return; if (eth.PayloadPacket is not IPv4Packet ip) return; if (ip.PayloadPacket is not TcpPacket tcp) return; Console.WriteLine($"{ip.SourceAddress}:{tcp.SourcePort} -> " + $"{ip.DestinationAddress}:{tcp.DestinationPort}"); }注意这里用is not做类型守卫,顺手避开空引用。很多教程喜欢把每层都new一个解析对象,其实Packet.ParsePacket已经把嵌套关系建好了,直接用PayloadPacket逐层取就行,自己重新拼一堆解析类反而容易出错,维护成本也高。
4.2 解析Modbus TCP负载的例子
拿到TCP报文后,载荷在tcp.PayloadData里。以Modbus TCP为例,前7个字节是MBAP头,包括事务ID(2字节)、协议ID(2字节)、长度(2字节)、单元ID(1字节),第8字节开始才是功能码。这段解析逻辑在工业联调里非常常用:
var payload = tcp.PayloadData; if (payload.Length < 8) return; var transId = (ushort)((payload[0] << 8) | payload[1]); var funcCode = payload[7]; if (funcCode == 0x03) { var startAddr = (ushort)((payload[8] << 8) | payload[9]); var quantity = (ushort)((payload[10] << 8) | payload[11]); Console.WriteLine($"事务ID=0x{transId:X4} 功能码=03(读保持寄存器) " + $"起始地址={startAddr} 数量={quantity}"); }大端字节序是Modbus的老规矩,高位在前,所以payload[0] << 8再或上payload[1],拼出来的才是正确的事务ID。凡是碰工业协议,字节序问题一定要在解析层统一处理,否则后面每个功能码都要踩一遍。另外,事务ID在请求和响应里必须一致,这个字段是配对请求响应的关键,联调时很多“设备没反应”的问题,其实都是事务ID对不上导致的。
4.3 顺手把原始包写成pcap文件
调试过程中,除了实时打印,还经常要把原始包落盘,方便后续用Wireshark离线分析。SharpPcap提供了CaptureFileWriterDevice,用法很简单:
var writer = new CaptureFileWriterDevice(@"D:\capture.pcap", FileMode.Create); ... writer.Write(e.GetPacket());我的经验是:程序里同时开一条抓包事件通道(实时解析、统计)和一条文件通道(原样写盘),两边互不干扰。这样既不影响实时判断,也留了事后细查的素材。很多问题当时看不出规律,第二天导进Wireshark慢慢翻反而能发现异常。
5. 实测踩坑记录:跑不通往往就卡在这几处
5.1 权限、平台、驱动:先冒出来的三个坎
第一个坑是权限。抓包必须在管理员模式下运行,否则打开设备时会抛权限异常,这个没有捷径,VS里右键项目选择“以管理员身份运行”,或者发布后用管理员身份启动exe。
第二个坑是平台位数。Npcap驱动虽然同时提供32位和64位的wpcap.dll,但如果项目平台不匹配,或者机器上残留了老的WinPcap,打开设备时就会出现各种莫名其妙的异常。新装机建议直接上Npcap,别在WinPcap上纠结。
第三个坑是驱动残留。一台机器上如果同时装过WinPcap和Npcap,有时Npcap会装不上,或者抓包明显变慢。我遇到过一次,把WinPcap卸载干净、重装Npcap之后才恢复正常。这套组合拳打完,90%的“打开设备失败”都能解决。
5.2 本机回环流量抓不到是怎么回事
用默认网卡列表抓本机两个进程之间的通信,经常一个包都看不到。Windows上回环流量要靠Npcap专用的Loopback Adapter才能抓,装Npcap时没勾loopback选项,或者设备列表里没选这个适配器,就会一直空着。判断方法很简单:抓包前先在设备列表里找Description带“Adapter for loopback traffic capture”的项,选中它再抓。这个小坑很容易让人怀疑人生,其实是选错了网卡。
5.3 高流量下丢包:内核缓冲区要舍得给
流量一大就开始连续漏包,多半是内核缓冲区不够。SharpPcap的默认缓冲区在Windows上通常只有1MB,跑Modbus这种低频协议够用,但同一台电脑上如果还跑着文件传输、视频流之类的大流量,缓冲区瞬间就会被冲掉。我一般会调到32MB甚至更大:
device.Open(new DeviceConfiguration { Mode = DeviceModes.Promiscuous, ReadTimeout = 1000, BufferSize = 32 * 1024 * 1024 });注意,缓冲区不是越大越好,太大会拖慢读取延迟;抓包和业务并发时,建议先做一轮性能测试压出合适的值。另一个经验是,过滤器一定要在Open之后、StartCapture之前设置,让内核在入口就过滤掉不关心的包,比全部收上来再丢弃效率高几个数量级。
5.4 多网卡机器上选错网卡
笔记本、工控机通常有有线、无线、虚拟网卡好几块,枚举出来的列表不会直接告诉你哪块对应哪个业务。我的习惯是打印每块网卡的IPv4地址,直接按IP选网卡,比只看Description猜靠谱得多:
var ipv4 = device.Addresses.FirstOrDefault(a => a.Addr != null && a.Addr.type == Sockaddr.AddressTypes.AF_INET_AF_INET6); Console.WriteLine($"[{i}] {device.Description} - {ipv4?.Addr.ipAddress}");不同版本的SharpPcap里地址类型枚举名略有差异,如果编译不过,把AF_INET_AF_INET6换成对应版本的常量就行。选错网卡几乎是每个第一次用SharpPcap的人都会遇到的错误,列表里那个“Wireless”和你现场实际连的有线网口,抓出来的结果天差地别。
6. 源码拿到手之后:模块怎么拆、往哪扩展
6.1 项目里的模块划分参考
写抓包程序最忌讳把所有逻辑堆在一个文件里。我整理的源码是按职责拆的,结构大致如下:
PacketCapture/ ├── CaptureService.cs # 网卡枚举、打开、过滤、启停 ├── Parsers/ │ ├── EthernetParser.cs # 链路层解析 │ ├── ModbusTcpParser.cs # Modbus TCP载荷解析 │ └── HexDump.cs # 十六进制打印 ├── Writers/ │ └── PcapFileWriter.cs # pcap落盘 ├── Statistics.cs # 响应耗时、异常计数 └── Program.cs # 入口,串起整个流程CaptureService对外只暴露Start、Stop和OnPacketParsed事件,上层界面完全不用知道底层是SharpPcap还是别的驱动。这样哪天要换抓包实现,界面代码一行都不用改,只是替换一个服务类的事。
6.2 再往前一步可以做什么
这套代码的扩展空间其实很大。比如把解析层做成插件式,每个私有协议一个独立dll,按端口号分发;或者把实时统计结果输出成CSV,直接作为测试报告附件;再或者接到数据库里,把现场设备的通信质量长期记录下来做趋势分析。我在实际使用中最受益的一个改动,是把请求-响应配对逻辑加上时间戳,联调时一眼就能看出是设备回包慢,还是自己发送端的调度慢了。
这个项目里我唯一想强调的经验是:抓包工具不要指望一步到位。我自己的习惯是先把最小闭环跑通——枚举网卡、打开设备、打印出第一个包,确认环境和驱动都没问题,再往上加协议解析和统计功能。很多朋友拿到源码第一件事就是打开整个工程编译,结果版本不同、环境不同,卡在底层半天出不来。最小闭环跑通之后,每一步改动都有明确的验证点,后面反而快。真到了要维护几百个设备通信协议的时候,你会感谢当初把这个工具做得够扎实的自己。
本文还有配套的精品资源,点击获取