简介:一套基于WinPcap库实现的UDP发包程序源码包,面向网络编程初学者、协议学习者和需要底层数据包发送的开发者。UDP作为无连接传输协议,延迟低、效率高,常用于实时音视频与网络探针场景,该示例正好演示了如何在用户态通过WinPcap直接与网卡交互构造并发送UDP报文。压缩包内共349个文件,约5.94MB。核心内容为C++源码与工程配置(cpp/h/sln/vcxproj),并附有WinPcap静态库(libwpcap.a、libpacket.a),可直接编译运行;js/css/html等文件数量较多,可辅助阅读说明或作为展示页面,exe为编译产物,方便快速体验。已有188人学习浏览。通过阅读和运行该源码,可以掌握WinPcap设备枚举、句柄创建、数据包组装与发送的关键步骤,理解底层网络编程的完整流程,并可在现有代码基础上扩展为自定义发包工具或教学实验,兼顾理论与实践。
1. 为什么要在 WinPcap 里发 UDP:绕过协议栈,自己拼帧
做网络测试的人应该都遇到过这种尴尬:用原生 socket 发 UDP,系统协议栈把 MAC 头、IP 头和校验和全包办完了,你调了半天参数,最后还是只能靠 Wireshark 验收。这套基于 WinPcap 实现的 UDP 发包程序换了一条路——它把发送位置挪到数据链路层,网卡名、MAC 地址、IP 头、UDP 头全部手工填充,每次发包都要把每一字节算明白。源码包是完整的 C 工程,带静态库和时间戳记录文件,拿到就能编译。它适合三类人:正在抠 TCP/IP 协议细节的入门者、需要模拟特定设备发包的测试工程师,以及想彻底搞懂帧是怎么拼出来的底层爱好者。我把它拆开讲一遍,编译坑和抓包验收方法都写在后面,照着走能少折腾好几个晚上。
2. WinPcap 发包原理与源码包拆解:从用户态 API 到链路层网卡
2.1 为什么原生 socket 做不到:UDP 的“最后一公里”在驱动里
原生 UDP socket 的发送路径是固定的:sendto 把数据交给 socket 层,协议栈自动补上 UDP 头、IP 头,再通过路由表选出口网卡,最后由 NDIS 驱动封装成以太网帧。这条路最大的问题是黑匣子——你只控制 payload 和端口,头字段全部由系统决定。想改 TTL?得用 setsockopt 的 IP_TTL;想改源 MAC?协议栈根本没给你留接口;想发一个协议号是 99 的自定义 IP 包?走 socket 就更绕,最后抓包看到的还是被内核“修正”过的版本。
WinPcap 做的事恰好相反。它的 NPF 驱动挂在内核数据链路层,pcap_sendpacket 直接把一段字节流交到网卡 DMA 缓冲,中间不经过 TCP/IP 协议栈。你在用户态填充什么,线上就是什么,哪怕以太网类型字段写成非 IP 的 0x88CC,它也会照发不误。这个设计对日常工作有两个实际价值:一是能精确构造畸形包做协议健壮性测试;二是能验证某种报文在真实链路里的表现,而不是被协议栈悄悄改过的版本。
代价也很好理解:协议栈不给你擦屁股了。源目 MAC 要自己填,IP 校验和要自己算,UDP 长度不能填错,帧总长要和网卡 MTU 匹配。这套源码最值得读的也就是这一块:它把构造帧的每个字节都摆在明面上。
顺带说一句,也有人用 libnet 或 raw socket 做类似的事。raw socket 在 Windows 上受限很多,尤其发送自定义 IP 头会遇到权限和 SOCK_RAW 协议绑定问题;libnet 则是个封装层,学完它你还是不知道帧怎么拼。WinPcap 这套是直接面对最原始的缓冲区,最适合当教学骨架。
2.2 压缩包文件全解:工程文件、静态库与时间戳记录各是干什么的
先看资源包里的东西。压缩包文件不多,但头一次打开很容易被里面那串时间戳文件名迷惑。我把它拆成一张清单:
| 文件/目录名 | 类型 | 在工程里的作用 |
|---|---|---|
| WinPcap_UDP_Test.aps | Visual Studio 资源缓存 | 界面编辑器自动生成,编译时会刷新,不需要手动维护 |
| libwpcap.a | 静态库 | WinPcap 核心 API(pcap_* 函数)的归档库 |
| libpacket.a | 静态库 | Packet32 底层库,提供对 NPF 驱动的直接操作 |
| 20140228124512421 等带时间戳的文件 | 文本记录 | 构建或测试时自动生成的流水记录,编译器不读 |
| .h / .c 源文件 | 源码 | 发包逻辑主体,后面章节逐段展开 |
这里最容易看走眼的是两个库文件。WinPcap 官方安装包提供的是 wpcap.dll 和 packet.dll,运行时动态加载;而包里的是 .a 后缀归档库,说明工程在链接阶段就把库代码揉进可执行文件了。好处是部署机器不需要单独装运行时,坏处是链接器一旦找不到归档库,整个工程直接失败,所以第 3 章的库目录配置必须严格对齐解压路径。
需要注意 .a 是 MinGW/GCC 系的静态库格式,如果你用 MSVC 工具链编译,链接器并不认这个格式,需要换成官方 devpack 里的 .lib,或者干脆整套工程改用 MinGW 构建。这两条路我后文都标了操作点。
那批带时间戳的文件是交付时自动生成的记录,类似于构建日志的文本快照。我一般建议把它们移到 backup 子目录,别留在工程根目录,省得 VS 偶尔把它们当成杂项文件参与编译,或者被 PVS-Studio 之类的静态检查工具扫出奇怪告警。.aps 文件也要说一句:它记录的是对话框、图标之类的资源编辑状态,删了会自动重建,但如果你手动改了它,资源视图可能显示空白,这个属于经典自找麻烦。
2.3 WinPcap 发包的核心流程:pcap_open_live → 手工填包 → pcap_sendpacket
WinPcap 发包只有三步。第一步 pcap_open_live 打开网卡,拿到句柄;第二步把一条完整帧按以太网头 + IP 头 + UDP 头 + payload 的顺序填进一个字节缓冲区;第三步 pcap_sendpacket 把缓冲区交给驱动。三步里最难的是第二步,因为每一层的字段偏移都要手工算。
帧格式本身是固定的:以太网头 14 字节,IP 头 20 字节,UDP 头 8 字节,payload 自己定。如果你的 payload 是 16 字节,整帧长度就是 58 字节。这个长度是后面抓包验收时最重要的数字,错一字节都说明填充逻辑有问题。
pcap_sendpacket 是同步还是异步取决于驱动实现细节,但返回值只能说明字节是否成功交给了驱动,不保证对端真的收到。所以发完必须用 Wireshark 在出口网卡上抓一帧确认。这套流程我每次做底层发包都会强制走一遍,不确认等于白发。
关于设备名多说一句:pcap_open_live 第一参数不是网卡名,而是 \Device\NPF_{GUID} 这样的设备节点。最稳的拿法是用 pcap_findalldevs 枚举所有接口,打印出 name 字段再填进去。如果 Demo 里写死了设备名,大概率要改成你机器上的实际 GUID,否则 open 直接失败。虚拟网卡、蓝牙适配器也会被枚举到,过滤方法是看接口有没有 IPv4 地址,没有的跳过。
3. 把发包程序在 Visual Studio 里跑起来:工程配置与关键代码
3.1 环境准备:装 WinPcap 4.1.3 与开发库 devpack
准备环境分两步。第一步装 WinPcap 4.1.3 运行库,它会安装 NPF 内核驱动和系统服务;第二步装 Developer's Pack 开发包,里面才有 pcap.h 头文件和 wpcap.lib、packet.lib 导入库。很多人以为装完运行库就能编译,结果 pcap.h 都找不到,这一步最容易踩空。
解压 devpack 后要注意目录结构:Include 目录里是 pcap.h、pcap-stdinc.h 等头文件,Lib 目录里按平台分 x86 和 x64 两个子目录。我一般把整个 WpdPack 放到和工程同级的 third_party 目录下,不用默认 C 盘系统路径,一方面是权限干净,另一方面换机器重新拉代码时相对路径好配。
编译平台我建议先选 x86。这套老工程和静态库大多是 32 位时代的东西,x64 下虽然也有库,但 NPF 驱动的位数、wpcap.lib 导入库的位数都必须和 exe 一致,位对不上会出一堆 LNK 报错。先让 32 位跑通,再谈改 64 位。
3.2 工程配置的三个回调:附加包含目录、附加库目录、忽略特定库
拿到工程后别急着按 F7,先过三个配置点:
- 右键工程 → C/C++ → 附加包含目录:填入 WpdPack\Include。这保证 #include <pcap.h> 能被找到。
- 链接器 → 常规 → 附加库目录:填入 WpdPack\Lib\x86。这里不是填到 Lib 根目录,很多人就错在这一层,x64 和 x86 的库都堆在根目录的话,链接器可能抓到位数不对的那个。
- 链接器 → 输入 → 附加依赖项:按顺序填 wpcap.lib、packet.lib、ws2_32.lib。wpcap.lib 内部引用了 ws2_32 的符号,ws2_32 不排在后面就会出现 LNK2019。
还有两个小的:字符集选“使用多字节字符集”,老代码里如果出现 char* 与 TCHAR* 混用,Unicode 模式会编出一堆类型转换错误;运行时库选“多线程调试 (/MTd)”或“多线程 (/MT)”,避免在没装 VC 运行库的机器上弹“缺少 msvcr140.dll”。
如果你拿到的是源码工程而不是安装好的工程,直接在源文件顶部加三行 pragma 注释也能解决链接触底问题,比属性面板更直观:
#pragma comment(lib, "wpcap.lib") #pragma comment(lib, "packet.lib") #pragma comment(lib, "ws2_32.lib")这属于工程配置的“后悔药”:属性面板改错了可以随时改回来,pragma 写法则是把依赖关系写进源码,换环境不丢。二选一即可,别同时写,否则链接器可能重复处理同一库。
3.3 发包核心代码:从打开网卡到发送 UDP 报文的完整实现
源码包里最关键的就是这个发包函数,我把核心路径整理成一份可直接编译进测试工程的 C 文件。注意顺序:先 include winsock2.h,再 include pcap.h,顺序反了会报一堆 winsock 相关重定义错误。
#include <stdio.h> #include <string.h> #include <winsock2.h> /* htons / inet_addr 依赖它 */ #include <pcap.h> #pragma comment(lib, "wpcap.lib") #pragma comment(lib, "packet.lib") #pragma comment(lib, "ws2_32.lib") /* Internet Checksum:对 16 位字序列求反码和 */ unsigned short checksum(unsigned short *buf, int len) { unsigned long sum = 0; while (len > 1) { sum += *buf++; len -= 2; } if (len == 1) sum += *(unsigned char *)buf; while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16); return (unsigned short)(~sum); } /* 构造完整帧:14 字节以太网头 + 20 字节 IP 头 + 8 字节 UDP 头 + payload */ int build_udp_frame(unsigned char *frame, const unsigned char *src_mac, const unsigned char *dst_mac, unsigned long src_ip, unsigned long dst_ip, unsigned short src_port, unsigned short dst_port, unsigned char *payload, int payload_len) { int total = 14 + 20 + 8 + payload_len; /* 以太网头 */ memcpy(frame, dst_mac, 6); /* 目的 MAC */ memcpy(frame + 6, src_mac, 6); /* 源 MAC */ frame[12] = 0x08; frame[13] = 0x00; /* EtherType = IPv4 */ /* IP 头 */ unsigned char *ip = frame + 14; ip[0] = 0x45; /* IPv4,20 字节头长度 */ ip[1] = 0; /* DSCP/ECN 置 0 */ *(unsigned short *)(ip + 2) = htons(20 + 8 + payload_len); /* 总长度 */ *(unsigned short *)(ip + 4) = htons(0); /* 标识 */ *(unsigned short *)(ip + 6) = htons(0x4000); /* DF=1,禁止分片 */ ip[8] = 64; ip[9] = 17; /* TTL=64,协议=UDP */ *(unsigned short *)(ip + 10) = 0; /* 校验和先清零 */ memcpy(ip + 12, &src_ip, 4); /* 源 IP,inet_addr 已转网络序 */ memcpy(ip + 16, &dst_ip, 4); /* 目的 IP */ *(unsigned short *)(ip + 10) = checksum((unsigned short *)(ip), 20); /* UDP 头 */ unsigned char *udp = frame + 14 + 20; *(unsigned short *)(udp + 0) = htons(src_port); *(unsigned short *)(udp + 2) = htons(dst_port); *(unsigned short *)(udp + 4) = htons(8 + payload_len); /* UDP 长度 */ *(unsigned short *)(udp + 6) = 0; /* IPv4 下 UDP 校验和可置 0 */ memcpy(udp + 8, payload, payload_len); return total; /* 返回帧总长度 */ } int main(void) { pcap_t *handle; char errbuf[PCAP_ERRBUF_SIZE]; /* 设备名改成你机器上的实际 GUID,见 pcap_findalldevs 枚举结果 */ const char *dev = "\\Device\\NPF_{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"; /* 打开网卡:snaplen=65535 抓完整帧,promisc=1 混杂模式 */ handle = pcap_open_live(dev, 65535, 1, 1000, errbuf); if (handle == NULL) { printf("open failed: %s\n", errbuf); return 1; } unsigned char src_mac[6] = {0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E}; unsigned char dst_mac[6] = {0x0C, 0x42, 0xA1, 0xB2, 0xC3, 0xD4}; unsigned long src_ip = inet_addr("192.168.1.10"); unsigned long dst_ip = inet_addr("192.168.1.1"); unsigned char payload[] = "winpcap udp test"; unsigned char frame[1514]; int len = build_udp_frame(frame, src_mac, dst_mac, src_ip, dst_ip, 12345, 53, payload, (int)strlen(payload)); if (pcap_sendpacket(handle, frame, len) == 0) printf("send ok, %d bytes\n", len); else printf("send failed: %s\n", pcap_geterr(handle)); pcap_close(handle); return 0; }这段代码的填充逻辑从头到尾都要看懂,不该有任何一个魔法数字是闭眼抄的。以太网头 6 字节目的 MAC、6 字节源 MAC、2 字节类型,照着内存顺序填就好。IP 头里最容易错的是总长度字段,它只统计 IP 头加 UDP 头加数据,不含以太网头,所以是 20+8+payload_len,绝不是整帧长度。
校验和函数只在 IP 头 20 字节上做反码和,函数本身是教科书经典实现。UDP 校验和置 0 在 IPv4 里完全合法,这叫“禁止使用校验和”选项,抓包软件不会报错。如果你的目标环境要求对端严格校验,就得把伪头加进来一起算,工作量多一小截,后面第 6 章我会给扩展方向。
pcap_open_live 的参数表里,snaplen 对发包场景其实只影响抓包缓存,65535 是惯例;promisc=1 混不混杂对发送没影响,但建议开着,方便一边发一边用同一块网卡抓回环验证;to_ms 是读超时,发包用不到,填 1000 无妨。设备名错误时 open 会返回 NULL,errbuf 里写的是“WinPcap 未安装”还是“设备不存在”,先看它再动手查驱动。
4. 避坑:连编译都过不去的五个常见问题
这套工程逻辑不复杂,但我见到的报错几乎全集中在环境、库和长度计算上。下面五条是按发生率排序的,每一条都按现象 → 原因 → 解决写清楚。
4.1 pcap.h 找不到:开发包装错位置,头文件和库指向不同目录
现象:编译第一行就报 fatal error C1083: 无法打开包括文件“pcap.h”。
原因:只装了 WinPcap 运行库,那个安装包不带开发头文件;或者包含目录指向了 System32,那个文件夹里只有 DLL。运行库和开发包是两个东西,这是 WinPcap 时代最容易混淆的点。
解决:确认 Developer's Pack 已解压,把 C/C++ → 附加包含目录指到 WpdPack\Include。我一般把 WpdPack 整个目录放进工程同级,用相对路径配置,同事拉代码也不会出现 C 盘绝对路径失配的问题。如果还找不到,就在源码里改成 #include "pcap.h" 并确认文件确实在那个目录,排除搜索路径优先级干扰。
4.2 链接报错 unresolved external symbol:静态库顺序和工具链不匹配
现象:LNK2019 无法解析的外部符号 __imp_pcap_open_live,或者 __imp_pcap_sendpacket。
原因:wpcap.lib 本身是导入库,它内部依赖 ws2_32.lib 的字节序和 Socket 相关符号,如果 ws2_32.lib 排在它前面,链接器先处理 ws2_32 时还不知道需要哪些符号,回头就解析不到;另一个常见原因是把 MinGW 的 .a 库直接丢给 MSVC 链接器,格式不对导致所有 pcap_* 符号全部丢失。
解决:附加依赖项按 wpcap.lib、packet.lib、ws2_32.lib 的顺序写,ws2_32 永远最后。如果你手里的工程带的是 libwpcap.a、libpacket.a,要么把工程切到 MinGW 工具链,要么换成官方 devpack 里的 .lib,二选一,别混。
4.3 程序运行时报“WinPcap 未安装或 NPF 驱动未启动”
现象:pcap_open_live 返回 NULL,errbuf 里出现“WinPcap 未安装”“The system cannot find the device”之类提示。
原因:运行库装上了,但名为 npf 的内核服务被安全软件禁用,或服务未自动启动;设备名写死成别人的 GUID 也会报同一个错,别急着怪驱动。
解决:以管理员权限打开 cmd,执行 net start npf,再跑一次程序;如果服务状态是禁用,用 sc config npf start= auto 改回来。设备名问题就用 pcap_findalldevs 枚举本机全部接口,把返回的 name 字段原样拷进代码,这个动作我每次换机器都会做一遍。
提示:杀毒软件和系统加固工具可能会在每次重启后重新禁用 npf 服务,部署测试机器时记得把服务加入白名单。
4.4 发送成功但对方抓不到包:混用了 Winsock 与 pcap 两条路径
现象:pcap_sendpacket 返回 0,程序打印 send ok,但对端主机没有任何 UDP 报文到达,Wireshark 在出口也看不到。
原因:最典型的是把目标 IP 写成了 127.0.0.1,pcap 发包面对的是物理网卡,回环接口根本不经过 NPF 驱动,包直接消失;另一种是防火墙或交换机端口安全把伪造的 MAC 帧丢弃了,因为帧里的源 MAC 不属于本机网卡。
解决:测试拓扑改用两台机器物理直连,或者同机双网卡交叉线连,别拿回环做验证;如果必须发给本机服务,换成真实网卡 IP 并保证路由走那块网卡。我自己的习惯是先 ping 通对端再发包,把二层的可达性确认前置,能省下大量排查时间。
4.5 帧长度不对,包被网卡丢弃:算包长时漏掉了以太网头
现象:抓包工具上看到长度诡异的帧,比如 payload 只有 2 字节时总长是 44 而不是 44+12=58 之类的错位,或者干脆网卡静默丢弃。
原因:pcap_sendpacket 的第三个参数要求的是整帧长度,也就是从目的 MAC 到 payload 末尾的字节数;很多人把 IP 头里的 total_length 字段(20+8+payload_len)直接传了进去,导致丢失以太网头。IP 头 total_length 在设计上就不包含链路层头,这俩概念不是一回事。
解决:在 build 函数里直接返回 14+20+8+payload_len 作为帧总长,别在 main 里重新计算。如果代码里既有 ip->total_length 又有 send_len,把 send_len 赋值统一改成 len = 14 + ntohs(ip_total_length),只要改一处,后续重构也不会再悄悄漏掉这 14 字节。
5. 用 Wireshark 验收发包结果:把数据链路层头逐字节核对
5.1 抓包过滤规则:只看目标端口
代码跑通、send ok 打印出来之后,第一件要做的事不是去看对端,而是先在发送端网卡上抓一帧。Wireshark 也依赖 WinPcap/NPF,装好运行库才能抓包。开抓之前要停掉同网卡上的大流量任务,否则背景噪音会淹没你的测试帧。
过滤表达式不要用笼统的 udp,那会把 DNS、NTP 广播全捞进来:
udp.port == 12345加上端口条件后,显示区只会剩你构造的那路流量,看起来干净得多。Wireshark 的 udp.port 同时匹配源端口和目的端口,过滤条件不区分方向,正好适用。如果端口还是被占用的场景,可以再加上 ip.src == 192.168.1.10 把方向锁死,二选一或两个条件叠加都行。
抓包动作要在 pcap_sendpacket 返回之前就开始,最简单的方式是先让 Wireshark 跑起来再双击运行发包程序,或者干脆把抓包按钮和发包循环同时开。一次抓不到就先清空显示再发一次,通常第二次就能定位问题。
5.2 逐字节验三个头:MAC、IP 总长度、UDP 长度
抓到帧以后,在 Wireshark 的 Frame 层点击选中,看 Data 视图里的十六进制内容。以 16 字节 payload 为例,你应该看到完整 58 字节帧,各字段按下面对照:
| 检查点 | 期望值 | 对应源码位置 |
|---|---|---|
| 帧总长度 | 58 字节 | 14 + 20 + 8 + 16 |
| 目的 MAC | 0C:42:A1:B2:C3:D4 | dst_mac 数组 |
| 源 MAC | 00:1A:2B:3C:4D:5E | src_mac 数组 |
| 以太网类型 | 0x0800 | frame[12..13] |
| IP 头首字节 | 0x45 | ip[0] |
| IP 总长度 | 38(0x0026) | ip[2..3] |
| TTL / 协议 | 64 / 17 | ip[8] / ip[9] |
| 源端口 / 目的端口 | 12345 / 53 | udp[0..3] |
| UDP 长度 | 24(0x0018) | udp[4..5] |
| IP 校验和 | 正确,无红色标记 | checksum 函数结果 |
如果 IP 校验和算错,Wireshark 会在 IP 层标一个醒目的 [incorrect, should be 0x…],这种情况 99% 是 checksum 函数在 len 参数上传错了字节数,回去查调用点传的是不是 20。UDP 校验和显示为 0 是合法的,抓包软件只会标注“校验和未验证”,不会判错。
IP 总长度和 UDP 长度两个字段要在 Wireshark 里分别点开核对,它们数值不同但含义容易混。IP 总长度是 20+8+payload,UDP 长度是 8+payload,后者比前者少 20。看到这俩数对得上,三层封装基本就稳了。
5.3 一个常见的“发出去就消失”的现场:目标 MAC 写错
有个场景很迷惑:程序没有任何报错,Wireshark 发送端也看不到帧,但帧确实从驱动送出去了。这通常发生在目标 MAC 填错一个字节,或者填了对端网卡的 MAC 却没考虑交换机端口安全——交换机发现源 MAC 与端口学习表不符,直接把帧丢弃。
解决手法其实很土:先在 cmd 里 ping 一下目标 IP 让二层通信先建立,然后 arp -a 查看目标 IP 对应的 MAC,把这个真实值填进 dst_mac 数组。如果目标不在同一网段,要填的是网关 MAC,而不是服务器 MAC,这个细节能解释掉一半“发出去就消失”的现场。从那以后我再写这类 Demo,都会把 arp -a 的结果当成填 MAC 的唯一来源,不再手抄设备标签。
6. 把 Demo 改造成自己的发包器:两个常用的扩展方向
6.1 扩展1:伪装源 IP / 源 MAC,模拟特定设备发包
把 build_udp_frame 里的 src_mac 和 src_ip 换成你想要模拟的对象即可。UDP 无连接,对端不会像 TCP 那样做三次握手校验源地址,单向发包场景下伪装源基本零成本。典型用法是模拟传感器上报、模拟网关心跳、做准入设备的仿真测试。
要注意的是帧里的源 MAC 必须和实际接入交换机端口的设备对应,否则交换机会按端口安全策略丢帧。伪装源 IP 比源 MAC 安全一些,因为 IP 层一般不做过账校验,但也不要拿这套东西去干扰生产网里的真实设备。
提示:伪造源 MAC 会导致对端回包无法送达,只适合单向流量测试;需要在隔离的测试网段里做。
6.2 扩展2:批量循环发包加序号,做丢包率测试
在 payload 前四个字节写入一个递增序号,循环调用 pcap_sendpacket,对端接收程序按序号补洞统计。加序号的填充代码很短:
unsigned char payload[64]; int seq = 0; for (int i = 0; i < 10000; i++) { memcpy(payload, &seq, 4); /* 前 4 字节放序号 */ memcpy(payload + 4, "ping-payload", 12); int len = build_udp_frame(frame, src_mac, dst_mac, src_ip, dst_ip, 12345, 9999, payload, 16); pcap_sendpacket(handle, frame, len); seq++; Sleep(10); /* 控制发速,避免打满网卡 */ }循环里每次调用都重建一次帧,这是刻意保留的结构:你可以顺手在每个循环里改一个字段,比如把 TTL 从 64 递减,或者把源端口按 seq 递增,在不改动函数签名的情况下验证协议栈对畸形头的容忍度。想要测吞吐就把 Sleep 去掉换成微秒级延时,但注意百兆网卡满速约 14880 包每秒,超过这个数丢包率就不再是网络问题,是发送端自身瓶颈。
我最早拿这个 Demo 改包时,把目标网口选错了,对着管理网段一顿猛发,结果把整楼网络搞断,被运维同事拎着耳朵教训了半小时。从那以后,我每次改完自定义帧,都会先开 Wireshark 抓一把确认帧结构,再跨到对端验证,这个工序一次都不敢省。希望这篇拆解能帮你少走同样的弯路。
本文还有配套的精品资源,点击获取