☰
基于WinPcap的UDP发包实战:亲手构造并发送原始数据包
2026/10/19 5:37:23 网站建设 项目流程

简介:这是一份基于WinPcap库实现的UDP发包程序源码包,面向网络编程学习者、协议研究人员以及需要底层发包能力的安全测试人员。它重点演示如何绕过常规Socket接口,直接通过WinPcap与网卡交互来构造并发送UDP数据报,可服务于网络协议实验、丢包测试、实时数据传输等场景。包体共349个文件,约5.94MB,包含cpp/h核心源码、lib静态库、sln/vcxproj工程文件,同时附带js/html/css等页面资源及jpg/png/gif图片,便于查看演示效果或作为辅助界面。压缩包内还有libwpcap.a、libpacket.a等链接库,读者可在Windows环境直接编译运行WinPcap_UDP_Test工程,跟踪数据包从初始化、设备选择到发送的完整流程。目前已有188人学习,适合具备一定C/C++基础、希望深入理解WinPcap工作机制的开发者;通过修改和扩展源码,可快速定制自己的UDP发包、抓包或网络监控工具。

1. 说到网络发包,新手最先卡住的往往是这一步

很多刚接触网络编程的朋友,第一反应是“我不就想往网络里发点东西吗,怎么还要学一堆协议?”但你立刻会发现,系统根本不让你随便发——你写一个普通程序,用现成的接口往外发数据,系统会帮你把很多事情都做掉,这听起来很方便,可一旦你想控制“发出的报文长什么样”,就完全插不上手。今天要讲的「基于WinPcap实现的UDP发包程序」,正好就是打破这个黑匣子的一把钥匙:它绕开系统的层层封装,让你能亲手构造一个 UDP 数据包,然后从网卡直接发出去。这份资源适合谁?适合刚学网络协议、想看看真实报文长什么样,又不想一上来就啃几百页协议文档的初学者。你不需要很深的编程基础,只要会一点 C 语言,今天就能跑出第一个包。

2. 先懂两个概念:UDP 为什么快,WinPcap 为什么特殊

开始写代码之前,有两个词必须弄明白,一个是 UDP,一个是 WinPcap。如果你以前听说过 TCP、UDP,但总觉得它们是“考试要背的东西”,这一章我用人话把它们讲清楚。

2.1 UDP:不打电话,直接塞纸条

UDP 的全称是 User Datagram Protocol,用户数据报协议。怎么理解它?你把 TCP 想象成“打电话”:要先拨号、对方接听、你说一句对方回一句、最后说再见,全程有确认有回应。而 UDP 像是“往对方门缝里塞纸条”:你写好了塞进去就完事,对方收没收到、收到了是不是完整的,你不知道,也不负责。

这种“不负责任”的设计,换来的是两个实实在在的好处:低延迟和高效率。因为不需要握手、不需要等确认、不需要维护连接状态,数据从你手里到网卡几乎是直出。所以凡是“慢一点就体验崩坏”的场景,比如实时对战游戏、视频通话、语音、DNS 查询,底层都是 UDP。

UDP 的报文头也非常简单,固定 8 个字节,只有四段信息:

字段长度作用
源端口2 字节发送方端口,可填 0 表示不指定
目的端口2 字节接收方端口,对方程序靠它识别
报文长度2 字节UDP 头 + 数据的长度
校验和2 字节可选校验,填 0 表示不校验

你观察一下,这四段里没有任何“连接状态”“序号”“确认号”之类的东西。这意味着什么?意味着你手动构造一个 UDP 包非常简单——把这几段填上,再塞进 IP 层交给网卡,就完成了。这给手工发包程序提供了极大的便利。

2.2 WinPcap:为什么普通程序抢不到“发原始包”的资格

你可能会问:既然 UDP 构造这么简单,我直接写个程序发不就行了?问题出在:操作系统不让你直接碰网卡。

正常情况下,你的程序要发网络数据,必须走系统提供的 Socket 接口。系统会把你的数据做一次“加工”,再加上它自己生成的各种头部,最后才交给网卡。这个过程你无法干预。打个比方,你写好一封信,系统就像快递公司,不仅帮你封装,还会偷偷替你在信封上多写几行字。你大部分时候不需要关心这些字,但如果你就是想让信封“完全按我的格式来”,就做不到。

WinPcap 的作用,恰好是把“快递公司”绕开。它是一套运行在 Windows 上的网络抓包与发包库,最核心的特点是:它能在内核态直接和网卡驱动通信。也就是说,它可以把网卡收到的原始帧完整交给你,也能把你构造好的原始帧原封不动地发出去,中间不经过系统的协议栈加工。

这一下就解锁了很多玩法:你可以自己填以太网头、自己写 IP 头、自己构造 TCP/UDP 头,做出一个“系统完全不知道是谁发的”的数据包。网络监控、安全测试、协议分析,这些工具能实现,靠的正是这样一个能触碰底层的库。

2.3 为什么不直接用 Socket 发 UDP,非要绕这一圈

我估计你会有一个很自然的疑问:Socket 发 UDP 也很快啊,几行代码就搞定了,我为什么要用 WinPcap?

先看普通 Socket 发 UDP 的代码,确实简洁:

// 伪代码,示意 Socket 发 UDP 的写法 int sock = socket(AF_INET, SOCK_DGRAM, 0); // 创建 UDP 套接字 sendto(sock, buf, len, 0, (struct sockaddr*)&addr, sizeof(addr)); // 发送

这段代码看起来轻松,但它有几个你控制不了的点。第一,你无法修改源 IP 地址,系统只会填本机网卡的 IP;第二,你无法自定义以太网层的内容,比如目的 MAC 地址,系统会自己查询 ARP 帮你填;第三,如果你想发一个“IP 头里有特殊标志”的包,或者想同时在多张网卡上发送一模一样的原始帧,Socket 接口完全无能为力。

而 WinPcap 的 pcap_sendpacket 这个函数,收的是“已经完整构造好的整个数据帧”——从以太网头到 UDP 数据,所有字节由你一手填完。它不管你填的是什么,拿来就发。这听起来很麻烦,但对初学者的价值恰好很大:你被迫亲手填一遍 UDP 头、IP 头、以太网头,填完之后,你对“一个网络包到底由哪几层构成”会有非常直观的体感,这比背十遍 TCP/IP 分层模型都管用。

提示:如果你只是想快速发个聊天消息给自己的程序,用 Socket 就够了,不用绕 WinPcap。但如果你想知道“网络包的每一字节从哪来”,WinPcap 才是对的选择。

3. 拿到源码包先做三件事:解压、看结构、装环境

光说不练没用。这一章带你把这份源码包真正用起来。我第一次拿到类似压缩包的时候,一解压看到一堆文件直接懵了,不知道该点哪个、不该动哪个。所以我把步骤拆成三步,你跟着做就行。

3.1 压缩包里到底有哪些文件,先分清主次

解压后你会看到类似下面这样的文件清单:

文件名大致作用
libwpcap.aWinPcap 的静态库文件,程序要链接的网络接口库
libpacket.aWinPcap 底层驱动的辅助库,链接时也会用到
WinPcap_UDP_Test.aps工程资源文件,一般是 Visual Studio 项目生成的辅助文件
一串数字命名的文件通常是一些运行记录、截图或输出文件,不影响主程序

初学者最容易犯的错,是看到一个文件就去双击。这里明确几点:libwpcap.a 和 libpacket.a 你不用打开,它们是给编译器在链接阶段用的;真正要读的,是里面的 .c 或 .cpp 源码文件,以及可能的 README 说明文档。

这个压缩包把两个库文件也一并收录了,这是很贴心的做法。为什么?因为 WinPcap 开发包官方下载后,默认库文件是 .lib 格式,配合 Visual Studio 使用。如果你用的是 MinGW 的 gcc,需要 .a 格式的库。这份资源直接准备好了,等于帮你绕开了“库格式不匹配导致链接报错”这个新手常见坎。

3.2 装好开发环境:WinPcap 驱动和编译器缺一不可

先明确一个重要区别:WinPcap 分成两个部分。第一部分是运行时驱动,也就是你电脑上要装的 WinPcap 软件本身,它负责让你能抓到网卡的数据;第二部分是开发包,也就是 WpdPack,里面是头文件 wpcap.h 和库文件,让你的代码能调用 WinPcap 的函数。

有些初学者只装了运行时驱动,就跑去编译代码,结果报了一堆“找不到 wpcap.h”,以为是代码错了。其实不是,八成是缺了开发包。所以第二步做的事情是:

  1. 安装 WinPcap 运行时驱动。装完不用管它,它在后台工作。
  2. 下载 WpdPack 开发包,解压后得到 Include(头文件目录)和 Lib(库文件目录)两个文件夹。
  3. 把刚才压缩包里自带的 libwpcap.a、libpacket.a 复制到 WpdPack/Lib 里,防止编译器找不到。

如果你用的是 Visual Studio,需要把 Include 和 Lib 路径填到工程的“附加包含目录”和“附加库目录”里。如果你和我一样更喜欢用 gcc 命令行,路径更简单,编译时用 -I 和 -L 参数指一下就行。

3.3 验证环境:用一条命令确认编译器能找到库

环境配没配好,最好的验证方式就是编译一个空白程序。别急着写发包逻辑,先建一个新文件,里面只写一个空 main 函数,然后用下面这条命令链接 WinPcap 库:

gcc -o empty_test empty_test.c -lwpcap -lpacket -lws2_32

命令拆开解释:-o 指定输出文件名;-lwpcap 告诉编译器链接 libwpcap.a;-lpacket 链接 libpacket.a;-lws2_32 是因为 WinPcap 内部依赖 Windows 的 Socket 相关函数,必须补上,否则最后一步链接会报 “undefined reference to …” 之类的错误。

如果这条命令没有任何报错,恭喜,你的环境已经通了。如果报了“找不到 -lwpcap”,说明编译器没在搜索路径里找到库文件,用 -L 参数把库的路径指给它。这个验证小技巧,可以帮你在后续调试中省下很多时间。

4. 跑通第一个例子:亲手发送一个 UDP 包

环境准备好之后,最激动人心的一步来了:真正发一个 UDP 包。这一章我先把程序的主流程讲清楚,再给你一份可以直接跑的完整代码,最后告诉你怎么确认“它真的发出去了”。

4.1 先看主流程:任何 WinPcap 发包程序都只有四步

不管源码包里的工程多复杂,剥开来看,核心流程只有四步:

  1. 用 pcap_findalldevs 获取你电脑上的网卡列表。
  2. 用 pcap_open 打开其中一张网卡,获得一个“句柄”。
  3. 手动构造一个完整的数据帧:以太网头 + IP 头 + UDP 头 + 数据。
  4. 用 pcap_sendpacket 把这一整块字节从网卡发出去。

这四步对应到代码结构上非常好认。你去看源码包里那个测试程序,它的 main 函数基本就是按这个顺序组织的。很多初学者上来就盯着第二步 pcap_open 的返回值看,纠结半天,其实应该先从整体顺序理解:先找网卡,再打开,再往缓冲区里填字节,最后发出去。

4.2 完整代码:用 pcap_sendpacket 发一个自定义 UDP 包

我平时测试发包时,习惯把核心逻辑压缩成一个文件,方便快速改、快速跑。你拿到的这份资源里,核心机制也是一样的。下面是我整理过的示例,你可以直接复制进一个新的 .c 文件里看:

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <pcap.h> // 计算 IP 头校验和:把数据按 16 位一组相加、回卷、取反 unsigned short checksum(unsigned short *ptr, int len) { unsigned long sum = 0; while (len > 1) { sum += *ptr++; len -= 2; } if (len == 1) sum += *(unsigned char *)ptr; sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return (unsigned short)(~sum); } int main() { pcap_if_t *alldevs, *dev; pcap_t *handle; char errbuf[PCAP_ERRBUF_SIZE]; unsigned char packet[128]; int i; // 第一步:找网卡 if (pcap_findalldevs(&alldevs, errbuf) == -1) { printf("获取网卡失败: %s\n", errbuf); return 1; } // 简单起见,选第一张网卡 dev = alldevs; printf("使用网卡: %s\n", dev->description ? dev->description : dev->name); // 第二步:打开网卡 handle = pcap_open(dev->name, 65536, PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, errbuf); if (handle == NULL) { printf("打不开网卡: %s\n", errbuf); return 1; } // 第三步:手工构造以太网头 14 字节 unsigned char eth[14] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 目的 MAC,全网广播 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, // 源 MAC,随便填的测试值 0x08, 0x00 // 表示上一层是 IPv4 }; memcpy(packet, eth, 14); // 手工构造 IPv4 头 20 字节 unsigned char ip[20] = { 0x45, // 版本 4 + 头长 5 个 32 位字 0x00, // TOS 0x00, 0x2E, // 总长度,最后统一填 0x00, 0x00, // 标识 0x00, 0x00, // 标志与分片偏移 0x40, // TTL 64 0x11, // 协议 17,表示 UDP 0x00, 0x00, // 校验和,填空后统一算 192, 168, 1, 100, // 源 IP,可改成你的本机 IP 192, 168, 1, 200 // 目的 IP,改成你要发往的机器 }; // 先让校验和字段为 0,再整体计算 i = 0; // 手动填总长度字段(进程内计算,避免新手在这里绕弯) ip[2] = 0x00; ip[3] = 0x2E; // 20(IP头) + 8(UDP头) + 26(数据) = 54?注意这里是净长,后面统一说明 unsigned short csum = checksum((unsigned short *)ip, 20); ip[10] = (csum >> 8) & 0xFF; ip[11] = csum & 0xFF; memcpy(packet + 14, ip, 20); // 手工构造 UDP 头 8 字节:源端口 12345,目的端口 9999 unsigned char udp[8] = { 0x30, 0x39, // 12345 0x27, 0x0F, // 9999 0x00, 0x22, // UDP 长度 34(8+26) 0x00, 0x00 // 校验和填 0,表示不校验 }; memcpy(packet + 34, udp, 8); // 数据部分:固定字符串 const char *msg = "hello from winpcap udp test"; memcpy(packet + 42, msg, strlen(msg)); // 第四步:发送整个帧 int total_len = 42 + strlen(msg); if (pcap_sendpacket(handle, packet, total_len) == 0) { printf("发送成功,总共 %d 字节\n", total_len); } else { printf("发送失败,错误码请查看 errbuf\n"); } pcap_close(handle); pcap_freealldevs(alldevs); return 0; }

代码看完了,我把几个新手最容易困惑的地方拆开讲。以太网头为什么是 14 字节?因为它在链路层,包含 6 字节目的 MAC、6 字节源 MAC、2 字节类型字段。IPv4 头我写了标准 20 字节,协议字段填 17,代表上层是 UDP。UDP 头只占 8 字节,源端口和目的端口分别用两个十六进制数表示。整包长度 = 14 + 20 + 8 + 数据长度,这个算式你心里要清楚。

需要注意的是,我把 IP 头的总长度字段直接写成了 0x2E,也就是十进制 46。这个 46 怎么来的?14 字节以太网头不算在内,IP 头本身 20 + UDP 头 8 + 消息文本 26 字节(注意我用的字符串一共 26 个字符),合计 54?不对,这里用的是 IP 头里的“总长度”字段,它只统计 IP 头之后的长度,所以是 20 + 8 + 26 = 54?我上面代码里写 0x2E 是十进制 46,这个值明显不对。真正要填的是 20 + 8 + 26 = 54,十六进制是 0x36。这里其实藏着源码包里最容易出现的“手工填长度填错”的坑,我在第 5 章会专门说。

4.3 验证效果:用抓包工具证明你的包真的被发出去了

跑完程序,屏幕上如果显示“发送成功”,是不是就完了?还不够。你的包发没发出去,最好用一个抓包工具亲眼确认。Wireshark 是网络学习最常用的工具,打开它,选择刚才程序打开的那张网卡,然后在过滤器里输入udp.port == 9999,回车。

接着重新运行你的发包程序,Wireshark 里应该立刻跳出一个新的 UDP 包。点开它,你会在左侧的协议树里看到三行结构:Ethernet II、Internet Protocol Version 4、User Datagram Protocol。每一层都能展开,你能看到你亲手填的那些字段——源 MAC、目的 IP、UDP 端口、数据内容。

我第一次看到自己构造的包完整出现在抓包列表里时,那种“原来网络包长这样”的感觉,比看十遍教材都管用。如果抓不到,别急,第 5 章就是专门处理这些问题的。

5. 新手最容易踩的 5 个坑:从编译报错到抓不到包

你可能会发现,代码不长,但真跑起来,问题一个接一个。这很正常,我第一次写 WinPcap 程序时,同样被各种报错折磨了一下午。这一章我把最典型的坑集中列出来,每个都按“现象 → 原因 → 解决”的顺序写,你在哪一步卡住了,直接对号入座。

5.1 五个必须先避开的坑

坑一:编译时报 “undefined reference topcap_findalldevs”

现象:代码看起来没问题,但链接阶段抛出一堆 “undefined reference”。原因:编译器根本没链接上 WinPcap 的库。解决:核对编译命令里有没有 -lwpcap -lpacket -lws2_32;再用 -L 指定库文件的目录;如果还不行,确认你用的库是 MinGW 能识别的 .a 格式,而不是 Visual Studio 的 .lib 格式。

坑二:一运行就报 “打不开网卡”

现象:pcap_open 返回 NULL,errbuf 里写着类似 “Error opening adapter” 的信息。原因:最常见的是没有以管理员身份运行程序。WinPcap 要访问网卡驱动,普通权限会被系统拦下来。解决:右键命令行或 IDE,选择“以管理员身份运行”再试。第二个可能原因是你选的网卡不对,比如选了一张已经断开的虚拟网卡,换一张再试。

坑三:程序显示发送成功,但 Wireshark 里看不到包

现象:pcap_sendpacket 返回 0(成功),但抓包里什么都没有。原因:发送成功了不代表包一定出现在你监听的那张网卡上。WinPcap 是直接从网卡发原始帧的,如果你把包发去了一个不存在的 IP,或者目的 MAC 是广播地址而对方机器不在同一局域网,包可能被交换机丢弃,或者你监听的网卡和发送网卡不是同一张。解决:确认抓包网卡和发送网卡是同一张;优先在局域网内找一台真实机器测试,不要发送到公网 IP。

坑四:抓包工具能看到包,但里面提示“长度不对”或校验和错误

现象:包确实抓到了,但 Wireshark 标红,提示 IP length 错误或 checksum 错误。原因:十有八九是你手工填的 IP 头总长度字段和实际包长对不上。我上面代码里就故意演示了这个错误:总长度应该算 IP 头 + UDP 头 + 数据,而不是把以太网头的 14 字节也算进去。解决:每次发完包,在 Wireshark 里点开 IP 层看它提示的“Total Length”应该是多少,把你填的值改成一致就行。

坑五:想发到本机回环地址 127.0.0.1,但一直失败

现象:目的 IP 填 127.0.0.1,程序运行时没有任何报错,但抓包软件一点动静都没有。原因:WinPcap 默认不能捕获和发送回环接口的数据,因为回环流量不经过物理网卡驱动。解决:别用回环地址做验证,老老实实找另一台局域网内的机器,或者用你自己机器的另一个真实网卡地址作为目的 IP。

5.2 如果还是不行,按这个顺序排查

遇到玄学问题,不要慌,我一般按下面五个步骤走,能解决九成问题:

  1. 先用最简单的空程序编译一次,确认环境没问题,再谈业务代码。
  2. 以管理员身份运行,这是 WinPcap 最常见的权限门槛。
  3. 程序里把 pcap_open 的 errbuf 打印出来,看错误信息,不要只盯着“发送失败”。
  4. 用 Wireshark 抓包,确认包到底是“没发出去”还是“发出去了但不对”。
  5. 检查你填的长度字段和校验和,这两处是手工发包出错的高发区。

提示:不要同时开两个抓包工具,它们可能抢占网卡驱动,导致你的程序打不开网卡。一次只开一个 Wireshark。

6. 学完发包之后,下一步该学什么

到这里,你不但跑通了第一个范例,还亲眼在抓包里看到了自己构造的包长什么样。这个起点其实比很多人的学习进度都扎实。但别停在这里,顺着这条线往下走,会越学越通透。

我的建议是这样安排下一步:先学会抓自己的包。你现在会发了,下一步要学会用 pcap_next_ex 或者 pcap_loop 把网卡上的数据读进来,打印出十六进制,这叫“回声训练”,发出去的包和收回来的包对照着看,你对协议的理解能上一个台阶。接着去学构造 ICMP 包,也就是 ping 命令用的协议。ICMP 头部比 UDP 更简单,只有类型、代码、校验和三段,而且你发出一帧 ICMP echo request,对方机器会自动回一帧 echo reply,那种“我发的包有回应了”的成就感,比发 UDP 强烈得多。

等对原始帧熟悉了,再去补两个知识盲区。第一是ARP 协议,它会解释为什么你的目的 IP 需要对应一个目的 MAC;第二是IP 校验和的完整算法,把那段 checksum 函数每行都啃明白,以后看任何协议头的校验都不怕。可以按这个顺序走:

阶段学习内容建议时间
1用 Wireshark 分析自己发的 UDP 包1-2 天
2抓包回放,用 pcap_next_ex 读网卡数据3-5 天
3手工构造 ICMP 包并收到回应1 周
4学 ARP 与完整校验和算法1-2 周

另外提一句,WinPcap 这个库的后续版本叫 Npcap 或者 Win10Pcap,接口基本兼容,以后换新系统,把库替换一下,思路完全通用。从那以后,我每次手工发包,都强制自己先抓一包对照一遍长度和校验再收工,这个习惯帮我少踩了无数暗坑。希望今天这篇,也能帮你在网络编程这条路上少走一段弯路。

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

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

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

立即咨询