资讯动态

C#网络抓包程序源码解析:基于SharpPcap的实战指南

发布时间:2026/10/20 6:57:54 来源:尧图企业网站定制
简介这是一份面向C#开发者与网络编程学习者的网络抓包实践资源基于开源SharpPcap库实现数据包捕获与解析可用于网络诊断、流量监控与安全分析等场景适合希望掌握套接字编程与网络协议解析的中级学习者。压缩包共17个文件约2.24MB包含png程序截图、exe可执行程序、dll动态库、xml文档、config配置及docx参考资料等兼顾直接运行与源码研读两种使用方式。目前已有307人学习下载。资源同时提供程序源码与编译好的可执行文件读者既能直接运行工具完成抓包操作也能通过阅读源码理解SharpPcap与PacketDotNet的调用方式、数据包解析流程及WinPcap环境配置思路配套截图与说明文档可辅助快速上手是提升C#网络编程能力的实用案例。1. 从一次抓包需求说起这个 C# 项目能帮你省下多少折腾有次排查一个上位机与采集板之间的通信问题日志里全是超时但设备厂商坚持说数据发出去了。当时手边没有现成的抓包工具能嵌进自己的诊断程序里临时装个通用抓包软件又没法把结果直接喂给业务代码做二次判断。后来翻到一个用 C# 写的 SharpPcap 抓包程序源码包才意识到这类需求其实完全可以自己掌控——它就是一个基于 SharpPcap 库的 C# 网络抓包程序附带完整源码能直接编译运行也能把抓包能力嵌进你自己的 .NET 项目里。这个项目适合两类人一类是想学网络协议分析、需要看得到每一层报文结构的开发者另一类是手里有 C# 上位机、工控软件或运维工具想加一个「抓包诊断」按钮却不想引入重型依赖的人。它不追求替代通用抓包软件而是给你一份能读、能改、能集成的源码底子。下面按「能干什么 → 怎么跑 → 怎么改」的顺序拆开讲。2. 项目定位与技术选型为什么是 C# 加 SharpPcap2.1 这个抓包程序到底解决什么问题从标题和关键词能看出这是一个 C# 网络抓包程序核心依赖是 SharpPcap。SharpPcap 是 .NET 平台对 libpcap/WinPcap/Npcap 的封装也就是说它本身不实现抓包驱动而是调用系统底层抓包库。这个定位决定了它的能力边界能抓到经过网卡的原始帧能按 BPF 过滤表达式筛选能把包解析成以太网、IP、TCP/UDP 等层次结构但它不是协议解析全家桶应用层协议要自己往上叠。项目正文为空说明没有额外的功能描述所以功能范围就以「SharpPcap 抓包程序」这个核心来推。常见做法是枚举网卡、选择设备、设置过滤规则、开始捕获、回调里解析并展示、支持停止和保存。这套流程在 SharpPcap 的示例里是标准骨架这个源码包大概率也是围绕它展开的。对读者来说价值不在于它多复杂而在于它把「抓包」这件事从命令行工具变成了可嵌入的 C# 代码。关键词里「c# 网络 网络 软件/插件」也印证了这一点它既可以当独立软件跑也可以当插件式模块塞进更大的网络诊断工具里。如果你正在做网络监控、协议调试、工控通信排查这份源码能省掉你从零封装 SharpPcap 的时间。2.2 技术栈拆解SharpPcap 在 .NET 里的位置SharpPcap 的工作方式分两层上层是 C# 的 API下层是系统抓包库。在 Windows 上通常依赖 NpcapWinPcap 的后续在 Linux 上依赖 libpcap。这意味着程序要跑起来目标机器上必须装好对应的底层驱动或库否则 SharpPcap 初始化时会直接报找不到设备。技术栈大致是层次组件作用语言运行时.NET / .NET Framework承载 C# 代码抓包封装SharpPcap提供设备枚举、捕获、过滤、包解析 API底层驱动Npcap / libpcap真正从网卡取数据包解析PacketDotNet常见搭配把原始字节解析成协议对象PacketDotNet 不是标题里写的但 SharpPcap 的官方示例和绝大多数实战项目都会搭配它来做协议解析否则你拿到的只是一堆字节数组。我一般会把它一起引入这样解析 IP、TCP、UDP 头字段时不用自己移位运算。提示SharpPcap 和 PacketDotNet 的版本要匹配版本错位时常见现象是编译通过但运行时解析方法找不到或者包类型转换失败。2.3 目录结构预判与关键文件源码包没有给出目录清单但按这类项目的常见组织方式结构通常如下以实际解压为准SharpPcapCapture/ ├── SharpPcapCapture.sln # 解决方案文件 ├── SharpPcapCapture/ │ ├── Program.cs # 入口控制台或窗体启动 │ ├── CaptureManager.cs # 抓包核心逻辑封装 │ ├── PacketParser.cs # 包解析与展示 │ ├── DeviceSelector.cs # 网卡枚举与选择 │ ├── app.config / *.csproj # 依赖与配置 │ └── Properties/ └── packages/ 或 NuGet 引用 # SharpPcap、PacketDotNet如果你拿到的是 WinForms 版本还会多出 MainForm.cs、MainForm.Designer.cs 这类界面文件。判断方式很简单看入口是 Console.ReadLine 还是 Application.Run。不管哪种抓包逻辑都会集中在一个管理类里改的时候优先找那个类。3. 跑起来环境准备与第一个可捕获的包3.1 环境准备Npcap 与项目依赖在 Windows 上跑这个项目第一步不是打开 Visual Studio而是确认底层抓包库装好了。SharpPcap 本身是托管库但它调用的 wpcap.dll 来自 Npcap。安装 Npcap 时有一个选项叫「Install Npcap in WinPcap API-compatible Mode」建议勾上否则部分老代码调用 WinPcap 接口会失败。安装完成后用命令行验证一下# 查看 Npcap 是否安装成功以及 wpcap.dll 是否存在 where wpcap.dll # 正常应输出类似 C:\Windows\System32\Npcap\wpcap.dll如果这条命令没有输出说明底层库没装好后面代码里CaptureDeviceList.Instance会返回空列表程序看起来「没报错但抓不到任何网卡」。这是最常见的翻车点之一。项目依赖通过 NuGet 还原常见包是 SharpPcap 和 PacketDotNet。用命令行还原# 在解决方案目录下执行还原所有 NuGet 包 dotnet restore SharpPcapCapture.sln # 如果项目是旧式 .NET Framework用 nuget restore nuget restore SharpPcapCapture.sln参数说明restore会根据 csproj 或 packages.config 里的引用去拉包网络不通时会卡在这里表现为「找不到 SharpPcap 命名空间」。解决方式是配置可用的 NuGet 源或者手动把包放进 packages 目录。3.2 枚举网卡并选择捕获设备抓包程序的第一步永远是列出网卡。SharpPcap 用CaptureDeviceList.Instance拿到所有设备每个设备有 Name、Description、Addresses 等属性。下面这段是常见写法using SharpPcap; using SharpPcap.LibPcap; // 获取本机所有可抓包的网卡设备 var devices CaptureDeviceList.Instance; if (devices.Count 0) { Console.WriteLine(没有找到任何网卡请检查 Npcap/libpcap 是否安装。); return; } // 打印网卡列表让用户选择 for (int i 0; i devices.Count; i) { var dev devices[i]; // Description 在 Windows 上通常带网卡型号Linux 上可能是接口名 Console.WriteLine($[{i}] {dev.Name} - {dev.Description}); } Console.Write(请输入要抓包的网卡编号: ); int index int.Parse(Console.ReadLine()); ILiveDevice selectedDevice devices[index];逻辑说明CaptureDeviceList.Instance是静态属性每次访问会重新枚举设备热插拔后建议重新获取。ILiveDevice是 SharpPcap 较新版本的接口类型老版本里是ICaptureDevice如果你编译时报「找不到 ILiveDevice」说明包版本较旧换成ICaptureDevice即可。参数方面dev.Name是系统内部标识dev.Description是给人看的选择时用编号最稳妥。注意如果列表里出现多个相似网卡优先选有 IP 地址的那个否则可能抓到的是虚拟网卡或未连接的口什么都抓不到。3.3 打开设备、设置过滤器并开始捕获选好设备后打开设备并挂上 BPF 过滤表达式能大幅减少无关包。下面这段是核心捕获循环// 打开设备ReadTimeout 表示读超时毫秒数超时后会触发回调 selectedDevice.Open(new DeviceConfiguration { Mode DeviceModes.Promiscuous, // 混杂模式能抓到经过网卡的所有帧 ReadTimeout 1000 // 1000ms 超时避免回调一直不触发 }); // 只抓 TCP 80 端口的包BPF 语法和通用抓包工具一致 selectedDevice.Filter tcp port 80; // 注册包到达回调 selectedDevice.OnPacketArrival (sender, e) { var rawPacket e.GetPacket(); // 这里先打印长度解析放到下一节 Console.WriteLine($抓到包长度 {rawPacket.Data.Length} 字节); }; selectedDevice.StartCapture(); Console.WriteLine(开始抓包按回车停止...); Console.ReadLine(); selectedDevice.StopCapture(); selectedDevice.Close();逻辑说明Open的DeviceConfiguration里Mode设为混杂模式后网卡会接收目的地址不是本机的帧这在排查广播、镜像口场景时有用但在交换网络里不一定能看到别人的单播流量。Filter是 BPF 表达式写错会抛异常常见错误是端口写成port 80正确写法是tcp port 80。OnPacketArrival是事件回调回调里不要做耗时操作否则会丢包解析和写盘建议放到队列里异步处理。参数怎么改ReadTimeout调小会让回调更频繁但 CPU 占用上升Filter留空表示抓所有包流量大时容易把内存打满。我一般会先设一个窄过滤确认链路通了再放宽。4. 核心模块拆解从原始字节到可读协议字段4.1 包解析用 PacketDotNet 拆开每一层抓到原始包只是第一步真正有用的是把字节解析成协议字段。PacketDotNet 提供Packet.ParsePacket传入链路层类型和原始字节返回一个 Packet 对象然后逐层取 EthernetPacket、IpPacket、TcpPacket 等。using PacketDotNet; // 在 OnPacketArrival 回调里解析 var rawPacket e.GetPacket(); // LinkLayerType 来自设备常见是以太网 Packet packet Packet.ParsePacket(rawPacket.LinkLayerType, rawPacket.Data); // 逐层提取注意每层都可能为 null var ethernet packet.ExtractEthernetPacket(); var ip packet.ExtractIPPacket(); var tcp packet.ExtractTcpPacket(); if (ip ! null) { Console.WriteLine($源IP {ip.SourceAddress} - 目的IP {ip.DestinationAddress}); } if (tcp ! null) { Console.WriteLine($源端口 {tcp.SourcePort} - 目的端口 {tcp.DestinationPort}); Console.WriteLine($标志位 {tcp.Flags}); }逻辑说明ExtractT会从当前包及其负载里找指定类型的层找不到返回 null所以每层都要判空。ip.SourceAddress返回的是System.Net.IPAddress直接 ToString 就是点分十进制。tcp.Flags是枚举能看出 SYN、ACK、FIN 等排查连接建立失败时特别有用。参数说明rawPacket.LinkLayerType决定解析起点如果是以太网就是 EthernetPacket如果是 Raw IP 则没有以太网头。抓 Loopback 接口时链路类型可能不同解析代码要兼容否则ExtractEthernetPacket()返回 null后面全空。4.2 过滤表达式BPF 语法与常见写法BPF 过滤器是抓包效率的关键。写得好回调里只处理你关心的包写得差等于把整条链路流量都拉进托管内存。常见表达式如下需求BPF 表达式抓指定 IPhost 192.168.1.10抓指定端口tcp port 8080抓某网段net 192.168.1.0/24排除某端口not port 22组合条件tcp and host 192.168.1.10 and port 443设置方式就是selectedDevice.Filter ...必须在StartCapture之前设置。如果表达式语法错误SharpPcap 会抛PcapException消息里通常带「syntax error」。排查时先把表达式简化到tcp确认能抓到再逐步加条件。提示过滤表达式里的 IP 和端口是网络字节序层面的匹配不涉及应用层内容。想按 HTTP 内容过滤BPF 做不到得在回调里自己判断负载字符串。4.3 捕获结果的展示与保存抓到并解析后通常要展示或落盘。展示可以走控制台也可以绑到 DataGridView。保存成 pcap 文件是常见需求SharpPcap 提供CaptureFileWriterDevice// 创建 pcap 写入设备路径和链路类型要对 var writer new CaptureFileWriterDevice(capture.pcap, selectedDevice.LinkType); selectedDevice.OnPacketArrival (sender, e) { var rawPacket e.GetPacket(); // 原样写入保证文件能被通用抓包工具打开 writer.Write(rawPacket); }; selectedDevice.StartCapture();逻辑说明CaptureFileWriterDevice的第二个参数是链路类型必须和抓包设备一致否则写出的文件在通用工具里打开会提示格式错误。writer.Write接收 RawCapture直接写原始字节不做解析这样性能最好。停止抓包后要调用writer.Close()否则文件头可能不完整。参数说明文件路径建议用绝对路径相对路径在服务或计划任务里容易写到意外目录。如果抓包量大写盘会成为瓶颈常见做法是加一个BlockingCollection做生产者消费者回调只入队单独线程写文件。5. 避坑与常见问题抓不到包时先查这几处5.1 设备列表为空现象程序启动后CaptureDeviceList.Instance.Count为 0或者打印「没有找到任何网卡」。原因底层抓包库没装或没装对。Windows 上缺 NpcapLinux 上缺 libpcap或者装了但当前用户没有权限访问。解决Windows 重新安装 Npcap 并勾选兼容模式Linux 用apt install libpcap-dev安装开发库运行时如果提示权限不足用setcap cap_net_raw,cap_net_admineip 你的程序授权而不是简单用 root 跑。5.2 能抓到包但解析全为 null现象回调触发了长度也打印了但ExtractIPPacket()返回 null。原因链路层类型判断错或者过滤到了非 IP 包比如 ARP。也可能是Packet.ParsePacket的第一个参数传错。解决先打印rawPacket.LinkLayerType确认类型以太网用 EthernetPacket 解析。对 ARP 包做判空跳过。如果抓的是 Loopback链路类型可能是 Raw需要换解析入口。5.3 回调里做耗时操作导致丢包现象抓包数量明显少于预期或者程序越跑越卡。原因OnPacketArrival在抓包线程上同步执行回调里做字符串拼接、写数据库、更新界面都会阻塞抓包。解决回调里只做入队用ConcurrentQueue或BlockingCollection把 RawCapture 交给后台线程处理。界面更新用Invoke切回 UI 线程且做批量刷新不要每包一次。5.4 过滤器设置后一个包都抓不到现象不设过滤器能抓到设了之后回调完全不触发。原因BPF 表达式写错或者过滤条件与当前流量不符。比如在只有 UDP 流量的链路上写tcp port 80。解决先把过滤器清空确认链路通再逐步加条件。用通用抓包工具验证同一表达式是否能抓到包排除表达式本身的问题。注意port 80和tcp port 80的区别前者会匹配 UDP 80。5.5 编译报找不到 SharpPcap 命名空间现象using SharpPcap;报错或者ILiveDevice类型不存在。原因NuGet 包没还原或者包版本与代码不匹配。解决先dotnet restore确认 packages 目录或全局缓存里有 SharpPcap。如果代码用的是新接口而包是旧版要么升级包要么把ILiveDevice改回ICaptureDevice。版本升级时注意 PacketDotNet 也要同步升两者 API 有联动。6. 二次开发把抓包能力嵌进自己的工具里6.1 封装成可复用的抓包服务直接改 Program.cs 只能跑一个独立程序想嵌进现有项目建议把抓包逻辑抽成一个服务类对外暴露开始、停止、事件三个口子。我一般会这样组织public class CaptureService { private ILiveDevice _device; private CancellationTokenSource _cts; // 包到达事件外部订阅后自行处理避免服务类耦合界面 public event ActionRawCapture PacketArrived; public void Start(string deviceName, string filter) { _device CaptureDeviceList.Instance .FirstOrDefault(d d.Name deviceName); if (_device null) throw new ArgumentException(设备不存在); _device.Open(new DeviceConfiguration { ReadTimeout 500 }); if (!string.IsNullOrEmpty(filter)) _device.Filter filter; _device.OnPacketArrival (s, e) PacketArrived?.Invoke(e.GetPacket()); _device.StartCapture(); } public void Stop() { _device?.StopCapture(); _device?.Close(); } }逻辑说明把设备查找、打开、过滤、事件转发收在一个类里外部只关心PacketArrived。这样无论是控制台、WinForms 还是 ASP.NET 后台服务都能复用同一套抓包逻辑。PacketArrived用 Action 而不是直接暴露 SharpPcap 事件是为了隔离依赖将来换库时改动面小。参数说明deviceName用设备 Name 而不是编号避免界面刷新后编号错位。filter允许为空空表示抓全部。ReadTimeout设 500ms 是为了让停止操作更跟手太大时 StopCapture 会等当前读超时。6.2 加一个环形缓冲避免内存无限增长抓包程序跑久了内存飙升多半是包对象没释放。SharpPcap 的 RawCapture 本身不大但如果解析后把 Packet 对象存进 List很快就会撑爆。常见做法是环形缓冲只保留最近 N 条// 固定容量队列超出后自动丢弃最旧的包 private readonly ConcurrentQueuePacketInfo _recent new(); private const int MaxKeep 5000; public void OnPacket(RawCapture raw) { var info ParseToInfo(raw); // 解析成轻量结构别存 Packet 对象 _recent.Enqueue(info); while (_recent.Count MaxKeep) _recent.TryDequeue(out _); }逻辑说明PacketInfo是自己定义的轻量类只存时间、源、目的、端口、长度等字段不持有 PacketDotNet 的对象引用。MaxKeep按内存和界面需求调5000 条对诊断够用。这样即使抓一整天内存也稳定在一个水位。6.3 验证改造是否生效的笨办法改完抓包服务后别急着接业务先用一个最小验证抓本机回环上的一个已知请求。比如本地起一个 HTTP 服务用tcp port 你的端口过滤发一次请求看回调是否触发、解析出的源目的端口是否对得上。这个办法能同时验证设备选择、过滤器、解析链路三处比直接上生产环境排查快得多。我踩过的坑是改完过滤表达式后忘了重新Open设备结果新过滤器没生效还以为是解析代码写错了查了半天。从那以后我每次改过滤器都强制走一遍「停止 → 重新打开 → 设置过滤 → 开始」的完整流程不图省事直接赋值。希望这份源码和上面的拆解能帮到你拿到包后先跑通最小链路再往上叠自己的业务逻辑。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑